diff --git a/Cargo.lock b/Cargo.lock
index 459048aa57..4c94019842 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11172,7 +11172,6 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tracing",
- "tracing-error",
"url",
"urlencoding",
"uuid 1.23.3",
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index 668c557a86..52f5c6d5c7 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -105,6 +105,7 @@ import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { useAppEvent } from '@/composables/use-app-event'
import { useAppSettings } from '@/composables/use-app-settings.ts'
import { useError } from '@/composables/use-error.js'
+import { useInstanceMetadataRefresh } from '@/composables/use-instance-metadata-refresh'
import { isDarkTheme, useTheme } from '@/composables/use-theme.ts'
import { config } from '@/config'
import { getAccountAppearance, rememberAccountAppearance } from '@/helpers/account-appearance.ts'
@@ -125,6 +126,7 @@ import {
get as getInstance,
get_global_synced_options,
run,
+ set_global_synced_option,
} from '@/helpers/instance'
import {
get as getCreds,
@@ -151,7 +153,7 @@ import {
} from '@/helpers/utils.js'
import { start_join_server, start_join_singleplayer_world } from '@/helpers/worlds.ts'
import i18n from '@/i18n.config'
-import { instanceKeys } from '@/pages/instance/query-options'
+import { instanceKeys, screenshotKeys } from '@/pages/instance/query-options'
import {
appUpdateState,
downloadAvailableAppUpdate,
@@ -190,6 +192,7 @@ const appTheme = useTheme()
const router = useRouter()
const route = useRoute()
const { channel: appEventChannel, events: appEvents } = setupAppEventsProvider()
+useInstanceMetadataRefresh(appEvents)
const breadcrumbManager = createBreadcrumbManager()
provideBreadcrumbManager(breadcrumbManager)
const canNavigateBack = ref(false)
@@ -1116,6 +1119,25 @@ watch(
settings.hide_nametag_skins_page = behavior.hide_nametag
settingsChanged = true
}
+
+ const showAllScreenshots = behavior.show_all_screenshots
+ if (typeof showAllScreenshots === 'boolean') {
+ const globalSyncedOptions =
+ globalSyncedOptionsQuery.data.value ??
+ (await queryClient.fetchQuery({
+ queryKey: ['global-synced-options'],
+ queryFn: get_global_synced_options,
+ }))
+ if (globalSyncedOptions.screenshots !== showAllScreenshots) {
+ const updatedGlobalSyncedOptions = await set_global_synced_option(
+ 'screenshots',
+ showAllScreenshots,
+ )
+ queryClient.setQueryData(['global-synced-options'], updatedGlobalSyncedOptions)
+ await queryClient.invalidateQueries({ queryKey: screenshotKeys.all })
+ }
+ }
+
for (const [flag, value] of Object.entries(behaviorFeatureFlags)) {
if (settings.feature_flags[flag] !== value) {
settings.feature_flags[flag] = value
diff --git a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
index d926b34069..99cc39a147 100644
--- a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
+++ b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
@@ -8,7 +8,7 @@ import {
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
-import { useQueryClient } from '@tanstack/vue-query'
+import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
@@ -17,17 +17,17 @@ import NavButton from '@/components/ui/NavButton.vue'
import { useAppEvent } from '@/composables/use-app-event'
import { handleSevereError } from '@/composables/use-error.js'
import { trackEvent } from '@/helpers/analytics'
-import { getInstanceIconUrl, kill, list, run } from '@/helpers/instance'
+import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
import { get_all } from '@/helpers/process'
import { showInstanceInFolder } from '@/helpers/utils'
-import { instanceKeys } from '@/pages/instance/query-options'
+import { instanceListQueryOptions } from '@/pages/instance/query-options'
const ITEM_SIZE = 52
const APPROX_USED_VERTICAL_SPACE = 475 // doesn't need to be exact lol just close enough so there's a little gap and no overflow
const STORAGE_KEY = 'modrinth-quick-instance-count'
const { handleError } = injectNotificationManager()
-const queryClient = useQueryClient()
+const instancesQuery = useQuery(instanceListQueryOptions())
const router = useRouter()
const instanceOptions = ref()
const runningInstances = ref([])
@@ -35,7 +35,24 @@ const runningInstances = ref([])
const { formatMessage } = useVIntl()
const maxAuto = ref(0)
-const allInstances = ref([])
+const allInstances = computed(() =>
+ (instancesQuery.data.value ?? []).slice().sort((a, b) => {
+ const dateACreated = dayjs(a.created)
+ const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0)
+
+ const dateBCreated = dayjs(b.created)
+ const dateBPlayed = b.last_played ? dayjs(b.last_played) : dayjs(0)
+
+ const dateA = dateACreated.isAfter(dateAPlayed) ? dateACreated : dateAPlayed
+ const dateB = dateBCreated.isAfter(dateBPlayed) ? dateBCreated : dateBPlayed
+
+ if (dateA.isSame(dateB)) {
+ return a.name.localeCompare(b.name)
+ }
+
+ return dateB - dateA
+ }),
+)
const dragging = ref(false)
const stored = localStorage.getItem(STORAGE_KEY)
@@ -134,40 +151,9 @@ const onDividerPointerUp = (event) => {
endDrag(event)
}
-const getInstances = async () => {
- const instances = await list().catch(handleError)
-
- for (const instance of instances) {
- queryClient.setQueryData(instanceKeys.detail(instance.id), instance)
- }
-
- allInstances.value = instances.sort((a, b) => {
- const dateACreated = dayjs(a.created)
- const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0)
-
- const dateBCreated = dayjs(b.created)
- const dateBPlayed = b.last_played ? dayjs(b.last_played) : dayjs(0)
-
- const dateA = dateACreated.isAfter(dateAPlayed) ? dateACreated : dateAPlayed
- const dateB = dateBCreated.isAfter(dateBPlayed) ? dateBCreated : dateBPlayed
-
- if (dateA.isSame(dateB)) {
- return a.name.localeCompare(b.name)
- }
-
- return dateB - dateA
- })
-}
-
-await getInstances()
+await instancesQuery.suspense().catch(handleError)
updateMaxAuto()
-useAppEvent('instance', async (event) => {
- if (event.event !== 'synced') {
- await getInstances()
- }
-})
-
useAppEvent('process', checkProcesses)
onMounted(() => {
diff --git a/apps/app-frontend/src/components/ui/screenshots-page/card.vue b/apps/app-frontend/src/components/ui/screenshots-page/card.vue
index adf1222ef8..f1de4c6532 100644
--- a/apps/app-frontend/src/components/ui/screenshots-page/card.vue
+++ b/apps/app-frontend/src/components/ui/screenshots-page/card.vue
@@ -3,11 +3,16 @@
@@ -91,7 +166,7 @@ watch(
ref="card"
role="button"
tabindex="0"
- class="group relative aspect-video min-w-0 cursor-pointer overflow-hidden rounded-xl border border-solid border-surface-5 bg-surface-2 p-0 text-left shadow-sm transition-[filter] hover:brightness-110 focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand"
+ class="group relative isolate aspect-video min-w-0 cursor-pointer overflow-hidden rounded-xl border border-solid border-surface-5 bg-surface-2 p-0 text-left shadow-sm transition-[filter] hover:brightness-110 focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand"
:class="{
'!border-contrast brightness-110': selected,
'!border-brand ring-2 ring-brand animate-pulse': highlighted,
@@ -115,7 +190,7 @@ watch(
>
-
+
@@ -193,3 +274,9 @@ watch(
+
+
diff --git a/apps/app-frontend/src/components/ui/screenshots-page/group.vue b/apps/app-frontend/src/components/ui/screenshots-page/group.vue
index 2a5c1c2997..358126f221 100644
--- a/apps/app-frontend/src/components/ui/screenshots-page/group.vue
+++ b/apps/app-frontend/src/components/ui/screenshots-page/group.vue
@@ -1,7 +1,7 @@
+
+
+
+
+ {{ formatMessage(messages.intro) }}
+
+
+
+
+
+ {{ formatMessage(messages[row.title]) }}
+
+
+ setTabVisible(row.tab, visible)"
+ />
+
+
+
+
+
diff --git a/apps/app-frontend/src/pages/instance/components/settings-modal/hooks-settings.vue b/apps/app-frontend/src/pages/instance/components/settings-modal/hooks-settings.vue
index b7f83911cb..3dfc0ca208 100644
--- a/apps/app-frontend/src/pages/instance/components/settings-modal/hooks-settings.vue
+++ b/apps/app-frontend/src/pages/instance/components/settings-modal/hooks-settings.vue
@@ -7,7 +7,6 @@ import { get } from '@/helpers/settings.ts'
import type { AppSettings } from '../../../../helpers/types'
import { injectInstanceSettings } from './instance-settings-context'
-import SettingsOptionsTransition from './settings-options-transition.vue'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
@@ -28,6 +27,16 @@ const hooks = ref({
post_exit: hooksRaw.post_exit ?? '',
})
+watch(overrideHooks, (enabled) => {
+ if (!enabled) {
+ hooks.value = {
+ pre_launch: globalSettings.hooks.pre_launch ?? '',
+ wrapper: globalSettings.hooks.wrapper ?? '',
+ post_exit: globalSettings.hooks.post_exit ?? '',
+ }
+ }
+})
+
const editInstanceObject = computed(() => ({
hooks: overrideHooks.value
? {
@@ -52,12 +61,11 @@ watch(
const messages = defineMessages({
hooks: {
id: 'instance.settings.tabs.hooks.title',
- defaultMessage: 'Game launch hooks',
+ defaultMessage: 'Custom game launch hooks',
},
hooksDescription: {
id: 'instance.settings.tabs.hooks.description',
- defaultMessage:
- 'Hooks allow advanced users to run certain system commands before and after launching the game.',
+ defaultMessage: 'Run instance-specific system commands before and after launching the game.',
},
hookVariablesDescription: {
id: 'instance.settings.tabs.hooks.variables.description',
@@ -139,62 +147,63 @@ const messages = defineMessages({
-
-
-
- {{ formatMessage(messages.preLaunch) }}
-
-
-
- {{ formatMessage(messages.preLaunchDescription) }}
-
+
+
+ {{ formatMessage(messages.preLaunch) }}
+
+
+
+ {{ formatMessage(messages.preLaunchDescription) }}
+
-
- {{ formatMessage(messages.wrapper) }}
-
-
-
- {{ formatMessage(messages.wrapperDescription) }}
-
+
+ {{ formatMessage(messages.wrapper) }}
+
+
+
+ {{ formatMessage(messages.wrapperDescription) }}
+
-
- {{ formatMessage(messages.postExit) }}
-
-
-
- {{ formatMessage(messages.postExitDescription) }}
-
+
+ {{ formatMessage(messages.postExit) }}
+
+
+
+ {{ formatMessage(messages.postExitDescription) }}
+
-
- {{ formatMessage(messages.hookVariablesDescription) }}
-
-
- - {{ formatMessage(messages.instanceNameDescription) }}
- - {{ formatMessage(messages.instanceIdDescription) }}
- - {{ formatMessage(messages.instanceDirDescription) }}
- - {{ formatMessage(messages.instanceMcDirDescription) }}
- - {{ formatMessage(messages.instanceJavaDescription) }}
- - {{ formatMessage(messages.instanceJavaArgsDescription) }}
-
+
+ {{ formatMessage(messages.hookVariablesDescription) }}
-
+
+ - {{ formatMessage(messages.instanceNameDescription) }}
+ - {{ formatMessage(messages.instanceIdDescription) }}
+ - {{ formatMessage(messages.instanceDirDescription) }}
+ - {{ formatMessage(messages.instanceMcDirDescription) }}
+ - {{ formatMessage(messages.instanceJavaDescription) }}
+ - {{ formatMessage(messages.instanceJavaArgsDescription) }}
+
+
diff --git a/apps/app-frontend/src/pages/instance/components/settings-modal/index.vue b/apps/app-frontend/src/pages/instance/components/settings-modal/index.vue
index e1598c30de..ac96db7719 100644
--- a/apps/app-frontend/src/pages/instance/components/settings-modal/index.vue
+++ b/apps/app-frontend/src/pages/instance/components/settings-modal/index.vue
@@ -1,6 +1,13 @@
diff --git a/packages/ui/src/components/image-viewer-editor/controls.vue b/packages/ui/src/components/image-viewer-editor/controls.vue
index 96527033a5..d28761ac89 100644
--- a/packages/ui/src/components/image-viewer-editor/controls.vue
+++ b/packages/ui/src/components/image-viewer-editor/controls.vue
@@ -19,7 +19,7 @@ import { useVIntl } from '#ui/composables/i18n'
import { commonMessages } from '#ui/utils/common-messages'
import { imageViewerEditorMessages as messages } from './image-viewer-editor-messages'
-import type { ScreenshotCensorMode, ScreenshotEraserMode } from './image-viewer-editor-types'
+import type { ScreenshotEraserMode } from './image-viewer-editor-types'
import type { useImageEditor } from './use-image-editor'
const props = defineProps<{
@@ -37,7 +37,6 @@ const {
color,
strokeWidth,
fontSize,
- censorMode,
eraserMode,
zoom,
isFit,
@@ -48,7 +47,6 @@ const {
canZoomIn,
hasColorProperty,
propertyValueKind,
- showCensorMode,
showEraserMode,
showCropControls,
cropWidth,
@@ -79,11 +77,9 @@ const hasPropertyControls = computed(
() =>
showCropControls.value ||
showEraserMode.value ||
- showCensorMode.value ||
hasColorProperty.value ||
Boolean(propertyValueKind.value),
)
-const censorModes: ScreenshotCensorMode[] = ['blur', 'solid']
const eraserModes: ScreenshotEraserMode[] = ['element', 'area']
const saveOptions = computed(() => [
{
@@ -112,10 +108,6 @@ function handleColorInput(event: Event) {
updateColor((event.target as HTMLInputElement).value)
}
-function formatCensorMode(value: ScreenshotCensorMode) {
- return formatMessage(value === 'blur' ? messages.blur : messages.solid)
-}
-
function formatEraserMode(value: ScreenshotEraserMode) {
return formatMessage(value === 'element' ? messages.element : messages.area)
}
@@ -144,15 +136,6 @@ function formatEraserMode(value: ScreenshotEraserMode) {
size="small"
hide-checkmark-icon
/>
-