refactor: 移除遥测并修复启动器流程

This commit is contained in:
2026-09-13 20:31:55 +08:00
parent 1fa56add19
commit 1593ac7a7c
175 changed files with 547 additions and 12210 deletions

View File

@ -85,7 +85,6 @@ import InstanceIconPickerModal from '@/components/ui/modal/InstanceIconPickerMod
import JavaDownloadConfirmationModal from '@/components/ui/modal/JavaDownloadConfirmationModal.vue'
import ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
import ModpackInstallModal from '@/components/ui/modal/ModpackInstallModal.vue'
import PrivacyConsentModal from '@/components/ui/modal/PrivacyConsentModal.vue'
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
import NavButton from '@/components/ui/NavButton.vue'
import NavRail from '@/components/ui/NavRail.vue'
@ -98,7 +97,6 @@ import { useDropImport } from '@/composables/useDropImport'
import { minecraftLaunchErrorKey } from '@/composables/useMinecraftLaunchError'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { AxolotlBrandConfig, config, getOfficialLabrinthBaseUrl } from '@/config'
import { trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
import { get_user, get_version } from '@/helpers/cache.js'
import { configureCurseForgeManualDownloadWatcher } from '@/helpers/curseforge'
@ -119,16 +117,13 @@ import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts'
import {
get as getSettings,
getLastBrowseContentProjectType,
getPrivacySettings,
getUpdateChannel,
getUpdatePreferences,
isBrowseContentProjectType,
type PrivacySettings,
savePrivacySettings,
set as setSettings,
} from '@/helpers/settings.ts'
import { getSidebarExpanded, setSidebarExpanded } from '@/helpers/sidebar-state.ts'
import { get_opening_command, initialize_state, set_discord_activity } from '@/helpers/state'
import { get_opening_command, initialize_state } from '@/helpers/state'
import {
areUpdatesEnabled,
backupAppDbForUpdate,
@ -369,8 +364,6 @@ watch(
)
const stateInitialized = ref(false)
const privacyConsentModal = ref<InstanceType<typeof PrivacyConsentModal>>()
const privacyConsentPending = ref(false)
const closeChoiceModal = ref<InstanceType<typeof NewModal>>()
const closeChoiceOpen = ref(false)
const closeChoiceRemember = ref(false)
@ -1055,9 +1048,6 @@ async function setupApp() {
theme,
accent_color,
locale,
telemetry,
telemetry_consent_version,
discord_rpc,
collapsed_navigation,
hide_nametag_skins_page,
advanced_rendering,
@ -1101,7 +1091,6 @@ async function setupApp() {
const dev = await isDev()
isDevEnvironment.value = dev
if (!onboarded && route.path !== '/') await router.replace('/')
privacyConsentPending.value = telemetry_consent_version < 1
showOnboarding.value = false
onboardingSettings.value = initialSettings
@ -1132,16 +1121,7 @@ async function setupApp() {
themeStore.devMode = developer_mode
themeStore.featureFlags = feature_flags
stateInitialized.value = true
if (privacyConsentPending.value) {
await nextTick()
privacyConsentModal.value?.show({
telemetry,
discord_rpc,
consent_version: telemetry_consent_version,
})
} else {
showOnboarding.value = !onboarded
}
showOnboarding.value = !onboarded
void reconcileMojangAuthSourceAtStartup().catch(handleError)
isMaximized.value = await getCurrentWindow().isMaximized()
@ -1274,42 +1254,7 @@ async function closeOnboardingSettings() {
}
async function scheduleStartupDialogs() {
if (!stateInitialized.value || privacyConsentPending.value || showOnboarding.value) return
}
async function handlePrivacyConsentSaved(privacy: PrivacySettings) {
privacyConsentPending.value = false
if (onboardingSettings.value) {
onboardingSettings.value.telemetry = privacy.telemetry
onboardingSettings.value.discord_rpc = privacy.discord_rpc
onboardingSettings.value.telemetry_consent_version = privacy.consent_version
}
if (!onboardingSettings.value?.onboarded) {
startOnboarding('main')
} else {
await scheduleStartupDialogs()
}
}
async function previewPrivacyConsentModal() {
try {
const current = await getPrivacySettings()
const privacy = await savePrivacySettings({
telemetry: false,
discord_rpc: current.discord_rpc,
consent_version: 0,
})
privacyConsentPending.value = true
if (onboardingSettings.value) {
onboardingSettings.value.telemetry = privacy.telemetry
onboardingSettings.value.discord_rpc = privacy.discord_rpc
onboardingSettings.value.telemetry_consent_version = privacy.consent_version
}
await nextTick()
privacyConsentModal.value?.show(privacy)
} catch (error) {
handleError(error)
}
if (!stateInitialized.value || showOnboarding.value) return
}
provide('replayOnboarding', replayOnboarding)
@ -1320,7 +1265,6 @@ provide(
)
provide('previewMinecraftCrashModal', () => minecraftCrashModal.value?.showPreview())
provide('showLauncherPopup', (_request: unknown) => {})
provide('previewPrivacyConsentModal', previewPrivacyConsentModal)
const stateFailed = ref(false)
stateInitialization
@ -1429,9 +1373,6 @@ loading.setEnabled(false)
let initialLoadToken = loading.begin()
let routerToken = null
let suspenseToken = null
let lastDiscordActivity = null
let discordActivityUpdate = Promise.resolve()
let suspensePending = false
const sidebarOverlayScrollbarsOptions = Object.freeze({
@ -1447,31 +1388,11 @@ router.beforeEach(() => {
routerToken = loading.begin()
})
function syncDiscordActivity(to: RouteLocationNormalizedLoaded) {
const activity =
typeof to.meta.discordActivity === 'string' ? to.meta.discordActivity : 'Idling...'
if (activity === lastDiscordActivity) return
lastDiscordActivity = activity
discordActivityUpdate = discordActivityUpdate
.then(() => set_discord_activity(activity))
.catch((error) => {
if (lastDiscordActivity === activity) lastDiscordActivity = null
console.error('Failed to update Discord activity', error)
})
}
router.afterEach((to, from, failure) => {
hideAllPoppers()
if (!failure) void invoke('lightweight_mode_set_route', { route: to.fullPath })
trackEvent('PageView', {
path: to.path,
fromPath: from.path,
failed: failure,
})
if (!failure) {
void directLinkSync?.()
if (stateInitialized.value) syncDiscordActivity(to)
}
setTimeout(() => {
if (!suspensePending && stateInitialized.value) {
@ -1508,7 +1429,6 @@ watch(
stateInitialized,
(ready) => {
if (ready) {
syncDiscordActivity(router.currentRoute.value)
if (initialLoadToken) {
loading.end(initialLoadToken)
initialLoadToken = null
@ -1624,7 +1544,6 @@ const dropImport = useDropImport({
onSkinsPage,
onSchematicWorkshopPage,
isSchematicFile,
trackEvent,
router,
})
@ -1836,9 +1755,6 @@ async function handleCommand(e) {
} else {
await install_create_modpack_instance(location).catch(handleError)
}
trackEvent('InstanceCreate', {
source: 'CreationModalFileDrop',
})
}
} else if (e.event === 'LaunchInstance') {
const instance = await getInstance(e.id).catch(() => null)
@ -2577,7 +2493,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
/>
<MinecraftCrashModal ref="minecraftCrashModal" @error="handleError" />
<JavaDownloadConfirmationModal ref="javaDownloadConfirmationModal" />
<PrivacyConsentModal ref="privacyConsentModal" @saved="handlePrivacyConsentSaved" />
<NewModal
ref="closeChoiceModal"
:header="formatMessage(messages.closeLauncherTitle)"

View File

@ -28,7 +28,6 @@ import LegacyProjectCard from '@/components/ui/LegacyProjectCard.vue'
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { trackEvent } from '@/helpers/analytics'
import { install_duplicate_instance } from '@/helpers/install'
import { kill, remove, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process.js'
@ -158,17 +157,9 @@ const handleOptionsClick = async (args) => {
})
if (!handled) handleSevereError(err, { instanceId: args.item.id })
})
trackEvent('InstanceStart', {
loader: args.item.loader,
game_version: args.item.game_version,
})
break
case 'stop':
await kill(args.item.id).catch(handleError)
trackEvent('InstanceStop', {
loader: args.item.loader,
game_version: args.item.game_version,
})
break
case 'add_content':
await router.push({

View File

@ -18,7 +18,6 @@ import { computed, ref, watch } from 'vue'
import { useHomeDashboardRuntime } from '@/components/home/home-dashboard-runtime'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import {
type DailyPlaytime,
type DailyPlaytimeEntry,
@ -200,11 +199,6 @@ function selectDay(dateKey: string) {
async function playInstance(instance: GameInstance) {
try {
await run(instance.id)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeCalendar',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -216,11 +210,6 @@ async function playInstance(instance: GameInstance) {
async function stopInstance(instance: GameInstance) {
await kill(instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeCalendar',
})
}
watch(() => anchor.value.getTime(), refreshPlaytime, { immediate: true })

View File

@ -25,7 +25,6 @@ import HomeGreeting from '@/components/home/HomeGreeting.vue'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events'
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
import { kill, run } from '@/helpers/instance'
@ -126,11 +125,6 @@ async function playInstance() {
loading.value = true
try {
await run(instance.id)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeMinimal',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -149,11 +143,6 @@ async function stopInstance() {
await kill(instance.id).catch(handleError)
running.value = false
trackEvent('InstanceStop', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeMinimal',
})
}
async function installInstance() {

View File

@ -22,7 +22,6 @@ import { computed, ref } from 'vue'
import type { HomeWidgetSize } from '@/components/home/home-dashboard'
import { useHomeDashboardRuntime } from '@/components/home/home-dashboard-runtime'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import { kill } from '@/helpers/instance'
import type { GameInstance } from '@/helpers/types'
import {
@ -107,11 +106,6 @@ async function joinServer(world: ServerWorld & WorldWithInstance, instance: Game
try {
await start_join_server(world.instance_id, world.address)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomePinnedServer',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -125,11 +119,6 @@ async function joinServer(world: ServerWorld & WorldWithInstance, instance: Game
async function stopInstance(instance: GameInstance) {
await kill(instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomePinnedServer',
})
}
async function unpinServer(world: ServerWorld & WorldWithInstance) {

View File

@ -7,7 +7,6 @@ import { getHomeWidgetCardDensity, type HomeWidgetSize } from '@/components/home
import { useHomeDashboardRuntime } from '@/components/home/home-dashboard-runtime'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import { kill, run } from '@/helpers/instance'
import type { GameInstance } from '@/helpers/types'
import {
@ -73,11 +72,6 @@ async function joinWorld(world: WorldWithInstance, instance: GameInstance) {
try {
await start_join_singleplayer_world(world.instance_id, world.path)
playingWorldKey.value = key
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomePinnedWorld',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -92,11 +86,6 @@ async function joinWorld(world: WorldWithInstance, instance: GameInstance) {
async function playInstance(instance: GameInstance) {
try {
await run(instance.id)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomePinnedWorld',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -109,11 +98,6 @@ async function playInstance(instance: GameInstance) {
async function stopInstance(instance: GameInstance) {
await kill(instance.id).catch(handleError)
playingWorldKey.value = null
trackEvent('InstanceStop', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomePinnedWorld',
})
}
</script>

View File

@ -15,7 +15,6 @@ import { useHomeDashboardRuntime } from '@/components/home/home-dashboard-runtim
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import { kill, run } from '@/helpers/instance'
import type { GameInstance } from '@/helpers/types'
import {
@ -117,11 +116,6 @@ async function joinWorld(world: WorldWithInstance, instance: GameInstance) {
await start_join_singleplayer_world(world.instance_id, world.path)
}
playingWorldKey.value = key
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeRecentWorld',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -136,11 +130,6 @@ async function joinWorld(world: WorldWithInstance, instance: GameInstance) {
async function playInstance(instance: GameInstance) {
try {
await run(instance.id)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeRecentWorld',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.id,
@ -153,11 +142,6 @@ async function playInstance(instance: GameInstance) {
async function stopInstance(instance: GameInstance) {
await kill(instance.id).catch(handleError)
playingWorldKey.value = null
trackEvent('InstanceStop', {
loader: instance.loader,
game_version: instance.game_version,
source: 'HomeRecentWorld',
})
}
</script>

View File

@ -28,7 +28,6 @@ import type { HomeWidgetPlacement, HomeWidgetSize } from '@/components/home/home
import { useHomeDashboardRuntime } from '@/components/home/home-dashboard-runtime'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import { kill, run } from '@/helpers/instance'
import type { GameInstance } from '@/helpers/types'
import {
@ -205,11 +204,6 @@ async function playInstance(targetInstance: GameInstance) {
starting.value = true
try {
await run(targetInstance.id)
trackEvent('InstanceStart', {
loader: targetInstance.loader,
game_version: targetInstance.game_version,
source: 'HomeInstanceWidget',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: targetInstance.id,
@ -230,11 +224,6 @@ async function playWorld() {
} else {
await start_join_singleplayer_world(instance.value.id, world.value.path)
}
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: 'HomeShortcutWidget',
})
} catch (error) {
const handled = await handleMinecraftLaunchError(error, {
instance_id: instance.value.id,

View File

@ -280,7 +280,6 @@ import MinecraftLoginModal from '@/components/ui/MinecraftLoginModal.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { compareMinecraftAccounts } from '@/helpers/accounts'
import { trackEvent } from '@/helpers/analytics'
import {
begin_yggdrasil_login,
delete_yggdrasil_password,
@ -678,7 +677,6 @@ async function onMicrosoftLogin(account: MinecraftCredential) {
loginDisabled.value = true
try {
await setAccount(account)
trackEvent('AccountLogIn')
} catch (error) {
handleSevereError(error)
} finally {
@ -833,7 +831,6 @@ async function addYggdrasilAccount() {
await persistYggdrasilPasswordPreference()
yggdrasilAccountModal.value?.hide()
await setAccount(result.credentials)
trackEvent('YggdrasilAccountAdd')
} else {
pendingYggdrasilFlowId.value = result.flow_id
pendingYggdrasilProfiles.value = result.profiles
@ -859,7 +856,6 @@ async function selectYggdrasilProfile(profileId: string) {
await persistYggdrasilPasswordPreference()
yggdrasilProfileModal.value?.hide()
await setAccount(account)
trackEvent('YggdrasilAccountAdd')
} catch (error) {
handleError(error as Error)
} finally {
@ -875,7 +871,6 @@ async function logout(account: MinecraftCredential) {
} else {
notifyAccountChange()
}
trackEvent('AccountLogOut')
}
async function copyAccountUuid(account: MinecraftCredential) {

View File

@ -212,7 +212,6 @@ import { useRoute, useRouter } from 'vue-router'
import AppUpdateButton from '@/components/ui/app-update-button/index.vue'
import { useInstallJobNotifications } from '@/composables/browse/install-job-notifications'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { trackEvent } from '@/helpers/analytics'
import { loading_listener, process_listener } from '@/helpers/events'
import { get_many as getInstances } from '@/helpers/instance'
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
@ -437,11 +436,6 @@ const stop = async (process: RunningProcess) => {
try {
await killProcess(process.uuid).catch(handleError)
trackEvent('InstanceStop', {
loader: process.instance.loader,
game_version: process.instance.game_version,
source: 'AppBar',
})
} catch (e) {
console.error(e)
}

View File

@ -23,7 +23,6 @@ import { computed, ref } from 'vue'
import { ChatIcon } from '@/assets/icons'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { AxolotlBrandConfig } from '@/config'
import { trackEvent } from '@/helpers/analytics'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { install_existing_instance } from '@/helpers/install'
import { cancel_directory_change } from '@/helpers/settings.ts'
@ -241,7 +240,6 @@ async function loginMinecraft() {
await set_default_user(loggedIn.profile.id).catch(handleError)
}
await trackEvent('AccountLogIn', { source: 'ErrorModal' })
loadingMinecraft.value = false
errorModal.value.hide()
} catch (err) {

View File

@ -23,7 +23,6 @@ import { useRouter } from 'vue-router'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events'
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
import { kill, run } from '@/helpers/instance'
@ -121,11 +120,6 @@ const play = async (e, context) => {
if (!handled) handleSevereError(err, { instanceId: props.instance.id })
})
.finally(() => {
trackEvent('InstanceStart', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: context,
})
})
loading.value = false
}
@ -136,11 +130,6 @@ const stop = async (e, context) => {
await kill(props.instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: context,
})
}
const repair = async (e) => {

View File

@ -59,7 +59,6 @@ import {
import { onUnmounted, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { java_discovery_listener } from '@/helpers/events'
import { find_filtered_jres } from '@/helpers/jre.js'
@ -131,9 +130,5 @@ const emit = defineEmits(['submit'])
function setJavaInstall(javaInstall) {
emit('submit', javaInstall)
detectJavaModal.value.hide()
trackEvent('JavaAutoDetect', {
path: javaInstall.path,
version: javaInstall.version,
})
}
</script>

View File

@ -112,7 +112,6 @@ import { computed, ref, watch } from 'vue'
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
import useJavaTest from '@/composables/useJavaTest'
import { trackEvent } from '@/helpers/analytics'
import { auto_install_java, find_filtered_jres, get_jre } from '@/helpers/jre.js'
const { handleError } = injectNotificationManager()
@ -196,9 +195,9 @@ const recommendedInstalled = computed(() => {
let hasInitialized = false
async function runTest(path) {
await testJavaInstallation(path, testVersion.value, true)
await testJavaInstallation(path, testVersion.value)
if (props.version != null) {
await recommendedJavaTest.testJavaInstallation(path, props.version, false)
await recommendedJavaTest.testJavaInstallation(path, props.version)
}
}
@ -212,9 +211,9 @@ watch(
(newPath) => {
if (newPath) {
if (!hasInitialized) {
testJavaInstallation(newPath, testVersion.value, false)
testJavaInstallation(newPath, testVersion.value)
if (props.version != null) {
recommendedJavaTest.testJavaInstallation(newPath, props.version, false)
recommendedJavaTest.testJavaInstallation(newPath, props.version)
}
hasInitialized = true
} else {
@ -242,9 +241,6 @@ async function handleJavaFileInput() {
}
}
trackEvent('JavaManualSelect', {
version: props.version,
})
commitSelection(result)
}
@ -279,7 +275,6 @@ async function reinstallJava() {
}
}
trackEvent('JavaReInstall', { path: path, version: props.version })
commitSelection(result)
runTest(result.path)
} finally {

View File

@ -14,7 +14,6 @@ import { computed, ref } from 'vue'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { list } from '@/helpers/instance'
import { add_server_to_instance, get_instance_worlds } from '@/helpers/worlds.ts'
@ -91,7 +90,6 @@ defineExpose({
instances.value = instanceValues
modal.value.show()
trackEvent('AddServerToInstanceStart', { source: 'AddServerToInstanceModal' })
},
})
@ -102,11 +100,6 @@ async function addServer(instance) {
instance.added = true
await queryClient.invalidateQueries({ queryKey: ['worlds', instance.id] })
trackEvent('AddServerToInstance', {
server_name: serverName.value,
instance_name: instance.name,
source: 'AddServerToInstanceModal',
})
} catch (err) {
handleError(err)
}

View File

@ -18,7 +18,6 @@ import { useRouter } from 'vue-router'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { install_duplicate_instance } from '@/helpers/install'
import { edit, edit_icon, get_full_path, remove } from '@/helpers/instance'
import { injectInstanceSettings } from '@/providers/instance-settings'
@ -49,10 +48,6 @@ const installing = computed(() => instance.value.install_stage !== 'installed')
async function duplicateInstance() {
await install_duplicate_instance(instance.value.id).catch(handleError)
trackEvent('InstanceDuplicate', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
}
function formatReleaseChannelLabel(channel: ReleaseChannel) {
@ -104,7 +99,6 @@ watch(selectedReleaseChannel, async (channel, previousChannel) => {
async function resetIcon() {
icon.value = undefined
await edit_icon(instance.value.id, null).catch(handleError)
trackEvent('InstanceRemoveIcon')
}
async function setIcon() {
@ -116,7 +110,6 @@ async function setIcon() {
icon.value = picked.path
try {
await edit_icon(instance.value.id, picked.path)
trackEvent('InstanceSetIcon')
} catch (error) {
icon.value = previousIcon
handleError(error)
@ -253,10 +246,6 @@ async function removeInstance() {
removing.value = true
const path = instance.value.id
trackEvent('InstanceRemove', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
await router.push({ path: '/' })
await remove(path).catch(handleError)

View File

@ -22,7 +22,6 @@ import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import SymlinkInstanceWarning from '@/components/ui/SymlinkInstanceWarning.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_versions, get_version } from '@/helpers/cache'
import { type CurseForgeFile, updateManagedCurseForgeModpack } from '@/helpers/curseforge'
import {
@ -333,10 +332,6 @@ provideInstallationSettings({
afterSave: async () => {
debug('afterSave: installing', { instanceId: instance.value.id })
await install_existing_instance(instance.value.id, false).catch(handleError)
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
debug('afterSave: done')
},
@ -345,40 +340,27 @@ provideInstallationSettings({
repairing.value = true
await install_existing_instance(instance.value.id, true).catch(handleError)
repairing.value = false
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
debug('repair: done')
},
async reinstallModpack() {
debug('reinstallModpack: called', { instanceId: instance.value.id })
reinstalling.value = true
let shouldTrack = false
try {
if (isImportedModpack.value) {
shouldTrack = await installLocalModpackFromPicker()
await installLocalModpackFromPicker()
} else if (isCurseForgeLinkedModpack.value) {
const fileId = Number(instance.value.link?.version_id)
if (!Number.isFinite(fileId)) {
throw new Error('Invalid CurseForge file ID')
}
await updateManagedCurseForgeModpack(instance.value.id, fileId).catch(handleError)
shouldTrack = true
} else {
await update_repair_modrinth(instance.value.id).catch(handleError)
shouldTrack = true
}
} finally {
reinstalling.value = false
}
if (shouldTrack) {
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
}
debug('reinstallModpack: done')
},
@ -386,13 +368,7 @@ provideInstallationSettings({
debug('swapModpack: called', { instanceId: instance.value.id })
reinstalling.value = true
try {
const installed = await installLocalModpackFromPicker()
if (installed) {
trackEvent('InstanceRepair', {
loader: instance.value.loader,
game_version: instance.value.game_version,
})
}
await installLocalModpackFromPicker()
} finally {
reinstalling.value = false
}

View File

@ -1,145 +0,0 @@
<script setup lang="ts">
import { ExternalIcon, ShieldIcon, SpinnerIcon } from '@modrinth/assets'
import {
ButtonStyled,
defineMessages,
injectNotificationManager,
NewModal,
Toggle,
useVIntl,
} from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
import { ref } from 'vue'
import { type PrivacySettings, savePrivacySettings } from '@/helpers/settings'
const emit = defineEmits<{
saved: [privacy: PrivacySettings]
}>()
const CONSENT_VERSION = 1
const { formatMessage } = useVIntl()
const { handleError } = injectNotificationManager()
const modal = ref<InstanceType<typeof NewModal>>()
const telemetry = ref(true)
const discordRpc = ref(true)
const saving = ref(false)
const messages = defineMessages({
title: {
id: 'app.privacy-consent.title',
defaultMessage: 'Privacy & security',
},
intro: {
id: 'app.privacy-consent.intro',
defaultMessage:
'Choose what Starlight may send or display. Nothing is sent until you confirm these choices.',
},
telemetry: {
id: 'app.privacy-consent.telemetry',
defaultMessage: 'Allow anonymous telemetry',
},
telemetryDescription: {
id: 'app.privacy-consent.telemetry-description',
defaultMessage:
'Helps count opted-in installations and daily active users. Full Minecraft logs and account credentials are never uploaded.',
},
discordRpc: {
id: 'app.privacy-consent.discord-rpc',
defaultMessage: 'Discord Rich Presence',
},
discordRpcDescription: {
id: 'app.privacy-consent.discord-rpc-description',
defaultMessage:
'Shows your current launcher or game activity in Discord when Discord is running.',
},
privacyPolicy: {
id: 'app.privacy-consent.privacy-policy',
defaultMessage: 'Read the privacy policy',
},
continue: {
id: 'app.privacy-consent.continue',
defaultMessage: 'Save and continue',
},
})
function show(current: PrivacySettings) {
telemetry.value = true
discordRpc.value = current.discord_rpc
modal.value?.show()
}
async function save() {
if (saving.value) return
saving.value = true
try {
const privacy = await savePrivacySettings({
telemetry: telemetry.value,
discord_rpc: discordRpc.value,
consent_version: CONSENT_VERSION,
})
modal.value?.hide()
emit('saved', privacy)
} catch (error) {
handleError(error)
} finally {
saving.value = false
}
}
defineExpose({ show })
</script>
<template>
<NewModal ref="modal" :header="formatMessage(messages.title)" :closable="false" max-width="600px">
<div class="flex flex-col gap-6">
<div class="flex items-start gap-3">
<ShieldIcon class="mt-0.5 size-6 shrink-0 text-brand" />
<p class="m-0 leading-relaxed text-primary">
{{ formatMessage(messages.intro) }}
</p>
</div>
<div class="flex items-center justify-between gap-5">
<div class="min-w-0">
<label for="consent-telemetry" class="font-semibold text-contrast">
{{ formatMessage(messages.telemetry) }}
</label>
<p class="mb-0 mt-1 text-sm leading-relaxed text-secondary">
{{ formatMessage(messages.telemetryDescription) }}
</p>
</div>
<Toggle id="consent-telemetry" v-model="telemetry" :disabled="saving" />
</div>
<div class="flex items-center justify-between gap-5">
<div class="min-w-0">
<label for="consent-discord-rpc" class="font-semibold text-contrast">
{{ formatMessage(messages.discordRpc) }}
</label>
<p class="mb-0 mt-1 text-sm leading-relaxed text-secondary">
{{ formatMessage(messages.discordRpcDescription) }}
</p>
</div>
<Toggle id="consent-discord-rpc" v-model="discordRpc" :disabled="saving" />
</div>
</div>
<template #actions>
<div class="flex items-center justify-between gap-4">
<ButtonStyled type="transparent">
<button type="button" :disabled="saving" @click="openUrl('https://axlmc.org/privacy')">
<ExternalIcon />
{{ formatMessage(messages.privacyPolicy) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button type="button" :disabled="saving" @click="save">
<SpinnerIcon v-if="saving" class="animate-spin" />
{{ formatMessage(messages.continue) }}
</button>
</ButtonStyled>
</div>
</template>
</NewModal>
</template>

View File

@ -204,15 +204,6 @@ export const onboardingMessages = defineMessages({
defaultMessage:
'Choose how content downloads and installs, from download sources to safety checks.',
},
privacyTitle: {
id: 'app.onboarding.privacy.title',
defaultMessage: 'Your data, your call',
},
privacyDescription: {
id: 'app.onboarding.privacy.description',
defaultMessage:
'Manage anonymous telemetry, Discord Rich Presence, and the Minecraft log analysis service whenever you need to.',
},
updatesTitle: { id: 'app.onboarding.updates.title', defaultMessage: 'Stay in the loop' },
updatesDescription: {
id: 'app.onboarding.updates.description',

View File

@ -21,7 +21,6 @@ import JetBrainsLogo from '@/assets/java-vendors/jetbrains.png'
import MicrosoftLogo from '@/assets/java-vendors/microsoft.png'
import OracleLogo from '@/assets/java-vendors/oracle.png'
import SapLogo from '@/assets/java-vendors/sap.png'
import { trackEvent } from '@/helpers/analytics'
import { download_java, list_java_feed_vendors, list_java_feed_versions } from '@/helpers/jre'
const { handleError } = injectNotificationManager()
@ -117,7 +116,6 @@ function backToVendors() {
async function downloadVersion(info) {
downloading.value = info.major_version
trackEvent('JavaDownload', { vendor: info.vendor, version: info.major_version })
modal.value?.hide()
const job = await download_java(info.vendor, info.major_version).catch(handleError)

View File

@ -23,7 +23,6 @@ const { formatMessage } = useVIntl()
const { addNotification } = injectNotificationManager()
const isDevEnvironment = await isDev()
const previewMinecraftCrashModal = inject<() => void>('previewMinecraftCrashModal')
const previewPrivacyConsentModal = inject<() => Promise<void>>('previewPrivacyConsentModal')
const messages = defineMessages({
resetToDefault: {
id: 'app.settings.feature-flags.reset-to-default',
@ -53,10 +52,6 @@ const messages = defineMessages({
id: 'app.settings.about.preview-minecraft-crash-modal',
defaultMessage: 'Preview Minecraft crash window',
},
previewPrivacyConsentModal: {
id: 'app.settings.about.preview-privacy-consent-modal',
defaultMessage: 'Preview privacy & security modal',
},
})
const settings = ref(await getSettings())
@ -127,9 +122,6 @@ watch(
<Button type="base" @click="previewMinecraftCrashModal?.()">
<WrenchIcon /> {{ formatMessage(messages.previewMinecraftCrashModal) }}
</Button>
<Button type="base" @click="previewPrivacyConsentModal?.()">
<WrenchIcon /> {{ formatMessage(messages.previewPrivacyConsentModal) }}
</Button>
</div>
</SettingsSection>
</template>

View File

@ -19,7 +19,6 @@ import MemoryAllocationDisplay from '@/components/ui/MemoryAllocationDisplay.vue
import DownloadJavaModal from '@/components/ui/settings/DownloadJavaModal.vue'
import InstalledJavaModal from '@/components/ui/settings/InstalledJavaModal.vue'
import useMemorySlider from '@/composables/useMemorySlider'
import { trackEvent } from '@/helpers/analytics'
import { collectGcContext } from '@/helpers/gc/context'
import { wait_for_install_job } from '@/helpers/install'
import { getJavaArgumentPresets } from '@/helpers/java-argument-presets'
@ -249,7 +248,6 @@ async function runScan(exhaustive) {
scanning.value = true
scanMode.value = 'quick'
trackEvent('JavaQuickScan', { source: 'settings' })
try {
await find_filtered_jres(null, false, true, false).catch(handleError)
} finally {
@ -262,7 +260,6 @@ async function confirmDeepScan() {
showDeepScanConfirm.value = false
scanning.value = true
scanMode.value = 'deep'
trackEvent('JavaDeepScan', { source: 'settings' })
try {
await find_filtered_jres(null, true, true, true).catch(handleError)
} finally {
@ -280,7 +277,6 @@ async function handleManualAdd() {
if (!javaInfo) return
await set_java_version(javaInfo).catch(handleError)
trackEvent('JavaManualSelect', { path: filePath })
}
async function onJavaDownloaded(job) {

View File

@ -1,156 +0,0 @@
<script setup lang="ts">
import { defineMessages, injectNotificationManager, Toggle, useVIntl } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { getPrivacySettings, setDiscordRpcEnabled, setTelemetryEnabled } from '@/helpers/settings'
import SettingsRow from './SettingsRow.vue'
import SettingsSaveStatus from './SettingsSaveStatus.vue'
const { formatMessage } = useVIntl()
const { handleError } = injectNotificationManager()
const privacy = ref(await getPrivacySettings())
const telemetrySaving = ref(false)
const discordSaving = ref(false)
const lastSaveState = ref<'idle' | 'saved' | 'error'>('idle')
const retrySave = ref<(() => void) | undefined>()
const messages = defineMessages({
telemetry: {
id: 'app.settings.privacy.telemetry',
defaultMessage: 'Allow telemetry',
},
telemetryDescription: {
id: 'app.settings.privacy.telemetry-description',
defaultMessage:
'Send one anonymous daily activity signal to improve usage statistics. Minecraft logs and account credentials are never uploaded.',
},
discordRpc: {
id: 'app.settings.privacy.discord-rpc',
defaultMessage: 'Discord Rich Presence',
},
discordRpcDescription: {
id: 'app.settings.privacy.discord-rpc-description',
defaultMessage: 'Show your current launcher or game activity in Discord.',
},
dataHandling: {
id: 'app.settings.privacy.data-handling',
defaultMessage:
'Telemetry uses a random installation identifier and sends only a daily activity signal. Turning telemetry off clears pending data immediately.',
},
})
const saveStatus = computed(() => {
if (telemetrySaving.value || discordSaving.value) return 'saving'
return lastSaveState.value
})
async function updateTelemetry(value: boolean) {
if (telemetrySaving.value) return
const previous = privacy.value.telemetry
privacy.value.telemetry = value
telemetrySaving.value = true
lastSaveState.value = 'idle'
retrySave.value = undefined
try {
const saved = await setTelemetryEnabled(value)
privacy.value.telemetry = saved.telemetry
privacy.value.consent_version = saved.consent_version
lastSaveState.value = 'saved'
} catch (error) {
privacy.value.telemetry = previous
retrySave.value = () => void updateTelemetry(value)
lastSaveState.value = 'error'
handleError(error)
} finally {
telemetrySaving.value = false
}
}
async function updateDiscordRpc(value: boolean) {
if (discordSaving.value) return
const previous = privacy.value.discord_rpc
privacy.value.discord_rpc = value
discordSaving.value = true
lastSaveState.value = 'idle'
retrySave.value = undefined
try {
const saved = await setDiscordRpcEnabled(value)
privacy.value.discord_rpc = saved.discord_rpc
lastSaveState.value = 'saved'
} catch (error) {
privacy.value.discord_rpc = previous
retrySave.value = () => void updateDiscordRpc(value)
lastSaveState.value = 'error'
handleError(error)
} finally {
discordSaving.value = false
}
}
</script>
<template>
<div class="flex w-full flex-col gap-4">
<header class="settings-page-header">
<SettingsSaveStatus :status="saveStatus" :retry="retrySave" />
</header>
<div class="settings-page-card">
<SettingsRow>
<template #label>
<span id="settings-target-privacy-telemetry" tabindex="-1">
{{ formatMessage(messages.telemetry) }}
</span>
</template>
<template #description>{{ formatMessage(messages.telemetryDescription) }}</template>
<template #control>
<Toggle
id="privacy-telemetry"
:model-value="privacy.telemetry"
:disabled="telemetrySaving"
@update:model-value="(value) => updateTelemetry(!!value)"
/>
</template>
</SettingsRow>
<SettingsRow>
<template #label>
<span id="settings-target-privacy-discord-rpc" tabindex="-1">
{{ formatMessage(messages.discordRpc) }}
</span>
</template>
<template #description>{{ formatMessage(messages.discordRpcDescription) }}</template>
<template #control>
<Toggle
id="privacy-discord-rpc"
:model-value="privacy.discord_rpc"
:disabled="discordSaving"
@update:model-value="(value) => updateDiscordRpc(!!value)"
/>
</template>
</SettingsRow>
</div>
<p class="settings-page-note">{{ formatMessage(messages.dataHandling) }}</p>
</div>
</template>
<style scoped>
.settings-page-card {
overflow: hidden;
border: 1px solid
var(--settings-card-border, color-mix(in srgb, var(--surface-4) 72%, transparent));
border-radius: var(--radius-md);
background: var(--surface-2);
}
.settings-page-header {
display: flex;
min-height: 0;
justify-content: flex-end;
}
.settings-page-note {
margin: 0;
color: var(--color-secondary);
font-size: 0.8125rem;
line-height: 1.5;
}
</style>

View File

@ -10,7 +10,6 @@ export type SettingsCategoryId =
| 'content-downloads'
| 'network-multiplayer'
| 'storage-backups'
| 'privacy-data'
| 'updates'
| 'about'
| 'feature-flags'
@ -106,15 +105,6 @@ export const settingsCategoryDefinitions: SettingsCategoryDefinition[] = [
group: 'data-privacy',
onboardingId: 'settings-tab-storage-backups',
},
{
id: 'privacy-data',
name: defineMessage({
id: 'app.settings.tabs.privacy-data',
defaultMessage: 'Privacy & data sharing',
}),
group: 'data-privacy',
onboardingId: 'settings-tab-privacy-data',
},
{
id: 'updates',
name: defineMessage({ id: 'app.settings.tabs.updates', defaultMessage: 'Updates' }),

View File

@ -72,10 +72,6 @@ const categoryContent: Record<SettingsCategoryId, Pick<SettingsCategory, 'icon'
icon: ArchiveIcon,
content: defineAsyncComponent(() => import('./StorageBackupSettings.vue')),
},
'privacy-data': {
icon: ShieldIcon,
content: defineAsyncComponent(() => import('./PrivacySettings.vue')),
},
updates: {
icon: RefreshCwIcon,
content: defineAsyncComponent(() => import('./UpdateSettings.vue')),

View File

@ -221,18 +221,6 @@ export const settingsSearchEntries: SettingsSearchEntry[] = [
label: message('app.crash-analysis.ai.settings.title', 'Crash AI explanation'),
keywords: [message('app.settings.tabs.launch-defaults', 'Launch & instance defaults')],
},
{
id: 'privacy-telemetry',
categoryId: 'privacy-data',
targetId: 'settings-target-privacy-telemetry',
label: message('app.settings.privacy.telemetry', 'Telemetry'),
},
{
id: 'privacy-discord-rpc',
categoryId: 'privacy-data',
targetId: 'settings-target-privacy-discord-rpc',
label: message('app.settings.privacy.discord-rpc', 'Discord rich presence'),
},
{
id: 'java-installations',
categoryId: 'java-performance',

View File

@ -28,7 +28,6 @@ const settingsComponentFiles = {
'content-downloads': ['./AppearanceSettings.vue', './ResourceManagementSettings.vue'],
'network-multiplayer': ['./ResourceManagementSettings.vue', './MultiplayerSettings.vue'],
'storage-backups': ['./ResourceManagementSettings.vue', './StorageSettings.vue'],
'privacy-data': ['./PrivacySettings.vue'],
updates: ['./UpdateSettings.vue'],
about: ['./AboutSettings.vue'],
'feature-flags': ['./FeatureFlagSettings.vue'],
@ -169,7 +168,7 @@ test('developer-only settings stay out of the normal search categories', () => {
'content-downloads',
'network-multiplayer',
])
assert.deepEqual(categoriesForGroup('data-privacy'), ['storage-backups', 'privacy-data'])
assert.deepEqual(categoriesForGroup('data-privacy'), ['storage-backups'])
assert.deepEqual(categoriesForGroup('support'), ['updates', 'about'])
assert.deepEqual(categoriesForGroup('developer'), [])
assert.deepEqual(categoriesForGroup('developer', true), ['feature-flags'])

View File

@ -26,7 +26,6 @@ import { useRouter } from 'vue-router'
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import { get_project } from '@/helpers/cache'
import { process_listener } from '@/helpers/events'
import { kill, run } from '@/helpers/instance'
@ -108,11 +107,6 @@ const play = async (event: MouseEvent) => {
if (!handled) handleSevereError(err, { instanceId: props.instance.id })
})
.finally(() => {
trackEvent('InstanceStart', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: 'InstanceItem',
})
})
emit('play')
loading.value = false
@ -122,11 +116,6 @@ const stop = async (event: MouseEvent) => {
event?.stopPropagation()
loading.value = true
await kill(props.instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: 'InstanceItem',
})
emit('stop')
loading.value = false
}

View File

@ -114,8 +114,6 @@ export interface DropImportOptions {
onSchematicWorkshopPage: ComputedRef<boolean>
/** Check if path is a schematic file */
isSchematicFile: (path: string) => boolean
/** Track analytics event */
trackEvent: (name: string, properties?: Record<string, unknown>) => void
/** Route to push */
router: Router
}
@ -135,7 +133,6 @@ export interface DropImportOptions {
* onSkinsPage,
* onSchematicWorkshopPage,
* isSchematicFile,
* trackEvent,
* router,
* route,
* })
@ -151,7 +148,6 @@ export function useDropImport(options: DropImportOptions) {
onSkinsPage,
onSchematicWorkshopPage,
isSchematicFile,
trackEvent,
router,
} = options
@ -906,7 +902,6 @@ export function useDropImport(options: DropImportOptions) {
clearDropProcessingNotification()
await installModpackFromPath(filePath, fileName, { persistUntilDone: true })
trackEvent('InstanceCreate', { source: 'DropConfirmModpack' })
await router.push('/library')
return
}

View File

@ -1,6 +1,5 @@
import { ref } from 'vue'
import { trackEvent } from '@/helpers/analytics'
import { get_jre, test_jre } from '@/helpers/jre.js'
export default function useJavaTest() {
@ -8,7 +7,7 @@ export default function useJavaTest() {
const javaTestResult = ref<boolean | null>(null)
let testDebounceTimer: ReturnType<typeof setTimeout> | null = null
async function runJavaTest(path: string, version: number | null, track = true) {
async function runJavaTest(path: string, version: number | null) {
if (testDebounceTimer) {
clearTimeout(testDebounceTimer)
testDebounceTimer = null
@ -28,10 +27,6 @@ export default function useJavaTest() {
javaTestResult.value = false
}
testingJava.value = false
if (track) {
trackEvent('JavaTest', { path, success: javaTestResult.value })
}
}
function testJavaInstallationDebounced(path: string, version: number | null, delay = 600) {
@ -43,8 +38,8 @@ export default function useJavaTest() {
testDebounceTimer = setTimeout(() => runJavaTest(path, version, false), delay)
}
async function testJavaInstallation(path: string, version: number | null, track = false) {
await runJavaTest(path, version, track)
async function testJavaInstallation(path: string, version: number | null) {
await runJavaTest(path, version)
}
return {

View File

@ -1,75 +0,0 @@
interface InstanceProperties {
loader: string
game_version: string
}
interface ProjectProperties extends InstanceProperties {
id: string
project_type: string
}
type AnalyticsEventMap = {
Launched: { version: string; dev: boolean; onboarded: boolean }
PageView: { path: string; fromPath: string; failed: unknown }
InstanceCreate: { source: string }
InstanceCreateStart: { source: string }
InstanceStart: InstanceProperties & { source: string }
InstanceStop: Partial<InstanceProperties> & { source?: string }
InstanceDuplicate: InstanceProperties
InstanceRepair: InstanceProperties
InstanceSetIcon: Record<string, never>
InstanceRemoveIcon: Record<string, never>
InstanceUpdateAll: InstanceProperties & { count: number; selected: boolean }
InstanceProjectUpdate: InstanceProperties & { id: string; name: string; project_type: string }
InstanceProjectDisable: InstanceProperties & {
id: string
name: string
project_type: string
disabled: boolean
}
InstanceProjectRemove: InstanceProperties & { id: string; name: string; project_type: string }
ProjectInstall: ProjectProperties & { version_id: string; title: string; source: string }
ProjectInstallStart: { source: string }
PackInstall: { id: string; version_id: string; title: string; source: string }
PackInstallStart: Record<string, never>
AccountLogIn: { source?: string }
AccountLogOut: Record<string, never>
JavaTest: { path: string; success: boolean }
JavaManualSelect: { version: string }
JavaAutoDetect: { path: string; version: string }
GalleryImageNext: { project_id: string; url: string }
GalleryImagePrevious: { project_id: string; url: unknown }
GalleryImageExpand: { project_id: string; url: string }
}
export type AnalyticsEvent = keyof AnalyticsEventMap
let optedIn = false
let debugEnabled = false
export const initAnalytics = () => {
optedIn = true
}
export const debugAnalytics = () => {
debugEnabled = true
}
export const optOutAnalytics = () => {
optedIn = false
}
export const optInAnalytics = () => {
optedIn = true
}
type OptionalArgs<T> = Record<string, never> extends T ? [properties?: T] : [properties: T]
export const trackEvent = <E extends AnalyticsEvent>(
eventName: E,
...args: OptionalArgs<AnalyticsEventMap[E]>
) => {
if (optedIn && debugEnabled) {
console.debug('[Axolotl telemetry disabled]', eventName, args[0])
}
}

View File

@ -652,10 +652,6 @@ export async function update_project(instanceId: string, projectPath: string): P
return await invoke('plugin:instance|instance_update_project', { instanceId, projectPath })
}
// Add a project to an instance from a version
// Returns a path to the new project file
export type DownloadReason = 'standalone' | 'dependency' | 'modpack' | 'update'
export interface ResolutionPreferences {
game_versions?: string[]
loaders?: string[]
@ -691,14 +687,10 @@ export interface ResolveContentPlan {
export async function add_project_from_version(
instanceId: string,
versionId: string,
reason: DownloadReason,
dependentOnVersionId?: string,
): Promise<string> {
return await invoke('plugin:instance|instance_add_project_from_version', {
instanceId,
versionId,
reason,
dependentOnVersionId,
})
}

View File

@ -175,9 +175,6 @@ export type AppSettings = {
home_widgets: HomeDashboardConfig | null
terracotta_public_nodes: string[]
telemetry: boolean
telemetry_consent_version: number
discord_rpc: boolean
onboarded: boolean
onboarding_version: number
onboarding_instance_tour_completed: boolean
@ -207,12 +204,6 @@ export type AppSettings = {
version: number
}
export type PrivacySettings = {
telemetry: boolean
discord_rpc: boolean
consent_version: number
}
type LegacyMirrorSettings = {
use_minecraft_mirror?: boolean
use_modrinth_mirror?: boolean
@ -301,22 +292,6 @@ export async function cancel_directory_change(): Promise<void> {
return await invoke('plugin:settings|cancel_directory_change')
}
export async function getPrivacySettings(): Promise<PrivacySettings> {
return await invoke('plugin:settings|privacy_get')
}
export async function savePrivacySettings(privacy: PrivacySettings): Promise<PrivacySettings> {
return await invoke('plugin:settings|privacy_set', { privacy })
}
export async function setTelemetryEnabled(enabled: boolean): Promise<PrivacySettings> {
return await invoke('plugin:settings|telemetry_set', { enabled })
}
export async function setDiscordRpcEnabled(enabled: boolean): Promise<PrivacySettings> {
return await invoke('plugin:settings|discord_rpc_set', { enabled })
}
export async function getProxyConfig(): Promise<ProxyConfig> {
return await invoke('plugin:settings|proxy_get')
}

View File

@ -44,10 +44,6 @@ export async function initialize_state() {
return await invoke<void>('initialize_state')
}
export async function set_discord_activity(activity: string) {
return await invoke<void>('set_discord_activity', { activity })
}
// Gets active progress bars
export async function progress_bars_list() {
return await invoke<Record<string, LoadingBar>>('plugin:utils|progress_bars_list')

View File

@ -1,7 +0,0 @@
import { invoke } from '@tauri-apps/api/core'
export function installTelemetryHandlers(): void {
window.addEventListener('online', () => {
void invoke('plugin:telemetry|notify_online').catch(() => undefined)
})
}

View File

@ -216,8 +216,6 @@ type AppSettings = {
close_behavior: 'ask' | 'close' | 'lightweight'
home_widgets: import('@/components/home/home-dashboard').HomeDashboardConfig | null
telemetry: boolean
discord_rpc: boolean
developer_mode: boolean
onboarded: boolean

View File

@ -5912,12 +5912,6 @@
"app.onboarding.mascot-alt": {
"message": "Starlight guide"
},
"app.onboarding.privacy.description": {
"message": "Manage anonymous telemetry, Discord Rich Presence, and the Minecraft log analysis service whenever you need to."
},
"app.onboarding.privacy.title": {
"message": "Your data, your call"
},
"app.onboarding.resources.description": {
"message": "Choose how content downloads and installs, from download sources to safety checks."
},
@ -5963,30 +5957,6 @@
"app.onboarding.welcome.title": {
"message": "Everything is ready"
},
"app.privacy-consent.continue": {
"message": "Save and continue"
},
"app.privacy-consent.discord-rpc": {
"message": "Discord Rich Presence"
},
"app.privacy-consent.discord-rpc-description": {
"message": "Shows your current launcher or game activity in Discord when Discord is running."
},
"app.privacy-consent.intro": {
"message": "Choose what Starlight may send or display. Nothing is sent until you confirm these choices."
},
"app.privacy-consent.privacy-policy": {
"message": "Read the privacy policy"
},
"app.privacy-consent.telemetry": {
"message": "Allow anonymous telemetry"
},
"app.privacy-consent.telemetry-description": {
"message": "Helps count opted-in installations and daily active users. Full Minecraft logs and account credentials are never uploaded."
},
"app.privacy-consent.title": {
"message": "Privacy & security"
},
"app.project.curseforge.loading": {
"message": "Loading CurseForge project…"
},
@ -6806,9 +6776,6 @@
"app.settings.about.preview-minecraft-crash-modal": {
"message": "Preview Minecraft crash window"
},
"app.settings.about.preview-privacy-consent-modal": {
"message": "Preview privacy & security modal"
},
"app.settings.about.product-title": {
"message": "About {productName}"
},
@ -7070,21 +7037,6 @@
"app.settings.java.view-installed": {
"message": "View installed Java"
},
"app.settings.privacy.data-handling": {
"message": "Telemetry uses a random installation identifier and sends only a daily activity signal. Turning telemetry off clears pending data immediately."
},
"app.settings.privacy.discord-rpc": {
"message": "Discord Rich Presence"
},
"app.settings.privacy.discord-rpc-description": {
"message": "Show your current launcher or game activity in Discord."
},
"app.settings.privacy.telemetry": {
"message": "Allow telemetry"
},
"app.settings.privacy.telemetry-description": {
"message": "Send one anonymous daily activity signal to improve usage statistics. Minecraft logs and account credentials are never uploaded."
},
"app.settings.resources.add-minecraft-directory": {
"message": "Add .minecraft directory"
},
@ -7466,9 +7418,6 @@
"app.settings.tabs.network-multiplayer": {
"message": "Network & multiplayer"
},
"app.settings.tabs.privacy-data": {
"message": "Privacy & data sharing"
},
"app.settings.tabs.storage": {
"message": "Storage"
},

View File

@ -104,24 +104,42 @@
"app.easteregg.color-mine.revealed": {
"message": "第 {row} 行,第 {column} 列,颜色 {color},周围同色 {number} 格"
},
"app.settings.developer.announcement-preview": { "message": "公告样式预览" },
"app.settings.developer.announcement-preview": {
"message": "公告样式预览"
},
"app.settings.developer.announcement-preview-description": {
"message": "使用本地示例预览真实公告组件,不请求远端公告,也不改变真实公告的已读状态。"
},
"app.settings.developer.preview-with-action": { "message": "显示可选的外部链接按钮" },
"app.settings.developer.preview-announcement-modal": { "message": "预览启动弹窗" },
"app.settings.developer.preview-announcement-popup": { "message": "预览 Popup 通知" },
"app.remote-announcements.preview-title": { "message": "公告样式预览" },
"app.settings.developer.preview-with-action": {
"message": "显示可选的外部链接按钮"
},
"app.settings.developer.preview-announcement-modal": {
"message": "预览启动弹窗"
},
"app.settings.developer.preview-announcement-popup": {
"message": "预览 Popup 通知"
},
"app.remote-announcements.preview-title": {
"message": "公告样式预览"
},
"app.remote-announcements.preview-summary": {
"message": "这是一条本地预览通知,点击“查看公告”可查看完整 Markdown 正文。"
},
"app.remote-announcements.preview-action": { "message": "访问官网" },
"app.remote-announcements.preview-action": {
"message": "访问官网"
},
"app.remote-announcements.preview-content": {
"message": "## 公告预览\n\n这是**示例正文**,不是已发布公告。\n\n- 支持标题、列表和链接\n- 默认自带关闭按钮\n\n> 预览不会影响真实公告的已读状态。\n\n| 类型 | 展示方式 |\n| --- | --- |\n| 启动弹窗 | 完整 Markdown 正文 |\n| Popup 通知 | 先显示摘要,再查看全文 |\n\n[访问官网](https://axlmc.org)"
},
"app.remote-announcements.view": { "message": "查看公告" },
"app.remote-announcements.unread": { "message": "未读" },
"app.remote-announcements.read-all": { "message": "将所有公告标为已读" },
"app.remote-announcements.view": {
"message": "查看公告"
},
"app.remote-announcements.unread": {
"message": "未读"
},
"app.remote-announcements.read-all": {
"message": "将所有公告标为已读"
},
"app.account.signed-in-as": {
"message": "登录身份:"
},
@ -5984,12 +6002,6 @@
"app.onboarding.mascot-alt": {
"message": "引导吉祥物"
},
"app.onboarding.privacy.description": {
"message": "匿名遥测、Discord 活动状态与 Minecraft 日志分析服务都可以在这里调整。"
},
"app.onboarding.privacy.title": {
"message": "你的数据,由你决定"
},
"app.onboarding.resources.description": {
"message": "选择内容的下载与安装方式,包括下载源和安全确认。"
},
@ -6035,30 +6047,6 @@
"app.onboarding.welcome.title": {
"message": "一切就绪"
},
"app.privacy-consent.continue": {
"message": "保存并继续"
},
"app.privacy-consent.discord-rpc": {
"message": "Discord 活动状态"
},
"app.privacy-consent.discord-rpc-description": {
"message": "Discord 运行时显示你当前使用启动器或游玩的状态。"
},
"app.privacy-consent.intro": {
"message": "选择 Starlight 可以发送或展示的内容。确认前不会发送任何遥测。"
},
"app.privacy-consent.privacy-policy": {
"message": "阅读隐私政策"
},
"app.privacy-consent.telemetry": {
"message": "允许匿名遥测"
},
"app.privacy-consent.telemetry-description": {
"message": "用于统计选择加入的安装量和每日活跃用户。绝不上传完整 Minecraft 日志或账户凭据。"
},
"app.privacy-consent.title": {
"message": "隐私与安全"
},
"app.project.curseforge.loading": {
"message": "正在加载 CurseForge 项目…"
},
@ -6851,9 +6839,6 @@
"app.settings.about.preview-minecraft-crash-modal": {
"message": "预览 Minecraft 崩溃窗口"
},
"app.settings.about.preview-privacy-consent-modal": {
"message": "预览隐私与安全弹窗"
},
"app.settings.about.product-title": {
"message": "关于 {productName}"
},
@ -7100,21 +7085,6 @@
"app.settings.java.view-installed": {
"message": "查看已安装 Java"
},
"app.settings.privacy.data-handling": {
"message": "遥测使用随机安装标识,仅发送每日活跃信号。关闭遥测会立即清空待发送数据。"
},
"app.settings.privacy.discord-rpc": {
"message": "Discord 活动状态"
},
"app.settings.privacy.discord-rpc-description": {
"message": "在 Discord 中显示你当前使用启动器或游玩的状态。"
},
"app.settings.privacy.telemetry": {
"message": "允许遥测"
},
"app.settings.privacy.telemetry-description": {
"message": "发送匿名每日活跃信号以优化使用统计。绝不上传 Minecraft 日志或账户凭据。"
},
"app.settings.resources.app-cache": {
"message": "应用缓存"
},
@ -7505,9 +7475,6 @@
"app.settings.tabs.network-multiplayer": {
"message": "网络与多人游戏"
},
"app.settings.tabs.privacy-data": {
"message": "隐私与数据共享"
},
"app.settings.tabs.resource-management": {
"message": "资源管理"
},
@ -10414,6 +10381,5 @@
},
"app.settings.about.easteregg.game-title": {
"message": "彩蛋小游戏"
}
}

View File

@ -5723,12 +5723,6 @@
"app.onboarding.mascot-alt": {
"message": "引導吉祥物"
},
"app.onboarding.privacy.description": {
"message": "匿名遙測、Discord 活動狀態與 Minecraft 日誌分析服務都可以在這裡調整。"
},
"app.onboarding.privacy.title": {
"message": "你的資料,由你決定"
},
"app.onboarding.resources.description": {
"message": "調節下載、儲存和啟動器資源。給風扇一點尊嚴。"
},
@ -5774,30 +5768,6 @@
"app.onboarding.welcome.title": {
"message": "一切就緒"
},
"app.privacy-consent.continue": {
"message": "儲存並繼續"
},
"app.privacy-consent.discord-rpc": {
"message": "Discord 活動狀態"
},
"app.privacy-consent.discord-rpc-description": {
"message": "Discord 執行時顯示你當前使用啟動器或遊玩的狀態。"
},
"app.privacy-consent.intro": {
"message": "選擇 Starlight 可以傳送或展示的內容。確認前不會傳送任何遙測。"
},
"app.privacy-consent.privacy-policy": {
"message": "閱讀隱私政策"
},
"app.privacy-consent.telemetry": {
"message": "允許匿名遙測"
},
"app.privacy-consent.telemetry-description": {
"message": "透過脫敏且限長的報告統計選擇加入的安裝量並排查啟動器錯誤。不會上傳完整 Minecraft 日誌或賬戶憑據。"
},
"app.privacy-consent.title": {
"message": "隱私與安全"
},
"app.project.curseforge.loading": {
"message": "正在載入 CurseForge 專案..."
},
@ -6566,9 +6536,6 @@
"app.settings.about.preview-minecraft-crash-modal": {
"message": "預覽 Minecraft 崩潰窗口"
},
"app.settings.about.preview-privacy-consent-modal": {
"message": "預覽隱私與安全彈窗"
},
"app.settings.about.product-title": {
"message": "關於 {productName}"
},
@ -6815,21 +6782,6 @@
"app.settings.java.view-installed": {
"message": "查看已安裝 Java"
},
"app.settings.privacy.data-handling": {
"message": "遙測使用隨機安裝標識。錯誤上下文離開裝置前會經過脫敏和長度限制。關閉遙測會立即清空待傳送報告。"
},
"app.settings.privacy.discord-rpc": {
"message": "Discord 活動狀態"
},
"app.settings.privacy.discord-rpc-description": {
"message": "在 Discord 中顯示你當前使用啟動器或遊玩的狀態。"
},
"app.settings.privacy.telemetry": {
"message": "允許遙測"
},
"app.settings.privacy.telemetry-description": {
"message": "傳送匿名每日活躍訊號和脫敏的啟動器錯誤報告。絕不上傳 Minecraft 日誌或賬戶憑據。"
},
"app.settings.resources.app-cache": {
"message": "應用程式快取"
},
@ -7220,9 +7172,6 @@
"app.settings.tabs.network-multiplayer": {
"message": "網路與多人遊戲"
},
"app.settings.tabs.privacy-data": {
"message": "隱私與資料共享"
},
"app.settings.tabs.resource-management": {
"message": "資源管理"
},

View File

@ -9,7 +9,6 @@ import { createApp } from 'vue'
import App from '@/App.vue'
import { overlayScrollbarsDirective } from '@/directives/overlayScrollbars'
import { installTelemetryHandlers } from '@/helpers/telemetry'
import i18nPlugin from '@/plugins/i18n'
import i18nDebugPlugin from '@/plugins/i18n-debug'
import router from '@/routes'
@ -18,8 +17,6 @@ const pinia = createPinia()
const app = createApp(App)
installTelemetryHandlers()
app.use(VueQueryPlugin)
app.use(router)
app.use(pinia)

View File

@ -137,11 +137,11 @@
</template>
</div>
<div
v-if="downloadTelemetry(job).length"
v-if="downloadDetails(job).length"
class="mt-1 flex flex-wrap items-center gap-2 text-sm text-secondary"
>
<template
v-for="(metric, index) in downloadTelemetry(job)"
v-for="(metric, index) in downloadDetails(job)"
:key="`${index}-${metric}`"
>
<BulletDivider v-if="index > 0" />
@ -1058,7 +1058,7 @@ function totalRequiredFiles(job: InstallJobSnapshot) {
return Math.max(job.summary.files_total ?? 0, job.items.length, completed + missing)
}
function downloadTelemetry(job: InstallJobSnapshot) {
function downloadDetails(job: InstallJobSnapshot) {
const summary = job.summary
const metrics: string[] = []
if (summary.source && !isRecoveryValidation(job)) {

View File

@ -419,7 +419,6 @@ import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { useNetworkStatus } from '@/composables/useNetworkStatus'
import { postUpgradeNoticeQueryKey, usePostUpgradeNotice } from '@/composables/usePostUpgradeNotice'
import { useSymlinkWarningDismiss } from '@/composables/useSymlinkWarningDismiss'
import { trackEvent } from '@/helpers/analytics'
import { get_project_v3 } from '@/helpers/cache.js'
import { instance_listener, process_listener } from '@/helpers/events'
import {
@ -776,11 +775,6 @@ const startInstance = async (context: string) => {
loading.value = false
}
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: context,
})
}
const stopInstance = async (context: string) => {
@ -790,11 +784,6 @@ const stopInstance = async (context: string) => {
playing.value = false
if (!instance.value) return
trackEvent('InstanceStop', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: context,
})
}
const handlePlayServer = async () => {

View File

@ -264,7 +264,6 @@ import ContentToggleDependenciesModal from '@/components/ui/modal/ContentToggleD
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
import { postUpgradeNoticeQueryKey, usePostUpgradeNotice } from '@/composables/usePostUpgradeNotice'
import { useWorldDatapacks } from '@/composables/useWorldDatapacks'
import { trackEvent } from '@/helpers/analytics'
import { get_project_versions, get_version, get_version_many } from '@/helpers/cache.js'
import { applyContentItemUpdates, matchesContentItem } from '@/helpers/content-item-state'
import { lookupContentWikiIds, translateContentItemTitles } from '@/helpers/content-search'
@ -1580,14 +1579,6 @@ async function toggleDisableMod(
const enabled = desiredEnabled ?? !mod.enabled
try {
await toggleWorldDatapackItem(mod, enabled)
trackEvent('InstanceProjectDisable', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
disabled: !enabled,
})
} catch (err) {
handleError(err as Error)
}
@ -1646,14 +1637,6 @@ async function applyToggleDisableMod(mod: ContentItem, enabled: boolean) {
enabled: actualEnabled,
})
trackEvent('InstanceProjectDisable', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
disabled: !actualEnabled,
})
} catch (err) {
applyContentItemToggleState(mod, operation.originalFileName, originalFilePath, {
file_path: originalFilePath,
@ -1733,14 +1716,6 @@ async function applyToggleDisableBatch(items: ContentItem[], enabled: boolean) {
)
for (const operation of operations) {
trackEvent('InstanceProjectDisable', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: operation.item.project?.id,
name: operation.item.project?.title ?? operation.originalFileName,
project_type: operation.item.project_type,
disabled: !enabled,
})
}
} catch (error) {
for (const operation of operations) {
@ -1831,13 +1806,6 @@ async function removeMod(mod: ContentItem) {
projects.value = projects.value.filter((x) => removedPath !== x.file_path)
}
trackEvent('InstanceProjectRemove', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
})
} catch (err) {
handleError(err as Error)
} finally {
@ -1999,13 +1967,6 @@ async function updateProject(mod: ContentItem) {
try {
await update_content_entry(props.instance.id, contentId)
trackEvent('InstanceProjectUpdate', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
})
} catch (err) {
handleError(err as Error)
throw err
@ -2024,13 +1985,6 @@ async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions
try {
await switch_content_entry_version(props.instance.id, contentId, version.id)
trackEvent('InstanceProjectUpdate', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
})
} catch (err) {
handleError(err as Error)
} finally {
@ -2046,13 +2000,6 @@ async function handleRollbackContent(mod: ContentItem) {
try {
await rollback_project(props.instance.id, mod.file_path)
trackEvent('InstanceProjectRollback', {
loader: props.instance.loader,
game_version: props.instance.game_version,
id: mod.project?.id,
name: mod.project?.title ?? mod.file_name,
project_type: mod.project_type,
})
} catch (err) {
handleError(err as Error)
} finally {

View File

@ -204,7 +204,6 @@ import ConfirmRemoveWorldModal from '@/components/ui/world/modal/ConfirmRemoveWo
import EditServerModal from '@/components/ui/world/modal/EditServerModal.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { useMinecraftLaunchError } from '@/composables/useMinecraftLaunchError'
import { trackEvent } from '@/helpers/analytics'
import { get_project, get_project_v3 } from '@/helpers/cache.js'
import { instance_listener } from '@/helpers/events'
import { get_game_versions } from '@/helpers/tags'
@ -592,20 +591,10 @@ async function joinWorld(world: World) {
const managedProjectId = instance.value.link?.project_id
if (managedProjectId && isManagedServerWorld(world)) {
await playServerProject(managedProjectId).catch(handleJoinError)
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: 'WorldsPage',
})
startingInstance.value = false
return
}
await start_join_server(instance.value.id, world.address).catch(handleJoinError)
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: 'WorldsPage',
})
} else if (world.type === 'singleplayer') {
await start_join_singleplayer_world(instance.value.id, world.path).catch(handleJoinError)
}

View File

@ -18,7 +18,6 @@ import {
} from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { trackEvent } from '@/helpers/analytics'
import {
type ProjectGalleryCaptionField,
projectGalleryTranslationSegmentId,
@ -130,10 +129,6 @@ function viewImage(entry: GalleryEntry) {
zoomedIn.value = false
viewerModal.value?.show()
trackEvent('GalleryImageExpand', {
project_id: props.project.id,
url: entry.image.url,
})
}
function changeImage(offset: number) {
@ -146,10 +141,6 @@ function changeImage(offset: number) {
selectedGalleryItem.value = filteredGallery.value[nextIndex]
zoomedIn.value = false
trackEvent(offset > 0 ? 'GalleryImageNext' : 'GalleryImagePrevious', {
project_id: props.project.id,
url: selectedGalleryItem.value.image.url,
})
}
function handleViewerHide() {

View File

@ -210,25 +210,6 @@
{{ installButtonLabel }}
</button>
</ButtonStyled>
<!-- 开服功能暂有问题,隐藏该按钮
<Transition name="start-server">
<ButtonStyled
v-if="serverCapableModpack"
key="modpack-start-server"
size="large"
type="outlined"
>
<button
v-tooltip="formatMessage(messages.startServer)"
type="button"
@click="openModpackServerFlow"
>
<ServerIcon />
{{ formatMessage(messages.startServer) }}
</button>
</ButtonStyled>
</Transition>
-->
<ButtonStyled size="large" circular type="transparent">
<OverflowMenu
:tooltip="`More options`"
@ -339,7 +320,6 @@
:translations="translations"
:translation-mode="translationMode"
:translation-style="translationStyle"
:start-server="(version) => openModpackServerFlow(version)"
/>
</template>
<template v-else> Project data couldn't not be loaded. </template>
@ -408,7 +388,6 @@ import {
PlusIcon,
ReportIcon,
SearchIcon,
ServerIcon,
SpinnerIcon,
StopCircleIcon,
} from '@modrinth/assets'
@ -424,7 +403,6 @@ import {
getLatestMatchingInstallVersion,
getTargetInstallPreferences,
injectNotificationManager,
injectPopupNotificationManager,
NavTabs,
OverflowMenu,
ProjectBackgroundGradient,
@ -501,7 +479,6 @@ import UpgradeProjectReturnBar from './UpgradeProjectReturnBar.vue'
dayjs.extend(relativeTime)
const { addNotification, handleError } = injectNotificationManager()
const popupNotificationManager = injectPopupNotificationManager()
const { install: installVersion } = injectContentInstall()
const contentSelection = injectContentSelection()
const route = useRoute()
@ -603,22 +580,6 @@ const messages = defineMessages({
id: 'project.actions.view-modpacks',
defaultMessage: 'View modpacks',
},
startServer: {
id: 'app.project.modpack-server.start',
defaultMessage: 'Start server',
},
serverCreated: {
id: 'app.project.modpack-server.created-title',
defaultMessage: 'Server created',
},
serverCreatedDescription: {
id: 'app.project.modpack-server.created-description',
defaultMessage: '{name} is ready. Configure and start it in Multiplayer.',
},
openServer: {
id: 'app.project.modpack-server.open',
defaultMessage: 'Open server',
},
})
const { installingServerProjects, playServerProject, showAddServerToInstanceModal } =
@ -642,15 +603,6 @@ const favoriteSaved = computed(() =>
data.value ? contentFavorites.isFavorite('modrinth', data.value.id) : false,
)
const serverCapableModpack = computed(
() => data.value?.project_type === 'modpack' && data.value.server_side !== 'unsupported',
)
async function openModpackServerFlow() {
// 服务器创建弹窗CreateModpackServerModal已在上游移除此流程暂不可用。
// 如需恢复:重新实现该组件,并在此处调用其 show() 方法。
}
async function toggleFavorite() {
if (!data.value || !favoriteSupported.value || favoritePending.value) return
await contentFavorites

View File

@ -30,16 +30,6 @@
<CheckIcon v-else />
</button>
</ButtonStyled>
<!-- 开服功能暂有问题,隐藏该按钮
<ButtonStyled v-if="serverCapable && startServer" circular type="transparent">
<button
v-tooltip="formatMessage(messages.startServer)"
@click.stop="() => startServer(version)"
>
<ServerIcon />
</button>
</ButtonStyled>
-->
<ButtonStyled circular type="transparent">
<OverflowMenu
v-if="false"
@ -87,7 +77,6 @@ import {
DownloadIcon,
ExternalIcon,
MoreVerticalIcon,
ServerIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
@ -98,7 +87,7 @@ import {
ProjectPageVersions,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { SwapIcon } from '@/assets/icons/index.js'
@ -117,10 +106,6 @@ const messages = defineMessages({
id: 'app.project.versions.add-to-another-instance',
defaultMessage: 'Add to another instance',
},
startServer: {
id: 'app.project.versions.start-server',
defaultMessage: 'Create server',
},
})
const props = defineProps({
@ -152,16 +137,8 @@ const props = defineProps({
type: String,
default: null,
},
startServer: {
type: Function,
default: null,
},
})
const serverCapable = computed(
() => props.project?.project_type === 'modpack' && props.project?.server_side !== 'unsupported',
)
const { handleError } = injectNotificationManager()
const route = useRoute()

View File

@ -15,7 +15,6 @@ import type { Router } from 'vue-router'
import type ContentInstallPreviewModal from '@/components/ui/ContentInstallPreviewModal.vue'
import type { ContentInstallPreviewData } from '@/components/ui/ContentInstallPreviewModal.vue'
import type { ModpackInstallModalData } from '@/components/ui/modal/ModpackInstallModal.vue'
import { trackEvent } from '@/helpers/analytics'
import {
get_organization,
get_project,
@ -868,12 +867,6 @@ export function createContentInstall(opts: {
})
removeInstallingItems(createdInstanceId, [project.id])
markInstanceContentChanged(createdInstanceId)
trackEvent('PackInstall', {
id: project.id,
version_id: version.id,
title: project.title,
source,
})
callback(version.id, [project.id])
} catch (err) {
debugState('createAndInstallCurseForgeModpack ERR', { err: String(err), createdInstanceId })
@ -997,7 +990,6 @@ export function createContentInstall(opts: {
if (sessionId !== currentSessionId) return
contentInstallModalOpen = true
modalRef?.show()
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
}
get_game_versions()
@ -1068,7 +1060,6 @@ export function createContentInstall(opts: {
await nextTick()
contentInstallModalOpen = true
modalRef?.show()
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
return sessionId
}
@ -1688,15 +1679,6 @@ export function createContentInstall(opts: {
if (storeInstance) storeInstance.installing = true
try {
await queueCurrentCurseForgeWorld(selectedInstance)
trackEvent('ProjectInstall', {
loader: selectedInstance.loader,
game_version: selectedInstance.game_version,
id: currentProject.id,
version_id: currentWorldFileId ?? '',
project_type: 'world',
title: currentProject.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(currentWorldFileId ?? undefined, [currentProject.id])
hideContentInstallModal()
} catch (err) {
@ -1758,15 +1740,6 @@ export function createContentInstall(opts: {
{ ...request, excluded_project_ids: excludedProjectIds },
{ title: currentProject.title, iconUrl: currentProject.icon_url },
)
trackEvent('ProjectInstall', {
loader: selectedInstance.loader,
game_version: selectedInstance.game_version,
id: currentProject.id,
version_id: version.id,
project_type: currentProject.project_type,
title: currentProject.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(version.id, [currentProject.id])
hideContentInstallModal()
} catch (err) {
@ -1786,15 +1759,6 @@ export function createContentInstall(opts: {
settleCurrentCallback()
return
}
trackEvent('ProjectInstall', {
loader: selectedInstance.loader,
game_version: selectedInstance.game_version,
id: currentProject.id,
version_id: version.id,
project_type: currentProject.project_type,
title: currentProject.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(version.id, [currentProject.id])
hideContentInstallModal()
} catch (err) {
@ -1830,15 +1794,6 @@ export function createContentInstall(opts: {
storeInstance.installed = primaryInstalled
storeInstance.installing = false
}
trackEvent('ProjectInstall', {
loader: selectedInstance.loader,
game_version: selectedInstance.game_version,
id: currentProject!.id,
version_id: version.id,
project_type: currentProject!.project_type,
title: currentProject!.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(primaryInstalled ? version.id : undefined, installedProjectIds)
} catch (err) {
if (storeInstance) storeInstance.installing = false
@ -1878,7 +1833,6 @@ export function createContentInstall(opts: {
await nextTick()
incompatibilityWarningModalRef?.show(version.id)
trackEvent('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
}
async function handleIncompatibilityWarningInstall(version: Labrinth.Versions.v2.Version) {
@ -1931,15 +1885,6 @@ export function createContentInstall(opts: {
incompatibilityWarningModalRef?.hide()
removeInstallingItems(instance.id, [project.id])
trackEvent('ProjectInstall', {
loader: instance.loader ?? '',
game_version: instance.game_version ?? '',
id: project.id,
version_id: version.id,
project_type: project.project_type,
title: project.title,
source: 'ProjectIncompatibilityWarningModal',
})
}
function handleIncompatibilityWarningCancel() {
@ -2056,16 +2001,6 @@ export function createContentInstall(opts: {
},
{ title: currentProject!.title, iconUrl: currentProject!.icon_url },
)
trackEvent('InstanceCreate', { source: 'ProjectInstallModal' })
trackEvent('ProjectInstall', {
loader: data.loader,
game_version: data.gameVersion,
id: currentProject!.id,
version_id: version.id,
project_type: currentProject!.project_type,
title: currentProject!.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(version.id, [currentProject!.id])
hideContentInstallModal()
return
@ -2085,16 +2020,6 @@ export function createContentInstall(opts: {
excludedCurseForgeProjectIds,
)
if (!job) return
trackEvent('InstanceCreate', { source: 'ProjectInstallModal' })
trackEvent('ProjectInstall', {
loader: data.loader,
game_version: data.gameVersion,
id: currentProject!.id,
version_id: version.id,
project_type: currentProject!.project_type,
title: currentProject!.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(version.id, [currentProject!.id])
hideContentInstallModal()
return
@ -2131,18 +2056,6 @@ export function createContentInstall(opts: {
: `/instance/${encodeURIComponent(id)}`,
)
trackEvent('InstanceCreate', {
source: 'ProjectInstallModal',
})
trackEvent('ProjectInstall', {
loader: data.loader,
game_version: data.gameVersion,
id: currentProject!.id,
version_id: version.id,
project_type: currentProject!.project_type,
title: currentProject!.title,
source: 'ProjectInstallModal',
})
settleCurrentCallback(version.id, installedProjectIds)
modalRef?.hide()
@ -2282,15 +2195,6 @@ export function createContentInstall(opts: {
{ ...installRequest, excluded_project_ids: excludedProjectIds },
{ title: project.title, iconUrl: project.icon_url },
)
trackEvent('ProjectInstall', {
loader: instance.loader,
game_version: instance.game_version,
id: project.id,
project_type: project.project_type,
version_id: version.id,
title: project.title,
source,
})
callback(version.id, [project.id])
} else {
await showIncompatibilityWarning(instance, project, projectVersions, version, callback)
@ -2402,15 +2306,6 @@ export function createContentInstall(opts: {
if (isVersionCompatible(version, project, instance)) {
const job = await queueCurrentCurseForgeVersion(instance, project, version)
if (!job) return
trackEvent('ProjectInstall', {
loader: instance.loader,
game_version: instance.game_version,
id: project.id,
project_type: project.project_type,
version_id: version.id,
title: project.title,
source,
})
callback(version.id, [project.id])
} else {
await showIncompatibilityWarning(instance, project, versions, version, callback)
@ -2457,7 +2352,6 @@ export function createContentInstall(opts: {
if (sessionId !== currentSessionId) return
contentInstallModalOpen = true
modalRef?.show()
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
try {
const candidates = (await list()).filter(
@ -2537,15 +2431,6 @@ export function createContentInstall(opts: {
const instance = await get(instanceId)
if (!instance) throw new Error(formatMessage(curseForgeWorldUnknownInstanceMessage))
await queueCurrentCurseForgeWorld(instance)
trackEvent('ProjectInstall', {
loader: instance.loader,
game_version: instance.game_version,
id: currentProject!.id,
version_id: file.id.toString(),
project_type: 'world',
title: currentProject!.title,
source,
})
callback(file.id.toString(), [currentProject!.id])
}
@ -2700,12 +2585,6 @@ export function createContentInstall(opts: {
if (instanceId) {
createInstanceCallback(instanceId)
}
trackEvent('PackInstall', {
id: project.id,
version_id: versionId,
title: project.title,
source,
})
callback(versionId)
},
handleModpackInstallCancel() {

View File

@ -4,7 +4,6 @@ import { createContext } from '@modrinth/ui'
import { type Ref, ref } from 'vue'
import type { Router } from 'vue-router'
import { trackEvent } from '@/helpers/analytics'
import { get_project, get_project_v3, get_version } from '@/helpers/cache.js'
import {
install_create_instance,
@ -163,11 +162,6 @@ export function createServerInstall(opts: {
action: async () => {
try {
await joinServer(project.id, serverAddress)
trackEvent('InstanceStart', {
loader: project.loader,
game_version: project.game_version,
source: 'ServerProject',
})
} catch (err) {
handleSevereError(err, { instanceId: project.id })
}
@ -198,11 +192,6 @@ export function createServerInstall(opts: {
action: async () => {
try {
if (serverAddress) await start_join_server(instance.id, serverAddress)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'ServerProject',
})
} catch (err) {
handleSevereError(err, { instanceId: instance.id })
}
@ -348,11 +337,6 @@ export function createServerInstall(opts: {
// Join server
try {
await joinServer(instance.id, serverAddress)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'ServerProject',
})
} catch (err) {
handleSevereError(err, { instanceId: instance.id })
}

View File

@ -13,7 +13,6 @@ import { useRouter } from 'vue-router'
import type UnknownPackWarningModal from '@/components/ui/install_flow/UnknownPackWarningModal.vue'
import type ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_versions, get_search_results } from '@/helpers/cache.js'
import { getCurseForgeFiles, hasCompatibleCurseForgeFile } from '@/helpers/curseforge'
import { install_job_listener } from '@/helpers/events.js'
@ -143,7 +142,6 @@ export function setupCreationModal(
},
{ name },
).catch(handleError)
trackEvent('InstanceCreate', { source: 'CreationModalModpack' })
}
async function handleCreate(config: CreationFlowContextValue) {
@ -222,7 +220,6 @@ export function setupCreationModal(
handleError(error)
}
}
trackEvent('InstanceCreate', { source: 'CreationModalImport' })
return
}
@ -238,7 +235,6 @@ export function setupCreationModal(
const splitPath = config.modpackFilePath.value.split(/[\\/]/)
const fileName = splitPath ? splitPath[splitPath.length - 1] : config.modpackFilePath.value
await installModpackFromPath(config.modpackFilePath.value, fileName)
trackEvent('InstanceCreate', { source: 'CreationModalModpackFile' })
return
}
@ -281,9 +277,6 @@ export function setupCreationModal(
gameDirOverride,
}).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModal',
})
} catch (err) {
handleError(err as Error)
}