From 491ad194b08610aefc44dcafb153aeb7933127f0 Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 18 Mar 2026 11:27:48 -0600 Subject: [PATCH 1/3] refactor: tabbed modal to use NewModal --- .../components/ui/modal/AppSettingsModal.vue | 88 ++--- .../ui/modal/InstanceSettingsModal.vue | 38 +- .../ui/src/components/modal/TabbedModal.vue | 186 +++++---- .../src/stories/modal/TabbedModal.stories.ts | 363 ++++++++++++++++++ 4 files changed, 534 insertions(+), 141 deletions(-) create mode 100644 packages/ui/src/stories/modal/TabbedModal.stories.ts diff --git a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue index 3328fed4e6..1635b46a70 100644 --- a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue @@ -20,9 +20,8 @@ import { } from '@modrinth/ui' import { getVersion } from '@tauri-apps/api/app' import { platform as getOsPlatform, version as getOsVersion } from '@tauri-apps/plugin-os' -import { computed, ref, watch } from 'vue' +import { ref, watch } from 'vue' -import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue' import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue' import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue' @@ -106,15 +105,13 @@ const tabs = [ }, ] -const modal = ref() +const modal = ref | null>(null) function show() { - modal.value.show() + modal.value?.show() } -const isOpen = computed(() => modal.value?.isOpen) - -defineExpose({ show, isOpen }) +defineExpose({ show }) const { progress, version: downloadingVersion } = injectAppUpdateDownloadProgress() @@ -138,8 +135,8 @@ function devModeCount() { settings.value.developer_mode = !!themeStore.devMode devModeCounter.value = 0 - if (!themeStore.devMode && tabs[modal.value.selectedTab].developerOnly) { - modal.value.setTab(0) + if (!themeStore.devMode && tabs[modal.value!.selectedTab].developerOnly) { + modal.value!.setTab(0) } } } @@ -152,49 +149,46 @@ const messages = defineMessages({ }) diff --git a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue index 3543a19b34..ef53d85c9f 100644 --- a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue @@ -12,14 +12,13 @@ import { Avatar, commonMessages, defineMessage, - NewModal, TabbedModal, type TabbedModalTab, useVIntl, } from '@modrinth/ui' import { useQueryClient } from '@tanstack/vue-query' import { convertFileSrc } from '@tauri-apps/api/core' -import { computed, nextTick, ref, useTemplateRef, watch } from 'vue' +import { computed, nextTick, ref, watch } from 'vue' import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue' import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue' @@ -102,8 +101,7 @@ const tabs = computed[]>(() => [ ]) const queryClient = useQueryClient() -const modal = ref() -const tabbedModal = useTemplateRef('tabbedModal') +const tabbedModal = ref | null>(null) function show(tabIndex?: number) { if (props.instance.linked_data?.project_id) { @@ -112,7 +110,7 @@ function show(tabIndex?: number) { queryFn: () => get_linked_modpack_info(props.instance.path, 'stale_while_revalidate'), }) } - modal.value.show() + tabbedModal.value?.show() if (tabIndex !== undefined) { nextTick(() => tabbedModal.value?.setTab(tabIndex)) } @@ -121,8 +119,18 @@ function show(tabIndex?: number) { defineExpose({ show }) - - - + diff --git a/packages/ui/src/components/modal/TabbedModal.vue b/packages/ui/src/components/modal/TabbedModal.vue index 05e495c08e..07198e03bd 100644 --- a/packages/ui/src/components/modal/TabbedModal.vue +++ b/packages/ui/src/components/modal/TabbedModal.vue @@ -1,24 +1,42 @@ - -const props = defineProps<{ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - tabs: Tab[] -}>() + diff --git a/packages/ui/src/stories/modal/TabbedModal.stories.ts b/packages/ui/src/stories/modal/TabbedModal.stories.ts new file mode 100644 index 0000000000..c476dcb4ec --- /dev/null +++ b/packages/ui/src/stories/modal/TabbedModal.stories.ts @@ -0,0 +1,363 @@ +import { + CoffeeIcon, + GameIcon, + GaugeIcon, + InfoIcon, + LanguagesIcon, + MonitorIcon, + PaintbrushIcon, + ReportIcon, + SettingsIcon, + ShieldIcon, + WrenchIcon, +} from '@modrinth/assets' +import type { StoryObj } from '@storybook/vue3-vite' +import { defineComponent, h, ref } from 'vue' + +import ButtonStyled from '../../components/base/ButtonStyled.vue' +import TabbedModal from '../../components/modal/TabbedModal.vue' + +function makeTabContent(label: string, lines = 3) { + return defineComponent({ + name: `${label}Tab`, + render() { + return h('div', { class: 'space-y-4 py-2' }, [ + h('h2', { class: 'text-xl font-bold text-contrast m-0' }, label), + ...Array.from({ length: lines }, (_, i) => + h('p', { class: 'text-secondary m-0' }, `${label} content paragraph ${i + 1}.`), + ), + ]) + }, + }) +} + +const meta = { + title: 'Modal/TabbedModal', + // @ts-ignore + component: TabbedModal, +} + +export default meta + +export const Default: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'general', defaultMessage: 'General' }, + icon: InfoIcon, + content: makeTabContent('General'), + }, + { + name: { id: 'appearance', defaultMessage: 'Appearance' }, + icon: PaintbrushIcon, + content: makeTabContent('Appearance'), + }, + { + name: { id: 'privacy', defaultMessage: 'Privacy' }, + icon: ShieldIcon, + content: makeTabContent('Privacy'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + +
+ `, + }), +} + +export const WithTitleSlot: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled, SettingsIcon }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'general', defaultMessage: 'General' }, + icon: InfoIcon, + content: makeTabContent('General'), + }, + { + name: { id: 'appearance', defaultMessage: 'Appearance' }, + icon: PaintbrushIcon, + content: makeTabContent('Appearance'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + + + +
+ `, + }), +} + +export const WithFooter: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'general', defaultMessage: 'General' }, + icon: InfoIcon, + content: makeTabContent('General'), + }, + { + name: { id: 'appearance', defaultMessage: 'Appearance' }, + icon: PaintbrushIcon, + content: makeTabContent('Appearance'), + }, + { + name: { id: 'privacy', defaultMessage: 'Privacy' }, + icon: ShieldIcon, + content: makeTabContent('Privacy'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + + + +
+ `, + }), +} + +export const WithBadge: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'general', defaultMessage: 'General' }, + icon: InfoIcon, + content: makeTabContent('General'), + }, + { + name: { id: 'language', defaultMessage: 'Language' }, + icon: LanguagesIcon, + content: makeTabContent('Language'), + badge: { id: 'beta', defaultMessage: 'Beta' }, + }, + { + name: { id: 'privacy', defaultMessage: 'Privacy' }, + icon: ShieldIcon, + content: makeTabContent('Privacy'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + +
+ `, + }), +} + +export const HiddenTabs: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'general', defaultMessage: 'General' }, + icon: InfoIcon, + content: makeTabContent('General'), + }, + { + name: { id: 'hidden', defaultMessage: 'Hidden Tab' }, + icon: ReportIcon, + content: makeTabContent('Hidden'), + shown: false, + }, + { + name: { id: 'appearance', defaultMessage: 'Appearance' }, + icon: PaintbrushIcon, + content: makeTabContent('Appearance'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + +
+ `, + }), +} + +export const ManyTabs: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'general', defaultMessage: 'General' }, + icon: InfoIcon, + content: makeTabContent('General'), + }, + { + name: { id: 'appearance', defaultMessage: 'Appearance' }, + icon: PaintbrushIcon, + content: makeTabContent('Appearance'), + }, + { + name: { id: 'language', defaultMessage: 'Language' }, + icon: LanguagesIcon, + content: makeTabContent('Language'), + }, + { + name: { id: 'privacy', defaultMessage: 'Privacy' }, + icon: ShieldIcon, + content: makeTabContent('Privacy'), + }, + { + name: { id: 'java', defaultMessage: 'Java and memory' }, + icon: CoffeeIcon, + content: makeTabContent('Java and memory'), + }, + { + name: { id: 'instances', defaultMessage: 'Default instance options' }, + icon: GameIcon, + content: makeTabContent('Default instance options'), + }, + { + name: { id: 'resources', defaultMessage: 'Resource management' }, + icon: GaugeIcon, + content: makeTabContent('Resource management'), + }, + { + name: { id: 'window', defaultMessage: 'Window' }, + icon: MonitorIcon, + content: makeTabContent('Window'), + }, + { + name: { id: 'hooks', defaultMessage: 'Launch hooks' }, + icon: WrenchIcon, + content: makeTabContent('Launch hooks'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + +
+ `, + }), +} + +export const WithTabProps: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + + const PropsDisplay = defineComponent({ + props: { username: { type: String, default: '' }, role: { type: String, default: '' } }, + render() { + return h('div', { class: 'space-y-2 py-2' }, [ + h('h2', { class: 'text-xl font-bold text-contrast m-0' }, 'User Profile'), + h('p', { class: 'text-primary m-0' }, `Username: ${this.username}`), + h('p', { class: 'text-primary m-0' }, `Role: ${this.role}`), + ]) + }, + }) + + const tabs = [ + { + name: { id: 'profile', defaultMessage: 'Profile' }, + icon: InfoIcon, + content: PropsDisplay, + props: { username: 'modrinth_user', role: 'Developer' }, + }, + { + name: { id: 'settings', defaultMessage: 'Settings' }, + icon: PaintbrushIcon, + content: makeTabContent('Settings'), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + +
+ `, + }), +} + +export const ScrollableContent: StoryObj = { + render: () => ({ + components: { TabbedModal, ButtonStyled }, + setup() { + const modalRef = ref | null>(null) + const tabs = [ + { + name: { id: 'long', defaultMessage: 'Long content' }, + icon: InfoIcon, + content: makeTabContent('Long content', 30), + }, + { + name: { id: 'short', defaultMessage: 'Short content' }, + icon: PaintbrushIcon, + content: makeTabContent('Short content', 2), + }, + ] + return { modalRef, tabs } + }, + template: /* html */ ` +
+ + + + +
+ `, + }), +} From 46b7286873ad307fc2248ec1e432c939bb899056 Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 18 Mar 2026 11:37:54 -0600 Subject: [PATCH 2/3] refactor: use DI for instance settings modal instead of passing down props --- .../ui/instance_settings/GeneralSettings.vue | 33 ++++---- .../ui/instance_settings/HooksSettings.vue | 15 ++-- .../InstallationSettings.vue | 80 +++++++++---------- .../ui/instance_settings/JavaSettings.vue | 25 +++--- .../ui/instance_settings/WindowSettings.vue | 13 +-- .../ui/modal/InstanceSettingsModal.vue | 28 ++++--- apps/app-frontend/src/helpers/types.d.ts | 6 -- .../src/providers/instance-settings.ts | 14 ++++ .../ui/src/components/modal/TabbedModal.vue | 12 +-- .../src/stories/modal/TabbedModal.stories.ts | 43 ---------- 10 files changed, 117 insertions(+), 152 deletions(-) create mode 100644 apps/app-frontend/src/providers/instance-settings.ts 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 ead9416bfa..f8d4052209 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue @@ -18,8 +18,9 @@ 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 { injectInstanceSettings } from '@/providers/instance-settings' -import type { GameInstance, InstanceSettingsTabProps } from '../../../helpers/types' +import type { GameInstance } from '../../../helpers/types' const { handleError } = injectNotificationManager() const { formatMessage } = useVIntl() @@ -27,21 +28,21 @@ const router = useRouter() const deleteConfirmModal = ref() -const props = defineProps() +const { instance } = injectInstanceSettings() -const title = ref(props.instance.name) -const icon: Ref = ref(props.instance.icon_path) -const groups = ref(props.instance.groups) +const title = ref(instance.name) +const icon: Ref = ref(instance.icon_path) +const groups = ref(instance.groups) const newCategoryInput = ref('') -const installing = computed(() => props.instance.install_stage !== 'installed') +const installing = computed(() => instance.install_stage !== 'installed') async function duplicateProfile() { - await duplicate(props.instance.path).catch(handleError) + await duplicate(instance.path).catch(handleError) trackEvent('InstanceDuplicate', { - loader: props.instance.loader, - game_version: props.instance.game_version, + loader: instance.loader, + game_version: instance.game_version, }) } @@ -52,7 +53,7 @@ const availableGroups = computed(() => [ async function resetIcon() { icon.value = undefined - await edit_icon(props.instance.path, null).catch(handleError) + await edit_icon(instance.path, null).catch(handleError) trackEvent('InstanceRemoveIcon') } @@ -70,7 +71,7 @@ async function setIcon() { if (!value) return icon.value = value - await edit_icon(props.instance.path, icon.value).catch(handleError) + await edit_icon(instance.path, icon.value).catch(handleError) trackEvent('InstanceSetIcon') } @@ -101,7 +102,7 @@ watch( [title, groups, groups], async () => { if (removing.value) return - await edit(props.instance.path, editProfileObject.value).catch(handleError) + await edit(instance.path, editProfileObject.value).catch(handleError) }, { deep: true }, ) @@ -109,11 +110,11 @@ watch( const removing = ref(false) async function removeProfile() { removing.value = true - const path = props.instance.path + const path = instance.path trackEvent('InstanceRemove', { - loader: props.instance.loader, - game_version: props.instance.game_version, + loader: instance.loader, + game_version: instance.game_version, }) await router.push({ path: '/' }) @@ -218,7 +219,7 @@ const messages = defineMessages({ :src="icon ? convertFileSrc(icon) : icon" size="108px" class="!border-4 group-hover:brightness-75" - :tint-by="props.instance.path" + :tint-by="instance.path" no-shadow />
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 ff2bb35e31..2171859ff9 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue @@ -10,22 +10,23 @@ import { computed, ref, watch } from 'vue' import { edit } from '@/helpers/profile' import { get } from '@/helpers/settings.ts' +import { injectInstanceSettings } from '@/providers/instance-settings' -import type { AppSettings, Hooks, InstanceSettingsTabProps } from '../../../helpers/types' +import type { AppSettings, Hooks } from '../../../helpers/types' const { handleError } = injectNotificationManager() const { formatMessage } = useVIntl() -const props = defineProps() +const { instance } = injectInstanceSettings() const globalSettings = (await get().catch(handleError)) as AppSettings const overrideHooks = ref( - !!props.instance.hooks.pre_launch || - !!props.instance.hooks.wrapper || - !!props.instance.hooks.post_exit, + !!instance.hooks.pre_launch || + !!instance.hooks.wrapper || + !!instance.hooks.post_exit, ) -const hooks = ref(props.instance.hooks ?? globalSettings.hooks) +const hooks = ref(instance.hooks ?? globalSettings.hooks) const editProfileObject = computed(() => { const editProfile: { @@ -41,7 +42,7 @@ const editProfileObject = computed(() => { watch( [overrideHooks, hooks], async () => { - await edit(props.instance.path, editProfileObject.value) + await edit(instance.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 9874e53468..2aeb390c17 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue @@ -27,17 +27,15 @@ import { update_repair_modrinth, } from '@/helpers/profile' import { get_game_versions, get_loaders } from '@/helpers/tags' +import { injectInstanceSettings } from '@/providers/instance-settings' -import type { InstanceSettingsTabProps, Manifest } from '../../../helpers/types' +import type { Manifest } from '../../../helpers/types' const { handleError } = injectNotificationManager() const { formatMessage } = useVIntl() const queryClient = useQueryClient() -const props = defineProps() -const emit = defineEmits<{ - unlinked: [] -}>() +const { instance, offline, isMinecraftServer, onUnlinked } = injectInstanceSettings() const [ fabric_versions, @@ -75,9 +73,9 @@ const [ ]) const { data: modpackInfo } = useQuery({ - queryKey: computed(() => ['linkedModpackInfo', props.instance.path]), - queryFn: () => get_linked_modpack_info(props.instance.path, 'must_revalidate'), - enabled: computed(() => !!props.instance.linked_data?.project_id && !props.offline), + queryKey: computed(() => ['linkedModpackInfo', instance.path]), + queryFn: () => get_linked_modpack_info(instance.path, 'must_revalidate'), + enabled: computed(() => !!instance.linked_data?.project_id && !offline), }) const repairing = ref(false) @@ -103,13 +101,13 @@ function getManifest(loader: string) { provideAppBackup({ async createBackup() { const allProfiles = await list() - const prefix = `${props.instance.name} - Backup #` + const prefix = `${instance.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(props.instance.path) + const newPath = await duplicate(instance.path) await edit(newPath, { name: `${prefix}${nextNum}` }) }, }) @@ -120,30 +118,30 @@ provideInstallationSettings({ const rows = [ { label: formatMessage(commonMessages.platformLabel), - value: formatLoaderLabel(props.instance.loader), + value: formatLoaderLabel(instance.loader), }, { label: formatMessage(commonMessages.gameVersionLabel), - value: props.instance.game_version, + value: instance.game_version, }, ] - if (props.instance.loader !== 'vanilla' && props.instance.loader_version) { + if (instance.loader !== 'vanilla' && instance.loader_version) { rows.push({ label: formatMessage(messages.loaderVersion, { - loader: formatLoaderLabel(props.instance.loader), + loader: formatLoaderLabel(instance.loader), }), - value: props.instance.loader_version, + value: instance.loader_version, }) } return rows }), - isLinked: computed(() => !!props.instance.linked_data?.locked), + isLinked: computed(() => !!instance.linked_data?.locked), isBusy: computed( () => - props.instance.install_stage !== 'installed' || + instance.install_stage !== 'installed' || repairing.value || reinstalling.value || - !!props.offline, + !!offline, ), modpack: computed(() => { if (!modpackInfo.value) return null @@ -154,9 +152,9 @@ provideInstallationSettings({ versionNumber: modpackInfo.value.version?.version_number, } }), - currentPlatform: computed(() => props.instance.loader), - currentGameVersion: computed(() => props.instance.game_version), - currentLoaderVersion: computed(() => props.instance.loader_version ?? ''), + currentPlatform: computed(() => instance.loader), + currentGameVersion: computed(() => instance.game_version), + currentLoaderVersion: computed(() => instance.loader_version ?? ''), availablePlatforms: loaders?.value?.map((x) => x.name) ?? [], resolveGameVersions(loader, showSnapshots) { @@ -199,50 +197,50 @@ provideInstallationSettings({ if (platform !== 'vanilla' && loaderVersionId) { editProfile.loader_version = loaderVersionId } - await edit(props.instance.path, editProfile).catch(handleError) + await edit(instance.path, editProfile).catch(handleError) }, afterSave: async () => { - await install(props.instance.path, false).catch(handleError) + await install(instance.path, false).catch(handleError) trackEvent('InstanceRepair', { - loader: props.instance.loader, - game_version: props.instance.game_version, + loader: instance.loader, + game_version: instance.game_version, }) }, async repair() { repairing.value = true - await install(props.instance.path, true).catch(handleError) + await install(instance.path, true).catch(handleError) repairing.value = false trackEvent('InstanceRepair', { - loader: props.instance.loader, - game_version: props.instance.game_version, + loader: instance.loader, + game_version: instance.game_version, }) }, async reinstallModpack() { reinstalling.value = true - await update_repair_modrinth(props.instance.path).catch(handleError) + await update_repair_modrinth(instance.path).catch(handleError) reinstalling.value = false trackEvent('InstanceRepair', { - loader: props.instance.loader, - game_version: props.instance.game_version, + loader: instance.loader, + game_version: instance.game_version, }) }, async unlinkModpack() { - await edit(props.instance.path, { + await edit(instance.path, { linked_data: null as unknown as undefined, }) await queryClient.invalidateQueries({ - queryKey: ['linkedModpackInfo', props.instance.path], + queryKey: ['linkedModpackInfo', instance.path], }) - emit('unlinked') + onUnlinked() }, getCachedModpackVersions: () => null, async fetchModpackVersions() { - const versions = await get_project_versions(props.instance.linked_data!.project_id!).catch( + const versions = await get_project_versions(instance.linked_data!.project_id!).catch( handleError, ) return (versions ?? []) as Labrinth.Versions.v2.Version[] @@ -255,25 +253,25 @@ provideInstallationSettings({ }, async onModpackVersionConfirm(version) { - await update_managed_modrinth_version(props.instance.path, version.id) + await update_managed_modrinth_version(instance.path, version.id) await queryClient.invalidateQueries({ - queryKey: ['linkedModpackInfo', props.instance.path], + queryKey: ['linkedModpackInfo', instance.path], }) }, updaterModalProps: computed(() => ({ isApp: true, currentVersionId: - modpackInfo.value?.update_version_id ?? props.instance.linked_data?.version_id ?? '', + modpackInfo.value?.update_version_id ?? instance.linked_data?.version_id ?? '', projectIconUrl: modpackInfo.value?.project?.icon_url, projectName: modpackInfo.value?.project?.title ?? 'Modpack', - currentGameVersion: props.instance.game_version, - currentLoader: props.instance.loader, + currentGameVersion: instance.game_version, + currentLoader: instance.loader, })), isServer: false, isApp: true, - showModpackVersionActions: !props.isMinecraftServer, + showModpackVersionActions: !isMinecraftServer.value, repairing, reinstalling, }) 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 0622de837a..edf99cde95 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/JavaSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/JavaSettings.vue @@ -14,34 +14,35 @@ import JavaSelector from '@/components/ui/JavaSelector.vue' import useMemorySlider from '@/composables/useMemorySlider' import { edit, get_optimal_jre_key } from '@/helpers/profile' import { get } from '@/helpers/settings.ts' +import { injectInstanceSettings } from '@/providers/instance-settings' -import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types' +import type { AppSettings } from '../../../helpers/types' const { handleError } = injectNotificationManager() const { formatMessage } = useVIntl() -const props = defineProps() +const { instance } = injectInstanceSettings() const globalSettings = (await get().catch(handleError)) as unknown as AppSettings -const overrideJavaInstall = ref(!!props.instance.java_path) -const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError)) -const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path }) +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 overrideJavaArgs = ref((props.instance.extra_launch_args?.length ?? 0) > 0) +const overrideJavaArgs = ref((instance.extra_launch_args?.length ?? 0) > 0) const javaArgs = ref( - (props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '), + (instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '), ) -const overrideEnvVars = ref((props.instance.custom_env_vars?.length ?? 0) > 0) +const overrideEnvVars = ref((instance.custom_env_vars?.length ?? 0) > 0) const envVars = ref( - (props.instance.custom_env_vars ?? globalSettings.custom_env_vars) + (instance.custom_env_vars ?? globalSettings.custom_env_vars) .map((x) => x.join('=')) .join(' '), ) -const overrideMemorySettings = ref(!!props.instance.memory) -const memory = ref(props.instance.memory ?? globalSettings.memory) +const overrideMemorySettings = ref(!!instance.memory) +const memory = ref(instance.memory ?? globalSettings.memory) const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as { maxMemory: number snapPoints: number[] @@ -79,7 +80,7 @@ watch( memory, ], async () => { - await edit(props.instance.path, editProfileObject.value) + await edit(instance.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 8bc1bc5fed..df7e625af7 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/WindowSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/WindowSettings.vue @@ -11,24 +11,25 @@ import { computed, type Ref, ref, watch } from 'vue' import { edit } from '@/helpers/profile' import { get } from '@/helpers/settings.ts' +import { injectInstanceSettings } from '@/providers/instance-settings' -import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types' +import type { AppSettings } from '../../../helpers/types' const { handleError } = injectNotificationManager() const { formatMessage } = useVIntl() -const props = defineProps() +const { instance } = injectInstanceSettings() const globalSettings = (await get().catch(handleError)) as AppSettings const overrideWindowSettings = ref( - !!props.instance.game_resolution || !!props.instance.force_fullscreen, + !!instance.game_resolution || !!instance.force_fullscreen, ) const resolution: Ref<[number, number]> = ref( - props.instance.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]), + instance.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]), ) const fullscreenSetting: Ref = ref( - props.instance.force_fullscreen ?? globalSettings.force_fullscreen, + instance.force_fullscreen ?? globalSettings.force_fullscreen, ) const editProfileObject = computed(() => { @@ -47,7 +48,7 @@ const editProfileObject = computed(() => { watch( [overrideWindowSettings, resolution, fullscreenSetting], async () => { - await edit(props.instance.path, editProfileObject.value) + await edit(instance.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 ef53d85c9f..b866f2e02e 100644 --- a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue @@ -27,12 +27,16 @@ import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue' import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue' import { get_project_v3 } from '@/helpers/cache' import { get_linked_modpack_info } from '@/helpers/profile' +import { provideInstanceSettings } from '@/providers/instance-settings' -import type { InstanceSettingsTabProps } from '../../../helpers/types' +import type { GameInstance } from '../../../helpers/types' const { formatMessage } = useVIntl() -const props = defineProps() +const props = defineProps<{ + instance: GameInstance + offline?: boolean +}>() const emit = defineEmits<{ unlinked: [] }>() @@ -40,6 +44,13 @@ const emit = defineEmits<{ const isMinecraftServer = ref(false) const handleUnlinked = () => emit('unlinked') +provideInstanceSettings({ + instance: props.instance, + offline: props.offline, + isMinecraftServer, + onUnlinked: handleUnlinked, +}) + watch( () => props.instance, (instance) => { @@ -57,7 +68,7 @@ watch( { immediate: true }, ) -const tabs = computed[]>(() => [ +const tabs = computed(() => [ { name: defineMessage({ id: 'instance.settings.tabs.general', @@ -121,16 +132,7 @@ defineExpose({ show })