Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7a72556
draft: options.txt syncing
IMB11 Sep 1, 2026
8ae5918
feat: qa
IMB11 Sep 1, 2026
b9fb781
fix: msgs
IMB11 Sep 1, 2026
a85c464
fix: lint + prepr
IMB11 Sep 1, 2026
c689997
fix: qa
IMB11 Sep 1, 2026
489f93e
chore: cleanup structure in app-lib
IMB11 Sep 1, 2026
05c22cd
chore: 26.3 + history readme
IMB11 Sep 1, 2026
8900a76
fix: qa
IMB11 Sep 1, 2026
037f41e
fix: qa
IMB11 Sep 1, 2026
a97b79d
fix: qa
IMB11 Sep 1, 2026
fda3f77
fix: qa
IMB11 Sep 1, 2026
52c7b9e
feat: full writeup of options.txt catalog history
IMB11 Sep 2, 2026
82e0b85
Add enum types
IMB11 Sep 2, 2026
37f6364
fix: qa
IMB11 Sep 2, 2026
52a8e52
fix: qa
IMB11 Sep 2, 2026
1cd782b
fix: qa
IMB11 Sep 2, 2026
992cd8a
qa: pass 4
IMB11 Sep 3, 2026
d5caaa4
qa
IMB11 Sep 3, 2026
bfa2e1a
chore: cleanup frontend impl
IMB11 Sep 3, 2026
80dcc74
fix: options.txt handling for synced resource packs
IMB11 Sep 3, 2026
f8b89ed
fix: try fix freeze issue
IMB11 Sep 4, 2026
bae9033
qa: pass
IMB11 Sep 4, 2026
9871f88
fix: qa
IMB11 Sep 4, 2026
36f63d4
fix: qa
IMB11 Sep 4, 2026
766cbd9
chore: panic
IMB11 Sep 4, 2026
07660d2
fix: ads using state before ready
IMB11 Sep 4, 2026
543f545
fix: qa
IMB11 Sep 4, 2026
2bfb7fa
fix: qa
IMB11 Sep 4, 2026
2147f93
fix: qa
IMB11 Sep 4, 2026
4b9e434
fix: qa
IMB11 Sep 4, 2026
5e6fc01
fix: not using right version field
IMB11 Sep 4, 2026
32fa326
fix: lint
IMB11 Sep 4, 2026
316e646
fix: qa
IMB11 Sep 4, 2026
f916678
fix: draft until continue or close
IMB11 Sep 4, 2026
1fe4dea
fix: qa
IMB11 Sep 4, 2026
b12dc79
fix: qa
IMB11 Sep 4, 2026
f91a52f
revert: icon change
IMB11 Sep 4, 2026
f394133
fix: fmt + lint
IMB11 Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 88 additions & 27 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
ChevronLeftIcon,
ChevronRightIcon,
CompassIcon,
ImagesIcon,
ImageIcon,
LogInIcon,
LogOutIcon,
NewspaperIcon,
Expand Down Expand Up @@ -91,21 +91,21 @@ import ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyIn
import ModrinthAccountRequiredModal from '@/components/ui/modal/ModrinthAccountRequiredModal.vue'
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
import NavButton from '@/components/ui/NavButton.vue'
import NewIconEditorNotification from '@/components/ui/new-icon-editor-notification/index.vue'
import { shouldShowNewIconEditorNotification } from '@/components/ui/new-icon-editor-notification/show-notification'
import OnboardingChecklist from '@/components/ui/onboarding-checklist/index.vue'
import PrideFundraiserBanner from '@/components/ui/PrideFundraiserBanner.vue'
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import SharedInstanceInviteHandler from '@/components/ui/shared-instances/shared-instance-invite-handler/index.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
import SurveyPopup from '@/components/ui/SurveyPopup.vue'
import SyncInstancesUpdateModal from '@/components/ui/sync-instances-update-modal/index.vue'
import WindowControls from '@/components/ui/WindowControls.vue'
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 { useQuickInstanceLimit } from '@/composables/use-quick-instance-limit.ts'
import { isDarkTheme, useTheme } from '@/composables/use-theme.ts'
import { config } from '@/config'
import { getAccountAppearance, rememberAccountAppearance } from '@/helpers/account-appearance.ts'
Expand All @@ -124,7 +124,6 @@ import { install_create_modpack_instance, install_get_modpack_preview } from '@/
import {
can_current_user_use_shared_instances,
get as getInstance,
get_global_synced_options,
run,
set_global_synced_option,
} from '@/helpers/instance'
Expand All @@ -137,8 +136,9 @@ import {
setActive,
} from '@/helpers/mr_auth.ts'
import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
import { appSettingsKeys, get as getSettings, set as setSettings } from '@/helpers/settings.ts'
import { get_opening_command, initialize_state } from '@/helpers/state'
import { globalSyncedOptionsQueryOptions, syncedOptionsKeys } from '@/helpers/synced-options'
import { hasActivePride26Midas, hasMidasBadge } from '@/helpers/user-campaigns.ts'
import { get_user_preferences } from '@/helpers/user-preferences.ts'
import { parse_modrinth_user_link } from '@/helpers/users'
Expand All @@ -153,7 +153,11 @@ import {
} from '@/helpers/utils.js'
import { start_join_server, start_join_singleplayer_world } from '@/helpers/worlds.ts'
import i18n from '@/i18n.config'
import { instanceKeys, screenshotKeys } from '@/pages/instance/query-options'
import {
instanceKeys,
instanceListQueryOptions,
screenshotKeys,
} from '@/pages/instance/query-options'
import {
appUpdateState,
downloadAvailableAppUpdate,
Expand Down Expand Up @@ -189,6 +193,7 @@ import {

const appSettings = useAppSettings()
const appTheme = useTheme()
const quickInstances = useQuickInstanceLimit()
const router = useRouter()
const route = useRoute()
const { channel: appEventChannel, events: appEvents } = setupAppEventsProvider()
Expand Down Expand Up @@ -384,6 +389,7 @@ const {
handleModpackDuplicateCreateAnyway,
handleModpackDuplicateGoToInstance,
onboardingChecklist,
tags,
} = setupProviders(
tauriApiClient,
notificationManager,
Expand Down Expand Up @@ -452,8 +458,7 @@ const isDevEnvironment = ref(false)

const stateInitialized = ref(false)
const globalSyncedOptionsQuery = useQuery({
queryKey: ['global-synced-options'],
queryFn: get_global_synced_options,
...globalSyncedOptionsQueryOptions(),
enabled: computed(() => stateInitialized.value),
})

Expand Down Expand Up @@ -705,16 +710,9 @@ function handleAdsConsentRequired(required) {
}

async function setupApp() {
tags.initialize()
await onboardingChecklist.initialize()

if (shouldShowNewIconEditorNotification(showChecklist.value)) {
addPopupNotification({
contentType: 'custom',
component: NewIconEditorNotification,
autoCloseMs: null,
})
}

const {
native_decorations,
theme,
Expand All @@ -725,6 +723,11 @@ async function setupApp() {
toggle_sidebar,
sync_theme_across_devices,
sync_behavior_across_devices,
sync_features_across_devices,
show_files_tab_in_instances,
show_worlds_tab_in_instances,
show_screenshots_tab_in_instances,
show_skin_selector_in_sidebar,
developer_mode,
feature_flags,
pending_update_toast_for_version,
Expand All @@ -749,10 +752,23 @@ async function setupApp() {
appTheme.advancedRendering = advanced_rendering
appTheme.syncAcrossDevices = sync_theme_across_devices
appSettings.syncBehaviorAcrossDevices = sync_behavior_across_devices
appSettings.syncFeaturesAcrossDevices = sync_features_across_devices
appSettings.hideNametagSkinsPage = hide_nametag_skins_page
appSettings.toggleSidebar = toggle_sidebar
appSettings.showFilesTabInInstances = show_files_tab_in_instances
appSettings.showWorldsTabInInstances = show_worlds_tab_in_instances
appSettings.showScreenshotsTabInInstances = show_screenshots_tab_in_instances
appSettings.showSkinSelectorInSidebar = show_skin_selector_in_sidebar
appSettings.devMode = developer_mode
stateInitialized.value = true
await nextTick()
if (
appSettings.getFeatureFlag('show_sync_instances_update_modal') ||
(pending_update_toast_for_version === version &&
(await queryClient.fetchQuery(instanceListQueryOptions())).length > 0)
) {
syncInstancesUpdateModal.value?.show()
}

await getCurrentWindow().onResized(async () => {
isMaximized.value = await getCurrentWindow().isMaximized()
Expand Down Expand Up @@ -1038,9 +1054,19 @@ const updateToPlayModal = ref()

const modrinthLoginModal = ref()
const appSettingsModal = ref()
const syncInstancesUpdateModal = ref()
provide(appSettingsModalOpenProfileKey, () => appSettingsModal.value?.showProfile())
provide(appSettingsModalOpenSyncedOptionsKey, () => appSettingsModal.value?.showSyncedOptions())

watch(
() => appSettings.getFeatureFlag('show_sync_instances_update_modal'),
(enabled) => {
if (enabled && stateInitialized.value) {
syncInstancesUpdateModal.value?.show()
}
},
)

watch(incompatibilityWarningModal, (modal) => {
if (modal) {
setContentIncompatibilityWarningModal(modal)
Expand Down Expand Up @@ -1096,7 +1122,6 @@ watch(

if (behavior && appSettings.syncBehaviorAcrossDevices) {
const behaviorFeatureFlags = {
worlds_in_home: behavior.show_jump_in,
compact_instance_cards: behavior.compact_instance_cards,
show_instance_play_time: behavior.show_play_time,
skip_unknown_pack_warning: !behavior.warn_on_unknown_modpacks,
Expand All @@ -1120,25 +1145,53 @@ watch(
settingsChanged = true
}

for (const [flag, value] of Object.entries(behaviorFeatureFlags)) {
if (settings.feature_flags[flag] !== value) {
settings.feature_flags[flag] = value
settingsChanged = true
}
}
}

if (behavior && appSettings.syncFeaturesAcrossDevices) {
const featureFlags = {
worlds_in_home: behavior.show_jump_in,
}
const featureSettings = {
show_files_tab_in_instances: 'showFilesTabInInstances',
show_worlds_tab_in_instances: 'showWorldsTabInInstances',
show_screenshots_tab_in_instances: 'showScreenshotsTabInInstances',
show_skin_selector_in_sidebar: 'showSkinSelectorInSidebar',
}
for (const [key, stateKey] of Object.entries(featureSettings)) {
const value = behavior[key] ?? settings[key]
appSettings[stateKey] = value
if (settings[key] !== value) {
settings[key] = value
settingsChanged = true
}
}
Object.assign(appSettings.featureFlags, featureFlags)
if (typeof behavior.quick_instance_count === 'number') {
quickInstances.setLimit(behavior.quick_instance_count)
}

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,
}))
(await queryClient.fetchQuery(globalSyncedOptionsQueryOptions()))
if (globalSyncedOptions.screenshots !== showAllScreenshots) {
const updatedGlobalSyncedOptions = await set_global_synced_option(
'screenshots',
showAllScreenshots,
)
queryClient.setQueryData(['global-synced-options'], updatedGlobalSyncedOptions)
queryClient.setQueryData(syncedOptionsKeys.global, updatedGlobalSyncedOptions)
await queryClient.invalidateQueries({ queryKey: screenshotKeys.all })
}
}

for (const [flag, value] of Object.entries(behaviorFeatureFlags)) {
for (const [flag, value] of Object.entries(featureFlags)) {
if (settings.feature_flags[flag] !== value) {
settings.feature_flags[flag] = value
settingsChanged = true
Expand All @@ -1148,6 +1201,7 @@ watch(

if (settingsChanged) {
await setSettings(settings)
queryClient.setQueryData(appSettingsKeys.all, settings)
}
})
.catch(handleError)
Expand Down Expand Up @@ -1419,8 +1473,10 @@ async function fetchIntercomToken() {
}

watch(
[showAd, adConsentAvailable],
async ([showAds, canManageConsent]) => {
[stateInitialized, showAd, adConsentAvailable],
async ([ready, showAds, canManageConsent]) => {
if (!ready) return

if (showAds) {
await init_ads_window(true)
return
Expand Down Expand Up @@ -2035,6 +2091,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<Suspense>
<AppSettingsModal ref="appSettingsModal" />
</Suspense>
<SyncInstancesUpdateModal ref="syncInstancesUpdateModal" />
<Suspense>
<ModrinthAccountRequiredModal ref="modrinthLoginModal" :request-auth="requestModrinthAuth" />
</Suspense>
Expand Down Expand Up @@ -2082,15 +2139,19 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
<CompassIcon />
</NavButton>
<NavButton v-tooltip.right="formatMessage(appMessages.skinSelectorLabel)" to="/skins">
<NavButton
v-if="appSettings.showSkinSelectorInSidebar"
v-tooltip.right="formatMessage(appMessages.skinSelectorLabel)"
to="/skins"
>
<ShirtIcon />
</NavButton>
<NavButton
v-if="globalSyncedOptionsQuery.data.value?.screenshots"
v-tooltip.right="formatMessage(messages.screenshots)"
to="/screenshots"
>
<ImagesIcon />
<ImageIcon />
</NavButton>
<NavButton
v-tooltip.right="formatMessage(messages.modrinthHosting)"
Expand Down
24 changes: 13 additions & 11 deletions apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { useRouter } from 'vue-router'
import NavButton from '@/components/ui/NavButton.vue'
import { useAppEvent } from '@/composables/use-app-event'
import { handleSevereError } from '@/composables/use-error.js'
import {
QUICK_INSTANCE_LIMIT_MAX,
useQuickInstanceLimit,
} from '@/composables/use-quick-instance-limit.ts'
import { trackEvent } from '@/helpers/analytics'
import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
import { get_all } from '@/helpers/process'
Expand All @@ -24,8 +28,6 @@ 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 instancesQuery = useQuery(instanceListQueryOptions())
const router = useRouter()
Expand Down Expand Up @@ -54,12 +56,14 @@ const allInstances = computed(() =>
}),
)
const dragging = ref(false)
const quickInstances = useQuickInstanceLimit()

const stored = localStorage.getItem(STORAGE_KEY)
const userLimit = ref(stored === null ? null : Number(stored))

const maxVisible = computed(() => Math.min(maxAuto.value, allInstances.value.length))
const visibleCount = computed(() => Math.min(userLimit.value ?? maxVisible.value, maxVisible.value))
const maxVisible = computed(() =>
Math.min(maxAuto.value, allInstances.value.length, QUICK_INSTANCE_LIMIT_MAX),
)
const visibleCount = computed(() =>
Math.min(quickInstances.limit.value ?? maxVisible.value, maxVisible.value),
)
const recentInstances = computed(() => allInstances.value.slice(0, visibleCount.value))
const canDrag = computed(() => maxVisible.value > 0)
const showOverdrag = ref(false)
Expand All @@ -74,11 +78,9 @@ const updateMaxAuto = () => {
const setLimit = (count) => {
const clamped = Math.max(0, Math.min(count, maxVisible.value))
if (clamped >= maxVisible.value) {
userLimit.value = null
localStorage.removeItem(STORAGE_KEY)
quickInstances.setLimit(null)
} else {
userLimit.value = clamped
localStorage.setItem(STORAGE_KEY, String(clamped))
quickInstances.setLimit(clamped)
}
}

Expand Down
Loading
Loading