fix: queue store stability + persistence (#5909)
* fix: queue store stability + persistence * fix: lint * feat: set to draft btn * feat: migrate to indexed db rather than local storage for moderation checklist storage (keep session + perms alone) * fix: storage cleanup + lint * fix: invalidation fixes
This commit is contained in:
@@ -754,10 +754,7 @@
|
||||
},
|
||||
{
|
||||
id: 'moderation-checklist',
|
||||
action: () => {
|
||||
moderationStore.setSingleProject(project.id)
|
||||
showModerationChecklist = true
|
||||
},
|
||||
action: openModerationChecklistFromMenu,
|
||||
color: 'orange',
|
||||
hoverOnly: true,
|
||||
shown:
|
||||
@@ -1046,7 +1043,7 @@
|
||||
>
|
||||
<ModerationChecklist
|
||||
:collapsed="collapsedModerationChecklist"
|
||||
@exit="showModerationChecklist = false"
|
||||
@exit="setModerationChecklistOpen(false)"
|
||||
@toggle-collapsed="collapsedModerationChecklist = !collapsedModerationChecklist"
|
||||
/>
|
||||
</div>
|
||||
@@ -1145,7 +1142,11 @@ import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
|
||||
import { versionQueryOptions } from '~/composables/queries/version'
|
||||
import { userCollectProject, userFollowProject } from '~/composables/user.js'
|
||||
import { useModerationStore } from '~/store/moderation.ts'
|
||||
import {
|
||||
loadChecklistOpenState,
|
||||
saveChecklistOpenState,
|
||||
} from '~/services/moderation-checklist-storage.ts'
|
||||
import { useModerationQueue } from '~/services/moderation-queue.ts'
|
||||
import { getReportPath, reportProject } from '~/utils/report-helpers.ts'
|
||||
|
||||
definePageMeta({
|
||||
@@ -1156,7 +1157,7 @@ const data = useNuxtApp()
|
||||
const route = useRoute()
|
||||
const signInRouteObj = computed(() => getSignInRouteObj(route))
|
||||
const config = useRuntimeConfig()
|
||||
const moderationStore = useModerationStore()
|
||||
const moderationQueue = useModerationQueue()
|
||||
const notifications = injectNotificationManager()
|
||||
const { addNotification } = notifications
|
||||
|
||||
@@ -2514,16 +2515,84 @@ async function copyPermalink() {
|
||||
|
||||
const collapsedChecklist = ref(false)
|
||||
|
||||
const showModerationChecklist = useLocalStorage(
|
||||
`show-moderation-checklist-${project.value?.id ?? 'unknown'}`,
|
||||
false,
|
||||
)
|
||||
const showModerationChecklist = ref(false)
|
||||
const collapsedModerationChecklist = useLocalStorage('collapsed-moderation-checklist', false)
|
||||
|
||||
if (import.meta.client && history && history.state && history.state.showChecklist) {
|
||||
showModerationChecklist.value = true
|
||||
function consumeShowChecklistHistoryState() {
|
||||
if (!import.meta.client) return false
|
||||
if (!window.history?.state?.showChecklist) return false
|
||||
|
||||
const state = { ...window.history.state }
|
||||
delete state.showChecklist
|
||||
window.history.replaceState(state, '', window.location.href)
|
||||
return true
|
||||
}
|
||||
|
||||
function setModerationChecklistOpen(open, projectId = project.value?.id) {
|
||||
showModerationChecklist.value = open
|
||||
if (projectId) {
|
||||
void saveChecklistOpenState(projectId, open)
|
||||
}
|
||||
}
|
||||
|
||||
function isProjectInActiveModerationQueue(projectId = project.value?.id) {
|
||||
return (
|
||||
!!projectId &&
|
||||
moderationQueue.isQueueMode &&
|
||||
moderationQueue.currentQueue.items.includes(projectId)
|
||||
)
|
||||
}
|
||||
|
||||
async function openModerationChecklistFromMenu() {
|
||||
const projectId = project.value?.id
|
||||
if (!projectId) return
|
||||
|
||||
await moderationQueue.ready
|
||||
if (!isProjectInActiveModerationQueue(projectId)) {
|
||||
await moderationQueue.setSingleProject(projectId)
|
||||
}
|
||||
|
||||
setModerationChecklistOpen(true)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => project.value?.id,
|
||||
async (projectId, _previousProjectId, onCleanup) => {
|
||||
if (!import.meta.client || !projectId) return
|
||||
|
||||
let cancelled = false
|
||||
onCleanup(() => {
|
||||
cancelled = true
|
||||
})
|
||||
|
||||
const openedFromNavigation = consumeShowChecklistHistoryState()
|
||||
await moderationQueue.ready
|
||||
if (cancelled) return
|
||||
|
||||
if (openedFromNavigation) {
|
||||
setModerationChecklistOpen(true)
|
||||
return
|
||||
}
|
||||
|
||||
const storedOpen = await loadChecklistOpenState(projectId)
|
||||
if (cancelled) return
|
||||
|
||||
if (storedOpen !== null) {
|
||||
showModerationChecklist.value = storedOpen
|
||||
return
|
||||
}
|
||||
|
||||
const shouldRecoverFromQueue =
|
||||
moderationQueue.isQueueMode && moderationQueue.getCurrentProjectId() === projectId
|
||||
showModerationChecklist.value = shouldRecoverFromQueue
|
||||
|
||||
if (shouldRecoverFromQueue) {
|
||||
void saveChecklistOpenState(projectId, true)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function closeDownloadModal(event) {
|
||||
downloadModal.value.hide(event)
|
||||
userSelectedPlatform.value = null
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
:set-status="setStatus"
|
||||
:current-member="currentMember"
|
||||
:auth="auth"
|
||||
@update-thread="(newThread) => (thread = newThread)"
|
||||
@update-thread="updateThread"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
@@ -132,6 +132,13 @@ const { data: thread } = useQuery({
|
||||
enabled: computed(() => !!project.value?.thread_id),
|
||||
})
|
||||
|
||||
function updateThread(newThread) {
|
||||
const threadId = newThread?.id ?? project.value?.thread_id
|
||||
if (!threadId) return
|
||||
|
||||
queryClient.setQueryData(['thread', threadId], newThread)
|
||||
}
|
||||
|
||||
async function setStatus(status) {
|
||||
startLoading()
|
||||
|
||||
|
||||
@@ -112,13 +112,13 @@ import ConfettiExplosion from 'vue-confetti-explosion'
|
||||
|
||||
import ModerationQueueCard from '~/components/ui/moderation/ModerationQueueCard.vue'
|
||||
import { enrichProjectBatch, type ModerationProject } from '~/helpers/moderation.ts'
|
||||
import { useModerationStore } from '~/store/moderation.ts'
|
||||
import { useModerationQueue } from '~/services/moderation-queue.ts'
|
||||
|
||||
useHead({ title: 'Projects queue - Modrinth' })
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const moderationStore = useModerationStore()
|
||||
const moderationQueue = useModerationQueue()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -331,18 +331,18 @@ function goToPage(page: number) {
|
||||
async function findFirstUnlockedProject(): Promise<ModerationProject | null> {
|
||||
let skippedCount = 0
|
||||
|
||||
while (moderationStore.hasItems) {
|
||||
const currentId = moderationStore.getCurrentProjectId()
|
||||
while (moderationQueue.hasItems) {
|
||||
const currentId = moderationQueue.getCurrentProjectId()
|
||||
if (!currentId) return null
|
||||
|
||||
const project = filteredProjects.value.find((p) => p.project.id === currentId)
|
||||
if (!project) {
|
||||
moderationStore.completeCurrentProject(currentId, 'skipped')
|
||||
await moderationQueue.completeCurrentProject(currentId, 'skipped')
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const lockStatus = await moderationStore.checkLock(currentId)
|
||||
const lockStatus = await moderationQueue.checkLock(currentId)
|
||||
|
||||
if (!lockStatus.locked || lockStatus.expired) {
|
||||
if (skippedCount > 0) {
|
||||
@@ -356,7 +356,7 @@ async function findFirstUnlockedProject(): Promise<ModerationProject | null> {
|
||||
}
|
||||
|
||||
// Project is locked, skip it
|
||||
moderationStore.completeCurrentProject(currentId, 'skipped')
|
||||
await moderationQueue.completeCurrentProject(currentId, 'skipped')
|
||||
skippedCount++
|
||||
} catch {
|
||||
return project
|
||||
@@ -371,7 +371,7 @@ async function moderateAllInFilter() {
|
||||
const startIndex = (currentPage.value - 1) * itemsPerPage
|
||||
const projectsFromCurrentPage = filteredProjects.value.slice(startIndex)
|
||||
const projectIds = projectsFromCurrentPage.map((queueItem) => queueItem.project.id)
|
||||
moderationStore.setQueue(projectIds)
|
||||
await moderationQueue.setQueue(projectIds)
|
||||
|
||||
// Find first unlocked project
|
||||
const targetProject = await findFirstUnlockedProject()
|
||||
@@ -402,12 +402,12 @@ async function startFromProject(projectId: string) {
|
||||
const projectIndex = filteredProjects.value.findIndex((p) => p.project.id === projectId)
|
||||
if (projectIndex === -1) {
|
||||
// Project not found in filtered list, just moderate it alone
|
||||
moderationStore.setSingleProject(projectId)
|
||||
await moderationQueue.setSingleProject(projectId)
|
||||
} else {
|
||||
// Start queue from this project onwards
|
||||
const projectsFromHere = filteredProjects.value.slice(projectIndex)
|
||||
const projectIds = projectsFromHere.map((queueItem) => queueItem.project.id)
|
||||
moderationStore.setQueue(projectIds)
|
||||
await moderationQueue.setQueue(projectIds)
|
||||
}
|
||||
|
||||
// Find first unlocked project
|
||||
|
||||
@@ -246,8 +246,14 @@ const reviewItem = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
function handleMarkComplete(_projectId: string) {
|
||||
queryClient.invalidateQueries({ queryKey: ['tech-reviews'] })
|
||||
async function handleMarkComplete(projectId: string) {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['tech-reviews'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['tech-review-project-report', projectId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['project', projectId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['project', 'v3', projectId] }),
|
||||
])
|
||||
}
|
||||
|
||||
const maliciousSummaryModalRef = ref<InstanceType<typeof MaliciousSummaryModal>>()
|
||||
|
||||
Reference in New Issue
Block a user