fix: 启用启动器更新并修复初始化竞态
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled

This commit is contained in:
Disy committed 2026-09-26 13:06:51 +08:00
1 parent 2359b80250
commit d2a319a4d5
45 files changed
+235 -178

No files matched your search

+19 -9
View File
@@ -63,9 +63,11 @@ import { openUrl } from '@tauri-apps/plugin-opener'
import { type as getOsType } from '@tauri-apps/plugin-os'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { hideAllPoppers } from 'floating-vue'
import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { type RouteLocationNormalizedLoaded, RouterView, useRoute, useRouter } from 'vue-router'
import InstancePlayerModal from '@/components/instance/InstancePlayerModal.vue'
import TaggedModDownloadsModal from '@/components/instance/TaggedModDownloadsModal.vue'
import InstanceExportModal from '@/components/lab/recipe-generator/InstanceExportModal.vue'
import AccountsCard from '@/components/ui/AccountsCard.vue'
import AppActionBar from '@/components/ui/AppActionBar.vue'
@@ -79,7 +81,6 @@ import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/
import MinecraftCrashModal from '@/components/ui/MinecraftCrashModal.vue'
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
import CurseForgeManualDownloadsModal from '@/components/ui/modal/CurseForgeManualDownloadsModal.vue'
import TaggedModDownloadsModal from '@/components/instance/TaggedModDownloadsModal.vue'
import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue'
import InstanceIconPickerModal from '@/components/ui/modal/InstanceIconPickerModal.vue'
import JavaDownloadConfirmationModal from '@/components/ui/modal/JavaDownloadConfirmationModal.vue'
@@ -90,9 +91,8 @@ import NavButton from '@/components/ui/NavButton.vue'
import NavRail from '@/components/ui/NavRail.vue'
import OnboardingOverlay from '@/components/ui/onboarding/OnboardingOverlay.vue'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
import SkinSiteSessionFrame from '@/components/ui/SkinSiteSessionFrame.vue'
import InstancePlayerModal from '@/components/instance/InstancePlayerModal.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
import WindowControls from '@/components/ui/WindowControls.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { useDropImport } from '@/composables/useDropImport'
@@ -138,7 +138,6 @@ import {
isDev,
isElevated,
isNetworkMetered,
restartApp,
setRestartAfterPendingUpdate,
} from '@/helpers/utils.js'
import { start_join_server, start_join_singleplayer_world } from '@/helpers/worlds.ts'
@@ -490,6 +489,12 @@ onMounted(async () => {
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
window.addEventListener(DIRECT_LINKS_SYNCED_EVENT, handleDirectLinkSyncReport)
try {
await stateInitialization
} catch {
return
}
checkUpdates()
void warnIfRunningElevated()
startDirectLinkSync()
@@ -1701,6 +1706,7 @@ onMounted(() => {
setServerUpdateToPlayModal(updateToPlayModal.value)
void (async () => {
try {
await stateInitialization
const ready = await invoke<{
pending_crashes: { instance_id: string; uuid: string }[]
pending_commands: Parameters<typeof handleCommand>[0][]
@@ -2423,9 +2429,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
{{ formatMessage(messages.authUnreachableBody) }}
</Admonition>
<div class="page-transition-grid grid min-h-full">
<RouterView v-slot="{ Component, route }">
<RouterView v-slot="{ Component, route: currentRoute }">
<Transition name="page-slide" :css="themeStore.getFeatureFlag('page_transitions')">
<div v-if="Component" :key="getPageTransitionKey(route)" class="page-transition-layer">
<div
v-if="Component"
:key="getPageTransitionKey(currentRoute)"
class="page-transition-layer"
>
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
<component :is="Component"></component>
</Suspense>
@@ -2489,7 +2499,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
:on-error-action="exportNotificationErrorLogs"
:error-action-label="formatMessage(messages.exportErrorLogs)"
/>
<MinecraftCrashModal ref="minecraftCrashModal" @error="handleError" />
<MinecraftCrashModal v-if="stateInitialized" ref="minecraftCrashModal" @error="handleError" />
<JavaDownloadConfirmationModal ref="javaDownloadConfirmationModal" />
<NewModal
ref="closeChoiceModal"
@@ -2577,7 +2587,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
@install="handleContentInstallModpackInstall"
@cancel="handleContentInstallModpackInstallCancel"
/>
<TaggedModDownloadsModal />
<TaggedModDownloadsModal v-if="stateInitialized" />
<CurseForgeManualDownloadsModal
ref="contentInstallCurseForgeManualDownloadsModal"
@view-instance="handleContentInstallModpackDuplicateGoToInstance"
@@ -28,8 +28,8 @@ import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { process_listener } from '@/helpers/events'
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
import { kill, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import { isInstanceLaunching } from '@/helpers/instance-launch-state'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { handleSevereError } from '@/store/error'
@@ -36,6 +36,7 @@ const props = withDefaults(
limit?: HomeRecentLimit
}>(),
{
dashboardSize: null,
limit: HOME_RECENT_DEFAULT_LIMIT,
},
)
@@ -146,11 +147,17 @@ async function stopInstance(instance: GameInstance) {
</script>
<template>
<section class="home-recent-worlds flex min-w-0 min-h-0 h-full flex-col gap-3" :data-size="dashboardSize">
<section
class="home-recent-worlds flex min-w-0 min-h-0 h-full flex-col gap-3"
:data-size="dashboardSize"
>
<div class="home-widget-heading flex min-w-0 h-8 flex-none items-center gap-2">
<h2>{{ formatMessage(messages.recentTitle) }}</h2>
</div>
<div v-if="recentItems.length > 0" class="home-recent-list flex min-w-0 min-h-0 flex-1 flex-col gap-1 overflow-x-hidden overflow-y-auto pr-1">
<div
v-if="recentItems.length > 0"
class="home-recent-list flex min-w-0 min-h-0 flex-1 flex-col gap-1 overflow-x-hidden overflow-y-auto pr-1"
>
<template
v-for="item in recentItems"
:key="item.type === 'world' ? worldKey(item.world) : `${item.instance.id}:instance`"
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ProgressBar, defineMessages, useVIntl } from '@modrinth/ui'
import { defineMessages, ProgressBar, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'
import { injectDownloadManager } from '@/providers/download-manager'
const props = defineProps<{ instanceId?: string; active?: boolean }>()
@@ -14,13 +14,14 @@ import {
import { users } from '@/helpers/auth'
import { getInstanceMode } from '@/helpers/hosted-packs'
import {
type InstancePlayer,
type PlayerChoice,
PlayerSelectionNavigatedAwayError,
registerInstancePlayerPicker,
saveInstancePlayer,
waitForSkinSiteSession,
type InstancePlayer,
type PlayerChoice,
} from '@/helpers/instance-player'
import { playerMessages as messages } from './instance-player-messages'
const { formatMessage } = useVIntl()
@@ -1,12 +1,14 @@
<script setup lang="ts">
import { ButtonStyled, useVIntl } from '@modrinth/ui'
import { onUnmounted, ref, watch } from 'vue'
import {
chooseInstancePlayer,
getInstancePlayer,
onInstancePlayerChanged,
type InstancePlayer,
onInstancePlayerChanged,
} from '@/helpers/instance-player'
import { playerMessages as messages } from './instance-player-messages'
const { formatMessage } = useVIntl()
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { NewModal, ProgressBar, ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { onMounted, onUnmounted, nextTick, ref, shallowRef } from 'vue'
import { ButtonStyled, defineMessages, NewModal, ProgressBar, useVIntl } from '@modrinth/ui'
import { nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { useRouter } from 'vue-router'
import { loading_listener } from '@/helpers/events'
import { progress_bars_list } from '@/helpers/state'
import { onHostedPackAttemptStarted } from '@/helpers/hosted-packs'
import { progress_bars_list } from '@/helpers/state'
import {
createTaggedModProgress,
type TaggedProgressEvent,
@@ -12,10 +12,14 @@
>
{{ formatMessage(messages.score, { score }) }}
</span>
<span class="shrink-0 whitespace-nowrap rounded-full bg-surface-3 px-2 py-0.5 tabular-nums text-secondary">
<span
class="shrink-0 whitespace-nowrap rounded-full bg-surface-3 px-2 py-0.5 tabular-nums text-secondary"
>
{{ formatMessage(messages.highestLevel, { level: highestLevel + 1 }) }}
</span>
<span class="shrink-0 whitespace-nowrap rounded-full bg-surface-3 px-2 py-0.5 tabular-nums text-secondary">
<span
class="shrink-0 whitespace-nowrap rounded-full bg-surface-3 px-2 py-0.5 tabular-nums text-secondary"
>
{{ formatMessage(messages.best, { score: bestScore }) }}
</span>
<NewButton type="base" size="sm" class="shrink-0" @click="settleGame">
@@ -80,7 +84,10 @@
</div>
</div>
</Transition>
<div v-if="gameOver && !endedManually" class="tide-drain pointer-events-none absolute inset-0 z-20" />
<div
v-if="gameOver && !endedManually"
class="tide-drain pointer-events-none absolute inset-0 z-20"
/>
<Transition name="panel">
<div
v-if="gameOver"
@@ -115,16 +122,26 @@
</div>
<div class="grid w-full min-w-0 grid-cols-3 gap-1.5">
<div class="flex min-w-0 flex-col items-center rounded-lg bg-surface-3 px-1 py-1">
<span class="text-[9px] text-secondary">{{ formatMessage(messages.levelLabel) }}</span>
<span class="text-sm font-bold tabular-nums text-contrast">{{ highestLevel + 1 }}</span>
<span class="text-[9px] text-secondary">{{
formatMessage(messages.levelLabel)
}}</span>
<span class="text-sm font-bold tabular-nums text-contrast">{{
highestLevel + 1
}}</span>
</div>
<div class="flex min-w-0 flex-col items-center rounded-lg bg-surface-3 px-1 py-1">
<span class="text-[9px] text-secondary">{{ formatMessage(messages.bestLabel) }}</span>
<span class="text-[9px] text-secondary">{{
formatMessage(messages.bestLabel)
}}</span>
<span class="text-sm font-bold tabular-nums text-contrast">{{ bestScore }}</span>
</div>
<div class="flex min-w-0 flex-col items-center rounded-lg bg-surface-3 px-1 py-1">
<span class="text-[9px] text-secondary">{{ formatMessage(messages.overtimesLabel) }}</span>
<span class="text-sm font-bold tabular-nums text-contrast">{{ overtimeCount }}</span>
<span class="text-[9px] text-secondary">{{
formatMessage(messages.overtimesLabel)
}}</span>
<span class="text-sm font-bold tabular-nums text-contrast">{{
overtimeCount
}}</span>
</div>
</div>
<div class="flex flex-wrap justify-center gap-2">
@@ -150,8 +167,8 @@ import blueStar from '@/assets/stars/blue.png'
import greenStar from '@/assets/stars/green.png'
import pinkStar from '@/assets/stars/pink.png'
import redStar from '@/assets/stars/red.png'
import yellowStar from '@/assets/stars/yellow.png'
import superStar from '@/assets/stars/super.png'
import yellowStar from '@/assets/stars/yellow.png'
interface Piece {
id: number
@@ -396,7 +413,14 @@ function mergePieces(a: Piece, b: Piece) {
score.value += points[level]
highestLevel.value = Math.max(highestLevel.value, level)
const isRainbow = level === colors.length - 1
spawnBurst(x, y, isRainbow ? rainbowColors : [colors[level]], 10 + level * 3, 130 + level * 20, 3.4)
spawnBurst(
x,
y,
isRainbow ? rainbowColors : [colors[level]],
10 + level * 3,
130 + level * 20,
3.4,
)
spawnShockwave(x, y, 46 + level * 26, 'rgba(255, 255, 255, 0.9)', Math.min(3, 1.4 + level * 0.3))
shakePower = Math.max(shakePower, Math.min(2 + level * 1.2, 7))
spawnFloater(x, y, `+${points[level]}`, 15 + level * 3, colors[level])
@@ -423,7 +447,14 @@ function easeOutBack(t: number) {
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2)
}
function spawnBurst(x: number, y: number, palette: string[], count: number, speed: number, size: number) {
function spawnBurst(
x: number,
y: number,
palette: string[],
count: number,
speed: number,
size: number,
) {
for (let i = 0; i < count; i++) {
if (particles.length >= 260) return
const angle = Math.random() * Math.PI * 2
@@ -508,7 +539,8 @@ function update(delta: number) {
if (overtimeStartAt >= 0) {
const overtimeElapsed = elapsed - overtimeStartAt
if (overtimeElapsed < 0.8) {
surfaceRatio = overtimeFromRatio + (0.05 - overtimeFromRatio) * easeInOutCubic(overtimeElapsed / 0.8)
surfaceRatio =
overtimeFromRatio + (0.05 - overtimeFromRatio) * easeInOutCubic(overtimeElapsed / 0.8)
} else {
overtimeStartAt = -1
}
@@ -670,17 +702,7 @@ function prepareBallSprite(src: string, level: number) {
sprite.height = Math.max(1, Math.round(bounds.h * scale))
const ctx = sprite.getContext('2d')
if (!ctx) return
ctx.drawImage(
image,
bounds.x,
bounds.y,
bounds.w,
bounds.h,
0,
0,
sprite.width,
sprite.height,
)
ctx.drawImage(image, bounds.x, bounds.y, bounds.w, bounds.h, 0, 0, sprite.width, sprite.height)
ballSprites[level] = sprite
}
image.src = src
@@ -692,9 +714,7 @@ function drawMergePiece(piece: Piece) {
const spawnProgress = Math.min(1, (elapsed - piece.spawnAt) / 0.22)
const spawnScale = spawnProgress >= 1 ? 1 : easeOutBack(spawnProgress)
const radius =
piece.radius *
spawnScale *
(isRainbow ? 1 + 0.05 * Math.sin(elapsed * 3.2 + piece.id) : 1)
piece.radius * spawnScale * (isRainbow ? 1 + 0.05 * Math.sin(elapsed * 3.2 + piece.id) : 1)
context.save()
context.translate(piece.x, piece.y)
const hue = (elapsed * 55) % 360
@@ -792,9 +812,7 @@ function draw() {
context.beginPath()
for (let x = 0; x <= width; x += 8) {
const waveY =
surfaceY +
Math.sin(x * 0.045 + elapsed * 1.8) * 2.5 +
Math.sin(x * 0.013 + elapsed * 0.9) * 2
surfaceY + Math.sin(x * 0.045 + elapsed * 1.8) * 2.5 + Math.sin(x * 0.013 + elapsed * 0.9) * 2
if (x === 0) context.moveTo(x, waveY)
else context.lineTo(x, waveY)
}
@@ -807,7 +825,12 @@ function draw() {
const progress = Math.min(1, (elapsed - waveStart) / 0.8)
const waveY = height * (1 - progress)
const bandHeight = Math.max(22, height * 0.1)
const gradient = context.createLinearGradient(0, waveY - bandHeight, 0, waveY + bandHeight * 0.5)
const gradient = context.createLinearGradient(
0,
waveY - bandHeight,
0,
waveY + bandHeight * 0.5,
)
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)')
gradient.addColorStop(0.55, 'rgba(96, 178, 255, 0.5)')
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)')
@@ -11,8 +11,8 @@ import {
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { save } from '@tauri-apps/plugin-dialog'
import { join } from '@tauri-apps/api/path'
import { save } from '@tauri-apps/plugin-dialog'
import { readDir, stat } from '@tauri-apps/plugin-fs'
import { ref } from 'vue'
@@ -20,8 +20,8 @@ import { process_listener } from '@/helpers/events'
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
import { kill, run } from '@/helpers/instance'
import { getDisplayInstanceIcon } from '@/helpers/instance-icons'
import { get_by_instance_id } from '@/helpers/process'
import { isInstanceLaunching } from '@/helpers/instance-launch-state'
import { get_by_instance_id } from '@/helpers/process'
import { showInstanceInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js'
@@ -98,7 +98,7 @@ const checkProcess = async () => {
if (Array.isArray(runningProcesses)) internalPlaying.value = runningProcesses.length > 0
}
const play = async (e, context) => {
const play = async (e) => {
e?.stopPropagation()
if (loading.value) return
await run(props.instance.id).catch(async (err) => {
@@ -111,7 +111,7 @@ const play = async (e, context) => {
await checkProcess()
}
const stop = async (e, context) => {
const stop = async (e) => {
e?.stopPropagation()
await kill(props.instance.id).catch(handleError)
await checkProcess()
@@ -205,7 +205,7 @@ onUnmounted(() => unlisten())
: formatMessage(isPlaying ? messages.launchAnother : commonMessages.playButton)
"
:disabled="offline && !installed"
@click="(e) => play(e, 'InstanceCard')"
@click="play"
@mousehover="checkProcess"
>
<!-- Translate for optical centering -->
@@ -295,7 +295,7 @@ onUnmounted(() => unlisten())
'scale-75 opacity-0 transition-all group-hover:scale-100 group-hover:opacity-100':
!disabled,
}"
@click="(e) => play(e, 'InstanceCard')"
@click="play"
@mousehover="checkProcess"
>
<PlayIcon class="translate-x-[1px]" />
@@ -522,7 +522,7 @@ async function copyShareUrl(): Promise<void> {
}
}
function _openAIAnalysis(): void {
function openAIAnalysis(): void {
if (!lastAnalysis?.combined_log) {
notifyNoLogContent()
return
@@ -603,7 +603,7 @@ onUnmounted(() => {
unlistenProcess?.()
})
defineExpose({ handleLaunchError, handleWarning, isLaunchFailure, showPreview, openAIAnalysis: _openAIAnalysis })
defineExpose({ handleLaunchError, handleWarning, isLaunchFailure, showPreview, openAIAnalysis })
</script>
<template>
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { clearHostedSession } from '@/helpers/hosted-packs'
import {
receiveSkinSiteMessage,
@@ -10,6 +9,7 @@ import {
skinSiteStatus,
skinSiteUser,
} from '@/composables/skin-site-session'
import { clearHostedSession } from '@/helpers/hosted-packs'
const frame = ref<HTMLIFrameElement>()
let lastMessage = 0
@@ -67,6 +67,7 @@ withDefaults(
}>(),
{
count: 1,
symlinkTarget: null,
},
)
@@ -127,7 +127,7 @@ interface IconBackground {
style: Record<string, string>
}
function background(
function makeBackground(
id: string,
name: MessageDescriptor,
top: string,
@@ -171,16 +171,16 @@ const backgrounds = [
backgroundSize: '16px 16px',
},
} satisfies IconBackground,
background('grass', backgroundNames.grass, '#7fc95b', '#2f7d32'),
background('ocean', backgroundNames.ocean, '#55b7ff', '#3157c8'),
background('amethyst', backgroundNames.amethyst, '#c084fc', '#6d28d9'),
background('sunset', backgroundNames.sunset, '#ffba52', '#e94b64'),
background('cherry', backgroundNames.cherry, '#ff9ec4', '#b93670'),
background('nether', backgroundNames.nether, '#ef5a46', '#6d1717'),
background('slime', backgroundNames.slime, '#b6ee55', '#44972f'),
background('deep-dark', backgroundNames.deepDark, '#245369', '#0c1f2b'),
background('stone', backgroundNames.stone, '#aeb5bd', '#525b66'),
background('midnight', backgroundNames.midnight, '#4d5f8f', '#171b2d'),
makeBackground('grass', backgroundNames.grass, '#7fc95b', '#2f7d32'),
makeBackground('ocean', backgroundNames.ocean, '#55b7ff', '#3157c8'),
makeBackground('amethyst', backgroundNames.amethyst, '#c084fc', '#6d28d9'),
makeBackground('sunset', backgroundNames.sunset, '#ffba52', '#e94b64'),
makeBackground('cherry', backgroundNames.cherry, '#ff9ec4', '#b93670'),
makeBackground('nether', backgroundNames.nether, '#ef5a46', '#6d1717'),
makeBackground('slime', backgroundNames.slime, '#b6ee55', '#44972f'),
makeBackground('deep-dark', backgroundNames.deepDark, '#245369', '#0c1f2b'),
makeBackground('stone', backgroundNames.stone, '#aeb5bd', '#525b66'),
makeBackground('midnight', backgroundNames.midnight, '#4d5f8f', '#171b2d'),
]
const { formatMessage } = useVIntl()
@@ -12,13 +12,13 @@ import {
import { getVersion } from '@tauri-apps/api/app'
import { invoke } from '@tauri-apps/api/core'
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
import { inject, nextTick, ref, watch } from 'vue'
import { nextTick, ref, watch } from 'vue'
import UpdateAnnouncementHistory from '@/components/ui/announcement/UpdateAnnouncementHistory.vue'
import {
betaDatabaseExists,
copyReleaseDatabaseToBeta,
copyDatabaseBetweenChannels,
copyReleaseDatabaseToBeta,
getCurrentAppDatabasePath,
getUpdateChannel,
getUpdatePreferences,
@@ -446,11 +446,14 @@ async function confirmDatabaseOperation() {
}),
})
} catch (error) {
const detail = error instanceof Error ? error.message : String(error)
handleError(
new Error(
targetChannel === activeChannel
? formatMessage(messages.databaseOperationActiveTarget)
: formatMessage(messages.databaseOperationFailed),
`${
targetChannel === activeChannel
? formatMessage(messages.databaseOperationActiveTarget)
: formatMessage(messages.databaseOperationFailed)
}: ${detail}`,
),
)
} finally {
@@ -468,7 +471,7 @@ function cancelDatabaseOperation() {
<div class="flex flex-col gap-6">
<SettingsSection :title="formatMessage(messages.title)">
<div class="update-channel-panel">
<p id="settings-target-updates-channel" class="m-0 text-sm leading-[1.45] text-secondary">
<p id="settings-target-updates-source" class="m-0 text-sm leading-[1.45] text-secondary">
{{ formatMessage(messages.description) }}
</p>
<div
@@ -1,4 +1,4 @@
import { defineMessage, type MessageDescriptor } from '@modrinth/ui'
import type { MessageDescriptor } from '@modrinth/ui'
export type SettingsCategoryId =
| 'interface'
@@ -28,100 +28,100 @@ export interface SettingsCategoryDefinition {
export const settingsCategoryDefinitions: SettingsCategoryDefinition[] = [
{
id: 'interface',
name: defineMessage({
name: {
id: 'app.settings.tabs.interface',
defaultMessage: 'Interface & appearance',
}),
},
group: 'launcher',
onboardingId: 'settings-tab-interface',
},
{
id: 'home-navigation',
name: defineMessage({
name: {
id: 'app.settings.tabs.home-navigation',
defaultMessage: 'Home & navigation',
}),
},
group: 'launcher',
onboardingId: 'settings-tab-home-navigation',
},
{
id: 'language-translation',
name: defineMessage({
name: {
id: 'app.settings.tabs.language-translation',
defaultMessage: 'Language & translation',
}),
},
group: 'launcher',
onboardingId: 'settings-tab-language-translation',
},
{
id: 'ai',
name: defineMessage({ id: 'app.settings.tabs.ai', defaultMessage: 'AI' }),
name: { id: 'app.settings.tabs.ai', defaultMessage: 'AI' },
group: 'launcher',
flushContent: true,
onboardingId: 'settings-tab-ai',
},
{
id: 'java-performance',
name: defineMessage({
name: {
id: 'app.settings.tabs.java-performance',
defaultMessage: 'Java & performance',
}),
},
group: 'game',
onboardingId: 'settings-tab-java-performance',
},
{
id: 'launch-defaults',
name: defineMessage({
name: {
id: 'app.settings.tabs.launch-defaults',
defaultMessage: 'Launch & instance defaults',
}),
},
group: 'game',
onboardingId: 'settings-tab-launch-defaults',
},
{
id: 'content-downloads',
name: defineMessage({
name: {
id: 'app.settings.tabs.content-downloads',
defaultMessage: 'Content & downloads',
}),
},
group: 'game',
onboardingId: 'settings-tab-content-downloads',
},
{
id: 'network-multiplayer',
name: defineMessage({
name: {
id: 'app.settings.tabs.network-multiplayer',
defaultMessage: 'Network & multiplayer',
}),
},
group: 'game',
onboardingId: 'settings-tab-network-multiplayer',
},
{
id: 'storage-backups',
name: defineMessage({
name: {
id: 'app.settings.tabs.storage-backups',
defaultMessage: 'Storage & backups',
}),
},
group: 'data-privacy',
onboardingId: 'settings-tab-storage-backups',
},
{
id: 'updates',
name: defineMessage({ id: 'app.settings.tabs.updates', defaultMessage: 'Updates' }),
name: { id: 'app.settings.tabs.updates', defaultMessage: 'Updates' },
group: 'support',
onboardingId: 'settings-tab-updates',
},
{
id: 'about',
name: defineMessage({ id: 'app.settings.tabs.about', defaultMessage: 'About' }),
name: { id: 'app.settings.tabs.about', defaultMessage: 'About' },
group: 'support',
},
{
id: 'feature-flags',
name: defineMessage({
name: {
id: 'settings.feature-flags.title',
defaultMessage: 'Feature flags',
}),
},
group: 'developer',
developerOnly: true,
},
@@ -15,7 +15,8 @@ import {
UsersIcon,
} from '@modrinth/assets'
import { commonMessages, defineMessages, type MessageDescriptor } from '@modrinth/ui'
import { defineAsyncComponent, type Component } from 'vue'
import { type Component, defineAsyncComponent } from 'vue'
import {
getVisibleSettingsCategoryDefinitions,
type SettingsCategoryDefinition,
@@ -29,8 +29,8 @@ import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { get_project } from '@/helpers/cache'
import { process_listener } from '@/helpers/events'
import { kill, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import { isInstanceLaunching } from '@/helpers/instance-launch-state'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { showInstanceInFolder } from '@/helpers/utils'
import { handleSevereError } from '@/store/error'
@@ -1,7 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { useHostedSync } from './useHostedSync.ts'
import { useHostedCreation, forgetHostedCreation } from './useHostedCreation.ts'
import {
clearHostedSession,
hostedCreate,
@@ -10,12 +9,11 @@ import {
onHostedPackAttemptStarted,
setInstanceMode,
} from '../helpers/hosted-packs.ts'
import {
openSkinSiteLogin,
receiveSkinSiteMessage,
requestSkinSiteLuck,
requestSkinSiteDownloadToken,
requestSkinSiteLuck,
requestSkinSitePlayers,
requestSkinSiteSkinUpdate,
resetSkinSiteSession,
@@ -27,6 +25,8 @@ import {
skinSiteStatus,
skinSiteUser,
} from './skin-site-session.ts'
import { forgetHostedCreation, useHostedCreation } from './useHostedCreation.ts'
import { useHostedSync } from './useHostedSync.ts'
test('reconnecting the same skin site frame waits for verification instead of reporting signed out', () => {
const frame = { postMessage() {} } as unknown as Window
@@ -13,9 +13,9 @@ import type {
SymlinkMethodChoice,
} from '@modrinth/ui'
import { useDebugLogger, useGlobalDrop, useInstanceContext, useVIntl } from '@modrinth/ui'
import { join } from '@tauri-apps/api/path'
import { computed, type ComputedRef, nextTick, ref } from 'vue'
import type { Router } from 'vue-router'
import { join } from '@tauri-apps/api/path'
import {
classifyDroppedItem,
@@ -1412,10 +1412,7 @@ export function useDropImport(options: DropImportOptions) {
cleanupLauncherZipTemp()
}
function resolvedInstancePath(
inst: SelectedInstance,
ctx: ImportContext | null,
): string | undefined {
function resolvedInstancePath(inst: SelectedInstance): string | undefined {
if (inst.compatibleMode) return inst.versionPath
return inst.path
}
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { createHostedDownloadFailures } from './hosted-download-failures.ts'
test('retry clears only its own failure before progress arrives and ignores late old events', () => {
@@ -1,4 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
import {
requestSkinSiteDownloadToken,
skinSiteStatus,
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { isInstanceLaunching, launchOnce } from './instance-launch-state.ts'
test('launches different instances concurrently and coalesces repeated pending clicks', async () => {
@@ -1,18 +1,19 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { useNetworkStatus } from '../composables/useNetworkStatus.ts'
import {
setSkinSiteFrame,
resetSkinSiteSession,
receiveSkinSiteMessage,
resetSkinSiteSession,
setSkinSiteFrame,
SKIN_SITE_ORIGIN,
} from '../composables/skin-site-session.ts'
import { useNetworkStatus } from '../composables/useNetworkStatus.ts'
import {
type InstancePlayer,
onInstancePlayerChanged,
prepareInstancePlayer,
registerInstancePlayerPicker,
saveInstancePlayer,
onInstancePlayerChanged,
type InstancePlayer,
} from './instance-player.ts'
test('instance player selection persists independently from the globally selected account', async () => {
+1 -1
View File
@@ -18,8 +18,8 @@ import { detectGcStrategy, GC_STRATEGY_DEFINITIONS } from '@/helpers/gc/strategi
import type { GcContext, ResolvedGcStrategyId } from '@/helpers/gc/types'
import { setLastGcLaunchReport } from '@/helpers/gc-notice'
import { getInstanceMode, prepareHostedSession } from '@/helpers/hosted-packs'
import { prepareInstancePlayer } from '@/helpers/instance-player'
import { launchOnce } from '@/helpers/instance-launch-state'
import { prepareInstancePlayer } from '@/helpers/instance-player'
import { AUTO_GC_PRESET_ARG } from '@/helpers/java-arguments'
import { get_jre, get_memory_status } from '@/helpers/jre.js'
import { get as getSettings } from '@/helpers/settings'
@@ -82,20 +82,20 @@ test('notice query uses the target instance value and registered Tauri command',
assert.match(tauriSource, /tauri::generate_handler!\[[\s\S]*instance_get_post_upgrade_notice/)
})
test('result uses Modrinth Card and Accordion for collapsed compatibility warnings', () => {
test('result collections use Modrinth Card and Accordion for collapsed compatibility warnings', () => {
const source = readFileSync(
new URL('../pages/instance/upgrade/UpgradeResultDetails.vue', import.meta.url),
new URL('../pages/instance/upgrade/UpgradeResultCollections.vue', import.meta.url),
'utf8',
)
assert.match(source, /<Card v-if="warningRows\.length"/)
assert.match(source, /<Accordion[\s\S]*:open-by-default="warningsExpandedByDefault"/)
assert.match(source, /<Card v-if="warnings\.length"/)
assert.match(source, /<Accordion[\s\S]*:open-by-default="warningsDefaultOpen"/)
assert.match(source, /<TriangleAlertIcon/)
})
test('runtime icon references use names exported by the assets package', () => {
const downloadsSource = readFileSync(new URL('../pages/Downloads.vue', import.meta.url), 'utf8')
const resultSource = readFileSync(
new URL('../pages/instance/upgrade/UpgradeResultDetails.vue', import.meta.url),
new URL('../pages/instance/upgrade/UpgradeResultCollections.vue', import.meta.url),
'utf8',
)
const assetsSource = readFileSync(
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { createTaggedModProgress } from './tagged-mod-progress.ts'
const parent = (id = 'parent', fraction: number | null = 0, error = '') => ({
+3 -4
View File
@@ -4,9 +4,9 @@ import { invoke } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import { isOfflineMode } from '@/composables/useNetworkStatus'
import { prepareInstancePlayer } from '@/helpers/instance-player'
import { getInstanceMode, prepareHostedSession } from '@/helpers/hosted-packs'
import { get_full_path, resolveGcLaunchIntent } from '@/helpers/instance'
import { prepareInstancePlayer } from '@/helpers/instance-player'
import { openPath } from '@/helpers/utils'
type BaseWorld = {
@@ -20,7 +20,6 @@ type BaseWorld = {
export type WorldType = 'singleplayer' | 'server'
export type DisplayStatus = 'normal' | 'hidden' | 'favorite'
export type SingleplayerWorld = BaseWorld & {
type: 'singleplayer'
path: string
@@ -251,7 +250,7 @@ export async function start_join_singleplayer_world(
world: string,
): Promise<unknown> {
await prepareInstancePlayer(instanceId)
if (await getInstanceMode(instanceId) === 'starlight') await prepareHostedSession()
if ((await getInstanceMode(instanceId)) === 'starlight') await prepareHostedSession()
const { args, gcIntent } = await resolveGcLaunchIntent(instanceId)
return await invoke('plugin:worlds|start_join_singleplayer_world', {
instanceId,
@@ -264,7 +263,7 @@ export async function start_join_singleplayer_world(
export async function start_join_server(instanceId: string, address: string): Promise<unknown> {
await prepareInstancePlayer(instanceId)
if (await getInstanceMode(instanceId) === 'starlight') await prepareHostedSession()
if ((await getInstanceMode(instanceId)) === 'starlight') await prepareHostedSession()
const { args, gcIntent } = await resolveGcLaunchIntent(instanceId)
return await invoke('plugin:worlds|start_join_server', {
instanceId,
+5 -7
View File
@@ -3,11 +3,12 @@ import { FolderOpenIcon, LeftArrowIcon, SparklesIcon } from '@modrinth/assets'
import { BigOptionButton, Button, defineMessages, useVIntl } from '@modrinth/ui'
import { inject, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import InstanceModeOptions from '@/components/instance/InstanceModeOptions.vue'
import HostedGameDirModal from '@/components/instance/HostedGameDirModal.vue'
import HostedPackProgress from '@/components/instance/HostedPackProgress.vue'
import type { InstanceMode } from '@/helpers/hosted-packs'
import InstanceModeOptions from '@/components/instance/InstanceModeOptions.vue'
import { useHostedCreation } from '@/composables/useHostedCreation'
import type { InstanceMode } from '@/helpers/hosted-packs'
const { formatMessage } = useVIntl()
const router = useRouter()
@@ -187,8 +188,8 @@ function handleImportExisting() {
/>
<BigOptionButton
data-onboarding-id="creation-method-import"
v-if="instanceMode === 'local'"
data-onboarding-id="creation-method-import"
:icon="FolderOpenIcon"
:title="formatMessage(messages.importTitle)"
:description="formatMessage(messages.importDescription)"
@@ -197,10 +198,7 @@ function handleImportExisting() {
/>
</div>
<HostedGameDirModal
ref="hostedGameDirModal"
@confirm="installHostedWithGameDir"
/>
<HostedGameDirModal ref="hostedGameDirModal" @confirm="installHostedWithGameDir" />
<HostedPackProgress :instance-id="createdInstance" :active="installing" />
<p v-if="installError" class="m-0 text-red" role="alert">{{ installError }}</p>
<p v-if="instanceMode === 'local'" class="m-0 text-sm text-secondary">
@@ -259,7 +259,7 @@ onMounted(() => {
/>
<div class="flex flex-col gap-1.5">
<ButtonStyled color="brand">
<button class="start-button" @click="startTranslation" :disabled="!canStart">
<button class="start-button" :disabled="!canStart" @click="startTranslation">
<PlayIcon />{{ formatMessage(messages.start) }}
</button>
</ButtonStyled>
+1 -1
View File
@@ -33,11 +33,11 @@ import {
defineMessages,
DropdownSelect,
injectNotificationManager,
type MessageDescriptor,
PopoutMenu,
Slider,
StyledInput,
Toggle,
type MessageDescriptor,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'
+1 -3
View File
@@ -2,10 +2,9 @@
import { UsersIcon } from '@modrinth/assets'
import { defineMessages, NavTabs, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import { RouterView, useRouter } from 'vue-router'
const { formatMessage } = useVIntl()
const route = useRoute()
const router = useRouter()
const messages = defineMessages({
@@ -13,7 +12,6 @@ const messages = defineMessages({
roomsTab: { id: 'app.multiplayer.tab.rooms', defaultMessage: 'Rooms' },
})
const activeTab = computed(() => (route.path.startsWith('/multiplayer/rooms') ? 'rooms' : 'rooms'))
const tabLinks = computed(() => [
{ label: formatMessage(messages.roomsTab), href: '/multiplayer/rooms', icon: UsersIcon },
])
+10 -10
View File
@@ -163,7 +163,7 @@
</button>
</ButtonStyled>
<ButtonStyled v-else-if="!isServerInstance" color="brand" size="large">
<button @click="startInstance('InstancePage')">
<button @click="startInstance">
<PlayIcon />
{{ formatMessage(playing ? messages.launchAnother : commonMessages.playButton) }}
</button>
@@ -184,7 +184,7 @@
},
{
id: 'launch_instance',
action: () => startInstance('InstancePage'),
action: startInstance,
},
]"
>
@@ -204,7 +204,7 @@
</ButtonStyled>
</div>
<ButtonStyled v-if="playing" color="red" size="large">
<button :disabled="stopping || loading" @click="stopInstance('InstancePage')">
<button :disabled="stopping || loading" @click="stopInstance">
<StopCircleIcon />
{{ formatMessage(stopping ? messages.stopping : messages.stopAll) }}
</button>
@@ -294,7 +294,7 @@
:is-server-instance="isServerInstance"
:open-settings="() => settingsModal?.show(1)"
@play="updatePlayState"
@stop="() => stopInstance('InstanceSubpage')"
@stop="stopInstance"
></component>
</Suspense>
</template>
@@ -400,9 +400,9 @@ import {
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import { useInstanceConsole } from '@/composables/useInstanceConsole'
import { useInstanceMode } from '@/composables/useInstanceMode'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { useInstanceMode } from '@/composables/useInstanceMode'
import { postUpgradeNoticeQueryKey, usePostUpgradeNotice } from '@/composables/usePostUpgradeNotice'
import { useSymlinkWarningDismiss } from '@/composables/useSymlinkWarningDismiss'
import { get_project_v3 } from '@/helpers/cache.js'
@@ -421,8 +421,8 @@ import {
run,
} from '@/helpers/instance'
import { getDisplayInstanceIcon } from '@/helpers/instance-icons'
import { get_by_instance_id } from '@/helpers/process'
import { isInstanceLaunching } from '@/helpers/instance-launch-state'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { createInstanceShortcut, showInstanceInFolder } from '@/helpers/utils.js'
import { refreshWorlds, type ServerStatus } from '@/helpers/worlds'
@@ -733,7 +733,7 @@ watch(
const options = ref<InstanceType<typeof ContextMenu> | null>(null)
const startInstance = async (context: string) => {
const startInstance = async () => {
if (!instance.value || loading.value) return
const id = props.id
const name = instance.value.name
@@ -780,7 +780,7 @@ watch(
{ immediate: true },
)
const stopInstance = async (context: string) => {
const stopInstance = async () => {
stopping.value = true
await kill(props.id).catch(handleError)
stopping.value = false
@@ -882,10 +882,10 @@ const handleRightClick = (event: MouseEvent) => {
const handleOptionsClick = async (args: { option: string; item: unknown }) => {
switch (args.option) {
case 'play':
await startInstance('InstancePageContextMenu')
await startInstance()
break
case 'stop':
await stopInstance('InstancePageContextMenu')
await stopInstance()
break
case 'add_content':
await router.push({
@@ -1408,7 +1408,8 @@ async function getUpdaterProjectVersions(
if (!versions) {
versions = (await get_project_versions(projectId).catch(() => null)) as
Labrinth.Versions.v2.Version[] | null
| Labrinth.Versions.v2.Version[]
| null
}
if (!versions && fetchError) {
@@ -1719,9 +1720,6 @@ async function applyToggleDisableBatch(items: ContentItem[], enabled: boolean) {
resultById,
false,
)
for (const operation of operations) {
}
} catch (error) {
for (const operation of operations) {
operation.optimisticPath = operation.originalFilePath
@@ -845,7 +845,7 @@ async function installSelected(fileId: string | null) {
}
if (isWorldMap.value) {
installing.value = true
await installCurseForgeWorld(project.value.id, fileId, instanceId.value, 'ProjectPage', () => {
await installCurseForgeWorld(project.value.id, fileId, instanceId.value, () => {
installing.value = false
}).catch((error) => {
installing.value = false
@@ -414,8 +414,8 @@ import {
ProjectSidebarServerInfo,
ProjectSidebarTags,
requestInstall,
SelectedProjectsFloatingBar,
ScrollToTopButton,
SelectedProjectsFloatingBar,
usesTargetGameVersion,
useVIntl,
} from '@modrinth/ui'
@@ -567,7 +567,7 @@ const messages = defineMessages({
favoritesLoading: {
id: 'app.content-favorites.loading',
defaultMessage: 'Updating favorites…',
},
},
viewDependents: {
id: 'project.actions.view-dependents',
defaultMessage: 'View dependents',
@@ -72,12 +72,7 @@
</template>
<script setup>
import {
CheckIcon,
DownloadIcon,
ExternalIcon,
MoreVerticalIcon,
} from '@modrinth/assets'
import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets'
import {
ButtonStyled,
commonMessages,
@@ -108,7 +103,7 @@ const messages = defineMessages({
},
})
const props = defineProps({
defineProps({
project: {
type: Object,
default: () => {},
@@ -517,7 +517,6 @@ export interface ContentInstallContext {
projectId: string | number,
fileId?: string | number | null,
instanceId?: string | null,
source?: string,
callback?: ContentInstallCallback,
) => Promise<void>
installingItems: Ref<Map<string, ContentItem[]>>
@@ -1884,7 +1883,6 @@ export function createContentInstall(opts: {
markInstanceContentChanged(instance.id)
incompatibilityWarningModalRef?.hide()
removeInstallingItems(instance.id, [project.id])
}
function handleIncompatibilityWarningCancel() {
@@ -2056,7 +2054,6 @@ export function createContentInstall(opts: {
: `/instance/${encodeURIComponent(id)}`,
)
settleCurrentCallback(version.id, installedProjectIds)
modalRef?.hide()
} catch (err) {
@@ -2116,10 +2113,14 @@ export function createContentInstall(opts: {
if (project.project_type === 'modpack') {
if (shouldShowInstallTargetModal) hideContentInstallModal()
const [versions, packs] = await Promise.all([
get_version_many(project.versions, 'must_revalidate') as Promise<Labrinth.Versions.v2.Version[]>,
get_version_many(project.versions, 'must_revalidate') as Promise<
Labrinth.Versions.v2.Version[]
>,
list(),
])
const sortedVersions = versions.sort((a, b) => dayjs(b.date_published).valueOf() - dayjs(a.date_published).valueOf())
const sortedVersions = versions.sort(
(a, b) => dayjs(b.date_published).valueOf() - dayjs(a.date_published).valueOf(),
)
pendingModpackInstall = {
project,
version: versionId ?? sortedVersions[0]?.id ?? '',
@@ -2132,7 +2133,10 @@ export function createContentInstall(opts: {
sortedVersions.map((version) => [
version.id,
packs
.filter((pack) => pack.link?.project_id === project.id && pack.link?.version_id === version.id)
.filter(
(pack) =>
pack.link?.project_id === project.id && pack.link?.version_id === version.id,
)
.map((pack) => ({ id: pack.id, name: pack.name })),
]),
)
@@ -2391,7 +2395,6 @@ export function createContentInstall(opts: {
projectId: string | number,
fileId?: string | number | null,
instanceId?: string | null,
source: string = 'unknown',
callback: ContentInstallCallback = () => {},
) {
const modalSessionId = instanceId ? null : beginInstallSession(callback)
@@ -2486,11 +2489,10 @@ export function createContentInstall(opts: {
projectId: string | number,
fileId?: string | number | null,
instanceId?: string | null,
source: string = 'unknown',
callback: ContentInstallCallback = () => {},
) {
return guardInstallRequest(
() => installCurseForgeWorldInternal(projectId, fileId, instanceId, source, callback),
() => installCurseForgeWorldInternal(projectId, fileId, instanceId, callback),
callback,
)
}
@@ -2532,8 +2534,7 @@ export function createContentInstall(opts: {
handleCurseForgeManualDownloadsImported,
async handleModpackInstall(versionId: string, name: string) {
if (!pendingModpackInstall) return
const { project, source, callback, createInstanceCallback, provider } =
pendingModpackInstall
const { project, source, callback, createInstanceCallback, provider } = pendingModpackInstall
pendingModpackInstall = null
if (provider === 'curseforge') {
const numericProjectId = Number(project.id.replace(/^curseforge:/, ''))
+1
View File
@@ -547,6 +547,7 @@ fn keyring_error(
.into()
}
#[cfg(test)]
fn parse_custom_uuid(uuid: Option<String>) -> Result<Option<uuid::Uuid>> {
let Some(uuid) = uuid else {
return Ok(None);
+4 -1
View File
@@ -548,6 +548,10 @@ fn schedule_tray_menu_update(app: &AppHandle) {
});
}
pub fn state_initialized(app: &AppHandle) {
schedule_tray_menu_update(app);
}
async fn rebuild_tray_menu(
app: &AppHandle,
active: bool,
@@ -708,7 +712,6 @@ pub fn init(app: &AppHandle) {
.build(app)
.expect("failed to create system tray");
let _tray = tray;
schedule_tray_menu_update(app);
let app_handle = app.clone();
app.listen("process", move |event| {
let Ok(payload) =
+10 -4
View File
@@ -315,6 +315,8 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
app.fs_scope()
.allow_directory(state.directories.servers_dir(), true)?;
lightweight_mode::state_initialized(&app);
Ok(())
}
@@ -569,10 +571,9 @@ fn is_dev() -> bool {
#[tauri::command]
fn are_updates_enabled() -> bool {
// Temporarily disabled: enabling the built-in updater causes an immediate
// crash on Windows (c0000409) at startup, pending a fix.
// StarLight launcher ships without the built-in update checker.
false
// The frontend waits for launcher state initialization before starting
// the built-in updater.
true
}
#[cfg(feature = "updater")]
@@ -910,6 +911,11 @@ fn main() {
Ok(())
});
#[cfg(feature = "updater")]
{
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
}
builder = builder
.plugin(api::ai::init())
.plugin(api::auth::init())
+7
View File
@@ -71,6 +71,13 @@
"schemes": ["starlight"]
},
"mobile": []
},
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQ0NDhERTUxN0E3MERBNDYKUldSRzJuQjZVZDVJMUpKSUJ6blVpVzJQSTgvSjlyeVRGK0ozRzcwVWtpbTJjbUl0V0NRcC9OQVoK",
"endpoints": ["https://skin.starlight.cool/starlight/launcher/latest"],
"windows": {
"installMode": "quiet"
}
}
},
"app": {