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:
Calum H.
2026-06-25 21:19:29 +00:00
committed by GitHub
co-authored by DJCheesusReal Truman Gao
parent ef4044534f
commit 734720e11e
353 changed files with 24745 additions and 9771 deletions
@@ -19,7 +19,8 @@ import { useRouter } from 'vue-router'
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
import { install_duplicate_instance } from '@/helpers/install'
import { edit, edit_icon, list, remove } from '@/helpers/instance'
import { injectInstanceSettings } from '@/providers/instance-settings'
import type { GameInstance } from '../../../helpers/types'
@@ -32,14 +33,14 @@ const queryClient = useQueryClient()
const deleteConfirmModal = ref()
const { instance } = injectInstanceSettings()
type ReleaseChannel = GameInstance['preferred_update_channel']
type ReleaseChannel = GameInstance['update_channel']
const releaseChannelOptions: ReleaseChannel[] = ['release', 'beta', 'alpha']
const title = ref(instance.value.name)
const icon: Ref<string | undefined> = ref(instance.value.icon_path)
const groups = ref([...instance.value.groups])
const savingReleaseChannel = ref(false)
const selectedReleaseChannel = ref<ReleaseChannel>(instance.value.preferred_update_channel)
const selectedReleaseChannel = ref<ReleaseChannel>(instance.value.update_channel)
const releaseChannelDisabledItems = computed<ReleaseChannel[]>(() =>
savingReleaseChannel.value ? [...releaseChannelOptions] : [],
)
@@ -48,8 +49,8 @@ const newCategoryInput = ref('')
const installing = computed(() => instance.value.install_stage !== 'installed')
async function duplicateProfile() {
await duplicate(instance.value.path).catch(handleError)
async function duplicateInstance() {
await install_duplicate_instance(instance.value.id).catch(handleError)
trackEvent('InstanceDuplicate', {
loader: instance.value.loader,
game_version: instance.value.game_version,
@@ -84,22 +85,22 @@ function formatReleaseChannelDescription(channel: ReleaseChannel) {
}
watch(
() => [instance.value.path, instance.value.preferred_update_channel] as const,
() => [instance.value.id, instance.value.update_channel] as const,
() => {
if (!savingReleaseChannel.value) {
selectedReleaseChannel.value = instance.value.preferred_update_channel
selectedReleaseChannel.value = instance.value.update_channel
}
},
)
watch(selectedReleaseChannel, async (channel, previousChannel) => {
const previousReleaseChannel = previousChannel ?? instance.value.preferred_update_channel
if (channel === instance.value.preferred_update_channel) return
const previousReleaseChannel = previousChannel ?? instance.value.update_channel
if (channel === instance.value.update_channel) return
savingReleaseChannel.value = true
const profilePath = instance.value.path
await edit(profilePath, { preferred_update_channel: channel })
.then(() => queryClient.invalidateQueries({ queryKey: ['linkedModpackInfo', profilePath] }))
const instanceId = instance.value.id
await edit(instanceId, { update_channel: channel })
.then(() => queryClient.invalidateQueries({ queryKey: ['linkedModpackInfo', instanceId] }))
.catch((error) => {
selectedReleaseChannel.value = previousReleaseChannel
handleError(error)
@@ -109,7 +110,7 @@ watch(selectedReleaseChannel, async (channel, previousChannel) => {
async function resetIcon() {
icon.value = undefined
await edit_icon(instance.value.path, null).catch(handleError)
await edit_icon(instance.value.id, null).catch(handleError)
trackEvent('InstanceRemoveIcon')
}
@@ -127,12 +128,12 @@ async function setIcon() {
if (!value) return
icon.value = value
await edit_icon(instance.value.path, icon.value).catch(handleError)
await edit_icon(instance.value.id, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')
}
const editProfileObject = computed(() => ({
const editInstanceObject = computed(() => ({
name: title.value.trim().substring(0, 32) ?? 'Instance',
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
}))
@@ -158,15 +159,15 @@ watch(
[title, groups, groups],
async () => {
if (removing.value) return
await edit(instance.value.path, editProfileObject.value).catch(handleError)
await edit(instance.value.id, editInstanceObject.value).catch(handleError)
},
{ deep: true },
)
const removing = ref(false)
async function removeProfile() {
async function removeInstance() {
removing.value = true
const path = instance.value.path
const path = instance.value.id
trackEvent('InstanceRemove', {
loader: instance.value.loader,
@@ -284,7 +285,7 @@ const messages = defineMessages({
</script>
<template>
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="removeProfile" />
<ConfirmDeleteInstanceModal ref="deleteConfirmModal" @delete="removeInstance" />
<div class="block">
<div class="float-end ml-10 relative group w-fit">
<div class="flex flex-col gap-1">
@@ -310,7 +311,7 @@ const messages = defineMessages({
:src="icon ? convertFileSrc(icon) : icon"
size="108px"
class="transition-[filter] group-hover:brightness-75"
:tint-by="instance.path"
:tint-by="instance.id"
no-shadow
/>
<div
@@ -350,7 +351,7 @@ const messages = defineMessages({
aria-labelledby="duplicate-instance-label"
:disabled="installing"
class="w-max !shadow-none"
@click="duplicateProfile"
@click="duplicateInstance"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</button>