Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6912e0aeb1 | ||
|
|
c7d1e44719 | ||
|
|
0a6fa0eb35 | ||
|
|
79fdddc075 | ||
|
|
7616f60eca | ||
|
|
6f77f13a04 | ||
|
|
b1f4431767 | ||
|
|
15b6575824 | ||
|
|
5bd785d780 | ||
|
|
94cbcef324 | ||
|
|
d8a6d44890 | ||
|
|
9b75ca4f3f | ||
|
|
913e0071dd | ||
|
|
bb6b8cb8d2 |
No files matched your search
+8
-8
@@ -1,10 +1,3 @@
|
||||
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
||||
[target."cfg(windows)"]
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
|
||||
# Reproducible public-service defaults for downstream desktop builds. Private
|
||||
# Modrinth services remain disabled by Axolotl capabilities at runtime.
|
||||
[env]
|
||||
@@ -12,5 +5,12 @@ MODRINTH_API_BASE_URL = "https://api.modrinth.com"
|
||||
MODRINTH_API_URL = "https://api.modrinth.com/v2/"
|
||||
MODRINTH_API_URL_V3 = "https://api.modrinth.com/v3/"
|
||||
MODRINTH_LAUNCHER_META_URL = "https://launcher-meta.modrinth.com/"
|
||||
MODRINTH_URL = "https://modrinth.com/"
|
||||
MODRINTH_SOCKET_URL = "wss://disabled.invalid/"
|
||||
MODRINTH_URL = "https://modrinth.com/"
|
||||
|
||||
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
||||
[target."cfg(windows)"]
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
@@ -22,8 +22,5 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: taiki-e/install-action@fd2f5e3d644b484055ebf4268f474c565f148f25 # v2.81.9
|
||||
with:
|
||||
tool: tombi@1.1.2
|
||||
- run: tombi lint
|
||||
- run: tombi fmt --check
|
||||
- run: npx --yes tombi@1.6.1 lint
|
||||
- run: npx --yes tombi@1.6.1 fmt --check
|
||||
@@ -38,6 +38,7 @@ node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
replay_pid*.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
|
||||
+6
-24
@@ -20,9 +20,7 @@ ariadne = { path = "packages/ariadne" }
|
||||
async-minecraft-ping = { path = "packages/async-minecraft-ping" }
|
||||
async-recursion = "1.1.1"
|
||||
async-trait = "0.1.89"
|
||||
async-tungstenite = { version = "0.31.0", default-features = false, features = [
|
||||
"futures-03-sink"
|
||||
] }
|
||||
async-tungstenite = { version = "0.31.0", default-features = false, features = ["futures-03-sink"] }
|
||||
async-walkdir = "2.1.0"
|
||||
async_zip = "0.0.18"
|
||||
base64 = "0.22.1"
|
||||
@@ -32,9 +30,7 @@ bytes = "1.10.1"
|
||||
cc = "1.2.56"
|
||||
chardetng = "0.1.17"
|
||||
chrono = "0.4.42"
|
||||
cidre = { version = "0.15.0", default-features = false, features = [
|
||||
"macos_15_0"
|
||||
] }
|
||||
cidre = { version = "0.15.0", default-features = false, features = ["macos_15_0"] }
|
||||
crc32fast = "1.5.0"
|
||||
daedalus = { path = "packages/daedalus" }
|
||||
dashmap = "6.1.0"
|
||||
@@ -58,11 +54,7 @@ hmac = "0.12.1"
|
||||
http = "1.3.1"
|
||||
hyper = "1.7.0"
|
||||
hyper-util = "0.1.17"
|
||||
image = { version = "0.25.5", default-features = false, features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp"
|
||||
] }
|
||||
image = { version = "0.25.5", default-features = false, features = ["jpeg", "png", "webp"] }
|
||||
indicatif = "0.18.0"
|
||||
itertools = "0.14.0"
|
||||
jsonwebtoken = "9.3.1"
|
||||
@@ -88,11 +80,7 @@ rand = "=0.8.5" # Locked on 0.8 until p256 updates to 0.9
|
||||
regex = "1.12.2"
|
||||
reqwest = { version = "0.12.24", default-features = false }
|
||||
rgb = "0.8.52"
|
||||
rustls = { version = "0.23.42", default-features = false, features = [
|
||||
"ring",
|
||||
"std",
|
||||
"tls12"
|
||||
] }
|
||||
rustls = { version = "0.23.42", default-features = false, features = ["ring", "std", "tls12"] }
|
||||
rustls-native-certs = "0.8.4"
|
||||
rustls-pki-types = "1.15.0"
|
||||
serde = "1.0.228"
|
||||
@@ -127,10 +115,7 @@ tempfile = "3.23.0"
|
||||
theseus = { path = "packages/app-lib" }
|
||||
thiserror = "2.0.17"
|
||||
tokio = "1.47.1"
|
||||
tokio-rustls = { version = "0.26.4", default-features = false, features = [
|
||||
"ring",
|
||||
"tls12"
|
||||
] }
|
||||
tokio-rustls = { version = "0.26.4", default-features = false, features = ["ring", "tls12"] }
|
||||
tokio-util = "0.7.16"
|
||||
toml = "0.8.19"
|
||||
tracing = "0.1.41"
|
||||
@@ -149,10 +134,7 @@ windows-core = "=0.61.2" # Locked on 0.61 until webview2-com updates to 0.62
|
||||
winreg = "0.55.0"
|
||||
xz2 = { version = "0.1.7", features = ["static"] }
|
||||
zbus = "5.11.0"
|
||||
zhconv = { version = "0.4.1", default-features = false, features = [
|
||||
"compress",
|
||||
"opencc"
|
||||
] }
|
||||
zhconv = { version = "0.4.1", default-features = false, features = ["compress", "opencc"] }
|
||||
zip = { version = "6.0.0", default-features = false, features = [
|
||||
"bzip2",
|
||||
"deflate",
|
||||
|
||||
@@ -63,7 +63,7 @@ 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, onMounted, onUnmounted, provide, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue'
|
||||
import { type RouteLocationNormalizedLoaded, RouterView, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import InstancePlayerModal from '@/components/instance/InstancePlayerModal.vue'
|
||||
@@ -141,6 +141,7 @@ import {
|
||||
isNetworkMetered,
|
||||
setRestartAfterPendingUpdate,
|
||||
} from '@/helpers/utils.js'
|
||||
import { installUiScale } from '@/helpers/ui-scale'
|
||||
import { start_join_server, start_join_singleplayer_world } from '@/helpers/worlds.ts'
|
||||
import i18n, { resolveInitialLocale } from '@/i18n.config'
|
||||
import {
|
||||
@@ -1271,6 +1272,9 @@ provide(
|
||||
)
|
||||
provide('previewMinecraftCrashModal', () => minecraftCrashModal.value?.showPreview())
|
||||
provide('showLauncherPopup', (_request: unknown) => {})
|
||||
provide('showStartupAnnouncement', () => startupAnnouncementModal.value?.show())
|
||||
|
||||
installUiScale()
|
||||
|
||||
const stateFailed = ref(false)
|
||||
stateInitialization
|
||||
@@ -1544,13 +1548,27 @@ const startupAnnouncementModal = ref<InstanceType<typeof StartupAnnouncementModa
|
||||
const STARTUP_ANNOUNCEMENT_KEY = 'starlight-startup-announcement-seen-v1'
|
||||
|
||||
function maybeShowStartupAnnouncement() {
|
||||
let alreadySeen = false
|
||||
try {
|
||||
alreadySeen = localStorage.getItem(STARTUP_ANNOUNCEMENT_KEY) !== null
|
||||
} catch (error) {
|
||||
console.warn('Failed to read startup announcement flag', error)
|
||||
}
|
||||
if (alreadySeen) return
|
||||
|
||||
const modal = startupAnnouncementModal.value
|
||||
if (!modal) {
|
||||
// The modal component has not mounted yet; try again on the next tick.
|
||||
void nextTick(maybeShowStartupAnnouncement)
|
||||
return
|
||||
}
|
||||
|
||||
modal.show()
|
||||
try {
|
||||
if (localStorage.getItem(STARTUP_ANNOUNCEMENT_KEY)) return
|
||||
localStorage.setItem(STARTUP_ANNOUNCEMENT_KEY, '1')
|
||||
} catch (error) {
|
||||
console.warn('Failed to read/write startup announcement flag', error)
|
||||
console.warn('Failed to write startup announcement flag', error)
|
||||
}
|
||||
startupAnnouncementModal.value?.show()
|
||||
}
|
||||
|
||||
// ── Drop import system ──────────────────────────────────────────────────
|
||||
|
||||
@@ -32,6 +32,90 @@ export const ANNOUNCEMENT_CHANGE_TYPES: readonly AnnouncementChangeType[] = [
|
||||
]
|
||||
|
||||
export const launcherAnnouncements: readonly LauncherAnnouncement[] = [
|
||||
{
|
||||
id: 'launcher-1.0.1-beta5',
|
||||
version: '1.0.1-beta5',
|
||||
publishedAt: '2026-10-01',
|
||||
title: {
|
||||
'en-US': 'Starlight Launcher 1.0.1-beta5',
|
||||
'zh-CN': 'Starlight Launcher 1.0.1-beta5',
|
||||
},
|
||||
changes: {
|
||||
added: [
|
||||
{
|
||||
'en-US':
|
||||
'Reworked the onboarding tour, covering the home page, StarLight login, announcements, instance creation, discovery, skins, library, lab, downloads, and update settings in order.',
|
||||
'zh-CN':
|
||||
'重做了新手导览,依次介绍主页、StarLight 登录、公告、创建实例、发现内容、皮肤、实例库、实验室、下载与更新设置。',
|
||||
},
|
||||
],
|
||||
fixed: [
|
||||
{
|
||||
'en-US':
|
||||
'Fixed the developer feature flags page rendering blank.',
|
||||
'zh-CN': '修复开发者“功能开关”页面空白的问题。',
|
||||
},
|
||||
{
|
||||
'en-US':
|
||||
'Fixed a build error that prevented the launcher from compiling.',
|
||||
'zh-CN': '修复导致启动器无法编译的构建错误。',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'launcher-1.0.1-beta4',
|
||||
version: '1.0.1-beta4',
|
||||
publishedAt: '2026-09-30',
|
||||
title: {
|
||||
'en-US': 'Starlight Launcher 1.0.1-beta4',
|
||||
'zh-CN': 'Starlight Launcher 1.0.1-beta4',
|
||||
},
|
||||
changes: {
|
||||
fixed: [
|
||||
{
|
||||
'en-US':
|
||||
'Fixed a build error in 1.0.1-beta3 that prevented the launcher from compiling.',
|
||||
'zh-CN': '修复 1.0.1-beta3 中导致启动器无法编译的构建错误。',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'launcher-1.0.1-beta3',
|
||||
version: '1.0.1-beta3',
|
||||
publishedAt: '2026-09-30',
|
||||
title: {
|
||||
'en-US': 'Starlight Launcher 1.0.1-beta3',
|
||||
'zh-CN': 'Starlight Launcher 1.0.1-beta3',
|
||||
},
|
||||
changes: {
|
||||
added: [
|
||||
{
|
||||
'en-US':
|
||||
'Added a launcher UI scale setting under Appearance, which also helps when embedded pages are cut off.',
|
||||
'zh-CN': '在“界面与外观”中新增启动器缩放设置,同时可缓解内嵌网页被裁切的问题。',
|
||||
},
|
||||
{
|
||||
'en-US':
|
||||
'Added a one-time welcome announcement on first launch, with an entry in the home sidebar.',
|
||||
'zh-CN': '新增首次启动时的欢迎公告弹窗,并在主页侧栏提供入口。',
|
||||
},
|
||||
],
|
||||
fixed: [
|
||||
{
|
||||
'en-US':
|
||||
'Fixed the browse page going blank without a CurseForge API key; the navigation tabs now stay visible.',
|
||||
'zh-CN': '修复未配置 CurseForge API Key 时浏览页整页空白的问题,导航栏现在保持可见。',
|
||||
},
|
||||
{
|
||||
'en-US':
|
||||
'Fixed the about page license links and the update changelog link to point at the correct repository.',
|
||||
'zh-CN': '修复关于页许可证链接与更新日志链接,改为指向正确的仓库。',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'launcher-1.0.1-beta2',
|
||||
version: '1.0.1-beta2',
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { NewspaperIcon, RightArrowIcon } from '@modrinth/assets'
|
||||
import { inject } from 'vue'
|
||||
|
||||
const showAnnouncement = inject<(() => void) | undefined>('showStartupAnnouncement')
|
||||
|
||||
function open() {
|
||||
showAnnouncement?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
data-onboarding-id="home-announcement"
|
||||
class="flex min-w-0 flex-col gap-3 border-0 border-b-[1px] border-solid border-[--brand-gradient-border] p-4"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="group flex w-full cursor-pointer flex-col gap-2 border-0 bg-transparent p-0 text-left"
|
||||
@click="open"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<NewspaperIcon class="size-4 shrink-0 text-secondary" aria-hidden="true" />
|
||||
<h2 class="m-0 truncate text-lg">公告</h2>
|
||||
<RightArrowIcon
|
||||
class="ml-auto size-4 shrink-0 text-secondary transition-transform group-hover:translate-x-0.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p class="m-0 text-sm leading-relaxed text-secondary">点击查看最新公告</p>
|
||||
</button>
|
||||
</section>
|
||||
</template>
|
||||
@@ -30,6 +30,8 @@ function hide() {
|
||||
function openLink(url: string) {
|
||||
void openUrl(url)
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -61,7 +61,7 @@ const historyAnnouncements = computed(() =>
|
||||
<UpdateAnnouncementContent
|
||||
:announcement="currentAnnouncement"
|
||||
:version="currentVersion"
|
||||
:external-url="currentAnnouncement?.externalUrl ?? AxolotlBrandConfig.website"
|
||||
:external-url="currentAnnouncement?.externalUrl ?? AxolotlBrandConfig.releasesUrl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +102,7 @@ const historyAnnouncements = computed(() =>
|
||||
<UpdateAnnouncementContent
|
||||
:announcement="announcement"
|
||||
:show-header="false"
|
||||
:external-url="announcement.externalUrl"
|
||||
:external-url="announcement.externalUrl ?? `${AxolotlBrandConfig.releasesUrl}/tag/v${announcement.version}`"
|
||||
/>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
@@ -276,6 +276,48 @@ export const onboardingMessages = defineMessages({
|
||||
},
|
||||
skip: { id: 'app.onboarding.action.skip', defaultMessage: 'Leave the tour' },
|
||||
mascotAlt: { id: 'app.onboarding.mascot-alt', defaultMessage: 'Starlight guide' },
|
||||
homeIntroTitle: { id: 'app.onboarding.home-intro.title', defaultMessage: 'Welcome home' },
|
||||
homeIntroDescription: {
|
||||
id: 'app.onboarding.home-intro.description',
|
||||
defaultMessage:
|
||||
'This is your home page. It shows your instances, recent activity, and quick shortcuts to get playing.',
|
||||
},
|
||||
accountLoginTitle: {
|
||||
id: 'app.onboarding.account-login.title',
|
||||
defaultMessage: 'Sign in to StarLight',
|
||||
},
|
||||
accountLoginDescription: {
|
||||
id: 'app.onboarding.account-login.description',
|
||||
defaultMessage:
|
||||
'Sign in to the StarLight skin site here to use your StarLight players, install hosted modpacks, and sync your account.',
|
||||
},
|
||||
announcementTitle: {
|
||||
id: 'app.onboarding.announcement.title',
|
||||
defaultMessage: 'Latest announcements',
|
||||
},
|
||||
announcementDescription: {
|
||||
id: 'app.onboarding.announcement.description',
|
||||
defaultMessage:
|
||||
'Open the announcement entry in the sidebar to read the latest news, rules, and server notices.',
|
||||
},
|
||||
createStarlightTitle: {
|
||||
id: 'app.onboarding.create-starlight.title',
|
||||
defaultMessage: 'Create a StarLight instance',
|
||||
},
|
||||
createStarlightDescription: {
|
||||
id: 'app.onboarding.create-starlight.description',
|
||||
defaultMessage:
|
||||
'Use Create new instance here. A StarLight instance installs the server-selected modpack and versions, then keeps them updated before each launch.',
|
||||
},
|
||||
settingsUpdatesTitle: {
|
||||
id: 'app.onboarding.settings-updates.title',
|
||||
defaultMessage: 'Stay up to date',
|
||||
},
|
||||
settingsUpdatesDescription: {
|
||||
id: 'app.onboarding.settings-updates.description',
|
||||
defaultMessage:
|
||||
'The Updates tab controls the update channel, checks for new launcher versions, and shows the changelog.',
|
||||
},
|
||||
})
|
||||
|
||||
const step = (
|
||||
@@ -331,20 +373,29 @@ const settingsTourSteps: Array<[string, string, MessageDescriptor, MessageDescri
|
||||
|
||||
export const onboardingTours: Record<OnboardingMode, OnboardingStep[]> = {
|
||||
main: [
|
||||
step(
|
||||
'welcome',
|
||||
'manual',
|
||||
copy(
|
||||
onboardingMessages.welcomeTitle,
|
||||
onboardingMessages.welcomeDescription,
|
||||
onboardingMessages.start,
|
||||
),
|
||||
inspect(
|
||||
'home-intro',
|
||||
'home-widget-grid',
|
||||
onboardingMessages.homeIntroTitle,
|
||||
onboardingMessages.homeIntroDescription,
|
||||
),
|
||||
inspect(
|
||||
'home-layout-switch',
|
||||
'home-layout-switch',
|
||||
onboardingMessages.homeLayoutTitle,
|
||||
onboardingMessages.homeLayoutDescription,
|
||||
'account-login',
|
||||
'account-entry',
|
||||
onboardingMessages.accountLoginTitle,
|
||||
onboardingMessages.accountLoginDescription,
|
||||
),
|
||||
inspect(
|
||||
'announcement',
|
||||
'home-announcement',
|
||||
onboardingMessages.announcementTitle,
|
||||
onboardingMessages.announcementDescription,
|
||||
),
|
||||
inspect(
|
||||
'create-starlight',
|
||||
'create-instance',
|
||||
onboardingMessages.createStarlightTitle,
|
||||
onboardingMessages.createStarlightDescription,
|
||||
),
|
||||
step(
|
||||
'discover-navigation',
|
||||
@@ -362,22 +413,6 @@ export const onboardingTours: Record<OnboardingMode, OnboardingStep[]> = {
|
||||
onboardingMessages.browseTitle,
|
||||
onboardingMessages.browseDescription,
|
||||
),
|
||||
step(
|
||||
'discover-favorites-navigation',
|
||||
'navigate',
|
||||
copy(
|
||||
onboardingMessages.favoritesTitle,
|
||||
onboardingMessages.favoritesDescription,
|
||||
onboardingMessages.clickFavorites,
|
||||
),
|
||||
control('browse-favorites-tab', '/browse/favorites'),
|
||||
),
|
||||
inspect(
|
||||
'discover-favorites-content',
|
||||
'browse-favorites-content',
|
||||
onboardingMessages.favoritesTitle,
|
||||
onboardingMessages.favoritesDescription,
|
||||
),
|
||||
step(
|
||||
'skins-navigation',
|
||||
'navigate',
|
||||
@@ -394,6 +429,22 @@ export const onboardingTours: Record<OnboardingMode, OnboardingStep[]> = {
|
||||
onboardingMessages.skinsPageTitle,
|
||||
onboardingMessages.skinsPageDescription,
|
||||
),
|
||||
step(
|
||||
'library-navigation',
|
||||
'navigate',
|
||||
copy(
|
||||
onboardingMessages.libraryTitle,
|
||||
onboardingMessages.libraryDescription,
|
||||
onboardingMessages.clickLibrary,
|
||||
),
|
||||
control('nav-library', '/library'),
|
||||
),
|
||||
inspect(
|
||||
'library-content',
|
||||
'library-content',
|
||||
onboardingMessages.libraryPageTitle,
|
||||
onboardingMessages.libraryPageDescription,
|
||||
),
|
||||
step(
|
||||
'lab-navigation',
|
||||
'navigate',
|
||||
@@ -428,7 +479,7 @@ export const onboardingTours: Record<OnboardingMode, OnboardingStep[]> = {
|
||||
),
|
||||
step(
|
||||
'settings-navigation',
|
||||
'activate',
|
||||
'navigate',
|
||||
copy(
|
||||
onboardingMessages.settingsTitle,
|
||||
onboardingMessages.settingsDescription,
|
||||
@@ -436,95 +487,15 @@ export const onboardingTours: Record<OnboardingMode, OnboardingStep[]> = {
|
||||
),
|
||||
control('nav-settings', '/settings'),
|
||||
),
|
||||
...settingsTourSteps.map(([id, targetId, title, description], index) =>
|
||||
step(id, 'activate', copy(title, description, onboardingMessages.clickTab), {
|
||||
...control(targetId),
|
||||
closeSettingsAfter: index === settingsTourSteps.length - 1,
|
||||
}),
|
||||
),
|
||||
step(
|
||||
'library-navigation',
|
||||
'navigate',
|
||||
copy(
|
||||
onboardingMessages.libraryTitle,
|
||||
onboardingMessages.libraryDescription,
|
||||
onboardingMessages.clickLibrary,
|
||||
),
|
||||
control('nav-library', '/library'),
|
||||
),
|
||||
inspect(
|
||||
'library-content',
|
||||
'library-content',
|
||||
onboardingMessages.libraryPageTitle,
|
||||
onboardingMessages.libraryPageDescription,
|
||||
),
|
||||
step(
|
||||
'create-instance',
|
||||
'navigate',
|
||||
copy(
|
||||
onboardingMessages.createTitle,
|
||||
onboardingMessages.createDescription,
|
||||
onboardingMessages.clickCreate,
|
||||
),
|
||||
control('create-instance', '/create'),
|
||||
),
|
||||
inspect(
|
||||
'creation-instance-mode',
|
||||
'creation-instance-mode',
|
||||
onboardingMessages.instanceModeTitle,
|
||||
onboardingMessages.instanceModeDescription,
|
||||
),
|
||||
step(
|
||||
'creation-flow',
|
||||
'settings-updates',
|
||||
'activate',
|
||||
copy(
|
||||
onboardingMessages.creationTitle,
|
||||
onboardingMessages.creationDescription,
|
||||
onboardingMessages.clickCreationMethod,
|
||||
onboardingMessages.settingsUpdatesTitle,
|
||||
onboardingMessages.settingsUpdatesDescription,
|
||||
onboardingMessages.clickTab,
|
||||
),
|
||||
{
|
||||
targetId: 'creation-methods',
|
||||
branchByTarget: {
|
||||
'creation-method-starlight': { next: 'complete' },
|
||||
'creation-method-custom': { creationPath: 'custom', next: 'creation-name' },
|
||||
'creation-method-import': { next: 'complete' },
|
||||
},
|
||||
},
|
||||
),
|
||||
inspect(
|
||||
'creation-name',
|
||||
'creation-name',
|
||||
onboardingMessages.creationNameTitle,
|
||||
onboardingMessages.creationNameDescription,
|
||||
),
|
||||
inspect(
|
||||
'creation-loader',
|
||||
'creation-loader',
|
||||
onboardingMessages.creationLoaderTitle,
|
||||
onboardingMessages.creationLoaderDescription,
|
||||
),
|
||||
step(
|
||||
'creation-version',
|
||||
'inspect',
|
||||
copy(
|
||||
onboardingMessages.creationVersionTitle,
|
||||
onboardingMessages.creationVersionDescription,
|
||||
onboardingMessages.continueArea,
|
||||
),
|
||||
{
|
||||
targetId: 'creation-game-version',
|
||||
nextByCreationPath: { custom: 'creation-confirm' },
|
||||
},
|
||||
),
|
||||
step(
|
||||
'creation-confirm',
|
||||
'inspect',
|
||||
copy(
|
||||
onboardingMessages.creationConfirmTitle,
|
||||
onboardingMessages.creationConfirmDescription,
|
||||
onboardingMessages.finishArea,
|
||||
),
|
||||
{ targetId: 'creation-confirm' },
|
||||
control('settings-tab-updates'),
|
||||
),
|
||||
],
|
||||
instance: [
|
||||
|
||||
@@ -26,6 +26,7 @@ import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { getOS } from '@/helpers/utils'
|
||||
import { setUiScale, uiScale } from '@/helpers/ui-scale'
|
||||
import { useTheming } from '@/store/state'
|
||||
import {
|
||||
type AccentColor,
|
||||
@@ -209,6 +210,15 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'Enables advanced rendering such as blur effects that may cause performance issues without hardware-accelerated rendering.',
|
||||
},
|
||||
uiScaleTitle: {
|
||||
id: 'app.appearance-settings.ui-scale.title',
|
||||
defaultMessage: 'Launcher scale',
|
||||
},
|
||||
uiScaleDescription: {
|
||||
id: 'app.appearance-settings.ui-scale.description',
|
||||
defaultMessage:
|
||||
'Scale the whole launcher interface. A smaller scale fits more content, which helps when embedded pages are cut off.',
|
||||
},
|
||||
pageTransitionsTitle: {
|
||||
id: 'app.appearance-settings.page-transitions.title',
|
||||
defaultMessage: 'Page transition animations',
|
||||
@@ -1020,6 +1030,43 @@ watch(
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow stacked>
|
||||
<template #label>
|
||||
<span id="settings-target-appearance-ui-scale" tabindex="-1">
|
||||
{{ formatMessage(messages.uiScaleTitle) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #description>{{ formatMessage(messages.uiScaleDescription) }}</template>
|
||||
<template #control>
|
||||
<div class="flex w-full flex-col gap-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="preset in [100, 110, 125, 150]"
|
||||
:key="preset"
|
||||
type="button"
|
||||
class="min-w-16 rounded-lg border border-solid px-3 py-1.5 text-sm font-semibold transition-colors"
|
||||
:class="
|
||||
uiScale === preset
|
||||
? 'border-brand bg-brand-highlight text-brand'
|
||||
: 'border-surface-4 bg-surface-3 text-secondary hover:border-surface-5 hover:text-contrast'
|
||||
"
|
||||
@click="setUiScale(preset)"
|
||||
>
|
||||
{{ preset }}%
|
||||
</button>
|
||||
</div>
|
||||
<Slider
|
||||
id="ui-scale"
|
||||
:model-value="uiScale"
|
||||
:min="50"
|
||||
:max="200"
|
||||
:step="5"
|
||||
unit="%"
|
||||
@update:model-value="(value) => setUiScale(Number(value))"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow>
|
||||
<template #label>
|
||||
<span id="settings-target-appearance-page-transitions" tabindex="-1">
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Toggle,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { inject, watch } from 'vue'
|
||||
import { inject, ref, watch } from 'vue'
|
||||
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { isDev } from '@/helpers/utils'
|
||||
|
||||
@@ -3,6 +3,7 @@ import test from 'node:test'
|
||||
|
||||
import {
|
||||
clearHostedSession,
|
||||
HOSTED_ACCOUNT_REQUIRED_MESSAGE,
|
||||
hostedCreate,
|
||||
hostedDefault,
|
||||
hostedSync,
|
||||
@@ -46,6 +47,10 @@ test('hosted installation sends the JWT to native commands while Local needs no
|
||||
let createdId = 'instance'
|
||||
let instanceExists = false
|
||||
let tokenRequests = 0
|
||||
let accounts: Array<{
|
||||
account_type: string
|
||||
yggdrasil?: { api_root: string }
|
||||
}> = [{ account_type: 'microsoft' }]
|
||||
const frame = {
|
||||
postMessage(data: { type: string; requestId: string }) {
|
||||
if (data.type !== 'starlight-pack-token-request') return
|
||||
@@ -70,6 +75,14 @@ test('hosted installation sends the JWT to native commands while Local needs no
|
||||
__TAURI_INTERNALS__: {
|
||||
async invoke(command: string, args: Record<string, unknown>) {
|
||||
calls.push({ command, args })
|
||||
if (command === 'plugin:install|hosted_has_game_account')
|
||||
return accounts.some(
|
||||
(account) =>
|
||||
account.account_type === 'microsoft' ||
|
||||
(account.account_type === 'yggdrasil' &&
|
||||
account.yggdrasil?.api_root.replace(/\/+$/, '') ===
|
||||
'https://skin.starlight.cool/yggdrasil'),
|
||||
)
|
||||
if (command === 'plugin:install|hosted_sync' && syncResponse) return syncResponse()
|
||||
if (command === 'plugin:instance|instance_get')
|
||||
return instanceExists ? { id: args.instanceId } : null
|
||||
@@ -99,7 +112,13 @@ test('hosted installation sends the JWT to native commands while Local needs no
|
||||
await setInstanceMode('instance', 'starlight')
|
||||
assert.equal(tokenRequests, 4)
|
||||
assert.deepEqual(
|
||||
calls.map(({ command }) => command),
|
||||
calls
|
||||
.map(({ command }) => command)
|
||||
.filter(
|
||||
(command) =>
|
||||
command.startsWith('plugin:install|') &&
|
||||
command !== 'plugin:install|hosted_has_game_account',
|
||||
),
|
||||
[
|
||||
'hosted_set_session',
|
||||
'hosted_default',
|
||||
@@ -210,9 +229,35 @@ test('hosted installation sends the JWT to native commands while Local needs no
|
||||
assert.equal(tokenRequests, requestsBeforeLogout)
|
||||
const attempts: string[] = []
|
||||
const stopListening = onHostedPackAttemptStarted((id) => attempts.push(id))
|
||||
accounts = []
|
||||
const installCallsBeforeAccountFailure = calls.filter(
|
||||
({ command }) =>
|
||||
command.startsWith('plugin:install|') &&
|
||||
command !== 'plugin:install|hosted_has_game_account',
|
||||
).length
|
||||
await assert.rejects(hostedSync('failed-instance'), {
|
||||
message: HOSTED_ACCOUNT_REQUIRED_MESSAGE,
|
||||
})
|
||||
await assert.rejects(hostedCreate(), {
|
||||
message: HOSTED_ACCOUNT_REQUIRED_MESSAGE,
|
||||
})
|
||||
assert.deepEqual(attempts, [])
|
||||
assert.equal(
|
||||
calls.filter(
|
||||
({ command }) =>
|
||||
command.startsWith('plugin:install|') &&
|
||||
command !== 'plugin:install|hosted_has_game_account',
|
||||
).length,
|
||||
installCallsBeforeAccountFailure,
|
||||
)
|
||||
const unauthenticatedCreation = useHostedCreation()
|
||||
await unauthenticatedCreation.install()
|
||||
assert.equal(unauthenticatedCreation.installError.value, HOSTED_ACCOUNT_REQUIRED_MESSAGE)
|
||||
assert.equal(unauthenticatedCreation.createdInstance.value, undefined)
|
||||
accounts = [{ account_type: 'microsoft' }]
|
||||
const unauthenticatedRetry = hostedSync('failed-instance')
|
||||
assert.deepEqual(attempts, ['failed-instance'])
|
||||
await assert.rejects(unauthenticatedRetry)
|
||||
assert.deepEqual(attempts, ['failed-instance'])
|
||||
stopListening()
|
||||
await assert.rejects(hostedSync('failed-instance'))
|
||||
assert.equal(attempts.length, 1)
|
||||
|
||||
@@ -10,6 +10,10 @@ const createdInstance = ref<string>()
|
||||
const completed = ref(false)
|
||||
let generation = 0
|
||||
|
||||
function errorMessage(cause: unknown) {
|
||||
return cause instanceof Error ? cause.message : String(cause)
|
||||
}
|
||||
|
||||
export function forgetHostedCreation(instanceId: string) {
|
||||
if (createdInstance.value !== instanceId) return
|
||||
generation++
|
||||
@@ -27,7 +31,7 @@ export function markHostedCreationCompleted(instanceId: string) {
|
||||
export function markHostedCreationFailed(instanceId: string, cause: unknown) {
|
||||
if (createdInstance.value !== instanceId) return
|
||||
completed.value = false
|
||||
installError.value = String(cause)
|
||||
installError.value = errorMessage(cause)
|
||||
}
|
||||
|
||||
export function useHostedCreation() {
|
||||
@@ -63,7 +67,7 @@ export function useHostedCreation() {
|
||||
} catch (cause) {
|
||||
if (attempt === generation) {
|
||||
if (createdInstance.value) markHostedCreationFailed(createdInstance.value, cause)
|
||||
else installError.value = String(cause)
|
||||
else installError.value = errorMessage(cause)
|
||||
}
|
||||
} finally {
|
||||
installing.value = false
|
||||
|
||||
@@ -4,7 +4,8 @@ export const AxolotlBrandConfig = Object.freeze({
|
||||
productName: 'Starlight Launcher',
|
||||
shortProductName: 'Starlight',
|
||||
website: 'https://skin.starlight.cool/',
|
||||
repositoryUrl: 'https://git.starlight.cool/Disy/StarLight-Yggdrasil-Server',
|
||||
repositoryUrl: 'https://github.com/ApTx2012/Starlight_Lancher',
|
||||
releasesUrl: 'https://github.com/ApTx2012/Starlight_Lancher/releases',
|
||||
supportUrl: 'https://skin.starlight.cool/',
|
||||
qqGroupNumber: '000000000',
|
||||
qqChannelUrl: 'https://skin.starlight.cool/',
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
waitForSkinSiteSession,
|
||||
} from '../composables/skin-site-session.ts'
|
||||
|
||||
export const HOSTED_ACCOUNT_REQUIRED_MESSAGE = '您需要登录正版或皮肤站账号后才能操作'
|
||||
|
||||
let sessionUpdate: Promise<void> = Promise.resolve()
|
||||
const attemptListeners = new Set<(instanceId: string) => void>()
|
||||
|
||||
@@ -21,6 +23,13 @@ function startHostedPackAttempt(instanceId: string) {
|
||||
for (const listener of attemptListeners) listener(instanceId)
|
||||
}
|
||||
|
||||
export async function requireHostedGameAccount(): Promise<void> {
|
||||
const available = await invokeHosted<boolean>('plugin:install|hosted_has_game_account')
|
||||
if (!available) {
|
||||
throw new Error(HOSTED_ACCOUNT_REQUIRED_MESSAGE)
|
||||
}
|
||||
}
|
||||
|
||||
export function clearHostedSession(): Promise<void> {
|
||||
const update = sessionUpdate
|
||||
.catch(() => {})
|
||||
@@ -100,13 +109,16 @@ export interface HostedSyncResult {
|
||||
}
|
||||
export const hostedDefault = () =>
|
||||
invokeWithSession<HostedPublication>('plugin:install|hosted_default')
|
||||
export const hostedCreate = (gameDirRoot?: string | null) =>
|
||||
invokeWithSession<string>('plugin:install|hosted_create', {
|
||||
export const hostedCreate = async (gameDirRoot?: string | null) => {
|
||||
await requireHostedGameAccount()
|
||||
return invokeWithSession<string>('plugin:install|hosted_create', {
|
||||
gameDirRoot: gameDirRoot ?? null,
|
||||
})
|
||||
}
|
||||
export const hostedBinding = (instanceId: string) =>
|
||||
invokeHosted<HostedBinding | null>('plugin:install|hosted_binding', { instanceId })
|
||||
export const hostedSync = (instanceId: string) => {
|
||||
export const hostedSync = async (instanceId: string) => {
|
||||
await requireHostedGameAccount()
|
||||
startHostedPackAttempt(instanceId)
|
||||
return invokeWithSession<HostedSyncResult>('plugin:install|hosted_sync', { instanceId })
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
/**
|
||||
* 启动器界面缩放(百分比)。
|
||||
* 这是纯本地的显示偏好,用 localStorage 保存即可,无需写入后端设置数据库。
|
||||
*/
|
||||
const UI_SCALE_STORAGE_KEY = 'starlight-ui-scale'
|
||||
const MIN_UI_SCALE = 50
|
||||
const MAX_UI_SCALE = 200
|
||||
const DEFAULT_UI_SCALE = 100
|
||||
|
||||
/** 把缩放值限制在允许范围内。 */
|
||||
function clampUiScale(value: number): number {
|
||||
if (!Number.isFinite(value)) return DEFAULT_UI_SCALE
|
||||
return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(value)))
|
||||
}
|
||||
|
||||
/** 从 localStorage 读取已保存的缩放值。 */
|
||||
function readStoredUiScale(): number {
|
||||
try {
|
||||
const raw = localStorage.getItem(UI_SCALE_STORAGE_KEY)
|
||||
if (raw === null) return DEFAULT_UI_SCALE
|
||||
return clampUiScale(Number(raw))
|
||||
} catch {
|
||||
return DEFAULT_UI_SCALE
|
||||
}
|
||||
}
|
||||
|
||||
export const uiScale = ref(readStoredUiScale())
|
||||
|
||||
export function setUiScale(value: number): void {
|
||||
uiScale.value = clampUiScale(value)
|
||||
}
|
||||
|
||||
export function resetUiScale(): void {
|
||||
setUiScale(DEFAULT_UI_SCALE)
|
||||
}
|
||||
|
||||
export { DEFAULT_UI_SCALE, MAX_UI_SCALE, MIN_UI_SCALE }
|
||||
|
||||
/** 把缩放应用到文档根节点,并在变化时持久化。 */
|
||||
export function installUiScale(): void {
|
||||
const apply = (value: number) => {
|
||||
const factor = String(value / 100)
|
||||
const root = document.documentElement
|
||||
root.style.setProperty('--ui-scale', factor)
|
||||
// 用 CSS zoom 整体缩放界面(含内嵌网页),在 WebView2/WebKit 上最可靠。
|
||||
root.style.setProperty('zoom', factor)
|
||||
}
|
||||
|
||||
apply(uiScale.value)
|
||||
watch(uiScale, (value) => {
|
||||
apply(value)
|
||||
try {
|
||||
localStorage.setItem(UI_SCALE_STORAGE_KEY, String(value))
|
||||
} catch (error) {
|
||||
console.warn('保存界面缩放失败', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -773,6 +773,12 @@
|
||||
"app.appearance-settings.transparent-background.title": {
|
||||
"message": "Transparent background"
|
||||
},
|
||||
"app.appearance-settings.ui-scale.title": {
|
||||
"message": "Launcher scale"
|
||||
},
|
||||
"app.appearance-settings.ui-scale.description": {
|
||||
"message": "Scale the whole launcher interface. A smaller scale fits more content, which helps when embedded pages are cut off."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "If you attempt to install a Modrinth Pack file (.mrpack) that isn't hosted on Modrinth, we'll make sure you understand the risks before installing it."
|
||||
},
|
||||
@@ -5969,6 +5975,36 @@
|
||||
"app.onboarding.welcome.title": {
|
||||
"message": "Everything is ready"
|
||||
},
|
||||
"app.onboarding.home-intro.title": {
|
||||
"message": "Welcome home"
|
||||
},
|
||||
"app.onboarding.home-intro.description": {
|
||||
"message": "This is your home page. It shows your instances, recent activity, and quick shortcuts to get playing."
|
||||
},
|
||||
"app.onboarding.account-login.title": {
|
||||
"message": "Sign in to StarLight"
|
||||
},
|
||||
"app.onboarding.account-login.description": {
|
||||
"message": "Sign in to the StarLight skin site here to use your StarLight players, install hosted modpacks, and sync your account."
|
||||
},
|
||||
"app.onboarding.announcement.title": {
|
||||
"message": "Latest announcements"
|
||||
},
|
||||
"app.onboarding.announcement.description": {
|
||||
"message": "Open the announcement entry in the sidebar to read the latest news, rules, and server notices."
|
||||
},
|
||||
"app.onboarding.create-starlight.title": {
|
||||
"message": "Create a StarLight instance"
|
||||
},
|
||||
"app.onboarding.create-starlight.description": {
|
||||
"message": "Use Create new instance here. A StarLight instance installs the server-selected modpack and versions, then keeps them updated before each launch."
|
||||
},
|
||||
"app.onboarding.settings-updates.title": {
|
||||
"message": "Stay up to date"
|
||||
},
|
||||
"app.onboarding.settings-updates.description": {
|
||||
"message": "The Updates tab controls the update channel, checks for new launcher versions, and shows the changelog."
|
||||
},
|
||||
"app.project.curseforge.loading": {
|
||||
"message": "Loading CurseForge project…"
|
||||
},
|
||||
|
||||
@@ -805,6 +805,12 @@
|
||||
"app.appearance-settings.transparent-background.title": {
|
||||
"message": "透明背景"
|
||||
},
|
||||
"app.appearance-settings.ui-scale.title": {
|
||||
"message": "启动器缩放"
|
||||
},
|
||||
"app.appearance-settings.ui-scale.description": {
|
||||
"message": "缩放整个启动器界面。缩小后能显示更多内容,适合内嵌网页被裁切的情况。"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "如果你尝试安装一个未托管在 Modrinth 上的 Modrinth 整合包文件(.mrpack),我们会确保你在安装之前了解相关风险。"
|
||||
},
|
||||
@@ -6055,6 +6061,36 @@
|
||||
"app.onboarding.welcome.title": {
|
||||
"message": "一切就绪"
|
||||
},
|
||||
"app.onboarding.home-intro.title": {
|
||||
"message": "欢迎回家"
|
||||
},
|
||||
"app.onboarding.home-intro.description": {
|
||||
"message": "这里是你的主页,展示你的实例、最近活动和快速开始游戏的入口。"
|
||||
},
|
||||
"app.onboarding.account-login.title": {
|
||||
"message": "登录 StarLight"
|
||||
},
|
||||
"app.onboarding.account-login.description": {
|
||||
"message": "在这里登录 StarLight 皮肤站,即可使用皮肤站玩家、安装托管整合包并同步你的账号。"
|
||||
},
|
||||
"app.onboarding.announcement.title": {
|
||||
"message": "最新公告"
|
||||
},
|
||||
"app.onboarding.announcement.description": {
|
||||
"message": "打开侧栏的公告入口,查看最新消息、服规与服务器通知。"
|
||||
},
|
||||
"app.onboarding.create-starlight.title": {
|
||||
"message": "创建 StarLight 实例"
|
||||
},
|
||||
"app.onboarding.create-starlight.description": {
|
||||
"message": "点击这里的“创建新实例”。StarLight 实例会安装服务器指定的整合包与版本,并在每次启动前保持更新。"
|
||||
},
|
||||
"app.onboarding.settings-updates.title": {
|
||||
"message": "保持最新"
|
||||
},
|
||||
"app.onboarding.settings-updates.description": {
|
||||
"message": "“更新”页用于设置更新通道、检查启动器新版本并查看更新日志。"
|
||||
},
|
||||
"app.project.curseforge.loading": {
|
||||
"message": "正在加载 CurseForge 项目…"
|
||||
},
|
||||
|
||||
@@ -3093,7 +3093,7 @@ provideBrowseManager({
|
||||
|
||||
<template>
|
||||
<div data-onboarding-id="browse-content" class="flex flex-col gap-3 p-6">
|
||||
<BrowsePageLayout v-if="!isWorldMapBrowse || curseForgeCapability.configured">
|
||||
<BrowsePageLayout>
|
||||
<template #nav-tabs-actions>
|
||||
<ButtonStyled size="large" type="transparent">
|
||||
<button :disabled="translationLoading" @click="toggleTranslation">
|
||||
@@ -3193,14 +3193,14 @@ provideBrowseManager({
|
||||
{{ formatMessage(messages.fuzzySearchSuffix) }}
|
||||
</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
v-if="isWorldMapBrowse && !curseForgeCapability.configured"
|
||||
type="empty-inbox"
|
||||
:heading="formatMessage(messages.mapsUnavailable)"
|
||||
:description="formatMessage(messages.mapsUnavailableDescription)"
|
||||
/>
|
||||
</template>
|
||||
</BrowsePageLayout>
|
||||
<EmptyState
|
||||
v-else
|
||||
type="empty-inbox"
|
||||
:heading="formatMessage(messages.mapsUnavailable)"
|
||||
:description="formatMessage(messages.mapsUnavailableDescription)"
|
||||
/>
|
||||
<CreationFlowModal
|
||||
v-if="isServerContext && projectType === 'modpack'"
|
||||
ref="serverSetupModalRef"
|
||||
|
||||
@@ -10,7 +10,7 @@ import { computed, onUnmounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { getActivePlayerName } from '@/components/home/home-utils'
|
||||
import HomeDailyChallenge from '@/components/home/HomeDailyChallenge.vue'
|
||||
import HomeAnnouncementEntry from '@/components/home/HomeAnnouncementEntry.vue'
|
||||
import HomeInstancePickerModal from '@/components/home/HomeInstancePickerModal.vue'
|
||||
import HomeLaunchProgress from '@/components/home/HomeLaunchProgress.vue'
|
||||
import HomeMinecraftNews from '@/components/home/HomeMinecraftNews.vue'
|
||||
@@ -202,7 +202,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<HomeLaunchProgress />
|
||||
<HomePlayInsights />
|
||||
<HomeDailyChallenge />
|
||||
<HomeAnnouncementEntry />
|
||||
<HomeMinecraftNews />
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
+1
-7
@@ -56,13 +56,7 @@ zip = { workspace = true }
|
||||
|
||||
[dependencies.tauri]
|
||||
workspace = true
|
||||
features = [
|
||||
"devtools",
|
||||
"macos-private-api",
|
||||
"protocol-asset",
|
||||
"tray-icon",
|
||||
"unstable"
|
||||
]
|
||||
features = ["devtools", "macos-private-api", "protocol-asset", "tray-icon", "unstable"]
|
||||
|
||||
[build-dependencies]
|
||||
cc = { workspace = true }
|
||||
|
||||
@@ -16,6 +16,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
hosted_default,
|
||||
hosted_set_session,
|
||||
hosted_has_game_account,
|
||||
hosted_create,
|
||||
hosted_binding,
|
||||
hosted_sync,
|
||||
@@ -65,6 +66,11 @@ pub async fn hosted_default() -> Result<theseus::pack::hosted::Publication> {
|
||||
Ok(theseus::pack::hosted::default_publication().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hosted_has_game_account() -> Result<bool> {
|
||||
Ok(theseus::pack::hosted::has_game_account().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hosted_create(game_dir_root: Option<String>) -> Result<String> {
|
||||
Ok(theseus::pack::hosted::create(game_dir_root).await?)
|
||||
|
||||
@@ -1,6 +1,104 @@
|
||||
{
|
||||
"updated_at": "2026-09-27T00:38:49.605Z",
|
||||
"updated_at": "2026-10-01T01:32:20.780Z",
|
||||
"announcements": [
|
||||
{
|
||||
"id": "launcher-1.0.1-beta5",
|
||||
"version": "1.0.1-beta5",
|
||||
"publishedAt": "2026-10-01",
|
||||
"title": {
|
||||
"en-US": "Starlight Launcher 1.0.1-beta5",
|
||||
"zh-CN": "Starlight Launcher 1.0.1-beta5"
|
||||
},
|
||||
"changes": {
|
||||
"added": [
|
||||
{
|
||||
"en-US": "Reworked the onboarding tour, covering the home page, StarLight login, announcements, instance creation, discovery, skins, library, lab, downloads, and update settings in order.",
|
||||
"zh-CN": "重做了新手导览,依次介绍主页、StarLight 登录、公告、创建实例、发现内容、皮肤、实例库、实验室、下载与更新设置。"
|
||||
}
|
||||
],
|
||||
"fixed": [
|
||||
{
|
||||
"en-US": "Fixed the developer feature flags page rendering blank.",
|
||||
"zh-CN": "修复开发者“功能开关”页面空白的问题。"
|
||||
},
|
||||
{
|
||||
"en-US": "Fixed a build error that prevented the launcher from compiling.",
|
||||
"zh-CN": "修复导致启动器无法编译的构建错误。"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "launcher-1.0.1-beta4",
|
||||
"version": "1.0.1-beta4",
|
||||
"publishedAt": "2026-09-30",
|
||||
"title": {
|
||||
"en-US": "Starlight Launcher 1.0.1-beta4",
|
||||
"zh-CN": "Starlight Launcher 1.0.1-beta4"
|
||||
},
|
||||
"changes": {
|
||||
"fixed": [
|
||||
{
|
||||
"en-US": "Fixed a build error in 1.0.1-beta3 that prevented the launcher from compiling.",
|
||||
"zh-CN": "修复 1.0.1-beta3 中导致启动器无法编译的构建错误。"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "launcher-1.0.1-beta3",
|
||||
"version": "1.0.1-beta3",
|
||||
"publishedAt": "2026-09-30",
|
||||
"title": {
|
||||
"en-US": "Starlight Launcher 1.0.1-beta3",
|
||||
"zh-CN": "Starlight Launcher 1.0.1-beta3"
|
||||
},
|
||||
"changes": {
|
||||
"added": [
|
||||
{
|
||||
"en-US": "Added a launcher UI scale setting under Appearance, which also helps when embedded pages are cut off.",
|
||||
"zh-CN": "在“界面与外观”中新增启动器缩放设置,同时可缓解内嵌网页被裁切的问题。"
|
||||
},
|
||||
{
|
||||
"en-US": "Added a one-time welcome announcement on first launch, with an entry in the home sidebar.",
|
||||
"zh-CN": "新增首次启动时的欢迎公告弹窗,并在主页侧栏提供入口。"
|
||||
}
|
||||
],
|
||||
"fixed": [
|
||||
{
|
||||
"en-US": "Fixed the browse page going blank without a CurseForge API key; the navigation tabs now stay visible.",
|
||||
"zh-CN": "修复未配置 CurseForge API Key 时浏览页整页空白的问题,导航栏现在保持可见。"
|
||||
},
|
||||
{
|
||||
"en-US": "Fixed the about page license links and the update changelog link to point at the correct repository.",
|
||||
"zh-CN": "修复关于页许可证链接与更新日志链接,改为指向正确的仓库。"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "launcher-1.0.1-beta2",
|
||||
"version": "1.0.1-beta2",
|
||||
"publishedAt": "2026-09-28",
|
||||
"title": {
|
||||
"en-US": "Starlight Launcher 1.0.1-beta2",
|
||||
"zh-CN": "Starlight Launcher 1.0.1-beta2"
|
||||
},
|
||||
"changes": {
|
||||
"added": [
|
||||
{
|
||||
"en-US": "GitHub releases now include one complete update package for publishing to the StarLight skin site.",
|
||||
"zh-CN": "GitHub 发布现会生成一个可直接发布到 StarLight 皮肤站的完整更新包。"
|
||||
}
|
||||
],
|
||||
"fixed": [
|
||||
{
|
||||
"en-US": "Improved the reliability of live logs and database switching.",
|
||||
"zh-CN": "提高实时日志与数据库切换的可靠性。"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "launcher-1.0.1-beta1",
|
||||
"version": "1.0.1-beta1",
|
||||
|
||||
@@ -9,10 +9,7 @@ ariadne = { workspace = true }
|
||||
async-minecraft-ping = { workspace = true, features = ["srv"] }
|
||||
async-recursion = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
async-tungstenite = { workspace = true, features = [
|
||||
"tokio-runtime",
|
||||
"tokio-rustls-webpki-roots"
|
||||
] }
|
||||
async-tungstenite = { workspace = true, features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
||||
async-walkdir = { workspace = true }
|
||||
async_zip = { workspace = true, features = [
|
||||
"bzip2",
|
||||
@@ -119,13 +116,7 @@ tokio = { workspace = true, features = [
|
||||
"time",
|
||||
] }
|
||||
tokio-rustls = { workspace = true }
|
||||
tokio-util = { workspace = true, features = [
|
||||
"compat",
|
||||
"io",
|
||||
"io-util",
|
||||
"rt",
|
||||
"time"
|
||||
] }
|
||||
tokio-util = { workspace = true, features = ["compat", "io", "io-util", "rt", "time"] }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -5,8 +5,9 @@ mod transport;
|
||||
use crate::{
|
||||
State,
|
||||
state::{
|
||||
AppliedContentSetPatch, EditInstance, InstanceInstallStage,
|
||||
InstanceLaunchOverridesPatch, InstanceMode, ModLoader,
|
||||
AppliedContentSetPatch, Credentials, EditInstance,
|
||||
InstanceInstallStage, InstanceLaunchOverridesPatch, InstanceMode,
|
||||
MinecraftAccountType, ModLoader,
|
||||
},
|
||||
util::fetch::{
|
||||
DownloadRequest, Integrity, ResourceClass, configured_client,
|
||||
@@ -31,6 +32,9 @@ use tokio::{
|
||||
};
|
||||
|
||||
const API: &str = "https://skin.starlight.cool/starlight/mod/packs";
|
||||
const STARLIGHT_YGGDRASIL_API_ROOT: &str =
|
||||
"https://skin.starlight.cool/yggdrasil";
|
||||
const ACCOUNT_REQUIRED_MESSAGE: &str = "您需要登录正版或皮肤站账号后才能操作";
|
||||
const BINDING: &str = ".starlight-pack.json";
|
||||
const JOURNAL: &str = ".starlight-pack-pending.json";
|
||||
const MAX_HOSTED_CONCURRENT_FILES: usize = 8;
|
||||
@@ -426,10 +430,47 @@ pub async fn default_publication() -> crate::Result<Publication> {
|
||||
})
|
||||
}
|
||||
|
||||
fn is_supported_game_account(
|
||||
account_type: MinecraftAccountType,
|
||||
yggdrasil_api_root: Option<&str>,
|
||||
) -> bool {
|
||||
account_type == MinecraftAccountType::Microsoft
|
||||
|| (account_type == MinecraftAccountType::Yggdrasil
|
||||
&& yggdrasil_api_root.map(|value| value.trim_end_matches('/'))
|
||||
== Some(STARLIGHT_YGGDRASIL_API_ROOT))
|
||||
}
|
||||
|
||||
async fn has_game_account_in(state: &State) -> crate::Result<bool> {
|
||||
let accounts = Credentials::get_all_without_refresh(&state.pool).await?;
|
||||
Ok(accounts.iter().any(|account| {
|
||||
let account = account.value();
|
||||
is_supported_game_account(
|
||||
account.account_type,
|
||||
account
|
||||
.yggdrasil
|
||||
.as_ref()
|
||||
.map(|value| value.api_root.as_str()),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn has_game_account() -> crate::Result<bool> {
|
||||
let state = State::get().await?;
|
||||
has_game_account_in(&state).await
|
||||
}
|
||||
|
||||
async fn require_game_account(state: &State) -> crate::Result<()> {
|
||||
if !has_game_account_in(state).await? {
|
||||
return Err(invalid(ACCOUNT_REQUIRED_MESSAGE));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create(game_dir_root: Option<String>) -> crate::Result<String> {
|
||||
let state = State::get().await?;
|
||||
require_game_account(&state).await?;
|
||||
let publication = default_publication().await?;
|
||||
let runtime = &publication.manifest.runtime;
|
||||
let state = State::get().await?;
|
||||
// The pack's game files live in their own folder under the chosen root,
|
||||
// e.g. `<root>/<pack name>`. Avoid a `versions/<name>` layout: that shape
|
||||
// is reserved for externally linked launcher instances and would make the
|
||||
@@ -728,6 +769,8 @@ async fn recover(instance_id: &str) -> crate::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn synchronize(instance_id: &str) -> crate::Result<SyncResult> {
|
||||
let state = State::get().await?;
|
||||
require_game_account(&state).await?;
|
||||
let _guard = instance_gate(instance_id).lock_owned().await;
|
||||
if instance_mode(instance_id).await? != InstanceMode::StarLight {
|
||||
return Err(invalid(
|
||||
@@ -1492,6 +1535,27 @@ async fn synchronize_with_progress(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn hosted_operations_accept_only_microsoft_or_starlight_accounts() {
|
||||
assert!(is_supported_game_account(
|
||||
MinecraftAccountType::Microsoft,
|
||||
None
|
||||
));
|
||||
assert!(is_supported_game_account(
|
||||
MinecraftAccountType::Yggdrasil,
|
||||
Some("https://skin.starlight.cool/yggdrasil/")
|
||||
));
|
||||
assert!(!is_supported_game_account(
|
||||
MinecraftAccountType::Yggdrasil,
|
||||
Some("https://auth.example.com/yggdrasil")
|
||||
));
|
||||
assert!(!is_supported_game_account(
|
||||
MinecraftAccountType::Offline,
|
||||
None
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn running_pack_checks_tag_changes_without_writing_live_files() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in new issue
Block a user