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
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:
1 parent
2359b80250
commit
d2a319a4d5
45 files changed
+233
-176
No files matched your search
@@ -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),
|
||||
: 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 () => {
|
||||
|
||||
@@ -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 = '') => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -33,11 +33,11 @@ import {
|
||||
defineMessages,
|
||||
DropdownSelect,
|
||||
injectNotificationManager,
|
||||
type MessageDescriptor,
|
||||
PopoutMenu,
|
||||
Slider,
|
||||
StyledInput,
|
||||
Toggle,
|
||||
type MessageDescriptor,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
@@ -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 },
|
||||
])
|
||||
|
||||
@@ -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:/, ''))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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())
|
||||
|
||||
@@ -71,6 +71,13 @@
|
||||
"schemes": ["starlight"]
|
||||
},
|
||||
"mobile": []
|
||||
},
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQ0NDhERTUxN0E3MERBNDYKUldSRzJuQjZVZDVJMUpKSUJ6blVpVzJQSTgvSjlyeVRGK0ozRzcwVWtpbTJjbUl0V0NRcC9OQVoK",
|
||||
"endpoints": ["https://skin.starlight.cool/starlight/launcher/latest"],
|
||||
"windows": {
|
||||
"installMode": "quiet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::state::{
|
||||
};
|
||||
use crate::util::{fetch::write_cached_icon, io};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tracing::instrument]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -5855,7 +5855,7 @@ async fn download_to_path_inner(
|
||||
can_switch_route,
|
||||
retry_with_single_thread,
|
||||
);
|
||||
while attempts < file_attempt_budget {
|
||||
if attempts < file_attempt_budget {
|
||||
attempts += 1;
|
||||
tracing::debug!(
|
||||
path = %destination.display(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::minecraft::{
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The latest version of the format the fabric model structs deserialize to
|
||||
pub const CURRENT_FABRIC_FORMAT_VERSION: usize = 1;
|
||||
|
||||
Reference in new issue
Block a user