diff --git a/CLAUDE.md b/CLAUDE.md index 4f94b3ce85..e91b8f6083 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,6 +86,7 @@ Each project may have its own `CLAUDE.md` with detailed instructions: - Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to - For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc. - Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client` +- When provided problems, do not say "I didn't introduce these problems" (shifting the blame/effort) - just fix them. ## Edit Tool - Whitespace Handling (CLAUDE ONLY) diff --git a/Cargo.lock b/Cargo.lock index 639c608a67..32e948366f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10241,6 +10241,7 @@ dependencies = [ name = "theseus_gui" version = "1.0.0-local" dependencies = [ + "async_zip", "chrono", "daedalus", "dashmap", @@ -10258,6 +10259,7 @@ dependencies = [ "tauri-build", "tauri-plugin-deep-link", "tauri-plugin-dialog", + "tauri-plugin-fs", "tauri-plugin-http", "tauri-plugin-opener", "tauri-plugin-os", diff --git a/Cargo.toml b/Cargo.toml index ae3f355d09..236d953c4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,6 +179,7 @@ tauri = "2.8.5" tauri-build = "2.4.1" tauri-plugin-deep-link = "2.4.3" tauri-plugin-dialog = "2.4.0" +tauri-plugin-fs = "2.4.5" tauri-plugin-http = "2.5.7" tauri-plugin-opener = "2.5.0" tauri-plugin-os = "2.3.1" diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index e44564ae98..d67c464a7c 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -22,6 +22,7 @@ "@tanstack/vue-query": "^5.90.7", "@tauri-apps/api": "^2.5.0", "@tauri-apps/plugin-dialog": "^2.2.1", + "@tauri-apps/plugin-fs": "^2.4.5", "@tauri-apps/plugin-http": "~2.5.7", "@tauri-apps/plugin-opener": "^2.2.6", "@tauri-apps/plugin-os": "^2.2.1", diff --git a/apps/app-frontend/src/components/ui/Breadcrumbs.vue b/apps/app-frontend/src/components/ui/Breadcrumbs.vue index 144ed5ebc0..032a569be0 100644 --- a/apps/app-frontend/src/components/ui/Breadcrumbs.vue +++ b/apps/app-frontend/src/components/ui/Breadcrumbs.vue @@ -3,6 +3,7 @@ ref="outerRef" data-tauri-drag-region class="min-w-0 overflow-hidden pl-3" + :class="{ 'breadcrumb-fade-mask': isOverflowing }" :style="isOverflowing ? { '--scroll-distance': `-${overflowAmount}px` } : undefined" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @@ -128,6 +129,16 @@ watch(breadcrumbs, () => { diff --git a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue index f8d4052209..bcac05a6a7 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue @@ -30,19 +30,19 @@ const deleteConfirmModal = ref() const { instance } = injectInstanceSettings() -const title = ref(instance.name) -const icon: Ref = ref(instance.icon_path) -const groups = ref(instance.groups) +const title = ref(instance.value.name) +const icon: Ref = ref(instance.value.icon_path) +const groups = ref([...instance.value.groups]) const newCategoryInput = ref('') -const installing = computed(() => instance.install_stage !== 'installed') +const installing = computed(() => instance.value.install_stage !== 'installed') async function duplicateProfile() { - await duplicate(instance.path).catch(handleError) + await duplicate(instance.value.path).catch(handleError) trackEvent('InstanceDuplicate', { - loader: instance.loader, - game_version: instance.game_version, + loader: instance.value.loader, + game_version: instance.value.game_version, }) } @@ -53,7 +53,7 @@ const availableGroups = computed(() => [ async function resetIcon() { icon.value = undefined - await edit_icon(instance.path, null).catch(handleError) + await edit_icon(instance.value.path, null).catch(handleError) trackEvent('InstanceRemoveIcon') } @@ -71,7 +71,7 @@ async function setIcon() { if (!value) return icon.value = value - await edit_icon(instance.path, icon.value).catch(handleError) + await edit_icon(instance.value.path, icon.value).catch(handleError) trackEvent('InstanceSetIcon') } @@ -102,7 +102,7 @@ watch( [title, groups, groups], async () => { if (removing.value) return - await edit(instance.path, editProfileObject.value).catch(handleError) + await edit(instance.value.path, editProfileObject.value).catch(handleError) }, { deep: true }, ) @@ -110,11 +110,11 @@ watch( const removing = ref(false) async function removeProfile() { removing.value = true - const path = instance.path + const path = instance.value.path trackEvent('InstanceRemove', { - loader: instance.loader, - game_version: instance.game_version, + loader: instance.value.loader, + game_version: instance.value.game_version, }) await router.push({ path: '/' }) diff --git a/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue index 3061ea7fc6..ae576b6fce 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue @@ -22,9 +22,11 @@ const { instance } = injectInstanceSettings() const globalSettings = (await get().catch(handleError)) as AppSettings const overrideHooks = ref( - !!instance.hooks.pre_launch || !!instance.hooks.wrapper || !!instance.hooks.post_exit, + !!instance.value.hooks.pre_launch || + !!instance.value.hooks.wrapper || + !!instance.value.hooks.post_exit, ) -const hooks = ref(instance.hooks ?? globalSettings.hooks) +const hooks = ref(instance.value.hooks ?? globalSettings.hooks) const editProfileObject = computed(() => { const editProfile: { @@ -40,7 +42,7 @@ const editProfileObject = computed(() => { watch( [overrideHooks, hooks], async () => { - await edit(instance.path, editProfileObject.value) + await edit(instance.value.path, editProfileObject.value) }, { deep: true }, ) diff --git a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue index 2aa0201b0b..3ef0be1651 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue @@ -73,9 +73,9 @@ const [ ]) const { data: modpackInfo } = useQuery({ - queryKey: computed(() => ['linkedModpackInfo', instance.path]), - queryFn: () => get_linked_modpack_info(instance.path, 'must_revalidate'), - enabled: computed(() => !!instance.linked_data?.project_id && !offline), + queryKey: computed(() => ['linkedModpackInfo', instance.value.path]), + queryFn: () => get_linked_modpack_info(instance.value.path, 'must_revalidate'), + enabled: computed(() => !!instance.value.linked_data?.project_id && !offline), }) const repairing = ref(false) @@ -101,13 +101,13 @@ function getManifest(loader: string) { provideAppBackup({ async createBackup() { const allProfiles = await list() - const prefix = `${instance.name} - Backup #` + const prefix = `${instance.value.name} - Backup #` const existingNums = allProfiles .filter((p) => p.name.startsWith(prefix)) .map((p) => parseInt(p.name.slice(prefix.length), 10)) .filter((n) => !isNaN(n)) const nextNum = existingNums.length > 0 ? Math.max(...existingNums) + 1 : 1 - const newPath = await duplicate(instance.path) + const newPath = await duplicate(instance.value.path) await edit(newPath, { name: `${prefix}${nextNum}` }) }, }) @@ -118,27 +118,30 @@ provideInstallationSettings({ const rows = [ { label: formatMessage(commonMessages.platformLabel), - value: formatLoaderLabel(instance.loader), + value: formatLoaderLabel(instance.value.loader), }, { label: formatMessage(commonMessages.gameVersionLabel), - value: instance.game_version, + value: instance.value.game_version, }, ] - if (instance.loader !== 'vanilla' && instance.loader_version) { + if (instance.value.loader !== 'vanilla' && instance.value.loader_version) { rows.push({ label: formatMessage(messages.loaderVersion, { - loader: formatLoaderLabel(instance.loader), + loader: formatLoaderLabel(instance.value.loader), }), - value: instance.loader_version, + value: instance.value.loader_version, }) } return rows }), - isLinked: computed(() => !!instance.linked_data?.locked), + isLinked: computed(() => !!instance.value.linked_data?.locked), isBusy: computed( () => - instance.install_stage !== 'installed' || repairing.value || reinstalling.value || !!offline, + instance.value.install_stage !== 'installed' || + repairing.value || + reinstalling.value || + !!offline, ), modpack: computed(() => { if (!modpackInfo.value) return null @@ -149,9 +152,9 @@ provideInstallationSettings({ versionNumber: modpackInfo.value.version?.version_number, } }), - currentPlatform: computed(() => instance.loader), - currentGameVersion: computed(() => instance.game_version), - currentLoaderVersion: computed(() => instance.loader_version ?? ''), + currentPlatform: computed(() => instance.value.loader), + currentGameVersion: computed(() => instance.value.game_version), + currentLoaderVersion: computed(() => instance.value.loader_version ?? ''), availablePlatforms: loaders?.value?.map((x) => x.name) ?? [], resolveGameVersions(loader, showSnapshots) { @@ -194,50 +197,50 @@ provideInstallationSettings({ if (platform !== 'vanilla' && loaderVersionId) { editProfile.loader_version = loaderVersionId } - await edit(instance.path, editProfile).catch(handleError) + await edit(instance.value.path, editProfile).catch(handleError) }, afterSave: async () => { - await install(instance.path, false).catch(handleError) + await install(instance.value.path, false).catch(handleError) trackEvent('InstanceRepair', { - loader: instance.loader, - game_version: instance.game_version, + loader: instance.value.loader, + game_version: instance.value.game_version, }) }, async repair() { repairing.value = true - await install(instance.path, true).catch(handleError) + await install(instance.value.path, true).catch(handleError) repairing.value = false trackEvent('InstanceRepair', { - loader: instance.loader, - game_version: instance.game_version, + loader: instance.value.loader, + game_version: instance.value.game_version, }) }, async reinstallModpack() { reinstalling.value = true - await update_repair_modrinth(instance.path).catch(handleError) + await update_repair_modrinth(instance.value.path).catch(handleError) reinstalling.value = false trackEvent('InstanceRepair', { - loader: instance.loader, - game_version: instance.game_version, + loader: instance.value.loader, + game_version: instance.value.game_version, }) }, async unlinkModpack() { - await edit(instance.path, { + await edit(instance.value.path, { linked_data: null as unknown as undefined, }) await queryClient.invalidateQueries({ - queryKey: ['linkedModpackInfo', instance.path], + queryKey: ['linkedModpackInfo', instance.value.path], }) onUnlinked() }, getCachedModpackVersions: () => null, async fetchModpackVersions() { - const versions = await get_project_versions(instance.linked_data!.project_id!).catch( + const versions = await get_project_versions(instance.value.linked_data!.project_id!).catch( handleError, ) return (versions ?? []) as Labrinth.Versions.v2.Version[] @@ -250,20 +253,20 @@ provideInstallationSettings({ }, async onModpackVersionConfirm(version) { - await update_managed_modrinth_version(instance.path, version.id) + await update_managed_modrinth_version(instance.value.path, version.id) await queryClient.invalidateQueries({ - queryKey: ['linkedModpackInfo', instance.path], + queryKey: ['linkedModpackInfo', instance.value.path], }) }, updaterModalProps: computed(() => ({ isApp: true, currentVersionId: - modpackInfo.value?.update_version_id ?? instance.linked_data?.version_id ?? '', + modpackInfo.value?.update_version_id ?? instance.value.linked_data?.version_id ?? '', projectIconUrl: modpackInfo.value?.project?.icon_url, projectName: modpackInfo.value?.project?.title ?? 'Modpack', - currentGameVersion: instance.game_version, - currentLoader: instance.loader, + currentGameVersion: instance.value.game_version, + currentLoader: instance.value.loader, })), isServer: false, diff --git a/apps/app-frontend/src/components/ui/instance_settings/JavaSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/JavaSettings.vue index 39efa5fb1f..0649621d1b 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/JavaSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/JavaSettings.vue @@ -25,20 +25,24 @@ const { instance } = injectInstanceSettings() const globalSettings = (await get().catch(handleError)) as unknown as AppSettings -const overrideJavaInstall = ref(!!instance.java_path) -const optimalJava = readonly(await get_optimal_jre_key(instance.path).catch(handleError)) -const javaInstall = ref({ path: optimalJava.path ?? instance.java_path }) +const overrideJavaInstall = ref(!!instance.value.java_path) +const optimalJava = readonly(await get_optimal_jre_key(instance.value.path).catch(handleError)) +const javaInstall = ref({ path: optimalJava.path ?? instance.value.java_path }) -const overrideJavaArgs = ref((instance.extra_launch_args?.length ?? 0) > 0) -const javaArgs = ref((instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' ')) +const overrideJavaArgs = ref((instance.value.extra_launch_args?.length ?? 0) > 0) +const javaArgs = ref( + (instance.value.extra_launch_args ?? globalSettings.extra_launch_args).join(' '), +) -const overrideEnvVars = ref((instance.custom_env_vars?.length ?? 0) > 0) +const overrideEnvVars = ref((instance.value.custom_env_vars?.length ?? 0) > 0) const envVars = ref( - (instance.custom_env_vars ?? globalSettings.custom_env_vars).map((x) => x.join('=')).join(' '), + (instance.value.custom_env_vars ?? globalSettings.custom_env_vars) + .map((x) => x.join('=')) + .join(' '), ) -const overrideMemorySettings = ref(!!instance.memory) -const memory = ref(instance.memory ?? globalSettings.memory) +const overrideMemorySettings = ref(!!instance.value.memory) +const memory = ref(instance.value.memory ?? globalSettings.memory) const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as { maxMemory: number snapPoints: number[] @@ -76,7 +80,7 @@ watch( memory, ], async () => { - await edit(instance.path, editProfileObject.value) + await edit(instance.value.path, editProfileObject.value) }, { deep: true }, ) diff --git a/apps/app-frontend/src/components/ui/instance_settings/WindowSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/WindowSettings.vue index 342c820ccc..f5ba69568a 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/WindowSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/WindowSettings.vue @@ -22,12 +22,14 @@ const { instance } = injectInstanceSettings() const globalSettings = (await get().catch(handleError)) as AppSettings -const overrideWindowSettings = ref(!!instance.game_resolution || !!instance.force_fullscreen) +const overrideWindowSettings = ref( + !!instance.value.game_resolution || !!instance.value.force_fullscreen, +) const resolution: Ref<[number, number]> = ref( - instance.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]), + instance.value.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]), ) const fullscreenSetting: Ref = ref( - instance.force_fullscreen ?? globalSettings.force_fullscreen, + instance.value.force_fullscreen ?? globalSettings.force_fullscreen, ) const editProfileObject = computed(() => { @@ -46,7 +48,7 @@ const editProfileObject = computed(() => { watch( [overrideWindowSettings, resolution, fullscreenSetting], async () => { - await edit(instance.path, editProfileObject.value) + await edit(instance.value.path, editProfileObject.value) }, { deep: true }, ) diff --git a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue index b866f2e02e..76532a30c5 100644 --- a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue @@ -44,8 +44,10 @@ const emit = defineEmits<{ const isMinecraftServer = ref(false) const handleUnlinked = () => emit('unlinked') +const instanceRef = computed(() => props.instance) + provideInstanceSettings({ - instance: props.instance, + instance: instanceRef, offline: props.offline, isMinecraftServer, onUnlinked: handleUnlinked, diff --git a/apps/app-frontend/src/components/ui/modal/ModpackAlreadyInstalledModal.vue b/apps/app-frontend/src/components/ui/modal/ModpackAlreadyInstalledModal.vue index 6b16e80229..daf238793f 100644 --- a/apps/app-frontend/src/components/ui/modal/ModpackAlreadyInstalledModal.vue +++ b/apps/app-frontend/src/components/ui/modal/ModpackAlreadyInstalledModal.vue @@ -1,21 +1,31 @@