Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 58 additions & 83 deletions apps/app-frontend/src/providers/setup/server-install-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import {
type BrowseSelectedProject,
createContext,
type CreationFlowContextValue,
flushInstallQueue,
flushStoredServerAddonInstallQueue,
getStoredServerAddonInstallQueue,
injectModrinthClient,
injectNotificationManager,
type PendingServerContentInstall,
type PendingServerContentInstallType,
readPendingServerContentInstalls,
readStoredServerInstallQueue,
removePendingServerContentInstall,
writePendingServerContentInstallBaseline,
writeStoredServerInstallQueue,
} from '@modrinth/ui'
import { computed, type ComputedRef, nextTick, type Ref, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
Expand Down Expand Up @@ -89,37 +92,6 @@ function readQueryString(value: unknown): string | null {
return typeof value === 'string' && value.length > 0 ? value : null
}

function getQueueStorageKey(serverId: string | null, worldId: string | null) {
if (!serverId || !worldId) return null
return `server-install-queue:${serverId}:${worldId}`
}

function readStoredQueue(serverId: string | null, worldId: string | null) {
const key = getQueueStorageKey(serverId, worldId)
if (!key) return new Map<string, BrowseInstallPlan<InstallableSearchResult>>()
try {
const raw = localStorage.getItem(key)
if (!raw) return new Map<string, BrowseInstallPlan<InstallableSearchResult>>()
return new Map<string, BrowseInstallPlan<InstallableSearchResult>>(JSON.parse(raw))
} catch {
return new Map<string, BrowseInstallPlan<InstallableSearchResult>>()
}
}

function writeStoredQueue(
serverId: string | null,
worldId: string | null,
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
) {
const key = getQueueStorageKey(serverId, worldId)
if (!key) return
if (plans.size === 0) {
localStorage.removeItem(key)
return
}
localStorage.setItem(key, JSON.stringify(Array.from(plans.entries())))
}

function getQueuedInstallOwnerFallback(project: InstallableSearchResult) {
if (project.organization) {
const ownerId = project.organization_id ?? project.organization
Expand Down Expand Up @@ -235,7 +207,7 @@ export function createServerInstallContent(opts: {
const route = useRoute()
const router = useRouter()
const client = injectModrinthClient()
const { addNotification, handleError } = injectNotificationManager()
const { handleError } = injectNotificationManager()

const serverIdQuery = computed(() => readQueryString(route.query.sid))
const worldIdQuery = computed(() => readQueryString(route.query.wid))
Expand Down Expand Up @@ -340,7 +312,7 @@ export function createServerInstallContent(opts: {
}

if (resolvedWorldId) {
queuedServerInstalls.value = readStoredQueue(sid, resolvedWorldId)
queuedServerInstalls.value = readStoredServerInstallQueue(sid, resolvedWorldId)
await refreshServerInstalledContent(sid, resolvedWorldId)
}
}
Expand All @@ -358,7 +330,7 @@ export function createServerInstallContent(opts: {
if (sid !== prevSid) {
serverContentProjectIds.value = new Set()
serverContentInstallKeys.value = new Set()
queuedServerInstalls.value = readStoredQueue(sid, wid)
queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid)
try {
serverContextServerData.value = await client.archon.servers_v0.get(sid)
} catch (err) {
Expand All @@ -367,7 +339,7 @@ export function createServerInstallContent(opts: {
}

if (wid !== prevWid) {
queuedServerInstalls.value = readStoredQueue(sid, wid)
queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid)
}

if (wid && (sid !== prevSid || wid !== prevWid)) {
Expand Down Expand Up @@ -432,62 +404,75 @@ export function createServerInstallContent(opts: {
setQueuedServerInstallPlans(nextPlans)
}

function setStoredServerInstallPlans(
serverId: string,
worldId: string,
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
) {
if (serverId === serverIdQuery.value && worldId === effectiveServerWorldId.value) {
queuedServerInstalls.value = plans
}
writeStoredServerInstallQueue(serverId, worldId, plans)
}

async function flushQueuedServerInstalls(
serverId: string | null = serverIdQuery.value,
worldId: string | null = effectiveServerWorldId.value,
) {
if (queuedServerInstalls.value.size === 0) return true
if (isInstallingQueuedServerInstalls.value) return false

if (!serverId || !worldId) {
handleError(new Error('No server world is available for install.'))
return false
}

const installedProjectIds = new Set<string>()
const queuedPlans = getStoredServerAddonInstallQueue<InstallableSearchResult>(serverId, worldId)
if (queuedPlans.size === 0) return true

isInstallingQueuedServerInstalls.value = true
queuedInstallProgress.value = {
completed: 0,
total: queuedServerInstalls.value.size,
total: queuedPlans.size,
}

try {
const result = await flushInstallQueue({
queue: {
get: () => queuedServerInstalls.value,
set: (plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>) => {
queuedServerInstalls.value = plans
writeStoredQueue(serverId, worldId, plans)
},
},
install: async (plan) => {
await client.archon.content_v1.addAddon(serverId, worldId, {
project_id: plan.projectId,
version_id: plan.versionId,
})
installedProjectIds.add(plan.projectId)
},
onError: (error, plan) => {
removePendingServerContentInstall(serverId, worldId, plan.projectId)
handleError(error as Error)
},
onProgress: (completed, total) => {
queuedInstallProgress.value = { completed, total }
},
const result = await flushStoredServerAddonInstallQueue({
serverId,
worldId,
install: (plans) =>
client.archon.content_v1.addAddons(
serverId,
worldId,
plans.map((plan) => ({
project_id: plan.projectId,
version_id: plan.versionId,
})),
),
onQueueChange: (plans) => setStoredServerInstallPlans(serverId, worldId, plans),
})

if (installedProjectIds.size > 0) {
serverContentProjectIds.value = new Set([
...serverContentProjectIds.value,
...installedProjectIds,
])
serverContentInstallKeys.value = new Set([
...serverContentInstallKeys.value,
...installedProjectIds,
])
if (!result.ok) {
for (const plan of result.attemptedPlans) {
removePendingServerContentInstall(serverId, worldId, plan.projectId)
}
handleError(result.error as Error)
return false
}

return result.ok
queuedInstallProgress.value = {
completed: result.flushedPlans.length,
total: result.flushedPlans.length,
}
serverContentProjectIds.value = new Set([
...serverContentProjectIds.value,
...result.flushedPlans.map((plan) => plan.projectId),
])
serverContentInstallKeys.value = new Set([
...serverContentInstallKeys.value,
...result.flushedPlans.map((plan) => plan.projectId),
])

return true
} finally {
isInstallingQueuedServerInstalls.value = false
queuedInstallProgress.value = { completed: 0, total: 0 }
Expand Down Expand Up @@ -522,17 +507,7 @@ export function createServerInstallContent(opts: {
.catch((err) => handleError(err as Error))
}
await router.push(backUrl)

const ok = await flushQueuedServerInstalls(sid, wid)
if (!ok) {
queuedServerInstalls.value = new Map()
writeStoredQueue(sid, wid, new Map())
addNotification({
type: 'error',
title: 'Some projects failed to install',
text: 'Failed projects were not added. You can try installing them again.',
})
}
void flushQueuedServerInstalls(sid, wid)

return true
}
Expand All @@ -545,7 +520,7 @@ export function createServerInstallContent(opts: {
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
) {
queuedServerInstalls.value = plans
writeStoredQueue(serverIdQuery.value, effectiveServerWorldId.value, plans)
writeStoredServerInstallQueue(serverIdQuery.value, effectiveServerWorldId.value, plans)
}

function onServerFlowBack() {
Expand Down
Loading
Loading