mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 17:44:50 +00:00
feat: instances v2 (#6431)
* feat: base of instances v2 * feat: use old profiles with compat layer * prototype: instances v2 * fix: install_from using profile * fix: skins migration fix * fix: frontend still using profile path * fix: add update proj multiselect guard * fix: cargo fmt * fix: content missing fields * feat: break up app-lib/api/instance.rs * fix: check_content_updates mismatch * fix: updater modal cleanup w/new structure * feat: better update all handling * fix: remove preview_update_all * fix: feedback on bulk update + lint * fix: rem transitions * fix: change to jsonb * feat: app db backup after update * fix: lint * fix: sqlx prepare + use sqlx macros * fix: lint * fix: bugs * feat: defuck the installing process up * fix: bug of hell * fix: shear * fix: fmt * fix: install progress spacing + change mc/content/overrides to bytes * fix: lint * fix: prepr * fix: navtabs anim not working in app * fix: worlds.vue improvements + browse page fixes * feat: optimise queries + adapter fns * fix: lint * fix: lint * feat: shared modrinth-content-management crate (#6469) * feat: disable warnings setting * feat: add instances shortcuts (#6329) * Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. * Change route to /launch/profile/{id} for future extensibility * fix: ensure profile path is url decoded * fix: URL-decode profile path from deep link * fix: use urlencoding crate for URL decoding * feat: implement app instance shortcuts * feat: change windows shortcut creation to use windows api instead * feat: implement creating a shortcut launching world/server * format * fmt * fix multiline inline tables * pnpm prepr * feat: move create shortcut to last item * refactor: split up shortcuts.rs for individual platforms * refactor: turn profile launch url into url type * use string literal and add safety comment * pt2 * refactor: rename anything that's profile into instance * update mac shortcut --------- Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> --------- Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
This commit is contained in:
co-authored by
DJCheesusReal
Truman Gao
parent
ef4044534f
commit
734720e11e
@@ -6,12 +6,18 @@ import type { Router } from 'vue-router'
|
||||
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project, get_project_v3, get_version } from '@/helpers/cache.js'
|
||||
import { install_to_existing_profile } from '@/helpers/pack.js'
|
||||
import { create, edit, edit_icon, get, install as installProfile, list } from '@/helpers/profile.js'
|
||||
import {
|
||||
install_create_instance,
|
||||
install_create_modpack_instance,
|
||||
install_existing_instance,
|
||||
installJobInstanceId,
|
||||
wait_for_install_job,
|
||||
} from '@/helpers/install'
|
||||
import { edit, get, list } from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { ensureManagedServerWorldExists, getServerAddress } from '@/helpers/worlds'
|
||||
import { start_join_server } from '@/helpers/worlds.ts'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { ensureManagedServerWorldExists, getServerAddress } from '@/store/install.js'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
interface ModalRef<TShow extends (...args: any[]) => void = () => void> {
|
||||
@@ -99,14 +105,14 @@ export function createServerInstall(opts: {
|
||||
return installingServerProjects.value.includes(projectId)
|
||||
}
|
||||
|
||||
async function joinServer(profilePath: string, serverAddress: string | null) {
|
||||
async function joinServer(instanceId: string, serverAddress: string | null) {
|
||||
if (!serverAddress) return
|
||||
await start_join_server(profilePath, serverAddress)
|
||||
await start_join_server(instanceId, serverAddress)
|
||||
}
|
||||
|
||||
async function findInstalledInstance(projectId: string) {
|
||||
const packs = await list()
|
||||
return packs.find((pack) => pack.linked_data?.project_id === projectId) ?? null
|
||||
return packs.find((pack) => pack.link?.project_id === projectId) ?? null
|
||||
}
|
||||
|
||||
async function createVanillaInstance(
|
||||
@@ -114,30 +120,32 @@ export function createServerInstall(opts: {
|
||||
gameVersion: string,
|
||||
serverAddress: string | null,
|
||||
) {
|
||||
const profilePath = await create(
|
||||
project.title,
|
||||
const job = await install_create_instance({
|
||||
name: project.title,
|
||||
gameVersion,
|
||||
'vanilla',
|
||||
null,
|
||||
project.icon_url ?? null,
|
||||
false,
|
||||
{
|
||||
loader: 'vanilla',
|
||||
loaderVersion: null,
|
||||
iconPath: project.icon_url ?? null,
|
||||
link: {
|
||||
type: 'server_project',
|
||||
project_id: project.id,
|
||||
version_id: '',
|
||||
locked: true,
|
||||
},
|
||||
)
|
||||
})
|
||||
const instanceId = installJobInstanceId(job)
|
||||
if (!instanceId) return null
|
||||
|
||||
await ensureManagedServerWorldExists(profilePath, project.title, serverAddress)
|
||||
await wait_for_install_job(job.job_id)
|
||||
await ensureManagedServerWorldExists(instanceId, project.title, serverAddress)
|
||||
|
||||
return profilePath
|
||||
return instanceId
|
||||
}
|
||||
|
||||
async function updateVanillaGameVersion(instance: GameInstance, targetGameVersion: string) {
|
||||
if (instance.game_version === targetGameVersion) return
|
||||
|
||||
await edit(instance.path, { game_version: targetGameVersion })
|
||||
await installProfile(instance.path, false)
|
||||
await edit(instance.id, { game_version: targetGameVersion })
|
||||
const job = await install_existing_instance(instance.id, false)
|
||||
await wait_for_install_job(job.job_id)
|
||||
}
|
||||
|
||||
function showModpackInstallSuccess(project: GameInstance, serverAddress: string | null) {
|
||||
@@ -152,14 +160,14 @@ export function createServerInstall(opts: {
|
||||
label: 'Launch game',
|
||||
action: async () => {
|
||||
try {
|
||||
await joinServer(project.path, serverAddress)
|
||||
await joinServer(project.id, serverAddress)
|
||||
trackEvent('InstanceStart', {
|
||||
loader: project.loader,
|
||||
game_version: project.game_version,
|
||||
source: 'ServerProject',
|
||||
})
|
||||
} catch (err) {
|
||||
handleSevereError(err, { profilePath: project.path })
|
||||
handleSevereError(err, { instanceId: project.id })
|
||||
}
|
||||
},
|
||||
color: 'brand' as const,
|
||||
@@ -168,7 +176,7 @@ export function createServerInstall(opts: {
|
||||
: []),
|
||||
{
|
||||
label: 'Instance',
|
||||
action: () => opts.router.push(`/instance/${encodeURIComponent(project.path)}`),
|
||||
action: () => opts.router.push(`/instance/${encodeURIComponent(project.id)}`),
|
||||
},
|
||||
],
|
||||
autoCloseMs: null,
|
||||
@@ -187,14 +195,14 @@ export function createServerInstall(opts: {
|
||||
label: 'Launch game',
|
||||
action: async () => {
|
||||
try {
|
||||
if (serverAddress) await start_join_server(instance.path, serverAddress)
|
||||
if (serverAddress) await start_join_server(instance.id, serverAddress)
|
||||
trackEvent('InstanceStart', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
source: 'ServerProject',
|
||||
})
|
||||
} catch (err) {
|
||||
handleSevereError(err, { profilePath: instance.path })
|
||||
handleSevereError(err, { instanceId: instance.id })
|
||||
}
|
||||
},
|
||||
color: 'brand' as const,
|
||||
@@ -203,7 +211,7 @@ export function createServerInstall(opts: {
|
||||
: []),
|
||||
{
|
||||
label: 'Instance',
|
||||
action: () => opts.router.push(`/instance/${encodeURIComponent(instance.path)}`),
|
||||
action: () => opts.router.push(`/instance/${encodeURIComponent(instance.id)}`),
|
||||
},
|
||||
],
|
||||
autoCloseMs: null,
|
||||
@@ -211,8 +219,8 @@ export function createServerInstall(opts: {
|
||||
}
|
||||
|
||||
/**
|
||||
* Server projects that use modpack content have linked_data.project_id as
|
||||
* the server project id and linked_data.version_id as the modpack content version id.
|
||||
* Server projects that use modpack content have link.project_id as
|
||||
* the server project id and link.version_id as the modpack content version id.
|
||||
* The modpack content version can be of the same server project, or from a different project.
|
||||
*/
|
||||
async function installServerProject(serverProjectId: string) {
|
||||
@@ -229,46 +237,32 @@ export function createServerInstall(opts: {
|
||||
const contentVersionId = content.version_id
|
||||
const contentVersion = await get_version(contentVersionId, 'bypass')
|
||||
const contentProjectId = contentVersion.project_id
|
||||
const gameVersion = contentVersion.game_versions?.[0] ?? ''
|
||||
|
||||
const profilePath = await create(
|
||||
project.title,
|
||||
gameVersion,
|
||||
'vanilla',
|
||||
null,
|
||||
project.icon_url,
|
||||
true,
|
||||
const createJob = await install_create_modpack_instance(
|
||||
{
|
||||
project_id: serverProjectId,
|
||||
type: 'fromVersionId',
|
||||
project_id: contentProjectId,
|
||||
version_id: contentVersionId,
|
||||
locked: true,
|
||||
title: project.title,
|
||||
},
|
||||
{
|
||||
name: project.title,
|
||||
iconPath: project.icon_url ?? null,
|
||||
link: {
|
||||
type: 'server_project_modpack',
|
||||
server_project_id: serverProjectId,
|
||||
content_project_id: contentProjectId,
|
||||
content_version_id: contentVersionId,
|
||||
project_id: serverProjectId,
|
||||
version_id: contentVersionId,
|
||||
},
|
||||
},
|
||||
)
|
||||
const instanceId = installJobInstanceId(createJob)
|
||||
if (!instanceId) return
|
||||
|
||||
// Save the icon path before pack install overwrites it
|
||||
const profileBeforeInstall = await get(profilePath)
|
||||
const originalIconPath = profileBeforeInstall?.icon_path ?? null
|
||||
|
||||
await install_to_existing_profile(
|
||||
contentProjectId,
|
||||
contentVersionId,
|
||||
project.title,
|
||||
profilePath,
|
||||
)
|
||||
|
||||
// Pack install overwrites name, icon, and linked_data with the content project's values.
|
||||
// Restore them to point to the server project.
|
||||
await edit(profilePath, {
|
||||
name: project.title,
|
||||
linked_data: {
|
||||
project_id: serverProjectId,
|
||||
version_id: contentVersionId,
|
||||
locked: true,
|
||||
},
|
||||
})
|
||||
await edit_icon(profilePath, originalIconPath)
|
||||
|
||||
await ensureManagedServerWorldExists(profilePath, project.title, serverAddress)
|
||||
await wait_for_install_job(createJob.job_id)
|
||||
await ensureManagedServerWorldExists(instanceId, project.title, serverAddress)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,9 +297,13 @@ export function createServerInstall(opts: {
|
||||
if (installingServerProjects.value.includes(projectId)) return
|
||||
startInstallingServer(projectId)
|
||||
try {
|
||||
const path = await createVanillaInstance(project, recommendedGameVersion, serverAddress)
|
||||
if (path) {
|
||||
instance = await get(path)
|
||||
const instanceId = await createVanillaInstance(
|
||||
project,
|
||||
recommendedGameVersion,
|
||||
serverAddress,
|
||||
)
|
||||
if (instanceId) {
|
||||
instance = await get(instanceId)
|
||||
if (instance) showModpackInstallSuccess(instance, serverAddress)
|
||||
}
|
||||
} finally {
|
||||
@@ -324,10 +322,10 @@ export function createServerInstall(opts: {
|
||||
|
||||
if (!instance) return
|
||||
|
||||
await ensureManagedServerWorldExists(instance.path, project.title, serverAddress)
|
||||
await ensureManagedServerWorldExists(instance.id, project.title, serverAddress)
|
||||
|
||||
// Update existing instance if needed
|
||||
if (isModpack && instance.linked_data?.version_id !== modpackVersionId) {
|
||||
if (isModpack && instance.link?.version_id !== modpackVersionId) {
|
||||
updateToPlayModalRef?.show(instance, modpackVersionId, () => {
|
||||
showUpdateSuccess(instance, serverAddress)
|
||||
})
|
||||
@@ -347,14 +345,14 @@ export function createServerInstall(opts: {
|
||||
|
||||
// Join server
|
||||
try {
|
||||
await joinServer(instance.path, serverAddress)
|
||||
await joinServer(instance.id, serverAddress)
|
||||
trackEvent('InstanceStart', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
source: 'ServerProject',
|
||||
})
|
||||
} catch (err) {
|
||||
handleSevereError(err, { profilePath: instance.path })
|
||||
handleSevereError(err, { instanceId: instance.id })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user