mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: content upload session impl (#6992)
* feat: content upload session impl * fix: fixes * fix: prepr
This commit is contained in:
@@ -123,6 +123,7 @@ const {
|
|||||||
effectiveServerWorldId,
|
effectiveServerWorldId,
|
||||||
serverContextServerData,
|
serverContextServerData,
|
||||||
serverContentProjectIds,
|
serverContentProjectIds,
|
||||||
|
queuedServerInstallRootProjectIds,
|
||||||
queuedServerInstallProjectIds,
|
queuedServerInstallProjectIds,
|
||||||
queuedServerInstallCount,
|
queuedServerInstallCount,
|
||||||
selectedServerInstallProjects,
|
selectedServerInstallProjects,
|
||||||
@@ -143,6 +144,7 @@ const {
|
|||||||
enforceSetupModpackRoute,
|
enforceSetupModpackRoute,
|
||||||
getQueuedServerInstallPlans,
|
getQueuedServerInstallPlans,
|
||||||
setQueuedServerInstallPlans,
|
setQueuedServerInstallPlans,
|
||||||
|
resolveQueuedServerInstallPlan,
|
||||||
openServerModpackInstallFlow,
|
openServerModpackInstallFlow,
|
||||||
onServerFlowBack,
|
onServerFlowBack,
|
||||||
handleServerModpackFlowCreate,
|
handleServerModpackFlowCreate,
|
||||||
@@ -870,6 +872,7 @@ function getCardActions(
|
|||||||
['modpack', 'mod', 'plugin', 'datapack'].includes(currentProjectType)
|
['modpack', 'mod', 'plugin', 'datapack'].includes(currentProjectType)
|
||||||
) {
|
) {
|
||||||
const isQueued = queuedServerInstallProjectIds.value.has(projectResult.project_id)
|
const isQueued = queuedServerInstallProjectIds.value.has(projectResult.project_id)
|
||||||
|
const isQueuedRoot = queuedServerInstallRootProjectIds.value.has(projectResult.project_id)
|
||||||
const isInstallingSelection = isInstallingQueuedServerInstalls.value
|
const isInstallingSelection = isInstallingQueuedServerInstalls.value
|
||||||
const validatingInstall =
|
const validatingInstall =
|
||||||
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
|
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
|
||||||
@@ -897,14 +900,16 @@ function getCardActions(
|
|||||||
? CheckIcon
|
? CheckIcon
|
||||||
: PlusIcon,
|
: PlusIcon,
|
||||||
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
|
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
|
||||||
disabled: showAsInstalled || isInstalling || isInstallingSelection,
|
disabled:
|
||||||
|
showAsInstalled || isInstalling || isInstallingSelection || (isQueued && !isQueuedRoot),
|
||||||
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
|
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
|
||||||
type: 'outlined',
|
type: 'outlined',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
if (isQueued) {
|
if (isQueuedRoot) {
|
||||||
removeQueuedServerInstall(projectResult.project_id)
|
removeQueuedServerInstall(projectResult.project_id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (isQueued) return
|
||||||
|
|
||||||
const contentType = currentProjectType as BrowseInstallContentType
|
const contentType = currentProjectType as BrowseInstallContentType
|
||||||
const isModpack = contentType === 'modpack'
|
const isModpack = contentType === 'modpack'
|
||||||
@@ -913,7 +918,7 @@ function getCardActions(
|
|||||||
setProjectInstalling(projectResult.project_id, true)
|
setProjectInstalling(projectResult.project_id, true)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await requestInstall({
|
const plan = await requestInstall({
|
||||||
project: projectResult,
|
project: projectResult,
|
||||||
contentType,
|
contentType,
|
||||||
mode: isModpack ? 'immediate' : 'queue',
|
mode: isModpack ? 'immediate' : 'queue',
|
||||||
@@ -937,7 +942,9 @@ function getCardActions(
|
|||||||
iconUrl: plan.project.icon_url ?? undefined,
|
iconUrl: plan.project.icon_url ?? undefined,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
if (!isModpack) await resolveQueuedServerInstallPlan(plan)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (!isModpack) removeQueuedServerInstall(projectResult.project_id)
|
||||||
handleError(err as Error)
|
handleError(err as Error)
|
||||||
} finally {
|
} finally {
|
||||||
if (shouldShowInstalling) {
|
if (shouldShowInstalling) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { AbstractModrinthClient, Archon, Labrinth } from '@modrinth/api-client'
|
import type { Archon, Labrinth } from '@modrinth/api-client'
|
||||||
import {
|
import {
|
||||||
addPendingServerContentInstalls,
|
addPendingServerContentInstalls,
|
||||||
type BrowseInstallPlan,
|
type BrowseInstallPlan,
|
||||||
@@ -6,15 +6,17 @@ import {
|
|||||||
createContext,
|
createContext,
|
||||||
type CreationFlowContextValue,
|
type CreationFlowContextValue,
|
||||||
flushStoredServerAddonInstallQueue,
|
flushStoredServerAddonInstallQueue,
|
||||||
|
getServerAddonInstallPlanProjectIds,
|
||||||
getStoredServerAddonInstallQueue,
|
getStoredServerAddonInstallQueue,
|
||||||
|
getTargetInstallPreferences,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
type ModpackSearchResult,
|
type ModpackSearchResult,
|
||||||
type PendingServerContentInstall,
|
type PendingServerContentInstall,
|
||||||
type PendingServerContentInstallType,
|
type PendingServerContentInstallType,
|
||||||
readPendingServerContentInstalls,
|
|
||||||
readStoredServerInstallQueue,
|
readStoredServerInstallQueue,
|
||||||
removePendingServerContentInstall,
|
removePendingServerContentInstall,
|
||||||
|
resolveServerAddonInstallPlans,
|
||||||
writePendingServerContentInstallBaseline,
|
writePendingServerContentInstallBaseline,
|
||||||
writeStoredServerInstallQueue,
|
writeStoredServerInstallQueue,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
@@ -54,6 +56,7 @@ export interface ServerInstallContentContext {
|
|||||||
effectiveServerWorldId: ComputedRef<string | null>
|
effectiveServerWorldId: ComputedRef<string | null>
|
||||||
serverContextServerData: Ref<Archon.Servers.v0.Server | null>
|
serverContextServerData: Ref<Archon.Servers.v0.Server | null>
|
||||||
serverContentProjectIds: Ref<Set<string>>
|
serverContentProjectIds: Ref<Set<string>>
|
||||||
|
queuedServerInstallRootProjectIds: ComputedRef<Set<string>>
|
||||||
queuedServerInstallProjectIds: ComputedRef<Set<string>>
|
queuedServerInstallProjectIds: ComputedRef<Set<string>>
|
||||||
queuedServerInstallCount: ComputedRef<number>
|
queuedServerInstallCount: ComputedRef<number>
|
||||||
selectedServerInstallProjects: ComputedRef<BrowseSelectedProject[]>
|
selectedServerInstallProjects: ComputedRef<BrowseSelectedProject[]>
|
||||||
@@ -76,6 +79,9 @@ export interface ServerInstallContentContext {
|
|||||||
setQueuedServerInstallPlans: (
|
setQueuedServerInstallPlans: (
|
||||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||||
) => void
|
) => void
|
||||||
|
resolveQueuedServerInstallPlan: (
|
||||||
|
plan: BrowseInstallPlan<InstallableSearchResult>,
|
||||||
|
) => Promise<void>
|
||||||
openServerModpackInstallFlow: (request: ServerModpackSelectionRequest) => Promise<void>
|
openServerModpackInstallFlow: (request: ServerModpackSelectionRequest) => Promise<void>
|
||||||
onServerFlowBack: () => void
|
onServerFlowBack: () => void
|
||||||
handleServerModpackFlowCreate: (config: CreationFlowContextValue) => Promise<void>
|
handleServerModpackFlowCreate: (config: CreationFlowContextValue) => Promise<void>
|
||||||
@@ -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(
|
function getQueuedAddonInstallPlans(
|
||||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||||
) {
|
) {
|
||||||
@@ -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: {
|
export function createServerInstallContent(opts: {
|
||||||
serverSetupModalRef: Ref<ServerSetupModalHandle | null>
|
serverSetupModalRef: Ref<ServerSetupModalHandle | null>
|
||||||
}) {
|
}) {
|
||||||
@@ -250,7 +203,12 @@ export function createServerInstallContent(opts: {
|
|||||||
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<InstallableSearchResult>>>(
|
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<InstallableSearchResult>>>(
|
||||||
new Map(),
|
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 queuedServerInstallCount = computed(() => queuedServerInstalls.value.size)
|
||||||
const selectedServerInstallProjects = computed<BrowseSelectedProject[]>(() =>
|
const selectedServerInstallProjects = computed<BrowseSelectedProject[]>(() =>
|
||||||
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
|
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
|
||||||
@@ -462,6 +420,59 @@ export function createServerInstallContent(opts: {
|
|||||||
writeStoredServerInstallQueue(serverId, worldId, plans)
|
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(
|
async function flushQueuedServerInstalls(
|
||||||
serverId: string | null = serverIdQuery.value,
|
serverId: string | null = serverIdQuery.value,
|
||||||
worldId: string | null = effectiveServerWorldId.value,
|
worldId: string | null = effectiveServerWorldId.value,
|
||||||
@@ -486,15 +497,12 @@ export function createServerInstallContent(opts: {
|
|||||||
const result = await flushStoredServerAddonInstallQueue({
|
const result = await flushStoredServerAddonInstallQueue({
|
||||||
serverId,
|
serverId,
|
||||||
worldId,
|
worldId,
|
||||||
install: (plans) =>
|
install: async (plans) => {
|
||||||
client.archon.content_v1.addAddons(
|
const addons = await resolveQueuedAddonPlans(plans)
|
||||||
serverId,
|
if (addons.length > 0) {
|
||||||
worldId,
|
await client.archon.content_v1.addAddons(serverId, worldId, addons)
|
||||||
plans.map((plan) => ({
|
}
|
||||||
project_id: plan.projectId,
|
},
|
||||||
version_id: plan.versionId,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
onQueueChange: (plans) => setStoredServerInstallPlans(serverId, worldId, plans),
|
onQueueChange: (plans) => setStoredServerInstallPlans(serverId, worldId, plans),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -544,21 +552,10 @@ export function createServerInstallContent(opts: {
|
|||||||
writeStoredServerInstallQueue(sid, wid, plans)
|
writeStoredServerInstallQueue(sid, wid, plans)
|
||||||
writePendingServerContentInstallBaseline(sid, wid, serverContentInstallKeys.value)
|
writePendingServerContentInstallBaseline(sid, wid, serverContentInstallKeys.value)
|
||||||
addPendingServerContentInstalls(sid, wid, getQueuedInstallPlaceholderFallbacks(plans))
|
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)
|
await router.push(backUrl)
|
||||||
void flushQueuedServerInstalls(sid, wid)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -627,6 +624,7 @@ export function createServerInstallContent(opts: {
|
|||||||
effectiveServerWorldId,
|
effectiveServerWorldId,
|
||||||
serverContextServerData,
|
serverContextServerData,
|
||||||
serverContentProjectIds,
|
serverContentProjectIds,
|
||||||
|
queuedServerInstallRootProjectIds,
|
||||||
queuedServerInstallProjectIds,
|
queuedServerInstallProjectIds,
|
||||||
queuedServerInstallCount,
|
queuedServerInstallCount,
|
||||||
selectedServerInstallProjects,
|
selectedServerInstallProjects,
|
||||||
@@ -647,6 +645,7 @@ export function createServerInstallContent(opts: {
|
|||||||
enforceSetupModpackRoute,
|
enforceSetupModpackRoute,
|
||||||
getQueuedServerInstallPlans,
|
getQueuedServerInstallPlans,
|
||||||
setQueuedServerInstallPlans,
|
setQueuedServerInstallPlans,
|
||||||
|
resolveQueuedServerInstallPlan,
|
||||||
openServerModpackInstallFlow,
|
openServerModpackInstallFlow,
|
||||||
onServerFlowBack,
|
onServerFlowBack,
|
||||||
handleServerModpackFlowCreate,
|
handleServerModpackFlowCreate,
|
||||||
|
|||||||
@@ -14,14 +14,15 @@ import {
|
|||||||
commonMessages,
|
commonMessages,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
flushStoredServerAddonInstallQueue,
|
flushStoredServerAddonInstallQueue,
|
||||||
|
getServerAddonInstallPlanProjectIds,
|
||||||
getStoredServerAddonInstallQueue,
|
getStoredServerAddonInstallQueue,
|
||||||
getTargetInstallPreferences,
|
getTargetInstallPreferences,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
readPendingServerContentInstalls,
|
|
||||||
readStoredServerInstallQueue,
|
readStoredServerInstallQueue,
|
||||||
removePendingServerContentInstall,
|
removePendingServerContentInstall,
|
||||||
requestInstall,
|
requestInstall,
|
||||||
|
resolveServerAddonInstallPlans,
|
||||||
stripServerRuntimeInstallFilters,
|
stripServerRuntimeInstallFilters,
|
||||||
stripServerRuntimeInstallOverrides,
|
stripServerRuntimeInstallOverrides,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
@@ -177,7 +178,12 @@ export function useServerInstallContent({
|
|||||||
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<ServerInstallSearchResult>>>(
|
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<ServerInstallSearchResult>>>(
|
||||||
readStoredServerInstallQueue(currentServerId.value, currentWorldId.value),
|
readStoredServerInstallQueue(currentServerId.value, currentWorldId.value),
|
||||||
)
|
)
|
||||||
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 queuedServerInstallCount = computed(() => queuedServerInstalls.value.size)
|
||||||
const selectedServerInstallProjects = computed(() =>
|
const selectedServerInstallProjects = computed(() =>
|
||||||
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
|
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
|
||||||
@@ -219,45 +225,6 @@ export function useServerInstallContent({
|
|||||||
writeStoredServerInstallQueue(serverId, worldId, plans)
|
writeStoredServerInstallQueue(serverId, worldId, plans)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getQueuedInstallOwner(project: ServerInstallSearchResult) {
|
|
||||||
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: `/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/${owner.username}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQueuedInstallPlaceholder(
|
function getQueuedInstallPlaceholder(
|
||||||
plan: BrowseInstallPlan<ServerInstallSearchResult>,
|
plan: BrowseInstallPlan<ServerInstallSearchResult>,
|
||||||
owner: PendingServerContentInstallInput['owner'],
|
owner: PendingServerContentInstallInput['owner'],
|
||||||
@@ -284,16 +251,6 @@ export function useServerInstallContent({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getQueuedInstallPlaceholders(
|
|
||||||
plans: Map<string, BrowseInstallPlan<ServerInstallSearchResult>>,
|
|
||||||
) {
|
|
||||||
return Promise.all(
|
|
||||||
getQueuedAddonInstallPlans(plans).map(async (plan) =>
|
|
||||||
getQueuedInstallPlaceholder(plan, await getQueuedInstallOwner(plan.project)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setProjectInstalling(projectId: string, installing: boolean) {
|
function setProjectInstalling(projectId: string, installing: boolean) {
|
||||||
const next = new Set(installingProjectIds.value)
|
const next = new Set(installingProjectIds.value)
|
||||||
if (installing) {
|
if (installing) {
|
||||||
@@ -437,32 +394,43 @@ export function useServerInstallContent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveAddonPlan(
|
||||||
|
plan: BrowseInstallPlan<ServerInstallSearchResult>,
|
||||||
|
existingProjectIds: string[],
|
||||||
|
) {
|
||||||
|
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(getServerInstallTargetPreferences(plan.contentType)),
|
||||||
|
existing_project_ids: existingProjectIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
return [resolved.primary, ...resolved.dependencies].map((item) => ({
|
||||||
|
projectId: item.project_id,
|
||||||
|
versionId: item.version_id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveAndStoreQueuedAddonPlan(
|
||||||
|
plan: BrowseInstallPlan<ServerInstallSearchResult>,
|
||||||
|
) {
|
||||||
|
const resolvedContent = await resolveAddonPlan(plan, Array.from(getServerInstalledProjectIds()))
|
||||||
|
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 })
|
||||||
|
serverInstallQueue.set(nextPlans)
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveQueuedAddonPlans(plans: BrowseInstallPlan<ServerInstallSearchResult>[]) {
|
async function resolveQueuedAddonPlans(plans: BrowseInstallPlan<ServerInstallSearchResult>[]) {
|
||||||
const existingProjectIds = getServerInstalledProjectIds()
|
return await resolveServerAddonInstallPlans({
|
||||||
const resolvedAddons: Array<{ project_id: string; version_id: string }> = []
|
plans,
|
||||||
|
existingProjectIds: getServerInstalledProjectIds(),
|
||||||
for (const plan of plans) {
|
resolvePlan: resolveAddonPlan,
|
||||||
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(getServerInstallTargetPreferences(plan.contentType)),
|
|
||||||
existing_project_ids: Array.from(existingProjectIds),
|
|
||||||
})
|
|
||||||
const content = [resolved.primary, ...resolved.dependencies]
|
|
||||||
|
|
||||||
for (const item of content) {
|
|
||||||
if (existingProjectIds.has(item.project_id)) continue
|
|
||||||
existingProjectIds.add(item.project_id)
|
|
||||||
resolvedAddons.push({
|
|
||||||
project_id: item.project_id,
|
|
||||||
version_id: item.version_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvedAddons
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInstallProjectVersions(projectId: string) {
|
function getInstallProjectVersions(projectId: string) {
|
||||||
@@ -533,10 +501,9 @@ export function useServerInstallContent({
|
|||||||
total: result.flushedPlans.length,
|
total: result.flushedPlans.length,
|
||||||
}
|
}
|
||||||
if (result.flushedPlans.length > 0) {
|
if (result.flushedPlans.length > 0) {
|
||||||
await Promise.all([
|
await queryClient.invalidateQueries({
|
||||||
queryClient.invalidateQueries({ queryKey: ['content', 'list', 'v1', serverId] }),
|
queryKey: ['content', 'list', 'v1', serverId],
|
||||||
queryClient.invalidateQueries({ queryKey: ['content', 'list'] }),
|
})
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -564,21 +531,10 @@ export function useServerInstallContent({
|
|||||||
...optimisticallyInstalledProjectIds.value,
|
...optimisticallyInstalledProjectIds.value,
|
||||||
])
|
])
|
||||||
addPendingServerContentInstalls(sid, wid, getQueuedInstallPlaceholderFallbacks(plans))
|
addPendingServerContentInstalls(sid, wid, getQueuedInstallPlaceholderFallbacks(plans))
|
||||||
void getQueuedInstallPlaceholders(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 navigateTo(backUrl)
|
await navigateTo(backUrl)
|
||||||
void flushQueuedServerInstalls(sid, wid)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -598,16 +554,17 @@ export function useServerInstallContent({
|
|||||||
const isModpack = contentType === 'modpack'
|
const isModpack = contentType === 'modpack'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!isModpack && queuedServerInstallProjectIds.value.has(project.project_id)) {
|
if (!isModpack && queuedServerInstallRootProjectIds.value.has(project.project_id)) {
|
||||||
removeQueuedServerInstall(project.project_id)
|
removeQueuedServerInstall(project.project_id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!isModpack && queuedServerInstallProjectIds.value.has(project.project_id)) return
|
||||||
|
|
||||||
if (isModpack || !queuedServerInstallProjectIds.value.has(project.project_id)) {
|
if (isModpack || !queuedServerInstallProjectIds.value.has(project.project_id)) {
|
||||||
setProjectInstalling(project.project_id, true)
|
setProjectInstalling(project.project_id, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
await requestInstall({
|
const plan = await requestInstall({
|
||||||
project,
|
project,
|
||||||
contentType,
|
contentType,
|
||||||
mode: isModpack ? 'immediate' : 'queue',
|
mode: isModpack ? 'immediate' : 'queue',
|
||||||
@@ -646,10 +603,13 @@ export function useServerInstallContent({
|
|||||||
ctx.modal.value?.setStage('final-config')
|
ctx.modal.value?.setStage('final-config')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if (!isModpack) await resolveAndStoreQueuedAddonPlan(plan)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
if (isModpack) {
|
if (isModpack) {
|
||||||
setProjectInstalling(project.project_id, false)
|
setProjectInstalling(project.project_id, false)
|
||||||
|
} else {
|
||||||
|
removeQueuedServerInstall(project.project_id)
|
||||||
}
|
}
|
||||||
handleError(e instanceof Error ? e : new Error(`Error installing content ${e}`))
|
handleError(e instanceof Error ? e : new Error(`Error installing content ${e}`))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -804,6 +764,7 @@ export function useServerInstallContent({
|
|||||||
hideSelectedServerInstalls,
|
hideSelectedServerInstalls,
|
||||||
installingProjectIds,
|
installingProjectIds,
|
||||||
optimisticallyInstalledProjectIds,
|
optimisticallyInstalledProjectIds,
|
||||||
|
queuedServerInstallRootProjectIds,
|
||||||
queuedServerInstallProjectIds,
|
queuedServerInstallProjectIds,
|
||||||
queuedServerInstallCount,
|
queuedServerInstallCount,
|
||||||
isInstallingQueuedServerInstalls,
|
isInstallingQueuedServerInstalls,
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ const {
|
|||||||
hideSelectedServerInstalls,
|
hideSelectedServerInstalls,
|
||||||
installingProjectIds,
|
installingProjectIds,
|
||||||
optimisticallyInstalledProjectIds,
|
optimisticallyInstalledProjectIds,
|
||||||
|
queuedServerInstallRootProjectIds,
|
||||||
queuedServerInstallProjectIds,
|
queuedServerInstallProjectIds,
|
||||||
queuedServerInstallCount,
|
queuedServerInstallCount,
|
||||||
isInstallingQueuedServerInstalls,
|
isInstallingQueuedServerInstalls,
|
||||||
@@ -327,6 +328,7 @@ function getCardActions(
|
|||||||
|
|
||||||
if (serverData.value) {
|
if (serverData.value) {
|
||||||
const isQueued = queuedServerInstallProjectIds.value.has(result.project_id)
|
const isQueued = queuedServerInstallProjectIds.value.has(result.project_id)
|
||||||
|
const isQueuedRoot = queuedServerInstallRootProjectIds.value.has(result.project_id)
|
||||||
const isInstalled =
|
const isInstalled =
|
||||||
projectResult.installed ||
|
projectResult.installed ||
|
||||||
optimisticallyInstalledProjectIds.value.has(result.project_id) ||
|
optimisticallyInstalledProjectIds.value.has(result.project_id) ||
|
||||||
@@ -362,7 +364,8 @@ function getCardActions(
|
|||||||
? CheckIcon
|
? CheckIcon
|
||||||
: DownloadIcon,
|
: DownloadIcon,
|
||||||
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
|
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
|
||||||
disabled: !!isInstalled || isInstalling || isInstallingSelection,
|
disabled:
|
||||||
|
!!isInstalled || isInstalling || isInstallingSelection || (isQueued && !isQueuedRoot),
|
||||||
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
|
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
|
||||||
type: 'outlined',
|
type: 'outlined',
|
||||||
onClick: () => serverInstall(projectResult),
|
onClick: () => serverInstall(projectResult),
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ export type UploadSessionFile = {
|
|||||||
filename: string
|
filename: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUploadSessionPath(scope: Kyros.UploadSessions.v1.Scope, worldId: string) {
|
||||||
|
return `/worlds/${worldId}/${scope}/upload-session`
|
||||||
|
}
|
||||||
|
|
||||||
export class KyrosUploadSessionsV1Module extends AbstractModule {
|
export class KyrosUploadSessionsV1Module extends AbstractModule {
|
||||||
public getModuleID(): string {
|
public getModuleID(): string {
|
||||||
return 'kyros_upload_sessions_v1'
|
return 'kyros_upload_sessions_v1'
|
||||||
@@ -17,7 +21,7 @@ export class KyrosUploadSessionsV1Module extends AbstractModule {
|
|||||||
worldId: string,
|
worldId: string,
|
||||||
): Promise<Kyros.UploadSessions.v1.UploadSessionResponse> {
|
): Promise<Kyros.UploadSessions.v1.UploadSessionResponse> {
|
||||||
return this.client.request<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
return this.client.request<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
||||||
`/worlds/${worldId}/files/upload-session`,
|
getUploadSessionPath(scope, worldId),
|
||||||
{
|
{
|
||||||
api: '',
|
api: '',
|
||||||
version: 'v1',
|
version: 'v1',
|
||||||
@@ -32,7 +36,7 @@ export class KyrosUploadSessionsV1Module extends AbstractModule {
|
|||||||
worldId: string,
|
worldId: string,
|
||||||
): Promise<Kyros.UploadSessions.v1.GetUploadSessionResponse> {
|
): Promise<Kyros.UploadSessions.v1.GetUploadSessionResponse> {
|
||||||
return this.client.request<Kyros.UploadSessions.v1.GetUploadSessionResponse>(
|
return this.client.request<Kyros.UploadSessions.v1.GetUploadSessionResponse>(
|
||||||
`/worlds/${worldId}/files/upload-session`,
|
getUploadSessionPath(scope, worldId),
|
||||||
{
|
{
|
||||||
api: '',
|
api: '',
|
||||||
version: 'v1',
|
version: 'v1',
|
||||||
@@ -58,7 +62,7 @@ export class KyrosUploadSessionsV1Module extends AbstractModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.client.upload<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
return this.client.upload<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
||||||
`/worlds/${worldId}/files/upload-session/${uploadId}/files`,
|
`${getUploadSessionPath(scope, worldId)}/${uploadId}/files`,
|
||||||
{
|
{
|
||||||
api: '',
|
api: '',
|
||||||
version: 'v1',
|
version: 'v1',
|
||||||
@@ -76,7 +80,7 @@ export class KyrosUploadSessionsV1Module extends AbstractModule {
|
|||||||
uploadId: string,
|
uploadId: string,
|
||||||
): Promise<Kyros.UploadSessions.v1.UploadSessionResponse> {
|
): Promise<Kyros.UploadSessions.v1.UploadSessionResponse> {
|
||||||
return this.client.request<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
return this.client.request<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
||||||
`/worlds/${worldId}/files/upload-session/${uploadId}/finalize`,
|
`${getUploadSessionPath(scope, worldId)}/${uploadId}/finalize`,
|
||||||
{
|
{
|
||||||
api: '',
|
api: '',
|
||||||
version: 'v1',
|
version: 'v1',
|
||||||
@@ -92,7 +96,7 @@ export class KyrosUploadSessionsV1Module extends AbstractModule {
|
|||||||
uploadId: string,
|
uploadId: string,
|
||||||
): Promise<Kyros.UploadSessions.v1.UploadSessionResponse> {
|
): Promise<Kyros.UploadSessions.v1.UploadSessionResponse> {
|
||||||
return this.client.request<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
return this.client.request<Kyros.UploadSessions.v1.UploadSessionResponse>(
|
||||||
`/worlds/${worldId}/files/upload-session/${uploadId}`,
|
`${getUploadSessionPath(scope, worldId)}/${uploadId}`,
|
||||||
{
|
{
|
||||||
api: '',
|
api: '',
|
||||||
version: 'v1',
|
version: 'v1',
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ useQuery({
|
|||||||
from_modpack: false,
|
from_modpack: false,
|
||||||
}),
|
}),
|
||||||
enabled: computed(() => !!worldId.value),
|
enabled: computed(() => !!worldId.value),
|
||||||
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const serverSettingsTabComponentMap = {
|
const serverSettingsTabComponentMap = {
|
||||||
|
|||||||
@@ -199,7 +199,6 @@ export function useServerPanelSync(options: UseServerPanelSyncOptions) {
|
|||||||
}
|
}
|
||||||
: current,
|
: current,
|
||||||
)
|
)
|
||||||
void queryClient.invalidateQueries({ queryKey: contentListKey(serverId) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWorldContentBaseUpdate(
|
function handleWorldContentBaseUpdate(
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export interface BrowseInstallTarget {
|
|||||||
loader?: string | null
|
loader?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BrowseResolvedInstallContent {
|
||||||
|
projectId: string
|
||||||
|
versionId: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal project shape needed by shared install resolution.
|
* Minimal project shape needed by shared install resolution.
|
||||||
*/
|
*/
|
||||||
@@ -59,6 +64,7 @@ export interface BrowseInstallPlan<TProject extends BrowseInstallProject = Brows
|
|||||||
contentType: BrowseInstallContentType
|
contentType: BrowseInstallContentType
|
||||||
preferences: BrowseInstallPreferences
|
preferences: BrowseInstallPreferences
|
||||||
source: BrowseInstallPlanSource
|
source: BrowseInstallPlanSource
|
||||||
|
resolvedContent?: BrowseResolvedInstallContent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -335,6 +341,15 @@ export interface FlushStoredServerAddonInstallQueueOptions<TProject extends Brow
|
|||||||
onQueueChange?: (plans: Map<string, BrowseInstallPlan<TProject>>) => void
|
onQueueChange?: (plans: Map<string, BrowseInstallPlan<TProject>>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResolveServerAddonInstallPlansOptions<TProject extends BrowseInstallProject> {
|
||||||
|
plans: readonly BrowseInstallPlan<TProject>[]
|
||||||
|
existingProjectIds: Iterable<string>
|
||||||
|
resolvePlan: (
|
||||||
|
plan: BrowseInstallPlan<TProject>,
|
||||||
|
existingProjectIds: string[],
|
||||||
|
) => Promise<BrowseResolvedInstallContent[]>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result of a queue flush. Failed plans are also written back to the queue.
|
* Result of a queue flush. Failed plans are also written back to the queue.
|
||||||
*/
|
*/
|
||||||
@@ -646,6 +661,62 @@ export function getStoredServerAddonInstallQueue<
|
|||||||
return addonPlans
|
return addonPlans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getServerAddonInstallPlanProjectIds<TProject extends BrowseInstallProject>(
|
||||||
|
plans: Iterable<BrowseInstallPlan<TProject>>,
|
||||||
|
) {
|
||||||
|
const projectIds = new Set<string>()
|
||||||
|
for (const plan of plans) {
|
||||||
|
projectIds.add(plan.projectId)
|
||||||
|
for (const item of plan.resolvedContent ?? []) {
|
||||||
|
projectIds.add(item.projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return projectIds
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveServerAddonInstallPlans<TProject extends BrowseInstallProject>({
|
||||||
|
plans,
|
||||||
|
existingProjectIds,
|
||||||
|
resolvePlan,
|
||||||
|
}: ResolveServerAddonInstallPlansOptions<TProject>) {
|
||||||
|
const installedProjectIds = new Set(existingProjectIds)
|
||||||
|
const resolutionExistingProjectIds = Array.from(installedProjectIds)
|
||||||
|
const resolvedPlans = await Promise.all(
|
||||||
|
plans.map(
|
||||||
|
async (plan) =>
|
||||||
|
plan.resolvedContent ?? (await resolvePlan(plan, resolutionExistingProjectIds)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const explicitProjectIds = new Set(plans.map((plan) => plan.projectId))
|
||||||
|
const addons = new Map<string, { project_id: string; version_id: string }>()
|
||||||
|
|
||||||
|
for (const plan of plans) {
|
||||||
|
if (installedProjectIds.has(plan.projectId)) continue
|
||||||
|
addons.set(plan.projectId, {
|
||||||
|
project_id: plan.projectId,
|
||||||
|
version_id: plan.versionId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const content of resolvedPlans) {
|
||||||
|
for (const item of content) {
|
||||||
|
if (
|
||||||
|
installedProjectIds.has(item.projectId) ||
|
||||||
|
explicitProjectIds.has(item.projectId) ||
|
||||||
|
addons.has(item.projectId)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addons.set(item.projectId, {
|
||||||
|
project_id: item.projectId,
|
||||||
|
version_id: item.versionId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(addons.values())
|
||||||
|
}
|
||||||
|
|
||||||
export async function flushStoredServerAddonInstallQueue<TProject extends BrowseInstallProject>({
|
export async function flushStoredServerAddonInstallQueue<TProject extends BrowseInstallProject>({
|
||||||
serverId,
|
serverId,
|
||||||
worldId,
|
worldId,
|
||||||
@@ -869,7 +940,22 @@ function isStoredBrowseInstallPlan(
|
|||||||
typeof record.versionId === 'string' &&
|
typeof record.versionId === 'string' &&
|
||||||
isStoredBrowseInstallContentType(record.contentType) &&
|
isStoredBrowseInstallContentType(record.contentType) &&
|
||||||
isStoredBrowseInstallPreferences(record.preferences) &&
|
isStoredBrowseInstallPreferences(record.preferences) &&
|
||||||
(record.source === 'filtered' || record.source === 'target')
|
(record.source === 'filtered' || record.source === 'target') &&
|
||||||
|
isOptionalStoredResolvedContent(record.resolvedContent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOptionalStoredResolvedContent(value: unknown) {
|
||||||
|
return (
|
||||||
|
value === undefined ||
|
||||||
|
(Array.isArray(value) &&
|
||||||
|
value.every(
|
||||||
|
(item) =>
|
||||||
|
!!item &&
|
||||||
|
typeof item === 'object' &&
|
||||||
|
typeof (item as Record<string, unknown>).projectId === 'string' &&
|
||||||
|
typeof (item as Record<string, unknown>).versionId === 'string',
|
||||||
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ const addonsQuery = useQuery({
|
|||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
|
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
|
||||||
enabled: computed(() => worldId.value !== null),
|
enabled: computed(() => worldId.value !== null),
|
||||||
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const modpack = computed(() => addonsQuery.data.value?.modpack ?? null)
|
const modpack = computed(() => addonsQuery.data.value?.modpack ?? null)
|
||||||
@@ -411,6 +412,9 @@ async function uploadLocalModpackWithSoftOverride() {
|
|||||||
|
|
||||||
provideInstallationSettings({
|
provideInstallationSettings({
|
||||||
closeSettings: serverSettings.closeModal,
|
closeSettings: serverSettings.closeModal,
|
||||||
|
afterSave: async () => {
|
||||||
|
serverSettings.closeModal?.()
|
||||||
|
},
|
||||||
onGameVersionHover: handleGameVersionHover,
|
onGameVersionHover: handleGameVersionHover,
|
||||||
loading: computed(() => !server.value || addonsQuery.isLoading.value),
|
loading: computed(() => !server.value || addonsQuery.isLoading.value),
|
||||||
installationInfo: computed(() => {
|
installationInfo: computed(() => {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
flushStoredServerAddonInstallQueue,
|
flushStoredServerAddonInstallQueue,
|
||||||
getStoredServerAddonInstallQueue,
|
getStoredServerAddonInstallQueue,
|
||||||
getTargetInstallPreferences,
|
getTargetInstallPreferences,
|
||||||
|
resolveServerAddonInstallPlans,
|
||||||
} from '../../../shared/browse-tab/composables/install-logic'
|
} from '../../../shared/browse-tab/composables/install-logic'
|
||||||
import ManagedContentModal from '../../../shared/content-tab/components/managed-content-modal/index.vue'
|
import ManagedContentModal from '../../../shared/content-tab/components/managed-content-modal/index.vue'
|
||||||
import ConfirmModpackUpdateModal from '../../../shared/content-tab/components/modals/ConfirmModpackUpdateModal.vue'
|
import ConfirmModpackUpdateModal from '../../../shared/content-tab/components/modals/ConfirmModpackUpdateModal.vue'
|
||||||
@@ -157,7 +158,7 @@ const contentQuery = useQuery({
|
|||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
|
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
|
||||||
enabled: computed(() => worldId.value !== null),
|
enabled: computed(() => worldId.value !== null),
|
||||||
staleTime: 0,
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isModpackContentModalOpen = ref(false)
|
const isModpackContentModalOpen = ref(false)
|
||||||
@@ -168,7 +169,7 @@ const modpackContentQuery = useQuery({
|
|||||||
from_modpack: true,
|
from_modpack: true,
|
||||||
}),
|
}),
|
||||||
enabled: computed(() => worldId.value !== null && !!contentQuery.data.value?.modpack),
|
enabled: computed(() => worldId.value !== null && !!contentQuery.data.value?.modpack),
|
||||||
staleTime: 0,
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const setupActionDisabled = computed(() => !canSetup.value || busyReasons.value.length > 0)
|
const setupActionDisabled = computed(() => !canSetup.value || busyReasons.value.length > 0)
|
||||||
@@ -395,37 +396,32 @@ function toResolvePreferences(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resolveStoredServerAddonPlans(plans: BrowseInstallPlan[]) {
|
async function resolveStoredServerAddonPlans(plans: BrowseInstallPlan[]) {
|
||||||
const existingProjectIds = getInstalledProjectIds()
|
return await resolveServerAddonInstallPlans({
|
||||||
const resolvedAddons: Array<{ project_id: string; version_id: string }> = []
|
plans,
|
||||||
|
existingProjectIds: getInstalledProjectIds(),
|
||||||
for (const plan of plans) {
|
resolvePlan: async (plan, existingProjectIds) => {
|
||||||
const target = getTargetInstallPreferences(
|
const target = getTargetInstallPreferences(
|
||||||
{
|
{
|
||||||
gameVersion: server.value?.mc_version,
|
gameVersion: server.value?.mc_version,
|
||||||
loader: server.value?.loader,
|
loader: server.value?.loader,
|
||||||
},
|
},
|
||||||
plan.contentType,
|
plan.contentType,
|
||||||
)
|
)
|
||||||
const resolved = await client.labrinth.content_v3.resolve({
|
const resolved = await client.labrinth.content_v3.resolve({
|
||||||
project_id: plan.projectId,
|
project_id: plan.projectId,
|
||||||
version_id: plan.versionId,
|
version_id: plan.versionId,
|
||||||
content_type: plan.contentType as Labrinth.Content.v3.ContentType,
|
content_type: plan.contentType as Labrinth.Content.v3.ContentType,
|
||||||
selected: toResolvePreferences(plan.preferences),
|
selected: toResolvePreferences(plan.preferences),
|
||||||
target: toResolvePreferences(target),
|
target: toResolvePreferences(target),
|
||||||
existing_project_ids: Array.from(existingProjectIds),
|
existing_project_ids: existingProjectIds,
|
||||||
})
|
|
||||||
|
|
||||||
for (const item of [resolved.primary, ...resolved.dependencies]) {
|
|
||||||
if (existingProjectIds.has(item.project_id)) continue
|
|
||||||
existingProjectIds.add(item.project_id)
|
|
||||||
resolvedAddons.push({
|
|
||||||
project_id: item.project_id,
|
|
||||||
version_id: item.version_id,
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvedAddons
|
return [resolved.primary, ...resolved.dependencies].map((item) => ({
|
||||||
|
projectId: item.project_id,
|
||||||
|
versionId: item.version_id,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addonMatchesPendingInstall(
|
function addonMatchesPendingInstall(
|
||||||
|
|||||||
@@ -1124,8 +1124,13 @@ const handleFilesystemOps = (data: Archon.Websocket.v0.WSFilesystemOpsEvent) =>
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newModInvalidateTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
const handleNewMod = () => {
|
const handleNewMod = () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['content', 'list'] })
|
if (newModInvalidateTimer) clearTimeout(newModInvalidateTimer)
|
||||||
|
newModInvalidateTimer = setTimeout(() => {
|
||||||
|
newModInvalidateTimer = null
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['content', 'list'] })
|
||||||
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInstallationResult = async (data: Archon.Websocket.v0.WSInstallationResultEvent) => {
|
const handleInstallationResult = async (data: Archon.Websocket.v0.WSInstallationResultEvent) => {
|
||||||
@@ -1520,6 +1525,10 @@ function initializeServer() {
|
|||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
isMounted.value = false
|
isMounted.value = false
|
||||||
|
if (newModInvalidateTimer) {
|
||||||
|
clearTimeout(newModInvalidateTimer)
|
||||||
|
newModInvalidateTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
saveWsStateToCache()
|
saveWsStateToCache()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user