Skip to content
Open
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
16 changes: 13 additions & 3 deletions apps/app-frontend/src/pages/Browse.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const {
effectiveServerWorldId,
serverContextServerData,
serverContentProjectIds,
queuedServerInstallRootProjectIds,
queuedServerInstallProjectIds,
queuedServerInstallCount,
selectedServerInstallProjects,
Expand All @@ -148,6 +149,7 @@ const {
enforceSetupModpackRoute,
getQueuedServerInstallPlans,
setQueuedServerInstallPlans,
resolveQueuedServerInstallPlan,
openServerModpackInstallFlow,
onServerFlowBack,
handleServerModpackFlowCreate,
Expand Down Expand Up @@ -845,6 +847,7 @@ function getCardActions(
['modpack', 'mod', 'plugin', 'datapack'].includes(currentProjectType)
) {
const isQueued = queuedServerInstallProjectIds.value.has(projectResult.project_id)
const isQueuedRoot = queuedServerInstallRootProjectIds.value.has(projectResult.project_id)
const isInstallingSelection = isInstallingQueuedServerInstalls.value
const validatingInstall =
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
Expand Down Expand Up @@ -872,14 +875,19 @@ function getCardActions(
? CheckIcon
: PlusIcon,
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
disabled: showAsInstalled || isInstalling || isInstallingSelection,
disabled:
showAsInstalled ||
isInstalling ||
isInstallingSelection ||
(isQueued && !isQueuedRoot),
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
type: 'outlined',
onClick: async () => {
if (isQueued) {
if (isQueuedRoot) {
removeQueuedServerInstall(projectResult.project_id)
return
}
if (isQueued) return

const contentType = currentProjectType as BrowseInstallContentType
const isModpack = contentType === 'modpack'
Expand All @@ -888,7 +896,7 @@ function getCardActions(
setProjectInstalling(projectResult.project_id, true)
}
try {
await requestInstall({
const plan = await requestInstall({
project: projectResult,
contentType,
mode: isModpack ? 'immediate' : 'queue',
Expand All @@ -912,7 +920,9 @@ function getCardActions(
iconUrl: plan.project.icon_url ?? undefined,
}),
})
if (!isModpack) await resolveQueuedServerInstallPlan(plan)
} catch (err) {
if (!isModpack) removeQueuedServerInstall(projectResult.project_id)
handleError(err as Error)
} finally {
if (shouldShowInstalling) {
Expand Down
157 changes: 79 additions & 78 deletions apps/app-frontend/src/providers/setup/server-install-content.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import type { AbstractModrinthClient, Archon, Labrinth } from '@modrinth/api-client'
import type { Archon, Labrinth } from '@modrinth/api-client'
import {
addPendingServerContentInstalls,
type BrowseInstallPlan,
type BrowseSelectedProject,
createContext,
type CreationFlowContextValue,
flushStoredServerAddonInstallQueue,
getServerAddonInstallPlanProjectIds,
getStoredServerAddonInstallQueue,
getTargetInstallPreferences,
injectModrinthClient,
injectNotificationManager,
type ModpackSearchResult,
type PendingServerContentInstall,
type PendingServerContentInstallType,
readPendingServerContentInstalls,
readStoredServerInstallQueue,
removePendingServerContentInstall,
resolveServerAddonInstallPlans,
writePendingServerContentInstallBaseline,
writeStoredServerInstallQueue,
} from '@modrinth/ui'
Expand Down Expand Up @@ -54,6 +56,7 @@ export interface ServerInstallContentContext {
effectiveServerWorldId: ComputedRef<string | null>
serverContextServerData: Ref<Archon.Servers.v0.Server | null>
serverContentProjectIds: Ref<Set<string>>
queuedServerInstallRootProjectIds: ComputedRef<Set<string>>
queuedServerInstallProjectIds: ComputedRef<Set<string>>
queuedServerInstallCount: ComputedRef<number>
selectedServerInstallProjects: ComputedRef<BrowseSelectedProject[]>
Expand All @@ -76,6 +79,9 @@ export interface ServerInstallContentContext {
setQueuedServerInstallPlans: (
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
) => void
resolveQueuedServerInstallPlan: (
plan: BrowseInstallPlan<InstallableSearchResult>,
) => Promise<void>
openServerModpackInstallFlow: (request: ServerModpackSelectionRequest) => Promise<void>
onServerFlowBack: () => void
handleServerModpackFlowCreate: (config: CreationFlowContextValue) => Promise<void>
Expand Down Expand Up @@ -112,48 +118,6 @@ function getQueuedInstallOwnerFallback(project: InstallableSearchResult) {
}
}

async function getQueuedInstallOwner(
client: AbstractModrinthClient,
project: InstallableSearchResult,
) {
const fallback = getQueuedInstallOwnerFallback(project)

try {
if (project.organization) {
const organization = await client.labrinth.projects_v3.getOrganization(project.project_id)
if (organization) {
return {
id: organization.id,
name: organization.name,
type: 'organization' as const,
avatar_url: organization.icon_url ?? undefined,
link: `https://modrinth.com/organization/${organization.slug}`,
}
}
}

const members = await client.labrinth.projects_v3.getMembers(project.project_id)
const owner =
members.find((member) => member.user.id === project.author_id)?.user ??
members.find((member) => member.is_owner || member.role === 'Owner')?.user ??
members[0]?.user

if (owner) {
return {
id: owner.id,
name: owner.username,
type: 'user' as const,
avatar_url: owner.avatar_url,
link: `/user/${encodeURIComponent(owner.username)}`,
}
}
} catch {
return fallback
}

return fallback
}

function getQueuedAddonInstallPlans(
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
) {
Expand Down Expand Up @@ -187,17 +151,6 @@ function getQueuedInstallPlaceholderFallbacks(
)
}

async function getQueuedInstallPlaceholders(
client: AbstractModrinthClient,
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
) {
return Promise.all(
getQueuedAddonInstallPlans(plans).map(async (plan) =>
getQueuedInstallPlaceholder(plan, await getQueuedInstallOwner(client, plan.project)),
),
)
}

export function createServerInstallContent(opts: {
serverSetupModalRef: Ref<ServerSetupModalHandle | null>
}) {
Expand Down Expand Up @@ -228,7 +181,12 @@ export function createServerInstallContent(opts: {
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<InstallableSearchResult>>>(
new Map(),
)
const queuedServerInstallProjectIds = computed(() => new Set(queuedServerInstalls.value.keys()))
const queuedServerInstallRootProjectIds = computed(
() => new Set(queuedServerInstalls.value.keys()),
)
const queuedServerInstallProjectIds = computed(() =>
getServerAddonInstallPlanProjectIds(queuedServerInstalls.value.values()),
)
const queuedServerInstallCount = computed(() => queuedServerInstalls.value.size)
const selectedServerInstallProjects = computed<BrowseSelectedProject[]>(() =>
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
Expand Down Expand Up @@ -426,6 +384,61 @@ export function createServerInstallContent(opts: {
writeStoredServerInstallQueue(serverId, worldId, plans)
}

function toResolvePreferences(
preferences?: BrowseInstallPlan<InstallableSearchResult>['preferences'],
): Labrinth.Content.v3.ResolutionPreferences {
return {
game_versions: preferences?.gameVersions,
loaders: preferences?.loaders,
}
}

async function resolveAddonPlan(
plan: BrowseInstallPlan<InstallableSearchResult>,
existingProjectIds: string[],
) {
const target = getTargetInstallPreferences(
{
gameVersion: serverContextServerData.value?.mc_version,
loader: serverContextServerData.value?.loader,
},
plan.contentType,
)
const resolved = await client.labrinth.content_v3.resolve({
project_id: plan.projectId,
version_id: plan.versionId,
content_type: plan.contentType as Labrinth.Content.v3.ContentType,
selected: toResolvePreferences(plan.preferences),
target: toResolvePreferences(target),
existing_project_ids: existingProjectIds,
})

return [resolved.primary, ...resolved.dependencies].map((item) => ({
projectId: item.project_id,
versionId: item.version_id,
}))
}

async function resolveQueuedServerInstallPlan(
plan: BrowseInstallPlan<InstallableSearchResult>,
) {
const resolvedContent = await resolveAddonPlan(plan, Array.from(serverContentProjectIds.value))
const storedPlan = queuedServerInstalls.value.get(plan.projectId)
if (!storedPlan || storedPlan.versionId !== plan.versionId) return

const nextPlans = new Map(queuedServerInstalls.value)
nextPlans.set(plan.projectId, { ...storedPlan, resolvedContent })
setQueuedServerInstallPlans(nextPlans)
}

async function resolveQueuedAddonPlans(plans: BrowseInstallPlan<InstallableSearchResult>[]) {
return await resolveServerAddonInstallPlans({
plans,
existingProjectIds: serverContentProjectIds.value,
resolvePlan: resolveAddonPlan,
})
}

async function flushQueuedServerInstalls(
serverId: string | null = serverIdQuery.value,
worldId: string | null = effectiveServerWorldId.value,
Expand All @@ -450,15 +463,12 @@ export function createServerInstallContent(opts: {
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,
})),
),
install: async (plans) => {
const addons = await resolveQueuedAddonPlans(plans)
if (addons.length > 0) {
await client.archon.content_v1.addAddons(serverId, worldId, addons)
}
},
onQueueChange: (plans) => setStoredServerInstallPlans(serverId, worldId, plans),
})

Expand Down Expand Up @@ -508,21 +518,10 @@ export function createServerInstallContent(opts: {
writeStoredServerInstallQueue(sid, wid, plans)
writePendingServerContentInstallBaseline(sid, wid, serverContentInstallKeys.value)
addPendingServerContentInstalls(sid, wid, getQueuedInstallPlaceholderFallbacks(plans))
void getQueuedInstallPlaceholders(client, plans)
.then((items) => {
const pendingProjectIds = new Set(
readPendingServerContentInstalls(sid, wid).map((item) => item.projectId),
)
addPendingServerContentInstalls(
sid,
wid,
items.filter((item) => pendingProjectIds.has(item.projectId)),
)
})
.catch((err) => handleError(err as Error))
}
const installed = await flushQueuedServerInstalls(sid, wid)
if (!installed) return false
await router.push(backUrl)
void flushQueuedServerInstalls(sid, wid)

return true
}
Expand Down Expand Up @@ -591,6 +590,7 @@ export function createServerInstallContent(opts: {
effectiveServerWorldId,
serverContextServerData,
serverContentProjectIds,
queuedServerInstallRootProjectIds,
queuedServerInstallProjectIds,
queuedServerInstallCount,
selectedServerInstallProjects,
Expand All @@ -611,6 +611,7 @@ export function createServerInstallContent(opts: {
enforceSetupModpackRoute,
getQueuedServerInstallPlans,
setQueuedServerInstallPlans,
resolveQueuedServerInstallPlan,
openServerModpackInstallFlow,
onServerFlowBack,
handleServerModpackFlowCreate,
Expand Down
Loading
Loading