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
+44 -32
View File
@@ -41,14 +41,14 @@ import {
get_search_results_v3,
get_version_many,
} from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events.js'
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
import { instance_listener } from '@/helpers/events.js'
import {
get as getInstance,
get_installed_project_ids as getInstalledProjectIds,
} from '@/helpers/profile.js'
} from '@/helpers/instance'
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { get_profile_worlds } from '@/helpers/worlds'
import { get_instance_worlds } from '@/helpers/worlds'
import { injectContentInstall } from '@/providers/content-install'
import { injectServerInstall } from '@/providers/server-install'
import {
@@ -56,6 +56,7 @@ import {
provideServerInstallContent,
} from '@/providers/setup/server-install-content'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useTheming } from '@/store/state'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -67,6 +68,7 @@ const debugLog = useDebugLogger('Browse')
const router = useRouter()
const route = useRoute()
const themeStore = useTheming()
const serverSetupModalRef = ref<InstanceType<typeof CreationFlowModal> | null>(null)
const serverInstallContent = createServerInstallContent({ serverSetupModalRef })
provideServerInstallContent(serverInstallContent)
@@ -131,10 +133,10 @@ type Instance = {
install_stage: string
icon_path?: string
name: string
linked_data?: {
link?: {
type: string
project_id: string
version_id: string
locked: boolean
}
}
@@ -186,7 +188,7 @@ async function refreshInstalledProjectIds() {
if (!route.query.i) return
if (route.query.from === 'worlds') {
const worlds = await get_profile_worlds(route.query.i as string).catch(handleError)
const worlds = await get_instance_worlds(route.query.i as string).catch(handleError)
if (!worlds) return
const serverProjectIds = worlds
@@ -224,10 +226,10 @@ async function initInstanceContext() {
await refreshInstalledProjectIds()
if (instance.value?.linked_data?.project_id) {
debugLog('checking linked project for server status', instance.value.linked_data.project_id)
if (instance.value?.link?.project_id) {
debugLog('checking linked project for server status', instance.value.link.project_id)
const projectV3 = await get_project_v3(
instance.value.linked_data.project_id,
instance.value.link.project_id,
'must_revalidate',
).catch(handleError)
if (projectV3?.minecraft_server != null) {
@@ -448,7 +450,7 @@ const browseTitle = computed(() =>
)
breadcrumbs.setName('BrowseTitle', browseTitle.value)
if (instance.value) {
const instanceLink = `/instance/${encodeURIComponent(instance.value.path)}`
const instanceLink = `/instance/${encodeURIComponent(instance.value.id)}`
breadcrumbs.setContext({
name: instance.value.name,
link: isFromWorlds.value ? `${instanceLink}/worlds` : instanceLink,
@@ -467,6 +469,21 @@ onBeforeRouteLeave(() => {
const projectType = ref<ProjectType>(route.params.projectType as ProjectType)
function resetInstanceContext() {
if (!instance.value) return
debugLog('instance context removed, resetting')
instance.value = null
installedProjectIds.value = null
instanceHideInstalled.value = false
newlyInstalled.value = []
hiddenInstanceProjectIds.value = new Set()
hiddenInstanceProjectIdsInitialized.value = false
isServerInstance.value = false
breadcrumbs.setName('BrowseTitle', formatMessage(messages.discoverContent))
breadcrumbs.setContext(null)
}
watch(
() => route.params.projectType as ProjectType,
async (newType) => {
@@ -479,16 +496,14 @@ watch(
debugLog('projectType route param changed', { from: projectType.value, to: newType })
projectType.value = newType
},
)
if (!route.query.i && instance.value) {
debugLog('instance context removed, resetting')
instance.value = null
installedProjectIds.value = null
instanceHideInstalled.value = false
newlyInstalled.value = []
isServerInstance.value = false
breadcrumbs.setName('BrowseTitle', formatMessage(messages.discoverContent))
breadcrumbs.setContext(null)
watch(
() => route.query.i,
(instanceId) => {
if (!instanceId && route.path.startsWith('/browse')) {
resetInstanceContext()
}
},
)
@@ -564,6 +579,7 @@ const installContext = computed(() => {
queuedCount: queuedServerInstallCount.value,
selectedProjects: selectedServerInstallProjects.value,
isInstallingSelected: isInstallingQueuedServerInstalls.value,
skipNonEssentialWarnings: themeStore.getFeatureFlag('skip_non_essential_warnings'),
installProgress: queuedInstallProgress.value,
clearQueued: clearQueuedServerInstalls,
clearSelected: clearQueuedServerInstalls,
@@ -578,7 +594,7 @@ const installContext = computed(() => {
loader: instance.value.loader,
gameVersion: instance.value.game_version,
iconSrc: instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : null,
backUrl: `/instance/${encodeURIComponent(instance.value.path)}${isFromWorlds.value ? '/worlds' : ''}`,
backUrl: `/instance/${encodeURIComponent(instance.value.id)}${isFromWorlds.value ? '/worlds' : ''}`,
backLabel: formatMessage(messages.backToInstance),
heading: formatMessage(
isFromWorlds.value ? messages.addServersToInstance : commonMessages.installingContentLabel,
@@ -809,7 +825,7 @@ function getCardActions(
await installVersion(
projectResult.project_id,
selectedInstall.versionId,
instance.value ? instance.value.path : null,
instance.value ? instance.value.id : null,
'SearchCard',
(versionId, installedProjectIds) => {
setProjectInstalling(projectResult.project_id, false)
@@ -982,20 +998,16 @@ if (instance.value?.game_version) {
}
}
await searchState.refreshSearch()
void searchState.refreshSearch()
type UnlistenFn = () => void
let isUnmounted = false
let unlistenProfiles: UnlistenFn | null = null
let unlistenInstances: UnlistenFn | null = null
onMounted(() => {
profile_listener(async (event: { event: string; profile_path_id: string }) => {
if (
instance.value &&
event.profile_path_id === instance.value.path &&
event.event === 'synced'
) {
instance_listener(async (event: { event: string; instance_id: string }) => {
if (instance.value && event.instance_id === instance.value.id && event.event === 'synced') {
await refreshInstalledProjectIds()
await searchState.refreshSearch()
}
@@ -1006,14 +1018,14 @@ onMounted(() => {
return
}
unlistenProfiles = unlisten
unlistenInstances = unlisten
})
.catch(handleError)
})
onUnmounted(() => {
isUnmounted = true
unlistenProfiles?.()
unlistenInstances?.()
})
function getProjectBrowseQuery() {