mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34: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
@@ -1,10 +1,8 @@
|
||||
import type {
|
||||
AbstractPopupNotificationManager,
|
||||
AbstractWebNotificationManager,
|
||||
CreationFlowContextValue,
|
||||
CreationFlowModal,
|
||||
} from '@modrinth/ui'
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { provide, ref, useTemplateRef } from 'vue'
|
||||
import type { ComponentExposed } from 'vue-component-type-helpers'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -14,29 +12,21 @@ import type ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlre
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_versions, get_search_results } from '@/helpers/cache.js'
|
||||
import { import_instance } from '@/helpers/import.js'
|
||||
import {
|
||||
type CreatePackLocation,
|
||||
install_create_instance,
|
||||
install_create_modpack_instance,
|
||||
install_get_modpack_preview,
|
||||
} from '@/helpers/install'
|
||||
import { list } from '@/helpers/instance'
|
||||
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata.js'
|
||||
import { create_profile_and_install, create_profile_and_install_from_file } from '@/helpers/pack'
|
||||
import { create, list } from '@/helpers/profile.js'
|
||||
import type { InstanceLoader } from '@/helpers/types'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
export function setupCreationModal(
|
||||
notificationManager: AbstractWebNotificationManager,
|
||||
popupNotificationManager: AbstractPopupNotificationManager,
|
||||
) {
|
||||
export function setupCreationModal(notificationManager: AbstractWebNotificationManager) {
|
||||
const { handleError } = notificationManager
|
||||
const { formatMessage } = useVIntl()
|
||||
const router = useRouter()
|
||||
|
||||
const messages = defineMessages({
|
||||
installingModpackTitle: {
|
||||
id: 'app.creation-modal.installing-modpack.title',
|
||||
defaultMessage: 'Installing modpack...',
|
||||
},
|
||||
installingModpackDescription: {
|
||||
id: 'app.creation-modal.installing-modpack.description',
|
||||
defaultMessage: '{fileName}',
|
||||
},
|
||||
})
|
||||
const themeStore = useTheming()
|
||||
|
||||
const installationModal =
|
||||
useTemplateRef<ComponentExposed<typeof CreationFlowModal>>('installationModal')
|
||||
@@ -65,7 +55,13 @@ export function setupCreationModal(
|
||||
name: string,
|
||||
iconUrl?: string,
|
||||
) {
|
||||
await create_profile_and_install(projectId, versionId, name, iconUrl).catch(handleError)
|
||||
await install_create_modpack_instance({
|
||||
type: 'fromVersionId',
|
||||
project_id: projectId,
|
||||
version_id: versionId,
|
||||
title: name,
|
||||
icon_url: iconUrl,
|
||||
}).catch(handleError)
|
||||
trackEvent('InstanceCreate', { source: 'CreationModalModpack' })
|
||||
}
|
||||
|
||||
@@ -75,12 +71,12 @@ export function setupCreationModal(
|
||||
const { projectId, versionId, name, iconUrl } = config.modpackSelection.value
|
||||
|
||||
const instances = await list().catch(handleError)
|
||||
const existingInstance = instances?.find((i) => i.linked_data?.project_id === projectId)
|
||||
const existingInstance = instances?.find((i) => i.link?.project_id === projectId)
|
||||
|
||||
if (existingInstance) {
|
||||
if (existingInstance && !themeStore.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
pendingModpackCreation.value = { projectId, versionId, name, iconUrl }
|
||||
installationModal.value?.hide()
|
||||
modpackAlreadyInstalledModal.value?.show(existingInstance.name, existingInstance.path)
|
||||
modpackAlreadyInstalledModal.value?.show(existingInstance.name, existingInstance.id)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -108,24 +104,28 @@ export function setupCreationModal(
|
||||
}
|
||||
|
||||
if (config.modpackFilePath.value) {
|
||||
const waitingNotification = popupNotificationManager.addPopupNotification({
|
||||
title: formatMessage(messages.installingModpackTitle),
|
||||
text: formatMessage(messages.installingModpackDescription, {
|
||||
fileName: config.modpackFilePath.value.split('/').pop() ?? config.modpackFilePath.value,
|
||||
}),
|
||||
type: 'info',
|
||||
autoCloseMs: null,
|
||||
waiting: true,
|
||||
})
|
||||
const location: CreatePackLocation = {
|
||||
type: 'fromFile',
|
||||
path: config.modpackFilePath.value,
|
||||
}
|
||||
const preview = await install_get_modpack_preview(location)
|
||||
|
||||
await create_profile_and_install_from_file(
|
||||
config.modpackFilePath.value,
|
||||
(createProfile, fileName) => {
|
||||
popupNotificationManager.removeNotification(waitingNotification.id)
|
||||
unknownPackWarningModal.value?.show(createProfile, fileName)
|
||||
},
|
||||
).catch(handleError)
|
||||
popupNotificationManager.removeNotification(waitingNotification.id)
|
||||
if (preview.unknownFile) {
|
||||
const splitPath = config.modpackFilePath.value.split(/[\\/]/)
|
||||
const fileName = splitPath
|
||||
? splitPath[splitPath.length - 1]
|
||||
: config.modpackFilePath.value
|
||||
if (unknownPackWarningModal.value) {
|
||||
unknownPackWarningModal.value?.show(
|
||||
() => install_create_modpack_instance(location).then(() => undefined),
|
||||
fileName,
|
||||
)
|
||||
} else {
|
||||
await install_create_modpack_instance(location)
|
||||
}
|
||||
} else {
|
||||
await install_create_modpack_instance(location)
|
||||
}
|
||||
trackEvent('InstanceCreate', { source: 'CreationModalModpackFile' })
|
||||
return
|
||||
}
|
||||
@@ -140,14 +140,13 @@ export function setupCreationModal(
|
||||
const iconPath = config.instanceIconPath.value ?? null
|
||||
const name = config.instanceName.value.trim() || config.autoInstanceName.value
|
||||
|
||||
await create(
|
||||
await install_create_instance({
|
||||
name,
|
||||
config.selectedGameVersion.value!,
|
||||
loader as InstanceLoader,
|
||||
gameVersion: config.selectedGameVersion.value!,
|
||||
loader: loader as InstanceLoader,
|
||||
loaderVersion,
|
||||
iconPath,
|
||||
false,
|
||||
).catch(handleError)
|
||||
}).catch(handleError)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModal',
|
||||
@@ -171,9 +170,9 @@ export function setupCreationModal(
|
||||
await proceedWithModpackCreation(projectId, versionId, name, iconUrl)
|
||||
}
|
||||
|
||||
function handleModpackDuplicateGoToInstance(instancePath: string) {
|
||||
function handleModpackDuplicateGoToInstance(instanceId: string) {
|
||||
pendingModpackCreation.value = null
|
||||
router.push(`/instance/${encodeURIComponent(instancePath)}/`)
|
||||
router.push(`/instance/${encodeURIComponent(instanceId)}/`)
|
||||
}
|
||||
|
||||
function handleBrowseModpacks() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { provideFilePicker } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { readFile } from '@tauri-apps/plugin-fs'
|
||||
|
||||
@@ -7,21 +7,50 @@ function getFileName(path: string, fallback: string) {
|
||||
return path.split(/[\\/]/).pop() || fallback
|
||||
}
|
||||
|
||||
function getDialogPath(result: string | { path?: string } | null | undefined) {
|
||||
if (!result) return null
|
||||
return typeof result === 'string' ? result : (result.path ?? null)
|
||||
}
|
||||
|
||||
async function createFileFromPath(path: string, fallbackName: string, type?: string) {
|
||||
const bytes = await readFile(path)
|
||||
const name = getFileName(path, fallbackName)
|
||||
return new File([bytes], name, type ? { type } : undefined)
|
||||
}
|
||||
|
||||
async function createNativeFileFromPath(path: string, fallbackName: string, type?: string) {
|
||||
const bytes = await invoke<number[]>('plugin:files|file_read_dragged_file', { path })
|
||||
const name = getFileName(path, fallbackName)
|
||||
return new File([new Uint8Array(bytes)], name, type ? { type } : undefined)
|
||||
}
|
||||
|
||||
export function setupFilePickerProvider() {
|
||||
provideFilePicker({
|
||||
async pickFiles(options) {
|
||||
const result = await open({
|
||||
multiple: options?.multiple ?? true,
|
||||
})
|
||||
if (!result) return []
|
||||
|
||||
const paths = Array.isArray(result) ? result : [result]
|
||||
return await Promise.all(
|
||||
paths
|
||||
.map(getDialogPath)
|
||||
.filter((path): path is string => !!path)
|
||||
.map(async (path) => ({
|
||||
file: await createNativeFileFromPath(path, 'file'),
|
||||
path,
|
||||
previewUrl: convertFileSrc(path),
|
||||
})),
|
||||
)
|
||||
},
|
||||
async pickImage() {
|
||||
const result = await open({
|
||||
multiple: false,
|
||||
filters: [{ name: 'Image', extensions: ['png', 'jpeg', 'jpg', 'svg', 'webp', 'gif'] }],
|
||||
})
|
||||
if (!result) return null
|
||||
const path = result.path ?? result
|
||||
const path = getDialogPath(result)
|
||||
if (!path) return null
|
||||
const file = await createFileFromPath(path, 'icon')
|
||||
return { file, path, previewUrl: convertFileSrc(path) }
|
||||
@@ -32,7 +61,7 @@ export function setupFilePickerProvider() {
|
||||
filters: [{ name: 'Modpack', extensions: ['mrpack'] }],
|
||||
})
|
||||
if (!result) return null
|
||||
const path = result.path ?? result
|
||||
const path = getDialogPath(result)
|
||||
if (!path) return null
|
||||
if (options?.readFile === false) {
|
||||
// Instance imports stream from the native path, keeping large packs out of JS memory.
|
||||
|
||||
Reference in New Issue
Block a user