From 8d32af7d7c5fe56b4f799609a27c14609b8eaab6 Mon Sep 17 00:00:00 2001 From: crowetic Date: Thu, 4 Dec 2025 12:00:14 -0800 Subject: [PATCH 1/3] Made multiple changes to both DataExplorer and the manifest/loading therein, and the announcements section and loading there. Also introduced a new publish method for announcements that should hopefully allow queued publishing and batch publishing with notifications. --- src/components/common/PublishQueueStatus.tsx | 23 + src/components/common/QdnPublishStatus.tsx | 2 +- src/components/news/AnnouncementDialog.tsx | 137 +----- src/components/news/NewsPublisher.tsx | 28 +- src/components/news/QAssetsNewsSection.tsx | 66 ++- src/hooks/useQdnProgressivePublisher.ts | 85 ---- src/hooks/useQdnResources.ts | 12 +- src/notifications/notificationService.ts | 2 - src/pages/manage/data/DataExplorer.tsx | 127 ++++-- .../data/hooks/useResolveResourceBase64.ts | 24 +- src/state/publishQueue.ts | 395 ++++++++++++++++++ src/utils/news.ts | 74 ++-- src/utils/notify.ts | 10 +- src/utils/qmailNotifications.ts | 116 +---- 14 files changed, 674 insertions(+), 427 deletions(-) create mode 100644 src/components/common/PublishQueueStatus.tsx delete mode 100644 src/hooks/useQdnProgressivePublisher.ts create mode 100644 src/state/publishQueue.ts diff --git a/src/components/common/PublishQueueStatus.tsx b/src/components/common/PublishQueueStatus.tsx new file mode 100644 index 0000000..158eb38 --- /dev/null +++ b/src/components/common/PublishQueueStatus.tsx @@ -0,0 +1,23 @@ +import QdnPublishStatus from './QdnPublishStatus'; +import { usePublishQueue } from '../../state/publishQueue'; + +type Props = { + fallbackLabel?: string; +}; + +export default function PublishQueueStatus({ fallbackLabel }: Props) { + const state = usePublishQueue(); + const activeJob = state.activeJobId + ? state.jobs.find((job) => job.id === state.activeJobId) + : null; + + if (!activeJob) return null; + + return ( + + ); +} diff --git a/src/components/common/QdnPublishStatus.tsx b/src/components/common/QdnPublishStatus.tsx index e7b20b9..ba4436b 100644 --- a/src/components/common/QdnPublishStatus.tsx +++ b/src/components/common/QdnPublishStatus.tsx @@ -1,6 +1,6 @@ import { Box, Button, LinearProgress, Typography } from '@mui/material'; import type { PublishJobProgress, PublishJobStatus } from '../../utils/qdnProgressivePublisher'; -import type { PublishThrottleState } from '../../hooks/useQdnProgressivePublisher'; +import type { PublishThrottleState } from '../../state/publishQueue'; type Props = { progress: PublishJobProgress | null; diff --git a/src/components/news/AnnouncementDialog.tsx b/src/components/news/AnnouncementDialog.tsx index b2b107e..4ad5fbf 100644 --- a/src/components/news/AnnouncementDialog.tsx +++ b/src/components/news/AnnouncementDialog.tsx @@ -23,8 +23,9 @@ import { } from '@mui/material'; import { useAuth } from 'qapp-core'; import TiptapEditor from '../TipTapEditor'; -import QdnPublishStatus from '../common/QdnPublishStatus'; +import PublishQueueStatus from '../common/PublishQueueStatus'; import { prepareHtmlForPublish } from '../../utils/publicationPublisher'; +import { invalidateAnnouncementCache, dispatchNewsRefreshEvent } from '../../utils/news'; import { objectToBase64 } from '../../utils/data'; import { sendNotification } from '../../notifications/notificationService'; import { NOTIF_GROUP_ID } from '../../notifications/notifyIndex'; @@ -32,10 +33,9 @@ import { qaAnnouncementPrefix } from '../../constants/qdnConstants'; import { uniqueId6 } from '../../utils/ids'; import { getAccountGroups, type GroupSummary } from '../../utils/qortalApi'; import type { NotifScope } from '../../types/notifications'; -import { QmailPartialError } from '../../utils/qmailNotifications'; import type { NotificationRecipient } from '../../utils/notificationRecipients'; import { prepareQmailRecipients } from '../../utils/qmailRecipientCache'; -import { useQdnProgressivePublisher } from '../../hooks/useQdnProgressivePublisher'; +import { enqueueQdnPublishJob } from '../../state/publishQueue'; import { PublishJobError } from '../../utils/qdnProgressivePublisher'; type Props = { @@ -46,22 +46,6 @@ type Props = { const APP_HOME_LINK = 'qortal://APP/Q-Assets'; -type QmailPartial = { - title: string; - identifier: string; - sent: number; - total: number; - savedAt: number; -}; - -function saveQmailPartial(info: QmailPartial) { - try { - localStorage.setItem('qassets_qmail_partial', JSON.stringify(info)); - } catch { - /* ignore */ - } -} - export default function AnnouncementDialog({ open, onClose, @@ -79,52 +63,6 @@ export default function AnnouncementDialog({ const [groupOptions, setGroupOptions] = useState([]); const [groupsLoading, setGroupsLoading] = useState(false); const [notificationGroupId, setNotificationGroupId] = useState(''); - const { - publish: publishAnnouncementResources, - progress: qdnProgress, - throttle: qdnThrottle, - } = useQdnProgressivePublisher(); - const [qmailThrottle, setQmailThrottle] = useState<{ - sent: number; - total: number; - secondsLeft: number; - resolver: (v: boolean) => void; - identifier: string; - title: string; - } | null>(null); - - useEffect(() => { - if (!qmailThrottle) return; - const id = setInterval(() => { - setQmailThrottle((prev) => { - if (!prev) return prev; - const next = prev.secondsLeft - 1; - if (next <= 0) { - prev.resolver(true); - return null; - } - return { ...prev, secondsLeft: next }; - }); - }, 1000); - return () => clearInterval(id); - }, [qmailThrottle]); - - const cancelQmailThrottle = () => { - if (!qmailThrottle) return; - saveQmailPartial({ - title: qmailThrottle.title, - identifier: qmailThrottle.identifier, - sent: qmailThrottle.sent, - total: qmailThrottle.total, - savedAt: Date.now(), - }); - qmailThrottle.resolver(false); - setQmailThrottle(null); - setBusy(false); - setErr( - `Q-Mail paused after ${qmailThrottle.sent}/${qmailThrottle.total}. Saved for re-publish later.` - ); - }; useEffect(() => { if (!address) { @@ -177,7 +115,7 @@ export default function AnnouncementDialog({ const announcementBase64 = await objectToBase64(annPayload); - await publishAnnouncementResources({ + const queued = enqueueQdnPublishJob({ label: 'Announcement publish', resources: [ { @@ -188,23 +126,10 @@ export default function AnnouncementDialog({ }, ], }); - - const qmailOptions = () => ({ - batchSize: 10, - onThrottle: (ctx: { sent: number; total: number; delayMs: number; nextIndex: number }) => - new Promise((resolve) => { - setQmailThrottle({ - sent: ctx.sent, - total: ctx.total, - secondsLeft: Math.ceil(ctx.delayMs / 1000), - resolver: resolve, - identifier, - title, - }); - }), - onProgress: ({ sent, total }: { sent: number; total: number }) => - setQmailThrottle((prev) => (prev ? { ...prev, sent, total } : prev)), - }); + if (!queued) throw new Error('Unable to queue announcement publish.'); + await queued.completion; + invalidateAnnouncementCache(); + dispatchNewsRefreshEvent(); if ((notifyMail || notifyChat) && address) { const extraGroupId = @@ -258,7 +183,6 @@ export default function AnnouncementDialog({ publisher, qdnResource: { publisher: userName, identifier }, links, - qmailOptions: qmailOptions(), deliveries: { internal: { enabled: true, chatPingGroupId: notifyChat ? NOTIF_GROUP_ID : undefined }, qmail: notifyMail @@ -283,7 +207,6 @@ export default function AnnouncementDialog({ publisher, qdnResource: { publisher: userName, identifier }, links, - qmailOptions: qmailOptions(), deliveries: { internal: { enabled: true }, qmail: notifyMail @@ -319,18 +242,6 @@ export default function AnnouncementDialog({ const declineReported = lower.includes('user declined request'); if (e instanceof PublishJobError) { setErr(e.message || 'Announcement publishing cancelled.'); - } else if (e instanceof QmailPartialError || e?.code === 'QMAIL_PARTIAL') { - saveQmailPartial({ - title, - identifier, - sent: e.sent ?? 0, - total: e.total ?? 0, - savedAt: Date.now(), - }); - setErr( - e?.message || - `Q-Mail paused after ${e.sent ?? 0}/${e.total ?? 0}. Saved progress for re-publish.` - ); } else { setErr( declineReported @@ -339,7 +250,7 @@ export default function AnnouncementDialog({ ); } } finally { - if (!qmailThrottle && !qdnThrottle) setBusy(false); + setBusy(false); } } @@ -442,35 +353,7 @@ export default function AnnouncementDialog({ - - {qmailThrottle && ( - `1px solid ${t.palette.warning.main}`, - borderRadius: 1, - bgcolor: (t) => t.palette.warning.light, - color: (t) => t.palette.getContrastText(t.palette.warning.light), - }} - > - - Q-Mail throttled - - - Sent {qmailThrottle.sent}/{qmailThrottle.total}. Auto-retrying in{' '} - {qmailThrottle.secondsLeft}s. - - - - - - )} + {err && {err}} diff --git a/src/components/news/NewsPublisher.tsx b/src/components/news/NewsPublisher.tsx index 60a4efc..9babb19 100644 --- a/src/components/news/NewsPublisher.tsx +++ b/src/components/news/NewsPublisher.tsx @@ -14,7 +14,7 @@ import { } from '@mui/material'; import { useAuth } from 'qapp-core'; import TiptapEditor from '../TipTapEditor'; -import QdnPublishStatus from '../common/QdnPublishStatus'; +import PublishQueueStatus from '../common/PublishQueueStatus'; import { prepareHtmlForPublish } from '../../utils/publicationPublisher'; import { assetNewsItemId } from '../../constants/qdnConstants'; import { isNameAdminOfGroupId } from '../../utils/access'; @@ -22,7 +22,7 @@ import { uniqueId6 } from '../../utils/ids'; import { useAlert } from '../alerts'; import { publishScopedNotification } from '../../utils/notificationPublisher'; import { objectToBase64 } from '../../utils/data'; -import { useQdnProgressivePublisher } from '../../hooks/useQdnProgressivePublisher'; +import { enqueueQdnPublishJob } from '../../state/publishQueue'; import { PublishJobError } from '../../utils/qdnProgressivePublisher'; export default function NewsPublisher({ @@ -58,16 +58,6 @@ export default function NewsPublisher({ }; const { alert } = useAlert(); - const { - publish: publishNewsResources, - progress: qdnProgress, - throttle: qdnThrottle, - } = useQdnProgressivePublisher(); - - const showQdnStatus = - (qdnProgress && qdnProgress.status !== 'completed' && qdnProgress.status !== 'cancelled') || - !!qdnThrottle; - const handlePublish = async () => { if (!userName) { await alert('You need a Qortal name to publish.'); @@ -91,7 +81,7 @@ export default function NewsPublisher({ setPublishing(true); try { - await publishNewsResources({ + const queued = enqueueQdnPublishJob({ label: 'Asset news publish', resources: [ { @@ -102,6 +92,8 @@ export default function NewsPublisher({ }, ], }); + if (!queued) throw new Error('Unable to queue news publish'); + await queued.completion; const assetLink = `qortal://APP/Q-Assets/assets/${assetId}`; const links = [ @@ -207,15 +199,7 @@ export default function NewsPublisher({ } /> - {showQdnStatus && ( - - - - )} + - + @@ -3760,6 +3793,16 @@ export default function DataExplorer() { {loadingAllPages ? 'Loading…' : 'Load remaining'} )} + {activeName && manifestLoadState === 'success' && ( + + )} {manifestBoundaryReached && hasMore && !ignoreManifestCache && ( @@ -3769,7 +3812,9 @@ export default function DataExplorer() { action={