mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546b4c3ecb | ||
|
|
38081e6692 |
@@ -164,7 +164,7 @@ jobs:
|
||||
with:
|
||||
context: ./apps/labrinth/docker-stage
|
||||
file: ./apps/labrinth/Dockerfile
|
||||
push: true
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||
annotations: ${{ steps.docker-meta.outputs.annotations }}
|
||||
|
||||
Generated
+24
-4
@@ -3059,6 +3059,29 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elasticsearch"
|
||||
version = "9.1.0-alpha.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bb303aa6e1d28c0c86b6fbfe484fd0fd3f512629aeed1ac4f6b85f81d9834a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"dyn-clone",
|
||||
"flate2",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"reqwest 0.12.24",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"tokio",
|
||||
"url",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.8"
|
||||
@@ -5252,6 +5275,7 @@ dependencies = [
|
||||
"dotenv-build",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"elasticsearch",
|
||||
"eyre",
|
||||
"futures",
|
||||
"futures-util",
|
||||
@@ -10565,8 +10589,6 @@ version = "1.0.0-local"
|
||||
dependencies = [
|
||||
"async_zip",
|
||||
"chrono",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"daedalus",
|
||||
"dashmap",
|
||||
"either",
|
||||
@@ -10574,7 +10596,6 @@ dependencies = [
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"native-dialog",
|
||||
"objc2-app-kit",
|
||||
"paste",
|
||||
"path-util",
|
||||
"serde",
|
||||
@@ -10600,7 +10621,6 @@ dependencies = [
|
||||
"urlencoding",
|
||||
"uuid 1.18.1",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
|
||||
+1
-3
@@ -64,8 +64,6 @@ clickhouse = "0.14.0"
|
||||
color-eyre = "0.6.5"
|
||||
color-thief = "0.2.2"
|
||||
const_format = "0.2.34"
|
||||
core-foundation = "0.10.1"
|
||||
core-graphics = "0.24.0"
|
||||
daedalus = { path = "packages/daedalus" }
|
||||
darling = { version = "0.23" }
|
||||
dashmap = "6.1.0"
|
||||
@@ -79,6 +77,7 @@ dotenv-build = "0.1.1"
|
||||
dotenvy = "0.15.7"
|
||||
dunce = "1.0.5"
|
||||
either = "1.15.0"
|
||||
elasticsearch = "9.1.0-alpha.1"
|
||||
encoding_rs = "0.8.35"
|
||||
enumset = "1.1.10"
|
||||
eyre = "0.6.12"
|
||||
@@ -125,7 +124,6 @@ murmur2 = "0.1.0"
|
||||
native-dialog = "0.9.2"
|
||||
notify = { version = "8.2.0", default-features = false }
|
||||
notify-debouncer-mini = { version = "0.7.0", default-features = false }
|
||||
objc2-app-kit = { version = "0.3.2", default-features = false }
|
||||
p256 = "0.13.2"
|
||||
parking_lot = "0.12.5"
|
||||
paste = "1.0.15"
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
"test": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intercom/messenger-js-sdk": "^0.0.14",
|
||||
"@modrinth/api-client": "workspace:^",
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@sentry/vue": "^8.27.0",
|
||||
"@sfirew/minecraft-motd-parser": "^1.1.6",
|
||||
"@tanstack/vue-query": "5.90.7",
|
||||
"@tanstack/vue-query": "^5.90.7",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { Intercom, shutdown as shutdownIntercom } from '@intercom/messenger-js-sdk'
|
||||
import {
|
||||
AuthFeature,
|
||||
ModrinthApiError,
|
||||
NodeAuthFeature,
|
||||
nodeAuthState,
|
||||
PanelVersionFeature,
|
||||
@@ -52,7 +52,6 @@ import {
|
||||
providePopupNotificationManager,
|
||||
useDebugLogger,
|
||||
useFormatBytes,
|
||||
useHostingIntercom,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
@@ -88,6 +87,7 @@ import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import WindowControls from '@/components/ui/WindowControls.vue'
|
||||
import { useIntercomPositioning } from '@/composables/intercom-positioning'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import { config } from '@/config'
|
||||
import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
@@ -98,7 +98,6 @@ import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
|
||||
import { create_profile_and_install_from_file } from '@/helpers/pack'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
import {
|
||||
@@ -108,7 +107,6 @@ import {
|
||||
getUpdateSize,
|
||||
isDev,
|
||||
isNetworkMetered,
|
||||
setRestartAfterPendingUpdate,
|
||||
} from '@/helpers/utils.js'
|
||||
import i18n from '@/i18n.config'
|
||||
import { createContentInstall, provideContentInstall } from '@/providers/content-install'
|
||||
@@ -131,36 +129,15 @@ import { AppPopupNotificationManager } from './providers/app-popup-notifications
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const APP_LEFT_NAV_WIDTH = '4rem'
|
||||
const APP_SIDEBAR_WIDTH = 300
|
||||
const INTERCOM_BUBBLE_DEFAULT_PADDING = 20
|
||||
const credentials = ref()
|
||||
const sidebarToggled = ref(true)
|
||||
const unsubscribeSidebarToggle = themeStore.$subscribe(() => {
|
||||
sidebarToggled.value = !themeStore.toggleSidebar
|
||||
})
|
||||
const forceSidebar = computed(
|
||||
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
|
||||
)
|
||||
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
|
||||
const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
|
||||
const hostingIntercomIdentityKey = computed(() => {
|
||||
const rawServerId = route.params.id
|
||||
const serverId = Array.isArray(rawServerId) ? rawServerId[0] : rawServerId
|
||||
const userId = credentials.value?.user_id ?? credentials.value?.user?.id ?? 'anonymous'
|
||||
return `${userId}:${serverId ?? 'hosting'}`
|
||||
})
|
||||
const hostingIntercom = useHostingIntercom({
|
||||
enabled: computed(() => hostingRouteActive.value && !!credentials.value?.session),
|
||||
appId: 'ykeritl9',
|
||||
fetchToken: fetchIntercomToken,
|
||||
identityKey: hostingIntercomIdentityKey,
|
||||
horizontalPadding: computed(() =>
|
||||
sidebarVisible.value
|
||||
? APP_SIDEBAR_WIDTH + INTERCOM_BUBBLE_DEFAULT_PADDING
|
||||
: INTERCOM_BUBBLE_DEFAULT_PADDING,
|
||||
),
|
||||
})
|
||||
const intercomBubblePositioning = useIntercomPositioning({ route, themeStore })
|
||||
const {
|
||||
sidebarToggled,
|
||||
forceSidebar,
|
||||
sidebarVisible,
|
||||
intercomBubblePosition,
|
||||
updateIntercomBubbleStyles,
|
||||
clearIntercomBubbleStyles,
|
||||
} = intercomBubblePositioning
|
||||
|
||||
const notificationManager = new AppNotificationManager()
|
||||
provideNotificationManager(notificationManager)
|
||||
@@ -195,11 +172,7 @@ provideModrinthClient(tauriApiClient)
|
||||
providePageContext({
|
||||
hierarchicalSidebarAvailable: ref(true),
|
||||
showAds: ref(false),
|
||||
floatingActionBarOffsets: {
|
||||
left: ref(APP_LEFT_NAV_WIDTH),
|
||||
right: computed(() => (sidebarVisible.value ? `${APP_SIDEBAR_WIDTH}px` : '0px')),
|
||||
},
|
||||
intercomBubble: hostingIntercom.intercomBubble,
|
||||
...intercomBubblePositioning.pageContext,
|
||||
featureFlags: {
|
||||
serverRamAsBytesAlwaysOn: computed(() =>
|
||||
themeStore.getFeatureFlag('server_ram_as_bytes_always_on'),
|
||||
@@ -283,7 +256,8 @@ onMounted(async () => {
|
||||
onUnmounted(async () => {
|
||||
document.querySelector('body').removeEventListener('click', handleClick)
|
||||
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
|
||||
unsubscribeSidebarToggle()
|
||||
shutdownHostingIntercom()
|
||||
clearIntercomBubbleStyles()
|
||||
|
||||
await unlistenUpdateDownload?.()
|
||||
})
|
||||
@@ -616,6 +590,8 @@ const incompatibilityWarningModal = ref()
|
||||
const installToPlayModal = ref()
|
||||
const updateToPlayModal = ref()
|
||||
|
||||
const credentials = ref()
|
||||
|
||||
const modrinthLoginFlowWaitModal = ref()
|
||||
|
||||
setupAuthProvider(credentials, async (_redirectPath) => {
|
||||
@@ -685,6 +661,10 @@ const hasPlus = computed(
|
||||
const showAd = computed(
|
||||
() => sidebarVisible.value && !hasPlus.value && credentials.value !== undefined,
|
||||
)
|
||||
const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
|
||||
|
||||
let intercomBooting = false
|
||||
let intercomBooted = false
|
||||
|
||||
async function fetchIntercomToken() {
|
||||
const creds = await getCreds()
|
||||
@@ -693,10 +673,8 @@ async function fetchIntercomToken() {
|
||||
}
|
||||
|
||||
const params = new URLSearchParams()
|
||||
const rawServerId = route.params.id
|
||||
const serverId = Array.isArray(rawServerId) ? rawServerId[0] : rawServerId
|
||||
if (route.path.startsWith('/hosting/manage/') && typeof serverId === 'string') {
|
||||
params.set('server_id', serverId)
|
||||
if (route.path.startsWith('/hosting/manage/') && typeof route.params.id === 'string') {
|
||||
params.set('server_id', route.params.id)
|
||||
}
|
||||
const query = params.size > 0 ? `?${params.toString()}` : ''
|
||||
|
||||
@@ -712,6 +690,69 @@ async function fetchIntercomToken() {
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async function bootIntercom() {
|
||||
if (
|
||||
intercomBooting ||
|
||||
intercomBooted ||
|
||||
!hostingRouteActive.value ||
|
||||
!credentials.value?.session
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
intercomBooting = true
|
||||
console.debug('[APP][INTERCOM] initializing secure support chat')
|
||||
try {
|
||||
const { token } = await fetchIntercomToken()
|
||||
Intercom({
|
||||
app_id: 'ykeritl9',
|
||||
intercom_user_jwt: token,
|
||||
session_duration: 1000 * 60 * 60 * 24,
|
||||
alignment: 'right',
|
||||
horizontal_padding: intercomBubblePosition.value.horizontalPadding,
|
||||
vertical_padding: intercomBubblePosition.value.verticalPadding,
|
||||
})
|
||||
intercomBooted = true
|
||||
} catch (error) {
|
||||
console.warn('[APP][INTERCOM] failed to initialize secure support chat', error)
|
||||
} finally {
|
||||
intercomBooting = false
|
||||
}
|
||||
}
|
||||
|
||||
function shutdownHostingIntercom() {
|
||||
if (!intercomBooted && !intercomBooting) return
|
||||
shutdownIntercom()
|
||||
intercomBooting = false
|
||||
intercomBooted = false
|
||||
}
|
||||
|
||||
watch(
|
||||
intercomBubblePosition,
|
||||
(position) => {
|
||||
updateIntercomBubbleStyles(position)
|
||||
if (intercomBooted) {
|
||||
window.Intercom?.('update', {
|
||||
horizontal_padding: position.horizontalPadding,
|
||||
vertical_padding: position.verticalPadding,
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
[hostingRouteActive, credentials],
|
||||
([active]) => {
|
||||
if (active) {
|
||||
void bootIntercom()
|
||||
} else {
|
||||
shutdownHostingIntercom()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(showAd, () => {
|
||||
if (!showAd.value) {
|
||||
hide_ads_window(true)
|
||||
@@ -982,40 +1023,11 @@ async function downloadUpdate(versionToDownload) {
|
||||
|
||||
async function installUpdate() {
|
||||
restarting.value = true
|
||||
try {
|
||||
await setRestartAfterPendingUpdate(true)
|
||||
} catch (e) {
|
||||
restarting.value = false
|
||||
handleError(e)
|
||||
return
|
||||
}
|
||||
setTimeout(async () => {
|
||||
await handleClose()
|
||||
}, 250)
|
||||
}
|
||||
|
||||
async function openModrinthProjectLinkInApp(parsed) {
|
||||
const { slug, pathSuffix, url } = parsed
|
||||
const loadToken = loading.begin()
|
||||
try {
|
||||
const { id } = await tauriApiClient.labrinth.projects_v2.check(slug)
|
||||
const query = mergeUrlQuery(route.query, url)
|
||||
await router.push({
|
||||
path: `/project/${id}${pathSuffix}`,
|
||||
query,
|
||||
hash: url.hash || undefined,
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof ModrinthApiError && err.statusCode === 404) {
|
||||
openUrl(url.href)
|
||||
} else {
|
||||
handleError(err)
|
||||
}
|
||||
} finally {
|
||||
loading.end(loadToken)
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
@@ -1028,12 +1040,7 @@ function handleClick(e) {
|
||||
!target.href.startsWith('https://tauri.localhost') &&
|
||||
!target.href.startsWith('http://tauri.localhost')
|
||||
) {
|
||||
const parsed = parseModrinthLink(target.href)
|
||||
if (target.target !== '_blank' && parsed) {
|
||||
void openModrinthProjectLinkInApp(parsed)
|
||||
} else {
|
||||
openUrl(target.href)
|
||||
}
|
||||
openUrl(target.href)
|
||||
}
|
||||
e.preventDefault()
|
||||
break
|
||||
@@ -1564,15 +1571,11 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
|
||||
.app-grid-navbar {
|
||||
grid-area: nav;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.app-grid-statusbar {
|
||||
grid-area: status;
|
||||
padding-right: var(--window-controls-width, 0px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
[data-tauri-drag-region-exclude] {
|
||||
@@ -1640,12 +1643,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
&.app-contents::before {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
*,
|
||||
:deep(*) {
|
||||
box-shadow: none !important;
|
||||
--tw-drop-shadow:;
|
||||
}
|
||||
}
|
||||
|
||||
.app-sidebar::before {
|
||||
@@ -1668,7 +1665,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
}
|
||||
|
||||
.app-contents::before {
|
||||
z-index: 30;
|
||||
z-index: 1;
|
||||
content: '';
|
||||
position: fixed;
|
||||
left: var(--left-bar-width);
|
||||
@@ -1778,6 +1775,14 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
--os-handle-bg-active: var(--color-scrollbar) !important;
|
||||
}
|
||||
|
||||
.intercom-lightweight-app-launcher,
|
||||
.intercom-launcher-frame,
|
||||
iframe[name='intercom-launcher-frame'] {
|
||||
right: var(--app-support-launcher-right, 20px) !important;
|
||||
bottom: var(--app-support-launcher-bottom, 20px) !important;
|
||||
z-index: 9 !important;
|
||||
}
|
||||
|
||||
.mac {
|
||||
.app-grid-statusbar {
|
||||
padding-left: 5rem;
|
||||
|
||||
@@ -37,6 +37,6 @@ defineProps({
|
||||
|
||||
.progress-bar__fill {
|
||||
height: 100%;
|
||||
transition: width 0.3s ease-out;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -126,7 +126,7 @@ watch(
|
||||
function fakeLoadingIncrease() {
|
||||
if (loadingProgress.value < 95) {
|
||||
setTimeout(() => {
|
||||
loadingProgress.value += 2
|
||||
loadingProgress.value += 1
|
||||
fakeLoadingIncrease()
|
||||
}, 5)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
hover-color-fill="background"
|
||||
circular
|
||||
>
|
||||
<button class="relative expanded-button close-button" @click="handleClose">
|
||||
<button class="relative expanded-button" @click="handleClose">
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -82,12 +82,8 @@ const handleClose = async () => {
|
||||
</script>
|
||||
<style scoped>
|
||||
.expanded-button::before {
|
||||
inset: -9px -6px;
|
||||
inset: -6px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.expanded-button.close-button::before {
|
||||
inset: -9px -9px -9px -6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,7 +35,7 @@ const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { instance, offline, isMinecraftServer, onUnlinked, closeModal } = injectInstanceSettings()
|
||||
const { instance, offline, isMinecraftServer, onUnlinked } = injectInstanceSettings()
|
||||
|
||||
const [
|
||||
fabric_versions,
|
||||
@@ -113,7 +113,6 @@ provideAppBackup({
|
||||
})
|
||||
|
||||
provideInstallationSettings({
|
||||
closeSettings: closeModal,
|
||||
loading: ref(false),
|
||||
installationInfo: computed(() => {
|
||||
const rows = [
|
||||
|
||||
@@ -45,19 +45,12 @@ const isMinecraftServer = ref(false)
|
||||
const handleUnlinked = () => emit('unlinked')
|
||||
|
||||
const instanceRef = computed(() => props.instance)
|
||||
const queryClient = useQueryClient()
|
||||
const tabbedModal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
|
||||
function hide() {
|
||||
tabbedModal.value?.hide()
|
||||
}
|
||||
|
||||
provideInstanceSettings({
|
||||
instance: instanceRef,
|
||||
offline: props.offline,
|
||||
isMinecraftServer,
|
||||
onUnlinked: handleUnlinked,
|
||||
closeModal: hide,
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -120,6 +113,9 @@ const tabs = computed<TabbedModalTab[]>(() => [
|
||||
},
|
||||
])
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const tabbedModal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
|
||||
function show(tabIndex?: number) {
|
||||
if (props.instance.linked_data?.project_id) {
|
||||
queryClient.prefetchQuery({
|
||||
@@ -133,7 +129,7 @@ function show(tabIndex?: number) {
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ show, hide })
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
<template>
|
||||
<TabbedModal
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { CheckIcon, PlayIcon, PlusIcon, StopCircleIcon } from '@modrinth/assets'
|
||||
import type { CardAction } from '@modrinth/ui'
|
||||
import { commonMessages, defineMessages, useDebugLogger, useVIntl } from '@modrinth/ui'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import { onUnmounted, ref, shallowRef } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { kill, list as listInstances } from '@/helpers/profile.js'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { add_server_to_profile, getServerLatency } from '@/helpers/worlds'
|
||||
import { getServerAddress } from '@/store/install.js'
|
||||
|
||||
interface BrowseServerInstance {
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
interface ContextMenuHandle {
|
||||
showMenu: (
|
||||
event: MouseEvent,
|
||||
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
|
||||
options: { name: string }[],
|
||||
) => void
|
||||
}
|
||||
|
||||
interface ContextMenuOptionClick {
|
||||
option: 'open_link' | 'copy_link'
|
||||
item: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject
|
||||
}
|
||||
|
||||
export interface UseAppServerBrowseOptions {
|
||||
instance: Ref<BrowseServerInstance | null>
|
||||
isFromWorlds: ComputedRef<boolean>
|
||||
allInstalledIds: ComputedRef<Set<string>>
|
||||
newlyInstalled: Ref<string[]>
|
||||
installingServerProjects: Ref<string[]>
|
||||
playServerProject: (projectId: string) => Promise<void>
|
||||
showAddServerToInstanceModal: (serverName: string, serverAddress: string) => void
|
||||
handleError: (error: unknown) => void
|
||||
router: Router
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
addToInstance: {
|
||||
id: 'app.browse.add-to-instance',
|
||||
defaultMessage: 'Add to instance',
|
||||
},
|
||||
addToInstanceName: {
|
||||
id: 'app.browse.add-to-instance-name',
|
||||
defaultMessage: 'Add to {instanceName}',
|
||||
},
|
||||
added: {
|
||||
id: 'app.browse.added',
|
||||
defaultMessage: 'Added',
|
||||
},
|
||||
alreadyAdded: {
|
||||
id: 'app.browse.already-added',
|
||||
defaultMessage: 'Already added',
|
||||
},
|
||||
})
|
||||
|
||||
export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
const { formatMessage } = useVIntl()
|
||||
const debugLog = useDebugLogger('BrowseServer')
|
||||
const serverPings = shallowRef<Record<string, number | undefined>>({})
|
||||
const serverPingCache = new Map<string, number | undefined>()
|
||||
const pendingServerPings = new Map<string, Promise<number | undefined>>()
|
||||
const runningServerProjects = ref<Record<string, string>>({})
|
||||
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
|
||||
const contextMenuRef = ref<ContextMenuHandle | null>(null)
|
||||
let serverPingCacheActive = true
|
||||
let unlistenProcesses: (() => void) | null = null
|
||||
|
||||
async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
|
||||
debugLog('checkServerRunningStates', { hitCount: hits.length })
|
||||
const packs = await listInstances().catch((error) => {
|
||||
options.handleError(error)
|
||||
return []
|
||||
})
|
||||
const newRunning: Record<string, string> = {}
|
||||
for (const hit of hits) {
|
||||
const inst = packs.find(
|
||||
(pack: GameInstance) => pack.linked_data?.project_id === hit.project_id,
|
||||
)
|
||||
if (inst) {
|
||||
const processes = await get_by_profile_path(inst.path).catch(() => [])
|
||||
if (Array.isArray(processes) && processes.length > 0) {
|
||||
newRunning[hit.project_id] = inst.path
|
||||
}
|
||||
}
|
||||
}
|
||||
debugLog('runningServerProjects updated', newRunning)
|
||||
runningServerProjects.value = newRunning
|
||||
}
|
||||
|
||||
async function handleStopServerProject(projectId: string) {
|
||||
debugLog('handleStopServerProject', projectId)
|
||||
const instancePath = runningServerProjects.value[projectId]
|
||||
if (!instancePath) return
|
||||
await kill(instancePath).catch(() => {})
|
||||
const { [projectId]: _, ...rest } = runningServerProjects.value
|
||||
runningServerProjects.value = rest
|
||||
}
|
||||
|
||||
async function handlePlayServerProject(projectId: string) {
|
||||
debugLog('handlePlayServerProject', projectId)
|
||||
await options.playServerProject(projectId)
|
||||
checkServerRunningStates(lastServerHits.value)
|
||||
}
|
||||
|
||||
async function handleAddServerToInstance(project: Labrinth.Search.v3.ResultSearchProject) {
|
||||
debugLog('handleAddServerToInstance', { projectId: project.project_id, name: project.name })
|
||||
const address = getServerAddress(project.minecraft_java_server)
|
||||
if (!address) return
|
||||
|
||||
if (options.instance.value) {
|
||||
try {
|
||||
await add_server_to_profile(
|
||||
options.instance.value.path,
|
||||
project.name,
|
||||
address,
|
||||
'prompt',
|
||||
project.project_id,
|
||||
project.minecraft_java_server?.content?.kind,
|
||||
)
|
||||
options.newlyInstalled.value.push(project.project_id)
|
||||
} catch (error) {
|
||||
options.handleError(error)
|
||||
}
|
||||
} else {
|
||||
options.showAddServerToInstanceModal(project.name, address)
|
||||
}
|
||||
}
|
||||
|
||||
async function pingServerHits(hits: Labrinth.Search.v3.ResultSearchProject[]) {
|
||||
debugLog('pingServerHits', { hitCount: hits.length })
|
||||
const pingsToFetch = hits.flatMap((hit) => {
|
||||
const address = hit.minecraft_java_server?.address
|
||||
if (!address) return []
|
||||
return [{ hit, address }]
|
||||
})
|
||||
const nextPings = { ...serverPings.value }
|
||||
for (const { hit, address } of pingsToFetch) {
|
||||
if (serverPingCache.has(address)) {
|
||||
nextPings[hit.project_id] = serverPingCache.get(address)
|
||||
}
|
||||
}
|
||||
serverPings.value = nextPings
|
||||
|
||||
await Promise.all(
|
||||
pingsToFetch.map(async ({ hit, address }) => {
|
||||
if (serverPingCache.has(address)) return
|
||||
|
||||
let pending = pendingServerPings.get(address)
|
||||
if (!pending) {
|
||||
pending = getServerLatency(address)
|
||||
.then((latency) => {
|
||||
if (serverPingCacheActive) serverPingCache.set(address, latency)
|
||||
return latency
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed to ping server ${address}:`, error)
|
||||
if (serverPingCacheActive) serverPingCache.set(address, undefined)
|
||||
return undefined
|
||||
})
|
||||
.finally(() => {
|
||||
pendingServerPings.delete(address)
|
||||
})
|
||||
pendingServerPings.set(address, pending)
|
||||
}
|
||||
|
||||
const latency = await pending
|
||||
if (!serverPingCacheActive) return
|
||||
serverPings.value = { ...serverPings.value, [hit.project_id]: latency }
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function updateServerHits(hits: Labrinth.Search.v3.ResultSearchProject[]) {
|
||||
lastServerHits.value = hits
|
||||
pingServerHits(hits)
|
||||
checkServerRunningStates(hits)
|
||||
}
|
||||
|
||||
function getServerModpackContent(project: Labrinth.Search.v3.ResultSearchProject) {
|
||||
const content = project.minecraft_java_server?.content
|
||||
if (content?.kind === 'modpack') {
|
||||
const { project_name, project_icon, project_id } = content
|
||||
if (!project_name) return undefined
|
||||
return {
|
||||
name: project_name,
|
||||
icon: project_icon ?? undefined,
|
||||
onclick:
|
||||
project_id !== project.project_id
|
||||
? () => {
|
||||
options.router.push(`/project/${project_id}`)
|
||||
}
|
||||
: undefined,
|
||||
showCustomModpackTooltip: project_id === project.project_id,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getServerCardActions(
|
||||
serverResult: Labrinth.Search.v3.ResultSearchProject,
|
||||
): CardAction[] {
|
||||
const isInstalled = options.allInstalledIds.value.has(serverResult.project_id)
|
||||
|
||||
if (options.isFromWorlds.value && options.instance.value) {
|
||||
return [
|
||||
{
|
||||
key: 'add-to-instance',
|
||||
label: formatMessage(isInstalled ? messages.added : messages.addToInstance),
|
||||
icon: isInstalled ? CheckIcon : PlusIcon,
|
||||
disabled: isInstalled,
|
||||
color: 'brand',
|
||||
type: 'outlined',
|
||||
onClick: () => handleAddServerToInstance(serverResult),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const actions: CardAction[] = []
|
||||
|
||||
actions.push({
|
||||
key: 'add',
|
||||
label: '',
|
||||
icon: isInstalled ? CheckIcon : PlusIcon,
|
||||
disabled: isInstalled,
|
||||
circular: true,
|
||||
tooltip: isInstalled
|
||||
? formatMessage(messages.alreadyAdded)
|
||||
: options.instance.value
|
||||
? formatMessage(messages.addToInstanceName, {
|
||||
instanceName: options.instance.value.name,
|
||||
})
|
||||
: formatMessage(commonMessages.addServerToInstanceButton),
|
||||
onClick: () => handleAddServerToInstance(serverResult),
|
||||
})
|
||||
|
||||
if (runningServerProjects.value[serverResult.project_id]) {
|
||||
actions.push({
|
||||
key: 'stop',
|
||||
label: formatMessage(commonMessages.stopButton),
|
||||
icon: StopCircleIcon,
|
||||
color: 'red',
|
||||
type: 'outlined',
|
||||
onClick: () => handleStopServerProject(serverResult.project_id),
|
||||
})
|
||||
} else {
|
||||
const isInstalling = options.installingServerProjects.value.includes(serverResult.project_id)
|
||||
actions.push({
|
||||
key: 'play',
|
||||
label: formatMessage(
|
||||
isInstalling ? commonMessages.installingLabel : commonMessages.playButton,
|
||||
),
|
||||
icon: PlayIcon,
|
||||
disabled: isInstalling,
|
||||
color: 'brand',
|
||||
type: 'outlined',
|
||||
onClick: () => handlePlayServerProject(serverResult.project_id),
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
function handleRightClick(
|
||||
event: MouseEvent,
|
||||
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
|
||||
) {
|
||||
contextMenuRef.value?.showMenu(event, result, [{ name: 'open_link' }, { name: 'copy_link' }])
|
||||
}
|
||||
|
||||
function handleOptionsClick(args: ContextMenuOptionClick) {
|
||||
const url = getProjectUrl(args.item)
|
||||
switch (args.option) {
|
||||
case 'open_link':
|
||||
openUrl(url)
|
||||
break
|
||||
case 'copy_link':
|
||||
navigator.clipboard.writeText(url)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
process_listener((event: { event: string; profile_path_id: string }) => {
|
||||
debugLog('process event', event)
|
||||
if (event.event === 'finished') {
|
||||
const projectId = Object.entries(runningServerProjects.value).find(
|
||||
([, path]) => path === event.profile_path_id,
|
||||
)?.[0]
|
||||
if (projectId) {
|
||||
const { [projectId]: _, ...rest } = runningServerProjects.value
|
||||
runningServerProjects.value = rest
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((unlisten) => {
|
||||
unlistenProcesses = unlisten
|
||||
})
|
||||
.catch(options.handleError)
|
||||
|
||||
onUnmounted(() => {
|
||||
serverPingCacheActive = false
|
||||
unlistenProcesses?.()
|
||||
serverPingCache.clear()
|
||||
pendingServerPings.clear()
|
||||
})
|
||||
|
||||
return {
|
||||
serverPings,
|
||||
contextMenuRef,
|
||||
updateServerHits,
|
||||
getServerModpackContent,
|
||||
getServerCardActions,
|
||||
handleRightClick,
|
||||
handleOptionsClick,
|
||||
}
|
||||
}
|
||||
|
||||
function getProjectUrl(
|
||||
item: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
|
||||
) {
|
||||
const projectType = 'project_types' in item ? item.project_types?.[0] : item.project_type
|
||||
return `https://modrinth.com/${projectType ?? 'project'}/${item.slug ?? item.project_id}`
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
interface ThemeStore {
|
||||
toggleSidebar: boolean
|
||||
$subscribe: (callback: () => void) => () => void
|
||||
}
|
||||
|
||||
interface IntercomBubblePosition {
|
||||
horizontalPadding: number
|
||||
verticalPadding: number
|
||||
}
|
||||
|
||||
const APP_LEFT_NAV_WIDTH = '4rem'
|
||||
const APP_SIDEBAR_WIDTH = 300
|
||||
const INTERCOM_BUBBLE_DEFAULT_PADDING = 20
|
||||
const INTERCOM_BUBBLE_WIDTH = 72
|
||||
const INTERCOM_BUBBLE_RIGHT_VAR = '--app-support-launcher-right'
|
||||
const INTERCOM_BUBBLE_BOTTOM_VAR = '--app-support-launcher-bottom'
|
||||
|
||||
export function useIntercomPositioning({
|
||||
route,
|
||||
themeStore,
|
||||
}: {
|
||||
route: RouteLocationNormalizedLoaded
|
||||
themeStore: ThemeStore
|
||||
}) {
|
||||
const sidebarToggled = ref(true)
|
||||
const unsubscribeSidebarToggle = themeStore.$subscribe(() => {
|
||||
sidebarToggled.value = !themeStore.toggleSidebar
|
||||
})
|
||||
|
||||
onUnmounted(unsubscribeSidebarToggle)
|
||||
|
||||
const forceSidebar = computed(
|
||||
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
|
||||
)
|
||||
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
|
||||
const defaultIntercomBubbleHorizontalPadding = computed(() =>
|
||||
sidebarVisible.value
|
||||
? APP_SIDEBAR_WIDTH + INTERCOM_BUBBLE_DEFAULT_PADDING
|
||||
: INTERCOM_BUBBLE_DEFAULT_PADDING,
|
||||
)
|
||||
const intercomBubbleRequestedHorizontalPadding = ref<number | null>(null)
|
||||
const intercomBubbleHorizontalPadding = computed(
|
||||
() =>
|
||||
intercomBubbleRequestedHorizontalPadding.value ??
|
||||
defaultIntercomBubbleHorizontalPadding.value,
|
||||
)
|
||||
const intercomBubbleVerticalClearance = ref<number | null>(null)
|
||||
const intercomBubblePosition = computed(() => ({
|
||||
horizontalPadding: intercomBubbleHorizontalPadding.value,
|
||||
verticalPadding: intercomBubbleVerticalClearance.value ?? INTERCOM_BUBBLE_DEFAULT_PADDING,
|
||||
}))
|
||||
const intercomBubbleHorizontalPaddingRequests = new Map<symbol, number>()
|
||||
const intercomBubbleClearanceRequests = new Map<symbol, number>()
|
||||
|
||||
function requestIntercomBubbleHorizontalPadding(id: symbol, padding: number | null) {
|
||||
if (padding === null) {
|
||||
intercomBubbleHorizontalPaddingRequests.delete(id)
|
||||
} else {
|
||||
intercomBubbleHorizontalPaddingRequests.set(id, padding)
|
||||
}
|
||||
|
||||
intercomBubbleRequestedHorizontalPadding.value =
|
||||
intercomBubbleHorizontalPaddingRequests.size > 0
|
||||
? Math.max(...intercomBubbleHorizontalPaddingRequests.values())
|
||||
: null
|
||||
}
|
||||
|
||||
function requestIntercomBubbleVerticalClearance(id: symbol, clearance: number | null) {
|
||||
if (clearance === null) {
|
||||
intercomBubbleClearanceRequests.delete(id)
|
||||
} else {
|
||||
intercomBubbleClearanceRequests.set(id, clearance)
|
||||
}
|
||||
|
||||
intercomBubbleVerticalClearance.value =
|
||||
intercomBubbleClearanceRequests.size > 0
|
||||
? Math.max(...intercomBubbleClearanceRequests.values())
|
||||
: null
|
||||
}
|
||||
|
||||
function updateIntercomBubbleStyles({
|
||||
horizontalPadding,
|
||||
verticalPadding,
|
||||
}: IntercomBubblePosition) {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
document.documentElement.style.setProperty(INTERCOM_BUBBLE_RIGHT_VAR, `${horizontalPadding}px`)
|
||||
document.documentElement.style.setProperty(INTERCOM_BUBBLE_BOTTOM_VAR, `${verticalPadding}px`)
|
||||
}
|
||||
|
||||
function clearIntercomBubbleStyles() {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
document.documentElement.style.removeProperty(INTERCOM_BUBBLE_RIGHT_VAR)
|
||||
document.documentElement.style.removeProperty(INTERCOM_BUBBLE_BOTTOM_VAR)
|
||||
}
|
||||
|
||||
return {
|
||||
sidebarToggled,
|
||||
forceSidebar,
|
||||
sidebarVisible,
|
||||
intercomBubblePosition,
|
||||
updateIntercomBubbleStyles,
|
||||
clearIntercomBubbleStyles,
|
||||
pageContext: {
|
||||
floatingActionBarOffsets: {
|
||||
left: ref(APP_LEFT_NAV_WIDTH),
|
||||
right: computed(() => (sidebarVisible.value ? `${APP_SIDEBAR_WIDTH}px` : '0px')),
|
||||
},
|
||||
intercomBubble: {
|
||||
width: ref(INTERCOM_BUBBLE_WIDTH),
|
||||
horizontalPadding: intercomBubbleHorizontalPadding,
|
||||
requestHorizontalPadding: requestIntercomBubbleHorizontalPadding,
|
||||
requestVerticalClearance: requestIntercomBubbleVerticalClearance,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import type {
|
||||
ContentItem,
|
||||
ContentModpackCardCategory,
|
||||
ContentModpackCardProject,
|
||||
ContentModpackCardVersion,
|
||||
ContentOwner,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import {
|
||||
get_content_items,
|
||||
get_linked_modpack_info,
|
||||
type LinkedModpackInfo,
|
||||
} from '@/helpers/profile'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import type { CacheBehaviour } from '@/helpers/types'
|
||||
|
||||
export type InstanceContentData = {
|
||||
path: string
|
||||
contentItems: ContentItem[] | null
|
||||
modpack: InstanceContentModpackData | null
|
||||
}
|
||||
|
||||
export type InstanceContentModpackData = {
|
||||
project: ContentModpackCardProject
|
||||
version: ContentModpackCardVersion
|
||||
owner: ContentOwner | null
|
||||
categories: ContentModpackCardCategory[]
|
||||
hasUpdate: boolean
|
||||
updateVersionId: string | null
|
||||
}
|
||||
|
||||
export async function loadInstanceContentData(
|
||||
path: string,
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
onError?: (error: Error) => unknown,
|
||||
): Promise<InstanceContentData> {
|
||||
const [contentItems, modpackInfo, allCategories] = await Promise.all([
|
||||
get_content_items(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
|
||||
get_linked_modpack_info(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
|
||||
get_categories().catch((error) => handleLoadError(error, onError)),
|
||||
])
|
||||
|
||||
return {
|
||||
path,
|
||||
contentItems: (contentItems as ContentItem[] | null | undefined) ?? null,
|
||||
modpack: normalizeLinkedModpackInfo(
|
||||
modpackInfo as LinkedModpackInfo | null | undefined,
|
||||
allCategories as ContentModpackCardCategory[] | null | undefined,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function handleLoadError(error: unknown, onError?: (error: Error) => unknown) {
|
||||
onError?.(error as Error)
|
||||
return null
|
||||
}
|
||||
|
||||
function normalizeLinkedModpackInfo(
|
||||
modpackInfo: LinkedModpackInfo | null | undefined,
|
||||
allCategories: ContentModpackCardCategory[] | null | undefined,
|
||||
): InstanceContentModpackData | null {
|
||||
if (!modpackInfo) return null
|
||||
|
||||
return {
|
||||
project: {
|
||||
...modpackInfo.project,
|
||||
slug: modpackInfo.project.slug ?? modpackInfo.project.id,
|
||||
icon_url: modpackInfo.project.icon_url ?? undefined,
|
||||
},
|
||||
version: {
|
||||
...modpackInfo.version,
|
||||
date_published: modpackInfo.version.date_published.toString(),
|
||||
},
|
||||
owner: modpackInfo.owner
|
||||
? {
|
||||
...modpackInfo.owner,
|
||||
avatar_url: modpackInfo.owner.avatar_url ?? undefined,
|
||||
}
|
||||
: null,
|
||||
categories: resolveLinkedModpackCategories(modpackInfo, allCategories),
|
||||
hasUpdate: modpackInfo.has_update,
|
||||
updateVersionId: modpackInfo.update_version_id,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLinkedModpackCategories(
|
||||
modpackInfo: LinkedModpackInfo,
|
||||
allCategories: ContentModpackCardCategory[] | null | undefined,
|
||||
) {
|
||||
if (!allCategories || !modpackInfo.project.categories) return []
|
||||
|
||||
const seen = new Set<string>()
|
||||
return allCategories.filter((category) => {
|
||||
if (modpackInfo.project.categories.includes(category.name) && !seen.has(category.name)) {
|
||||
seen.add(category.name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import type { LocationQuery, LocationQueryRaw } from 'vue-router'
|
||||
|
||||
const MODRINTH_HOSTNAMES = new Set(['modrinth.com', 'www.modrinth.com'])
|
||||
|
||||
const SUPPORTED_PROJECT_TYPES = new Set([
|
||||
'mod',
|
||||
'modpack',
|
||||
'resourcepack',
|
||||
'datapack',
|
||||
'plugin',
|
||||
'shader',
|
||||
'server',
|
||||
'project',
|
||||
])
|
||||
|
||||
export function parseModrinthLink(
|
||||
href: string,
|
||||
): { slug: string; pathSuffix: string; url: URL } | null {
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(href)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!MODRINTH_HOSTNAMES.has(url.hostname.toLowerCase())) {
|
||||
return null
|
||||
}
|
||||
|
||||
const segments = url.pathname.split('/').filter((p) => p.length > 0)
|
||||
if (segments.length < 2) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (SUPPORTED_PROJECT_TYPES.has(segments[0].toLowerCase())) {
|
||||
const slug = segments[1]
|
||||
if (!slug) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rest: string[] = segments.slice(2)
|
||||
const pathSuffix = toValidAppSubpath(rest)
|
||||
if (pathSuffix === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return { slug, pathSuffix, url }
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const SUPPORTED_SUBPATHS = ['versions', 'gallery']
|
||||
|
||||
function toValidAppSubpath(rest: string[]): string | null {
|
||||
if (rest.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const subroute = rest[0].toLowerCase()
|
||||
if (rest.length === 1 && SUPPORTED_SUBPATHS.includes(subroute)) {
|
||||
return `/${subroute}`
|
||||
}
|
||||
|
||||
if (rest.length === 2 && subroute === 'version') {
|
||||
return `/version/${rest[1]}`
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function mergeUrlQuery(routeQuery: LocationQuery, linkUrl: URL): LocationQueryRaw {
|
||||
const newQuery: LocationQueryRaw = { ...routeQuery }
|
||||
const keys = new Set<string>()
|
||||
linkUrl.searchParams.forEach((_value, key) => {
|
||||
keys.add(key)
|
||||
})
|
||||
for (const key of keys) {
|
||||
const values = linkUrl.searchParams.getAll(key)
|
||||
newQuery[key] = values.length === 1 ? values[0] : values
|
||||
}
|
||||
return newQuery
|
||||
}
|
||||
@@ -22,10 +22,6 @@ export async function removeEnqueuedUpdate() {
|
||||
return await invoke('remove_enqueued_update')
|
||||
}
|
||||
|
||||
export async function setRestartAfterPendingUpdate(should_restart) {
|
||||
return await invoke('set_restart_after_pending_update', { shouldRestart: should_restart })
|
||||
}
|
||||
|
||||
// One of 'Windows', 'Linux', 'MacOS'
|
||||
export async function getOS() {
|
||||
return await invoke('plugin:utils|get_os')
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "جاز تنزيل إصدار جافا {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "التنزيلات"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "غير متصل"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "صفحة الرئيسية"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "مكتبة"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "قد تكون خوادم مصادقة ماينكرافت معطلة حاليًا. تحقق من اتصالك بالإنترنت وحاول مرة أخرى لاحقًا."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "تعذر الوصول إلى خوادم المصادقة"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "أضف الخادم للنموذج"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "أضف الخوادم لنموذجك"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "أضف للنموذج"
|
||||
},
|
||||
@@ -38,6 +29,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "استكشف خوادم"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "تثبيت محتوى لنموذجك"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "جاري التثبيت"
|
||||
},
|
||||
@@ -56,6 +50,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "إسم حزمة التعديل"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "حدد الملفات والمجلدات المراد تضمينها في الحزمة"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "رقم الإصدار"
|
||||
},
|
||||
@@ -575,12 +572,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "العالم قيد الاستخدام"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "إضافة حساب"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "اختيار الحساب"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "مقدَّم من النسخة"
|
||||
},
|
||||
|
||||
@@ -1,114 +1,15 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Stahování Javy {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Stažené Soubory"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Skrýt více spuštěných instancí"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Použít jako primární instanci"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Žádné spuštěné instance"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primární instance"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Zobrazit další spuštěné instance"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Ukončit instanci"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Zobrazit probíhající stahování"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Zobrazit instanci"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Zobrazit záznamy"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Umožňuje pokročilé vykreslování, jako jsou efekty rozostření, které mohou způsobit problémy s výkonem bez hardwarového vykreslení."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Pokročilé vykreslování"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Vyberte si váš preferovaný motiv pro aplikaci Modrinth."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Barevný motiv"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Změňte stránku, na které se spouštěč otevře."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Domov"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Knihovna"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Výchozí úvodní stránka"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Zakáže zobrazení jmenovky nad vaší postavou na stránce skinů."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Skrýt jmenovku"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "V sekci „Vrátit se do hry“ na úvodní stránce jsou uvedeny nejnovější světy."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Skočit zpět do světů"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimalizovat spouštěč při spuštění procesu Minecraftu."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimalizovat spouštěč"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Použít systémový rámeček okna (vyžaduje restart aplikace)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Nativní dekorace"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Vyberte možnost"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Umožňí přepínat zobrazení postranního panelu."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Přepnout postranní panel"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Pokud se pokusíte nainstalovat soubor Modrinth Pack (.mrpack), který není hostován na Modrinth, zajistíme, abyste si před instalací uvědomili související rizika."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Upozorni mě před instalací neznámých modpacků"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Autořizační servery Minecraftu aktuálně nejsou k dispozici. Zkontrolujte si své připojení k internetu a zkuste to znovu později."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Připojení k autorizačním serverům se nezdařilo"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Přidat server do instance"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Přidat do instalace"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Přidat servery do instance"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Přidat do instalace"
|
||||
@@ -122,32 +23,14 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Už přidáno"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Zpět k instanci"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Prozkoumat obsah"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Prozkoumat servery"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Skrýt již přidané servery"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Balíčky Modifikací"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Přidání obsahu může narušit kompatibilitu při připojení k serveru. Veškerý přidaný obsah se navíc ztratí při aktualizaci obsahu instance serveru."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalování"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Probíhá instalace modpacku..."
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Nainstalovat obsah do instnce"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Přidej popis modpacku..."
|
||||
@@ -158,9 +41,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Exportovat modpack"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Zahrnout \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Jméno Modpacku"
|
||||
},
|
||||
@@ -168,7 +48,7 @@
|
||||
"message": "Jméno modpacku"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Nastavte, které soubory mají být do tohoto exportu zahrnuty"
|
||||
"message": "Vybrat soubory a složky co zahrnout do modpacku"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Číslo verze"
|
||||
@@ -251,9 +131,6 @@
|
||||
"app.instance.worlds.remove-server-description": {
|
||||
"message": "„{name}“ bude odstraněn z tvého seznamu, včetně hry, a nebude žádný způsob, jak ho obnovit."
|
||||
},
|
||||
"app.instance.worlds.remove-server-description-with-address": {
|
||||
"message": "'{name}' ({address}) bude odstraněn z vašeho seznamu, a to i ve hře, a nebude možné jej obnovit."
|
||||
},
|
||||
"app.instance.worlds.remove-server-title": {
|
||||
"message": "Opravdu chceš odstranit {name}?"
|
||||
},
|
||||
@@ -263,9 +140,6 @@
|
||||
"app.instance.worlds.this-server": {
|
||||
"message": "tento server"
|
||||
},
|
||||
"app.modal.install-to-play.content-required": {
|
||||
"message": "Požadovaný obsah"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "Nainstaluj ke hraní"
|
||||
},
|
||||
@@ -275,21 +149,12 @@
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural,one {#mód} other {#módy}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "Povinný modpack"
|
||||
},
|
||||
"app.modal.install-to-play.server-requires-mods": {
|
||||
"message": "Tento server k hraní vyžaduje módy. Klikni na instalovat pro získání potřebných módů z Modrinth."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Sdílená instance"
|
||||
},
|
||||
"app.modal.install-to-play.shared-server-instance": {
|
||||
"message": "Sdílená serverová instance"
|
||||
},
|
||||
"app.modal.install-to-play.view-contents": {
|
||||
"message": "Zobrazit obsah"
|
||||
},
|
||||
"app.modal.update-to-play.header": {
|
||||
"message": "Updatuj ke hraní"
|
||||
},
|
||||
@@ -299,12 +164,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Je nutná aktualizace ke hraní {name}. Prosím updatuj na nejnovější verzi k spuštění hry."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Tento projekt je již nainstalován"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Nainstalovat obsah do instnce"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Vývojářský režim povolen."
|
||||
},
|
||||
@@ -329,33 +188,9 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "Správa zdrojů"
|
||||
},
|
||||
"app.update-popup.body": {
|
||||
"message": "Aplikace Modrinth v{version} je připravena k instalaci! Nainstalujte aktualizaci nyní nebo automaticky po zavření aplikace Modrinth."
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "Stahování aplikace Modrinth v{version} bylo dokončeno. Nainstalujte aktualizaci nyní nebo automaticky po zavření aplikace Modrinth."
|
||||
},
|
||||
"app.update-popup.body.linux": {
|
||||
"message": "Aplikace Modrinth v{version} je k dispozici. Aktualizujte pomocí svého správce balíčků, abyste získali nejnovější funkce a opravy!"
|
||||
},
|
||||
"app.update-popup.body.metered": {
|
||||
"message": "Aplikace Modrinth v{version} je nyní k dispozici! Protože jste v měřené síti, nebyla stažena automaticky."
|
||||
},
|
||||
"app.update-popup.changelog": {
|
||||
"message": "Seznam změn"
|
||||
},
|
||||
"app.update-popup.download": {
|
||||
"message": "Stáhnout ({size})"
|
||||
},
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "Stahování bylo dokončeno"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "Načíst znovu"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "Je k dispozici aktualizace"
|
||||
},
|
||||
"app.update.complete-toast.text": {
|
||||
"message": "Kliknutím sem zobrazíte seznam změn."
|
||||
},
|
||||
@@ -371,24 +206,6 @@
|
||||
"app.update.reload-to-update": {
|
||||
"message": "Restartovat aplikaci pro nainstalování aktualizace"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "priklad.modrinth.gg"
|
||||
},
|
||||
"app.world.server-modal.select-an-option": {
|
||||
"message": "Vyberte možnost"
|
||||
},
|
||||
"app.world.world-item.incompatible-version": {
|
||||
"message": "Nekompatibilní verze {version}"
|
||||
},
|
||||
"app.world.world-item.not-played-yet": {
|
||||
"message": "Ještě nehráno"
|
||||
},
|
||||
"app.world.world-item.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.world.world-item.players-online": {
|
||||
"message": "{count} online"
|
||||
},
|
||||
"friends.action.add-friend": {
|
||||
"message": "Přidat přítele"
|
||||
},
|
||||
@@ -488,12 +305,6 @@
|
||||
"instance.edit-world.title": {
|
||||
"message": "Upravit svět"
|
||||
},
|
||||
"instance.files.adding-files": {
|
||||
"message": "Přidávání souborů ({completed}/{total})"
|
||||
},
|
||||
"instance.files.save-as": {
|
||||
"message": "Uložit jako..."
|
||||
},
|
||||
"instance.server-modal.address": {
|
||||
"message": "Adresa"
|
||||
},
|
||||
@@ -674,9 +485,6 @@
|
||||
"instance.worlds.incompatible_server": {
|
||||
"message": "Server je nekompatibilní"
|
||||
},
|
||||
"instance.worlds.linked_server": {
|
||||
"message": "Spravováno projektem serveru"
|
||||
},
|
||||
"instance.worlds.no_contact": {
|
||||
"message": "Server se nepodařilo spojit"
|
||||
},
|
||||
@@ -695,24 +503,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Svět je použit"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Přidat účet"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Účet Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Nejste přihlášeni"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Odstranit účet"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Vyberte účet"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Přihlásit se do Minecraftu"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Poskytováno instancí"
|
||||
},
|
||||
@@ -725,9 +515,6 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchronizováno z instancí"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Poskytováno serverem"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Jen klientské mody mohou být přidány na server"
|
||||
},
|
||||
@@ -736,26 +523,5 @@
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader zprostředkovává server"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Soubor je zkontrolován pouze v případě, že je nahrán na Modrinth, a to bez ohledu na jeho formát (včetně formátu .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Toto varování již nezobrazovat"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Potvrdit instalaci"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "Přesto nainstalovat"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Malware se často šíří prostřednictvím souborů s modpacky, které se sdílejí na platformách jako Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Tento soubor jsme na Modrinthu nenašli. Důrazně doporučujeme instalovat pouze soubory z důvěryhodných zdrojů."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Upozornění na neznámý soubor"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,4 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Downloader Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Gem flere kørende instances"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Gør til primære instance"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Ingen instances kørende"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primære instance"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Vis flere kørende instances"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Stop instance"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Vis aktive downloads"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Vis instance"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Vis logs"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Avanceret rendering"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Vælg dit foretrukne farvetema til Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Farvetema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Skift hvilken side launcheren åbner i."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Hjem"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Bibliotek"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Standard startside"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Slår nametagget over din spiller på skins siden fra."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Gem nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Inkluderer de sidste verdener i afsnittet \"Hop tilbage\" på startsiden."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Hop tilbage ind i verdener"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimer launcheren når Minecraft starter."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimer launcher"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Vælg en mulighed"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Hvis du prøver at installere en Modrinth Pack fil (.mrpack) som ikke er hosted på Modrinth, så skal vi nok sikre os du forstår riskoen før du installere den."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Advar mig før jeg installere en ukendt modpacks"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecraft authentication servere kan måske være nede lige nu. Tjek din internet forbindelse og prøv igen senere."
|
||||
},
|
||||
@@ -119,6 +35,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Modpack navn"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Vælg filer og mapper til at inkludere i pakken"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Versionsnummer"
|
||||
},
|
||||
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Authentifizierungsserver sind nicht erreichbar"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Server zu Instanz hinzufügen"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Zu Instanz hinzufügen"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Server zu deiner Instanz hinzufügen"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Zu Instanz hinzufügen"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Bereits hinzugefügt"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Zu Instanz zurückgehen"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Inhalte entdecken"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Bereits hinzugefügte Server ausblenden"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Bereits installierte Inhalte ausblenden"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Inhalt in Instanz installieren"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Das hinzufügen von Inhalten kann Kompatibilitätsprobleme beim beitreten eines Servers verursachen. Sämtliche hinzugefügte Inhalten gehen ausserdem beim aktualisieren der Server Instanz-Inhalte verloren."
|
||||
"app.browse.server.install": {
|
||||
"message": "Installieren"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Installiert"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Wird installiert"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Modpack exportieren"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "\"{file}\" einschliessen?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Modpaketname"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Modpaketname"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Konfiguriere, welche Dateien in diesem Export miteinbezogen werden"
|
||||
"message": "Wähle Dateien und Ordner zum hinzufügen im Paket aus"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Versionsnummer"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Eine aktualisierung zum spielen von {name} ist benötigt. Bitte aktualisiere auf die neuste Version um das Spiel zu starten."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Dieses Projekt ist bereits installiert"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Zurück zum Entdecken"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Inhalt in Instanz installieren"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Entwicklermodus aktiviert."
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"message": "Zur primären Instanz machen"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Keine aktiven Instanzen"
|
||||
"message": "Keine Instanzen laufen"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
@@ -48,7 +48,7 @@
|
||||
"message": "Farbschema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Ändere die Kategorie, die beim Öffnen des Launchers angezeigt wird."
|
||||
"message": "Ändere die Seite, die beim Öffnen des Launchers angezeigt wird."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Start"
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Authentifizierungsserver sind nicht erreichbar"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Server zu Instanz hinzufügen"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Server zur Instanz hinzufügen"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Zu Instanz hinzufügen"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Server zu deiner Instanz hinzufügen"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Zur Instanz hinzufügen"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Bereits hinzugefügt"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Zurück zur Instanz"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Inhalte entdecken"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Bereits hinzugefügte Server ausblenden"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Bereits installierte Inhalte ausblenden"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Inhalt in Instanz installieren"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Das Hinzufügen von Inhalten kann die Kompatibilität beim Beitreten zum Server beeinträchtigen. Außerdem gehen alle hinzugefügten Inhalte verloren, wenn du die Inhalte der Serverinstanz aktualisierst."
|
||||
"app.browse.server.install": {
|
||||
"message": "Installieren"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Installiert"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Wird installiert"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Modpack exportieren"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "\"{file}\" einschließen?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Modpackname"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Modpackname"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Konfiguriere, welche Dateien in diesen Export enthalten sind"
|
||||
"message": "Wähle Dateien und Ordner aus, die in das Paket sollen"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Versionsnummer"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Zum Spielen von {name} ist eine Aktualisierung erforderlich. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Dieses Projekt ist bereits installiert"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Zurück zum Entdecken"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Inhalt in Instanz installieren"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Entwicklermodus aktiviert."
|
||||
},
|
||||
@@ -525,7 +519,7 @@
|
||||
"message": "Wird gelöscht..."
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-button": {
|
||||
"message": "Duplizieren"
|
||||
"message": "Kopieren"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-button.tooltip.installing": {
|
||||
"message": "Kann während der Installation nicht dupliziert werden."
|
||||
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Cannot reach authentication servers"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Adding server to instance"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Add server to instance"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Add to an instance"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Add servers to your instance"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Add to instance"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Already added"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Back to instance"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Discover content"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Hide already added servers"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Hide already installed content"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Install content to instance"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content."
|
||||
"app.browse.server.install": {
|
||||
"message": "Install"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Installed"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installing"
|
||||
@@ -299,15 +305,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "An update is required to play {name}. Please update to the latest version to launch the game."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "This project is already installed"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Back to discover"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Install content to instance"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Developer mode enabled."
|
||||
},
|
||||
|
||||
@@ -12,43 +12,43 @@
|
||||
"message": "Hacer instancia principal"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "No hay instancias en ejecución"
|
||||
"message": "Ninguna instancia en ejecución"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Desconectado"
|
||||
"message": "Sin conexión"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instancia principal"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostrar más instancias en ejecución"
|
||||
"message": "Mostrar mas instancias en ejecución"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Detener instancia"
|
||||
"message": "Finalizar instancia"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Ver descargas en curso"
|
||||
"message": "Mostrar descargas activas"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Ver instancia"
|
||||
"message": "Mostrar instancia"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Ver registros"
|
||||
"message": "Mostrar registros"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Activa el renderizado avanzado, como los efectos de desenfoque, que pueden afectar el rendimiento sin aceleración por hardware."
|
||||
"message": "Habilita el renderizado avanzado, como los efectos de desenfoque, que pueden causar problemas de rendimiento sin renderizado acelerado por hardware."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderizado avanzado"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Selecciona el tema de color que prefieras para la Modrinth App."
|
||||
"message": "Seleccione su tema de color preferido para Modrinth en este dispositivo."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Color de la interfaz"
|
||||
"message": "Tema de color"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Cambia la página en la que se abre el launcher."
|
||||
"message": "Cambia la página en la que el launcher se abre en."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Inicio"
|
||||
@@ -57,28 +57,28 @@
|
||||
"message": "Librería"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Página de inicio predeterminada"
|
||||
"message": "Página predeterminada"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Desactiva la etiqueta de nombre arriba de tu personaje en la página de skins."
|
||||
"message": "Desactiva la etiqueta arriba de tu personaje en la página de skins."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Ocultar etiqueta de nombre"
|
||||
"message": "Ocultar etiqueta"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Incluye los mundos recientes en la sección \"Volver a jugar\" de la página de inicio."
|
||||
"message": "Incluye los mundos más recientes en la sección \"Vuelve a jugar tus mundos\" en la página de inicio."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Volver a jugar mundos"
|
||||
"message": "Vuelve a jugar tus mundos"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimiza el launcher al iniciar un proceso de Minecraft."
|
||||
"message": "Minimiza el launcher cuando un proceso de Minecraft empieza."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimizar launcher"
|
||||
"message": "Minimiza el launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Usar el borde de ventana del sistema (requiere reiniciar la aplicación)."
|
||||
"message": "Usa los sistemas de frame de Windows (se requiere resetear la aplicación)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Decoraciones nativas"
|
||||
@@ -87,16 +87,16 @@
|
||||
"message": "Selecciona una opción"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Permite mostrar u ocultar la barra lateral."
|
||||
"message": "Activa la posibilidad de alternar la barra lateral."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Mostrar u ocultar la barra lateral"
|
||||
"message": "Alternar barra lateral"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Si intentas instalar un paquete de Modrinth (.mrpack) que no está alojado en Modrinth, te explicaremos los riesgos antes de instalarlo."
|
||||
"message": "Si intentas instalar un archivo de paquete de Modrinth (.mrpack) que no esté alojado en Modrinth, te advertiremos de los riesgos antes de instalarlo."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Advertir antes de instalar modpacks desconocidos"
|
||||
"message": "Adviérteme antes de instalarme modpacks desconocidos"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Los servidores de autenticación de Minecraft pueden no estar funcionando en este momento. Verifica tu conexión a internet e inténtalo de nuevo más tarde."
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "No se puede acceder a los servidores de autenticación"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Añadiendo servidor a instancia"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Añadir servidor a instancia"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Añadir a una instancia"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Añade servidores a tu instancia"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Añadir a instancia"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Ya se ha añadido"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Volver a instancia"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Descubrir contenido"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Ocultar servidores ya añadidos"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Ocultar contenido instalado"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Instalar contenido a la instancia"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Añadir contenido puede romper la compatibilidad cuando te unes al servidor. Cualquier contenido añadido tambien se perderá cuando actualizes el contenido en la instancia del servidor."
|
||||
"app.browse.server.install": {
|
||||
"message": "Instalar"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Instalado"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalando"
|
||||
@@ -147,7 +153,7 @@
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Instalando modpack..."
|
||||
"message": "Instalando el modpack..."
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Introduce la descripción del modpack..."
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Exportar modpack"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "¿Incluir \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nombre del modpack"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Nombre del modpack"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Configura que archivos incluir en esta exportación"
|
||||
"message": "Selecciona archivos y carpetas para incluirlos en el pack"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Número de la versión"
|
||||
@@ -267,7 +270,7 @@
|
||||
"message": "Contenido requerido"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "Instalar para jugar"
|
||||
"message": "Instala para jugar"
|
||||
},
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "Instalar"
|
||||
@@ -291,7 +294,7 @@
|
||||
"message": "Ver contenidos"
|
||||
},
|
||||
"app.modal.update-to-play.header": {
|
||||
"message": "Actualizar para jugar"
|
||||
"message": "Actualiza para jugar"
|
||||
},
|
||||
"app.modal.update-to-play.update-required": {
|
||||
"message": "Actualización requerida"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Se requiere una actualización para jugar {name}. Por favor, actualiza a la versión más reciente para iniciar el juego."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Este proyecto está actualmente instalado"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Volver al explorador"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instalar contenido a la instancia"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modo desarrollador activado."
|
||||
},
|
||||
@@ -698,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "El mundo ya está en uso"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Añadir cuenta"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Cuenta de Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "No has inciado sesión"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Eliminar cuenta"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Seleccionar cuenta"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Inicia sesión en Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Proporcionado por la instancia"
|
||||
},
|
||||
@@ -744,7 +720,7 @@
|
||||
"message": "Un archivo solo es revisado si es subido a Modrinth, independientemente de su formato de archivo (incluyendo .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "No volver a mostrar esta advertencia"
|
||||
"message": "No mostrar esta advertencia otra vez"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Confirmar instalación"
|
||||
@@ -753,7 +729,7 @@
|
||||
"message": "Instalar de todos modos"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "El malware a menudo es distribuido mediante modpacks compartidos en aplicaciones como Discord."
|
||||
"message": "El malware es normalmente distribuido por archivos de modpack que son normalmente compartidas en aplicaciones como Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "No pudimos encontrar este archivo en Modrinth. Recomendamos solo instalar archivos de sitios de confianza."
|
||||
|
||||
@@ -104,6 +104,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "No se puede conectar con los servidores de autenticación"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Añadir servidor a la instancia"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Añadir servidor a tu instancia"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Añadir a la instancia"
|
||||
},
|
||||
@@ -125,9 +131,21 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Ocultar servidores ya añadidos"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Ocultar contenido instalado"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Instalar contenido a una instancia"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server.install": {
|
||||
"message": "Instalar"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Instalado"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalando"
|
||||
},
|
||||
@@ -152,6 +170,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Nombre del modpack"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Seleccione archivos y carpetas para incluir en el paquete"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Número de versión"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Todennuspalvelimiin ei saada yhteyttä"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Lisää palvelin instanssiin"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Lisää palvelimia instanssiisi"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Lisää instanssiin"
|
||||
},
|
||||
@@ -23,6 +29,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Löydä palvelimia"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Lataa sisältöä instanssiin"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Lisää modipaketin kuvaus..."
|
||||
},
|
||||
@@ -38,6 +47,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Modipaketin nimi"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Valitse tiedostot ja kansiot pakettiin"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Versio numero"
|
||||
},
|
||||
|
||||
@@ -1,109 +1,16 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Dina-download ang Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Mga downloads"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Itago ang mas marami pang tumatakbong instansiya"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Gumawa ng pangunahing instansiya"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Walang instansiya ang tumatakbo"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Pangunahing instansiya"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Ipakita ang mas marami pang tumatakbong instansiya"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Itigil ang instansiya"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Tignan ang mga active downloads"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Tignan ang instansiya"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Tignan ang logs"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Pagpapagana ng masalimuot na pagrerender katulad ng paglalabo na maaaring magdudulot ng mga isyu sa performance kapag walang hardware-acceleration."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Masalimuot na pagrerender"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Piliin ang iyong mas gustong kulay para sa Modrinth app."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema ng kulay"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Pagpapalit ng pahina na bubuksan ng launcher."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Tahanan"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Librarya"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Panimulang pahina ng paglapag"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Pagtatago ng nametag na nasa itaas ng iyong manlalaro sa loob ng pahina ng mga skin."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Itago ang nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Pagsasali ng mga kamakailang mundo sa seksiyong \"Jump back in\" sa pahina ng Tahanan."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Bumalik sa mga mundo"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Liitan ang launcher kung nag-start ang isang proseso ng Minecraft."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Liitin ang launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Gamitin ang system window frame (kailangan i-restart ang app)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Native decorations"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Pumili ng opsiyon"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Pagpapagana na mata-toggle ang sidebar."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "I-toggle ang sidebar"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Kung susubukan mong mag-install ng Modrinth Pack file (.mrpack) na hindi naka-host sa Modrinth, titiyakin namin na nakaaalam ka sa mga panganib bago ito ma-install."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Pakipaalala bago ko ma-install ang mga di-kilalang modpack"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Maaaring hindi maaabot ang mga authentication server ng Minecraft sa ngayon. Tingnan mo ang iyong internet connection at muling subukan mamaya."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Hindi maabot ang mga authentication server"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Idagdag ang server sa instansiya"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Idagdag ang mga server sa iyong instansiya"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Idagdag sa instansiya"
|
||||
},
|
||||
@@ -122,20 +29,8 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Tumuklas ng mga server"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Itago ang mga nailagay na mga servers"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Ini-install"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Ini-install ang modpack..."
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "I-install ang kontento sa instansiya"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Ilagay ang paglalarawan ng modpack..."
|
||||
@@ -146,9 +41,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Iluwas ang modpack"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Salihin ang \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Pangalan ng Modpack"
|
||||
},
|
||||
@@ -156,7 +48,7 @@
|
||||
"message": "Pangalan ng modpack"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Isaayos kung anong mga file ang isasali sa pagluwas"
|
||||
"message": "Pumili ng mga talaksan at folder na isasali sa pack"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Numero ng bersiyon"
|
||||
@@ -209,15 +101,6 @@
|
||||
"app.instance.worlds.add-server": {
|
||||
"message": "Magdagdag ng server"
|
||||
},
|
||||
"app.instance.worlds.browse-servers": {
|
||||
"message": "Mag-browse ng servers"
|
||||
},
|
||||
"app.instance.worlds.delete-world-description": {
|
||||
"message": "'{name}' ay **permanenteng mabubura**, at walang paraan upang maibalik muli."
|
||||
},
|
||||
"app.instance.worlds.delete-world-title": {
|
||||
"message": "Sigurado ka bang gusto mong permanenteng burahin ang world na ito?"
|
||||
},
|
||||
"app.instance.worlds.filter-modded": {
|
||||
"message": "Modded"
|
||||
},
|
||||
@@ -230,18 +113,6 @@
|
||||
"app.instance.worlds.filter-vanilla": {
|
||||
"message": "Vanilla"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-description": {
|
||||
"message": "Maglagay ng server o mag-browse upang mag-simula"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "Walang servers o worlds ang nalalagay"
|
||||
},
|
||||
"app.instance.worlds.remove-server-description": {
|
||||
"message": "'{name}' ay matatangal sa iyong listahan, pati sa loob ng laro, at walang paraan upang maibalik ito."
|
||||
},
|
||||
"app.instance.worlds.remove-server-description-with-address": {
|
||||
"message": "'{name}' ({address}) ay matatangal sa iyong listahan, pati sa loob ng laro, at walang paraan upang maibalik ito."
|
||||
},
|
||||
"app.instance.worlds.remove-server-title": {
|
||||
"message": "Sigurado ka bang gusto mong tanggalin ang {name}?"
|
||||
},
|
||||
@@ -362,15 +233,9 @@
|
||||
"app.world.world-item.incompatible-version": {
|
||||
"message": "Di-magkatugmang bersiyong {version}"
|
||||
},
|
||||
"app.world.world-item.not-played-yet": {
|
||||
"message": "Hindi pa nalalaro"
|
||||
},
|
||||
"app.world.world-item.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.world.world-item.players-online": {
|
||||
"message": "{count} online"
|
||||
},
|
||||
"friends.action.add-friend": {
|
||||
"message": "Magdagdag ng kaibigan"
|
||||
},
|
||||
@@ -470,12 +335,6 @@
|
||||
"instance.edit-world.title": {
|
||||
"message": "Baguhin ang mundo"
|
||||
},
|
||||
"instance.files.adding-files": {
|
||||
"message": "Idinadagdag ang mga file ({completed}/{total})"
|
||||
},
|
||||
"instance.files.save-as": {
|
||||
"message": "I-save bilang..."
|
||||
},
|
||||
"instance.server-modal.address": {
|
||||
"message": "Adres"
|
||||
},
|
||||
@@ -677,24 +536,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Ginagamit ang mundo"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Maglagay ng account"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraft account"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Hindi pa naka sign in"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Itanggal ang account"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Pumili ng account"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Mag sign in kay Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Sagot na ng instansiya"
|
||||
},
|
||||
@@ -718,26 +559,5 @@
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Ang loader ay handog na ng server"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Ang file ay nasusuri lamang kapag ito ay na-upload sa Modrinth, walang pili sa file format nito (kabilang na ang .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Huwag ipakita ang babalang ito ulit"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Kumpirmahin ang installation"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "I-install parin"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Ang malware ay madalas naidadala sa mga modpack files sa pamamagitan ng pag-bigay ng mga ito sa mga platforms kagaya ng Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Hindi namin mahanap ang file na ito sa Modrinth. Mahalagang mag-install ka lamang ng files galing sa mga mapagkakatiwalang sources."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Hindi kilalang file"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Téléchargement de Java {version} en cours"
|
||||
"message": "Java version {version} en cours de téléchargement"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Téléchargements"
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Impossible de contacter les serveurs d'authentification"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Ajouter le serveur à l'instance"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Ajouter à une instance"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Ajouter des serveurs à votre instance"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Ajouter à l'instance"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Déjà ajouté"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Retour à l'instance"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Découvrir du contenu"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Masquer les serveurs déjà ajoutés"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Masquer le contenu déjà ajouté"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Installer du contenu à l'instance"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Ajouter du contenu peut briser la comptabilité en rejoignant le serveur. Tout contenu en plus sera également perdu lorsque vous mettrez à jour le contenu de l'instance du serveur."
|
||||
"app.browse.server.install": {
|
||||
"message": "Installer"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Installé"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installation en cours"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Exporter le modpack"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Inclure « {file} » ?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nom du modpack"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Nom du modpack"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Configurez quels fichiers sont inclus dans cette exportation"
|
||||
"message": "Sélectionnez des fichiers et des dossiers à inclure dans le pack"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Numéro de version"
|
||||
@@ -189,7 +192,7 @@
|
||||
"message": "Supprimer l'instance"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "Ce modpack est déjà installé sur l'instance <bold>{instanceName}</bold>. Êtes-vous sûr·e de vouloir le dupliquer ?"
|
||||
"message": "Ce modpack est déjà installé sur l'instance <bold>{instanceName}</bold>. Êtes-vous sûr.e de vouloir le dupliquer ?"
|
||||
},
|
||||
"app.instance.modpack-already-installed.create": {
|
||||
"message": "Créer"
|
||||
@@ -228,7 +231,7 @@
|
||||
"message": "« {name} » sera supprimé **pour toujours**, et il sera impossible de le récupérer."
|
||||
},
|
||||
"app.instance.worlds.delete-world-title": {
|
||||
"message": "Êtes-vous sûr·e de vouloir supprimer ce monde pour toujours ?"
|
||||
"message": "Êtes-vous sûr.e de vouloir supprimer ce monde pour toujours ?"
|
||||
},
|
||||
"app.instance.worlds.filter-modded": {
|
||||
"message": "Moddé"
|
||||
@@ -255,7 +258,7 @@
|
||||
"message": "« {name} » ({address}) sera retiré de votre liste, y compris en jeu, et il sera impossible de le récupérer."
|
||||
},
|
||||
"app.instance.worlds.remove-server-title": {
|
||||
"message": "Êtes-vous sûr·e de vouloir retirer {name} ?"
|
||||
"message": "Êtes-vous sûr.e de vouloir retirer {name} ?"
|
||||
},
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "Rechercher {count} mondes..."
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Une mise à jour est requise pour jouer à {name}. Veuillez mettre à jour à la dernière version pour lancer le jeu."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Ce projet a déjà été installé"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Retour vers la page découvrir"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installer du contenu à l'instance"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Mode développeur activé."
|
||||
},
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "לא ניתן לגשת לשרתי האימות"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "הוסף שרת להתקנה"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "הוסף שרתים להתקנה שלך"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "הוספה להתקנה"
|
||||
},
|
||||
@@ -23,6 +29,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "גלה שרתים"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "הוספת התוכן להתקנה"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "הזן את תיאור חבילת המודים..."
|
||||
},
|
||||
@@ -38,6 +47,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "שם חבילת המודים"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "בחר קבצים ותיקיות להכללה בחבילה הזו"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "מספר גרסה"
|
||||
},
|
||||
|
||||
@@ -6,97 +6,19 @@
|
||||
"message": "Letöltések"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "További futó profilok elrejtése"
|
||||
"message": "További futó példányok elrejtése"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Beállítás elsődleges profilként"
|
||||
"message": "Elsődleges példány létrehozása"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Nincsenek futó profilok"
|
||||
"message": "Nincsenek futó példányok"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Elsődleges profil"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "További futó profilok megjelenítése"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Profil megállítása"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Aktív letöltések megtekintése"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Profil megtekintése"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Naplók megtekintése"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Lehetővé teszi olyan speciális megjelenítési effektusok használatát, mint például az elmosódás, amelyek hardveres gyorsítás nélkül teljesítménycsökkenést okozhatnak."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Fejlett renderelés"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Válaszd ki a Modrinth Apphoz a kívánt színsémát."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Téma"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Állítsd be, hogy az indító melyik oldalon nyíljon meg."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Kezdőlap"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Könyvtár"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Alapértelmezett nyitóoldal"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Letiltja a játékosod felett megjelenő névcímkét a Skinek oldalon."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Névcímke eltüntetése"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "A kezdőlap „Folytasd itt” részében a legújabb világok is megtalálhatók."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "„Folytasd itt” a világokat is"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimalizáld az indítót a Minecraft indításakor."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Indító minimalizálása"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "A rendszer ablakkeret használata (az alkalmazás újraindítása szükséges)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Rendszer ablakkeret használata"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Válassz egy lehetőséget"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Lehetővé teszi az oldalsáv be- és kikapcsolását."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Oldalsáv megjelenítése/elrejtése"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Ha olyan Modrinth Csomagfájlt (.mrpack) próbálsz telepíteni, amely nem a Modrinth szerverén található, a telepítés előtt gondoskodunk arról, hogy tisztában legyél a kockázatokkal."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Figyelmeztess, mielőtt ismeretlen modcsomagokat telepítenék"
|
||||
"message": "Elsődleges példány"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "A Minecraft hitelesítő szerverek lehet, hogy nem üzemelnek. Bizonyosodj meg róla, hogy van internetkapcsolatod és próbáld meg újra."
|
||||
@@ -104,11 +26,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Nem lehet elérni a hitelesítési kiszolgálókat"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Szerver hozzáadása a profilhoz"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Hozzáadás egy profilhoz"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Szerverek hozzáadása a profilodhoz"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Hozzáadás a profilhoz"
|
||||
@@ -122,9 +44,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Már hozzá van adva"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Vissza a profilhoz"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Tartalom böngészése"
|
||||
},
|
||||
@@ -134,21 +53,24 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Már hozzáadott szerverek elrejtése"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Már telepített tartalom elrejtése"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Tartalom letöltése a profilhoz"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modcsomagok"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "A tartalom hozzáadása ronthatja a kompatibilitást a szerverhez való csatlakozáskor. A hozzáadott tartalom elveszik, ha frissíted a szerverprofil tartalmát."
|
||||
"app.browse.server.install": {
|
||||
"message": "Letöltés"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Letöltve"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Letöltés..."
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Modcsomag telepítése..."
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Írd be a modcsomag leírását..."
|
||||
},
|
||||
@@ -158,9 +80,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Modcsomag exportálása"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Tartalmaz „{file}” fájlokat?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "A modcsomag neve"
|
||||
},
|
||||
@@ -168,7 +87,7 @@
|
||||
"message": "A modcsomag neve"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Állítsd be, hogy mely fájlok kerüljenek bele az exportba"
|
||||
"message": "Válaszd ki a csomagba felveendő fájlokat és mappákat"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Verziószám"
|
||||
@@ -297,16 +216,7 @@
|
||||
"message": "Frissítés szükséges"
|
||||
},
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Frissítés szükséges ehhez: {name}. Kérlek, frissíts a legújabb verzióra a játék elindításához."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Ez a projekt már telepítve van"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Vissza a böngészéshez"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Tartalom telepítése a profilba"
|
||||
"message": "\nFrissítés szükséges ehhez: {name}. Kérjük, frissíts a legújabb verzióra a játék elindításához"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Fejlesztői mód bekapcsolva."
|
||||
@@ -333,10 +243,10 @@
|
||||
"message": "Erőforráskezelés"
|
||||
},
|
||||
"app.update-popup.body": {
|
||||
"message": "A Modrinth App v{version} telepítésre kész! Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás a bezárásakor automatikusan frissül."
|
||||
"message": "A Modrinth App v{version} telepítésre kész! Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás bezárásakor automatikusan frissül."
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "A Modrinth App v{version} letöltése befejeződött. Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás a bezárásakor automatikusan frissül."
|
||||
"message": "A Modrinth App v{version} letöltése befejeződött. Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás bezárásakor automatikusan frissül."
|
||||
},
|
||||
"app.update-popup.body.linux": {
|
||||
"message": "A Modrinth App v{version} elérhető. Használd a csomagkezelőt a legújabb funkciók és javítások frissítéséhez!"
|
||||
@@ -669,7 +579,7 @@
|
||||
"message": "Ne mutasd a kezdőlapon"
|
||||
},
|
||||
"instance.worlds.game_already_open": {
|
||||
"message": "A profil már el van indítva"
|
||||
"message": "A profil már meg van nyitva"
|
||||
},
|
||||
"instance.worlds.hardcore": {
|
||||
"message": "Hardcore mód"
|
||||
@@ -698,24 +608,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "A világ használatban van"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Fiók hozzáadása"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraft-fiók"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Nem vagy bejelentkezve"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Fiók törlése"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Fiók kiválasztása"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Bejelentkezés a Minecraftba"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "A profil által van megadva"
|
||||
},
|
||||
@@ -739,26 +631,5 @@
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "A betöltőt a szerver biztosítja"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Egy fájlt csak akkor vizsgálunk meg, ha azt feltöltik a Modrinthra, függetlenül a fájlformátumtól (beleértve a .mrpack formátumot is)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Ne mutasd újra ezt a figyelmeztetést"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Telepítés megerősítése"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "Letöltés mindenképpen"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "A rosszindulatú programokat gyakran modcsomag-fájlokon keresztül terjesztik, például a Discordhoz hasonló platformokon történő megosztás révén."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Ezt a fájlt nem találtuk meg a Modrinthon. Határozottan javasoljuk, hogy kizárólag megbízható forrásokból származó fájlokat telepíts."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Figyelmeztetés ismeretlen fájlról"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,73 +3,25 @@
|
||||
"message": "Mengunduh Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Unduhan"
|
||||
"message": "Mengunduh"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Sembunyikan lebih banyak instans yang sedang berjalan"
|
||||
"message": "Sembunyikan lebih banyak instance yang sedang berjalan"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Jadikan sebagai instans utama"
|
||||
"message": "Jadikan sebagai instance utama"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Tidak ada instans yang sedang berjalan"
|
||||
"message": "Tidak ada instance yang sedang berjalan"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Luring"
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instans utama"
|
||||
"message": "Instance utama"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Tampilkan lebih banyak instans yang sedang berjalan"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Hentikan instans"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Lihat pengunduhan berlangsung"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Lihat instans"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Lihat catatan"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Menghidupkan renderasi tingkat lanjut seperti efek buram yang dapat menyebabkan masalah kinerja tanpa renderasi yang dipercepat perangkat keras."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderasi tingkat lanjut"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Pilih tema warna pilihan Anda untuk Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema warna"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Ubah halaman utama yang dibuka oleh peluncur."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Beranda"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Pustaka"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Mematikan label nama di atas wujud pemain Anda pada halaman rupa."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Sembunyikan label nama"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Kecilkan pluncur ketika proses Minecraft berjalan."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Kecilkan peluncur"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Pilih opsi"
|
||||
"message": "Tampilkan lebih banyak instance yang sedang berjalan"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Server autentikasi Minecraft mungkin sedang tidak tersedia saat ini. Periksa koneksi internet Anda dan coba lagi nanti."
|
||||
@@ -77,11 +29,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Tidak dapat terhubung ke server autentikasi"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Menambah server ke instans"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Tambah server ke instans"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Tambah ke instans"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Tambah server ke instans Anda"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Tambah ke instans"
|
||||
@@ -95,29 +47,14 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Telah ditambahkan"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Kembali ke instans"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Temukan konten"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Temukan server"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Sembunyikan server yang sudah ditambah"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Paket Mod"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Memasang"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Memasang paket mod..."
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Pasang konten ke instans"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Masukkan deskripsi paket mod..."
|
||||
@@ -128,9 +65,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Ekspor paket mod"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Sertakan \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nama Paket Mod"
|
||||
},
|
||||
@@ -138,7 +72,7 @@
|
||||
"message": "Nama paket mod"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Konfigurasikan berkas mana yang disertakan dalam pengeksporan ini"
|
||||
"message": "Pilih berkas dan folder yang ingin dimasukkan ke paket"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Nomor versi"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version}"
|
||||
"message": "Scaricando Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Download"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Mostra meno istanze in esecuzione"
|
||||
"message": "Nascondi le istanze in esecuzione"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Rendi primaria"
|
||||
@@ -21,19 +21,19 @@
|
||||
"message": "Primaria"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostra più istanze in esecuzione"
|
||||
"message": "Mostra le istanze in esecuzione"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Ferma l'istanza"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Mostra i download attivi"
|
||||
"message": "Vedi i download attivi"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Mostra l'istanza"
|
||||
"message": "Vedi l'istanza"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Mostra i log"
|
||||
"message": "Vedi i log"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Abilita alcuni effetti come la sfocatura, ma può causare problemi di prestazioni senza accelerazione hardware."
|
||||
@@ -63,28 +63,28 @@
|
||||
"message": "Rimuove il nametag sopra la testa del giocatore nella pagina delle skin."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Nascondi il nome"
|
||||
"message": "Nascondi nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Elenca i mondi recenti nella sezione \"Avvio rapido\" della pagina Home."
|
||||
"message": "Aggiunge i mondi recenti nella sezione \"Avvio rapido\" della pagina Home."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Avvio rapido nei mondi"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Riduci il launcher a icona quando si avvia Minecraft."
|
||||
"message": "Riduci il launcher quanto si avvia Minecraft."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Riduci a icona"
|
||||
"message": "Riduci il launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Usa la cornice di sistema (riavvio necessario)."
|
||||
"message": "Usa la cornice di sistema (riavvio richiesto)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Decorazioni native"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Scegli una pagina"
|
||||
"message": "Seleziona un'opzione"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Scegli se mostrare o nascondere la barra laterale."
|
||||
@@ -93,10 +93,10 @@
|
||||
"message": "Abilita la barra laterale"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Visti i rischi, apparirà un avviso prima di installare un file Modrinth Pack (.mrpack) non proveniente da Modrinth."
|
||||
"message": "Apparirà un avviso prima di installare un file Modrinth Pack (.mrpack) non proveniente da Modrinth, così da conoscerne i rischi."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Avvisami se installo pacchetti sconosciuti"
|
||||
"message": "Avvisami prima di installare pacchetti sconosciuti"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "I server di autenticazione di Minecraft stanno riscontrando problemi. Controlla la tua connessione a Internet e riprova più tardi."
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Impossibile raggiungere i server di autenticazione"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Aggiungendo il server all'istanza"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Aggiungi server all'istanza"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Aggiungi a un'istanza"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Aggiungi i server alla tua istanza"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Aggiungi all'istanza"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Già aggiunto"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Torna all'istanza"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Esplora i contenuti"
|
||||
},
|
||||
@@ -134,14 +131,23 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Nascondi server già aggiunti"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Nascondi contenuti già installati"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Installa contenuti nell'istanza"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Pacchetti di mod"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Aggiungere dei contenuti potrebbe portare problemi entrando nel server. Qualsiasi contenuto aggiunto sarà anche perso aggiornando i contenuti dell'istanza del server."
|
||||
"app.browse.server.install": {
|
||||
"message": "Installa"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Installato"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installazione"
|
||||
"message": "Installando"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Esporta pacchetto"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Includere \"{file}\" nell'esportazione?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nome del pacchetto"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Nome del pacchetto"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Seleziona i file da includere in questa esportazione"
|
||||
"message": "Seleziona i file e le cartelle da includere nel pacchetto"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Numero di versione"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "{name} richiede degli aggiornamenti. Installa l'ultima versione per poter giocare."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Questo progetto è già stato installato"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Torna a Esplora"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installa il contenuto nell'istanza"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modalità sviluppatore attiva."
|
||||
},
|
||||
@@ -447,7 +441,7 @@
|
||||
"message": "Nessuna amicizia corrisponde a ''{query}''"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "Cerca tra le amicizie..."
|
||||
"message": "Cerca amicizie..."
|
||||
},
|
||||
"friends.section.heading": {
|
||||
"message": "{title} - {count}"
|
||||
@@ -486,7 +480,7 @@
|
||||
"message": "Mondo di Minecraft"
|
||||
},
|
||||
"instance.edit-world.reset-icon": {
|
||||
"message": "Ripristina icona"
|
||||
"message": "Resetta icona"
|
||||
},
|
||||
"instance.edit-world.title": {
|
||||
"message": "Modifica mondo"
|
||||
@@ -546,7 +540,7 @@
|
||||
"message": "Cambia icona"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.select": {
|
||||
"message": "Scegli un'icona"
|
||||
"message": "Seleziona icona"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups": {
|
||||
"message": "Gruppi della libreria"
|
||||
@@ -741,7 +735,7 @@
|
||||
"message": "Il loader è determinato dal server"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Solo i file caricati su Modrinth vengono esaminati, qualunque sia il loro formato (.mrpack inclusi)."
|
||||
"message": "Un file è esaminato quando è caricato su Modrinth, indipendentemente dal suo formato (.mrpack inclusi)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Non mostrare più questo avviso"
|
||||
@@ -753,10 +747,10 @@
|
||||
"message": "Installa comunque"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Spesso i malware vengono nascosti nei pacchetti di mod, poi distribuiti su piattaforme come Discord."
|
||||
"message": "Molto spesso i malware sono distribuiti attraverso i pacchetti di mod, distribuiti in piattaforme come Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Non è stato possibile trovare questo file su Modrinth. Consigliamo di installare file solo da fonti attendibili."
|
||||
"message": "Non siamo riusciti a trovare questo file su Modrinth. Consigliamo di installare file solo da fonti di cui ti fidi."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Tipo di file sconosciuto"
|
||||
|
||||
@@ -104,6 +104,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "認証サーバーにアクセスできません"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "サーバーをインスタンスに追加"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "サーバーを自身のインスタンスに追加"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "インスタンスに追加"
|
||||
},
|
||||
@@ -125,9 +131,21 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "既に追加済みのサーバーを非表示にする"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "既にインストールされているコンテンツを非表示にする"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "コンテンツをインスタンスにインストールする"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modパック"
|
||||
},
|
||||
"app.browse.server.install": {
|
||||
"message": "インストール"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "インストール済み"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "インストール中"
|
||||
},
|
||||
@@ -152,6 +170,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Modパック名"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "パックに含めるファイルとフォルダーを選択"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "バージョン番号"
|
||||
},
|
||||
@@ -671,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "ワールドは使用中"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "アカウントを追加"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraftアカウント"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "サインインしていません"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "アカウントを一覧から削除"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "アカウントを選択"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Minecraftにサインイン"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "インスタンスによる条件"
|
||||
},
|
||||
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "인증 서버에 연결할 수 없습니다"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "인스턴스에 서버 추가"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "인스턴스에 추가"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "인스턴스에 서버 추가"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "인스턴스에 추가"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "이미 추가됨"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "인스턴스로 돌아가기"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "콘텐츠 탐색하기"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "이미 추가된 서버 숨기기"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "이미 설치된 콘텐츠 숨기기"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "인스턴스에 콘텐츠 설치"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "모드팩"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "콘텐츠를 추가하면 서버 접속 시 호환이 안 될 수 있습니다. 또한 서버 인스턴스 콘텐츠를 업데이트하면 임의로 추가된 콘텐츠는 모두 손실됩니다."
|
||||
"app.browse.server.install": {
|
||||
"message": "설치"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "설치됨"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "설치 중"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "모드팩 내보내기"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "\"{file}\"(을)를 포함할까요?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "모드팩 이름"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "모드팩 이름"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "내보내기에 어느 파일이 포함될지 구성"
|
||||
"message": "팩에 포함할 파일과 폴더 선택"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "버전 구분"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "{name}을(를) 플레이하려면 업데이트가 필요합니다. 게임을 실행하려면 최신 버전으로 업데이트해 주세요."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "이미 설치되어 있음"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "탐색으로 돌아가기"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "인스턴스에 콘텐츠 설치"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "개발자 모드가 활성화되었습니다."
|
||||
},
|
||||
@@ -698,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "사용 중인 세계"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "계정 추가"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "마인크래프트 계정"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "로그인하지 않음"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "계정 제거"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "계정 선택"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "마인크래프트 계정으로 로그인"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "인스턴스에서 관리"
|
||||
},
|
||||
|
||||
@@ -5,110 +5,20 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Muat Turun"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Sembunyikan lebih banyak pemasangan yang berjalan"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Jadikan pemasangan utama"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Tiada pemasangan yang berjalan"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Luar Talian"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Pemasangan utama"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Tunjukkan lebih banyak pemasangan yang berjalan"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Hentikan pemasangan"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Lihat muat turun aktif"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Lihat pemasangan"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Lihat log"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Mendayakan pemaparan lanjutan seperti kesan kabur yang boleh menyebabkan masalah prestasi tanpa pemaparan dipercepatkan perkakasan."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Pemaparan lanjutan"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Pilih tema warna pilihan anda untuk Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema warna"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Tukar laman utama yang dibuka oleh pelancar."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Laman utama"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Pustaka"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Laman pendaratan lalai"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Menyahdayakan tanda nama di atas pemain anda pada halaman kekulit."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Sembunyikan tanda nama"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Sertakan dunia terkini dalam bahagian \"Lompat kembali\" pada Laman Utama."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Lompat kembali ke dalam dunia"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Meminimumkan pelancar apabila proses Minecraft bermula."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimumkan pelancar"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Gunakan bingkai tetingkap sistem (aplikasi perlu dimulakan semula)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Hiasan asli"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Pilih satu pilihan"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Membolehkan keupayaan untuk menogol bar sisi."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Togol bar sisi"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Jika anda cuba memasang fail Modrinth Pack (.mrpack) yang tidak dihoskan pada Modrinth, kami akan memastikan anda memahami risikonya sebelum memasangnya."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Beri saya amaran sebelum memasang pek mod yang tidak diketahui"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Pelayan pengesahan Minecraft mungkin sedang tergendala sekarang. Periksa sambungan internet anda dan cuba lagi kemudian."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Tidak dapat mencapai pelayan pengesahan"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Menambah pelayan ke dalam pemasangan"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Tambahkan pelayan ke dalam pemasangan"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Tambahkan ke dalam pemasangan"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Tambahkan pelayan ke dalam pemasangan anda"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Tambahkan ke dalam pemasangan"
|
||||
@@ -122,32 +32,14 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Sudah ditambah"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Kembali ke pemasangan"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Temui kandungan"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Temui pelayan"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Sembunyikan pelayan yang sudah ditambah"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Pek Mod"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Menambah kandungan boleh merosakkan keserasian apabila menyertai pelayan. Sebarang kandungan yang ditambah juga akan hilang apabila anda mengemas kini kandungan pemasangan pelayan."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Sedang memasang"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Sedang memasang pek mod..."
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Pasang kandungan ke dalam pemasangan"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Masukkan keterangan pek mod..."
|
||||
@@ -158,9 +50,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Eksport pek mod"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Sertakan \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nama Pek Mod"
|
||||
},
|
||||
@@ -168,7 +57,7 @@
|
||||
"message": "Nama pek mod"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Konfigurasikan fail yang disertakan dalam eksport ini"
|
||||
"message": "Pilih fail dan folder untuk disertakan dalam pek mod"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Nombor versi"
|
||||
@@ -299,12 +188,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Kemas kini diperlukan untuk memainkan {name}. Sila kemas kini kepada versi terkini untuk melancarkan permainan."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Projek ini sudah dipasang"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Pasang kandungan ke dalam pemasangan"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Mod pembangun didayakan."
|
||||
},
|
||||
@@ -695,24 +578,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Dunia sedang digunakan"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Tambah akaun"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Akaun Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Tidak didaftar masuk"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Alih keluar akaun"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Pilih akaun"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Daftar masuk ke dalam Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Disediakan oleh pemasangan ini"
|
||||
},
|
||||
@@ -736,26 +601,5 @@
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Pemuat adalah disediakan oleh pelayan"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Sesuatu fail hanya disemak jika ia dimuat naik ke Modrinth, tanpa mengira format failnya (termasuk .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Jangan tunjukkan amaran ini lagi"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Sahkan pemasangan"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "Pasangkan juga"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Perisian hasad sering diedarkan melalui fail pek mod dengan berkongsinya di platform seperti Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Kami tidak dapat menemui fail ini di Modrinth. Kami sangat mengesyorkan anda untuk hanya memasang fail daripada sumber yang anda percayai."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Amaran fail tidak diketahui"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +1,15 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Java {version} aan het downloaden"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Verberg meer actieve instanties"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Maak primaire instantie"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Geen actieve instanties"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primaire instantie"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Toon meer actieve instanties"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Stop instantie"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Bekijk actieve downloads"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Bekijk instantie"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Bekijk logs"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Activeert geavanceerde weergaves zoals blur effecten die mogelijke prestatieproblemen opleveren zonder hardware-versnelde weergeving."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Geavanceerde weergave"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Selecteer je voorkeur kleurenthema voor de Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Kleurenthema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Wijzig de pagina waarop de launcher opent."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Home"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Bibliotheek"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Standaard openingspagina"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Schakelt het nametag boven jouw speler op de skins pagina uit."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Verberg nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Inclusief recente werelden in het gedeelte \"Ga terug\" op de startpagina."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Ga terug in werelden"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimaliseer de launcher als een Minecraft proces wordt gestart."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimaliseer launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Gebruik systeem venster frame (app opnieuw starten vereist)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Oorspronkelijke versieringen"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Selecteer een optie"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Schakelt de mogelijkheid om de zijbalk in- of uit te schakelen in."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Schakel zijbalk"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Als je probeert een Modrinth Pack-bestand (.mrpack) te installeren dat niet op Modrinth zelf wordt gehost, zorgen we ervoor dat je de risico's begrijpt voordat je het installeert."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Waarschuw mij voordat ik onbekende modpacks installeer"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecraft authenticatie servers zijn mogelijk offline. Controleer je internetverbinding en probeer opnieuw later."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Authenticatieservers kunnen niet worden bereikt"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Server toevoegen aan instantie"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Voeg server aan instantie toe"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Voeg toe aan een instantie"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Voeg servers aan instantie toe"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Voeg aan instantie toe"
|
||||
@@ -122,32 +23,14 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Al toegevoegd"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Terug naar instantie"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Ontdek content"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Ontdek servers"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Verberg al toegevoegde servers"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Het toevoegen van content kan de compatibiliteit verbreken bij het verbinden met de server. Alle toegevoegde inhoud gaat ook verloren wanneer u de content van de serverinstantie bijwerkt."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installeren"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Modpack aan het installeren..."
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Installeer inhoud naar instantie"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Voeg modpack beschrijving in..."
|
||||
@@ -158,9 +41,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Exporteer modpack"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Betrek \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Modpack Naam"
|
||||
},
|
||||
@@ -168,7 +48,7 @@
|
||||
"message": "Modpack naam"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Configureer welke bestanden bij deze export zijn betrokken"
|
||||
"message": "Selecteer bestanden en mappen om toe te voegen aan pack"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Versie nummer"
|
||||
@@ -299,12 +179,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Een update is vereist om {name} te spelen. Update naar de laatste versie om het spel te starten."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Dit project is al geïnstalleerd"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installeer content naar instantie"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Ontwikkelaarsmodus ingeschakeld."
|
||||
},
|
||||
@@ -695,24 +569,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Wereld wordt gebruikt"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Voeg account toe"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraft account"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Niet ingelogd"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Verwijder account"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Selecteer account"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Log in bij Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Aangeboden door de instantie"
|
||||
},
|
||||
@@ -736,26 +592,5 @@
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader is gegeven door de server"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Een bestand wordt alleen beoordeeld als het naar Modrinth is geüpload, ongeacht het bestandsformaat (inclusief .mrpack)."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Toon deze waarschuwing niet opnieuw"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "Bevestig installatie"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "Installeer toch"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Malware wordt vaak verspreid via modpack-bestanden door ze te delen op platforms zoals Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "We konden dit bestand niet vinden op Modrinth. We raden ten zeerste aan om alleen bestanden te installeren van bronnen die u vertrouwt."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "Waarschuwing voor onbekend bestand"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Nie udało się połączyć się z serwerami uwierzytelniania"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Dodawanie serwera do instancji"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Dodaj serwer do instancji"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Dodaj do instancji"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Dodaj serwery do swojej instancji"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Dodaj do instancji"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Już dodano"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Powrót do instancji"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Odkrywaj zawartość"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Ukryj już dodane serwery"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Ukryj już dodane zasoby"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Zainstaluj zasoby do instancji"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Paczki modów"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Dodawanie zawartości może zepsuć kompatybilność podczas dołączania na serwer. Jakakolwiek dodana zawartość zostanie stracona, gdy zaktualizujesz zawartość instancji serwera."
|
||||
"app.browse.server.install": {
|
||||
"message": "Zainstaluj"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Zainstalowano"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalowanie"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Eksportuj paczkę modów"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Dołączyć \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nazwa paczki modów"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Nazwa paczki modów"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Skonfiguruje jakie pliki zostaną załączone w tym eksporcie"
|
||||
"message": "Wybierz pliki i foldery, które mają znaleźć się w paczce"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Numer wersji"
|
||||
@@ -177,7 +180,7 @@
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Wszystkie dane z Twojej instancji zostaną trwale usunięte, w tym Twoje światy, pliki konfiguracji i jakakolwiek dodana zawartość."
|
||||
"message": "Wszystkie dane z Twojej instancji zostaną trwale usunięte, w tym Twoje światy, pliki konfiguracji i wszystkie zainstalowane treści."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "Tej akcji nie można odwrócić"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Aktualizacja jest wymagana, aby grać w {name}. Proszę zaktualizować do najnowszej wersji, aby uruchomić grę."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Ten projekt jest już zainstalowany"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Powróć do odkrywania"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Zainstaluj zasoby do instancji"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Tryb dewelopera włączony."
|
||||
},
|
||||
@@ -698,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Świat jest w użytku"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Dodaj konto"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Konto Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Nie zalogowano"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Usuń konto"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Wybierz konto"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Zaloguj się do Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Podane przez instancję"
|
||||
},
|
||||
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Não foi possível acessar os servidores de autenticação"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Adicionando servidor à instância"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Adicionar servidor à instância"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Adicionar a uma instância"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Adicionar servidores à sua instância"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Adicionar à instância"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Já adicionado"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Voltar a instância"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Descubra conteúdo"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Ocultar servidores já adicionados"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Ocultar conteúdo já adicionado"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Instalar conteúdo na instância"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Pacotes de mods"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Adicionar conteúdo pode quebrar a compatibilidade ao entrar no servidor. Qualquer conteúdo adicionado também será perdido ao atualizar o conteúdo da instância do servidor."
|
||||
"app.browse.server.install": {
|
||||
"message": "Instalar"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Instalado"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalando"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Exportar pacote de mods"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Incluir \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Nome do pacote de mods"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Nome do pacote de mods"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Configure quais arquivos serão incluídos nessa exportação"
|
||||
"message": "Selecione arquivos e pastas para incluir no pacote"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Número da versão"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "É necessária uma atualização para jogar {name}. Atualize para a versão mais recente para iniciar o jogo."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Este projeto já foi instalado"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Voltar ao descobrir"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instalar conteúdo para a instância"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modo de desenvolvedor ativado."
|
||||
},
|
||||
|
||||
@@ -1,67 +1,16 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "A descarregar a versão do java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Esconder mais instâncias a correr"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Transformar na instância principal"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Nenhuma instância a correr"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Instância principal"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Mostrar mais instâncias a correr"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Para instância"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Ver instâncias ativas"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Ver instância"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Ver logs"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderização avançada"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Seleciona a tua cor preferida para a Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Cor de tema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Mudar a página em que o launcher abre."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Casa"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Livraria"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Página de carregamento principal"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Os servidores de autenticação do Minecraft poderão estar em baixo de momento. Verifica a tua ligação à internet e tenta novamente mais tarde."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Não foi possível alcançar os servidores de autenticação"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Adicionar servidor à instância"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Adicionar servidores à tua instância"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Adicionar à instância"
|
||||
},
|
||||
@@ -80,6 +29,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Descobrir servidores"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Instalar conteúdo à instância"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Insere a descrição do modpack..."
|
||||
},
|
||||
@@ -95,6 +47,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Nome do Modpack"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Seleciona as pastas e ficheiros a incluir no pack"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Número da versão"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"message": "Скачивание Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Скачивание"
|
||||
"message": "Скачивания"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Скрыть другие активные сборки"
|
||||
@@ -104,12 +104,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Нет связи с серверами аутентификации"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Добавить сервер в сборку"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Добавление серверов в сборку"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Добавить в сборку"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Добавить в сборку"
|
||||
},
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Уже добавлено"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Вернуться к сборке"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Поиск проектов"
|
||||
},
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Скрывать добавленные"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Скрывать установленные"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Установка контента в сборку"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Сборки"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Добавление контента может нарушить совместимость при подключении к серверу. Также весь добавленный контент будет удалён при обновлении содержимого серверной сборки."
|
||||
"app.browse.server.install": {
|
||||
"message": "Установить"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Установлено"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Установка"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Экспорт сборки"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Добавить «{file}» в экспорт?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Название сборки"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Название сборки"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Выберите файлы для экспорта"
|
||||
"message": "Выберите файлы и папки для экспорта"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Номер версии"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Обновите {name} до последней версии, чтобы запустить игру."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Этот проект уже установлен"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Вернуться к поиску"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Установка контента в сборку"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Режим разработчика включён."
|
||||
},
|
||||
@@ -698,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Мир используется"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Добавить"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Учётная запись Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Вход не выполнен"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Удалить"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Выберите профиль"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Войти в Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Управляется сборкой"
|
||||
},
|
||||
@@ -741,7 +717,7 @@
|
||||
"message": "Загрузчик управляется сервером"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "Файлы любого формата проверены на безопасность, если они загружены на Modrinth (в том числе файлы .mrpack)."
|
||||
"message": "Проверяются только файлы, загруженные на Modrinth, в том числе файлы .mrpack."
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "Больше не предупреждать"
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Serveri za autentifikaciju su nedostupni"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Dodaj server na instancu"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Dodaj servere na tvoju instancu"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Dodaj na instancu"
|
||||
},
|
||||
@@ -23,6 +29,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Otkrij servere"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Instaliraj sadržaj na instancu"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Ukucaj opis modpacka..."
|
||||
},
|
||||
@@ -38,6 +47,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Ime modpacka"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Izaberi fajlove i foldere da uključiš u pack"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Broj verzije"
|
||||
},
|
||||
|
||||
@@ -1,67 +1,16 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "Laddar ner Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Nedladdningar"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Göm fler pågående instanser"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Gör till primärinstans"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Ingen pågående instans"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Primärinstans"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Visa fler pågående instanser"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Avsluta instans"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Visa aktiva nedladdningar"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Visa instans"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Visa loggar"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Aktiverar avancerad rendering som till exempel oskärpa vilket kan leda till prestandaproblem utan hårdvaruaccelererad rendering."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Avancerad rendering"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Välj färgtema för Modrinth-appen."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Färgtema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Hem"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Bibliotek"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Dölj namnskylt"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecrafts autentiseringsservrar kan vara nere just nu. Kontrollera din internetanslutning och försök igen senare."
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Kan ej nå autentiseringsservrarna"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Lägg till server till instans"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Lägg till servrar till din instans"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Lägg till i instans"
|
||||
},
|
||||
@@ -80,9 +29,18 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Upptäck servrar"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Installera innehåll till instans"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpaket"
|
||||
},
|
||||
"app.browse.server.install": {
|
||||
"message": "Installera"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Installerad"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installerar"
|
||||
},
|
||||
@@ -101,6 +59,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Modpaketets namn"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Välj filer och mappar att inkludera i paketet"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Versionsnummer"
|
||||
},
|
||||
|
||||
@@ -1,111 +1,18 @@
|
||||
{
|
||||
"app.action-bar.downloading-java": {
|
||||
"message": "ดาวน์โหลดเวอร์ชัน Java {version}"
|
||||
},
|
||||
"app.action-bar.downloads": {
|
||||
"message": "ดาวน์โหลด"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "ซ่อนการดำเนินการโปรแกรมเบื้องหลัง"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "กำหนดโปรแกรมหลัก"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "ไม่มีโปรแกรมใดดำเนินการ"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "ออฟไลน์"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "โปรแกรมหลัก"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "แสดงการดำเนินการโปรแกรมเบื้องหลังเพิ่มเติม"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "หยุดการทำงานโปรแกรม"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "ดูยอดการดาวน์โหลดที่ยังใช้งาน"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "ดูโปรแกรม"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "ดู Log"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "เปิดการใช้งานการเรนเดอร์ขั้นสูง เช่น เอฟเฟคการเบลอ อาจทำให้เกมเกิดปัญหาด้านประสิทธิภาพได้หากไม่มีอุปกรณ์ฮาร์ดแวร์ที่สามารถรองรับการเรนเดอร์ได้"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "การเรนเดอร์ขั้นสูง"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "เลือกธีมสีที่คุณชื่นชอบสำหรับ Modrinth บนอุปกรณ์นี้"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "ธีมสี"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "เปลี่ยนหน้าไปหน้าต่างที่ลันเชอร์เปิดขึ้นมา"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "หน้าหลัก"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "รายการ"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "หน้าเริ่มต้น"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "ปิดป้ายชื่อบนศีรษะของผู้เล่นในหน้าแสดงสกิน"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "ซ่อนป้ายชื่อ"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "การแสดงผลโลกล่าสุดที่ผู้เล่นเล่นในส่วน \"กระโดดกลับเข้าไป\" ในหน้าหลัก"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "กระโดดกลับเข้าไปในโลก"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "ย่อลันเชอร์เมื่อกระบวนการการรัน Minecraft เริ่มต้นแล้ว"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "ย่อลันเชอร์"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "ใช้ระบบกรอบหน้าต่าง (ต้องการการรีสตาร์ทโปรแกรมใหม่)"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "ของตกแต่งดั้งเดิม"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "เลือกตัวเลิือก"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "เปิดใช้งานตัวเลือกในการเลือกย่อหรือขยายแถบด้านข้าง"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "ย่อแถบด้านข้าง"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "หากคุณกำลังติดตั้งไฟล์แพ็กของ Modrinth (.mrpack) ที่ไม่ได้โฮสต์โดยตรงบน Modrinth เราจะแจ้งให้คุณตรวจสอบให้มั่นใจก่อนที่จะติดตั้งไฟล์แพ็กดังกล่าว"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "เตือนฉันก่อนติดตั้งแพ็กม็อดที่ไม่รู้จัก"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "เซิร์ฟเวอร์ตรวจสอบสิทธิ์ของ Minecraft อาจใช้งานไม่ได้ในขณะนี้ โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณและลองใหม่อีกครั้งในภายหลัง"
|
||||
},
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "ไม่สามารถเชื่อมต่อถึงเซิร์ฟเวอร์ได้"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "เพิ่มเซิร์ฟเวอร์ลงในอินสแตนซ์"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "เพิ่มเซิร์ฟเวอร์ลงในอินสแตนซ์ของคุณ"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "เพิ่มลงในโปรแกรม"
|
||||
"message": "เพิ่มลงในอินสแตนซ์"
|
||||
},
|
||||
"app.browse.add-to-instance-name": {
|
||||
"message": "เพิ่มลงใน {instanceName}"
|
||||
@@ -116,77 +23,44 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "เพิ่มแล้ว"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "สำรวจเนื้อหา"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "สำรวจเซิร์ฟเวอร์"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "ซ่อนเซิร์ฟเวอร์ที่ดำเนินการเพิ่มแล้ว"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "แพ็กม็อด"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "กำลังติดตั้ง"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "กำลังติดตั้งแพ็กม็อด..."
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "ติดตั้งเนื้อหาลงในอินสแตนซ์"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "กรอกคำอธิบายมอดแพ็ก.."
|
||||
},
|
||||
"app.export-modal.export-button": {
|
||||
"message": "ส่งออก"
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "ส่งออกแพ็กม็อด"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "รวมถึง \"{file}\" ใช่หรือไม่"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "ชื่อแพ็กม็อด"
|
||||
"message": "ชื่อมอดแพ็ก"
|
||||
},
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "ชื่อแพ็กม็อด"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "กำหนดไฟล์ที่ต้องการจะส่งออก"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "หมายเลขเวอร์ชัน"
|
||||
"message": "ชื่อ ม็อดแพ็ก"
|
||||
},
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "ข้อมูลทั้งหมดที่ถูกเก็บไว้ในโปรแกรมจะถูกลบออกอย่างถาวร รวมถึงโลก การตั้งค่า และเนื้อหาอื่นที่ติดตั้งไว้ทั้งหมด"
|
||||
"message": "ข้อมูลทั้งหมดในอินสแตนซ์ของคุณจะถูกลบอย่างถาวร รวมถึงโลก การตั้งค่า และเนื้อหาที่ติดตั้งไว้ทั้งหมด"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "การดำเนินการนี้ไม่สามารถย้อนคืนได้"
|
||||
},
|
||||
"app.instance.confirm-delete.delete-button": {
|
||||
"message": "ลบโปรแกรม"
|
||||
"message": "ลบอินสแตนซ์"
|
||||
},
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "ลบโปรแกรม"
|
||||
"message": "ลบอินสแตนซ์"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "แพ็กม็อดดังกล่าวติดตั้งไว้ใน <bold>{instanceName}</bold> อยู่แล้ว คุณแน่ใจหรือไม่ว่าต้องการจะทำซ้ำ?"
|
||||
"message": "มอดแพ็กนี้ถูกติดตั้งไว้ในอินสแตนซ์ <bold>{instanceName}</bold> อยู่แล้ว คุณแน่ใจหรือไม่ว่าต้องการสร้างสำเนาซ้ำ?"
|
||||
},
|
||||
"app.instance.modpack-already-installed.create": {
|
||||
"message": "สร้าง"
|
||||
},
|
||||
"app.instance.modpack-already-installed.header": {
|
||||
"message": "แพ็กม็อดดังกล่าวถูกติดตั้งไปแล้ว"
|
||||
"message": "ติดตั้งมอดแพ็กนี้อยู่แล้ว"
|
||||
},
|
||||
"app.instance.modpack-already-installed.instance": {
|
||||
"message": "โปรแกรม"
|
||||
"message": "อินสแตนซ์"
|
||||
},
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "โปรเจกต์"
|
||||
@@ -198,10 +72,7 @@
|
||||
"message": "เพิ่ม {count} โปรเจกต์เรียบร้อยแล้ว"
|
||||
},
|
||||
"app.instance.mods.share-text": {
|
||||
"message": "มาตรวจดูโปรเจกต์ต่าง ๆ ที่ฉันใช้ในแพ็กม็อดนี้กัน!"
|
||||
},
|
||||
"app.instance.mods.share-title": {
|
||||
"message": "แบ่งปันเนื้อหาแพ็กม็อด"
|
||||
"message": "มาตรวจดูโปรเจกต์ต่าง ๆ ที่ฉันใช้ในมอดแพ็กนี้กัน!"
|
||||
},
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "อัปโหลดสำเร็จ"
|
||||
@@ -210,16 +81,16 @@
|
||||
"message": "เพิ่มเซิร์ฟเวอร์"
|
||||
},
|
||||
"app.instance.worlds.browse-servers": {
|
||||
"message": "ค้นหาเซิร์ฟเวอร์"
|
||||
"message": "ค้นหาเซิฟเวอร์"
|
||||
},
|
||||
"app.instance.worlds.delete-world-description": {
|
||||
"message": "'{name}' จะ**ถูกลบออกอย่างถาวร** และจะไม่มีทางสามารถกู้คืนได้"
|
||||
"message": "'{name}' จะถูกลบอย่างถาวร** และจะไม่สามารถกู้คืนได้**"
|
||||
},
|
||||
"app.instance.worlds.delete-world-title": {
|
||||
"message": "คุณแน่ใจหรือไม่ว่าต้องการลบโลกนี้อย่างถาวร?"
|
||||
},
|
||||
"app.instance.worlds.filter-modded": {
|
||||
"message": "ม็อด"
|
||||
"message": "แบบมีมอด"
|
||||
},
|
||||
"app.instance.worlds.filter-offline": {
|
||||
"message": "ออฟไลน์"
|
||||
@@ -228,31 +99,31 @@
|
||||
"message": "ออนไลน์"
|
||||
},
|
||||
"app.instance.worlds.filter-vanilla": {
|
||||
"message": "วานิลลา"
|
||||
"message": "ต้นฉบับ"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-description": {
|
||||
"message": "เพิ่มเซิร์ฟเวอร์หรือค้นหาเพื่อเริ่มต้น"
|
||||
"message": "เพิ่มเซิร์ฟเวอร์หรือสำรวจเพื่อเริ่มต้น"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "ไม่มีเซิร์ฟเวอร์หรือโลกที่เพิ่มไว้"
|
||||
},
|
||||
"app.instance.worlds.remove-server-description": {
|
||||
"message": "'{name}' จะถูกลบออกจากรายการของคุณ ซึ่งจะรวมถึงในเกม และการลบดังกล่าวจะไม่มีทางกู้คืนได้อีก"
|
||||
"message": "'{name}' จะถูกลบออกจากรายชื่อของคุณ รวมถึงในเกมด้วย และจะไม่สามารถกู้คืนได้"
|
||||
},
|
||||
"app.instance.worlds.remove-server-description-with-address": {
|
||||
"message": "'{name}' ({address}) จะถูกลบออกจากรายการของคุณ ซึ่งจะรวมถึงในเกม และการลบดังกล่าวจะไม่มีทางกู้คืนได้อีก"
|
||||
"message": "'{name}' ({address}) จะถูกลบออกจากรายชื่อของคุณ รวมถึงในเกมด้วย และจะไม่สามารถกู้คืนได้"
|
||||
},
|
||||
"app.instance.worlds.remove-server-title": {
|
||||
"message": "คุณแน่ใจหรือไม่ว่าต้องการลบ {name}?"
|
||||
},
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "ค้าหาโลกทั้งหมด {count} โลก"
|
||||
"message": "ค้นหา {count} โลก"
|
||||
},
|
||||
"app.instance.worlds.this-server": {
|
||||
"message": "เซิร์ฟเวอร์นี้"
|
||||
"message": "เซิร์ฟเวอร์อันนี้"
|
||||
},
|
||||
"app.modal.install-to-play.content-required": {
|
||||
"message": "เนื้อหาที่จำเป็น"
|
||||
"message": "จำเป็นต้องมีเนื้อหา"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "ติดตั้งเพื่อเล่น"
|
||||
@@ -260,20 +131,17 @@
|
||||
"app.modal.install-to-play.install-button": {
|
||||
"message": "ติดตั้ง"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# ม็อด}}"
|
||||
},
|
||||
"app.modal.install-to-play.required-modpack": {
|
||||
"message": "มอดแพ็กที่จำเป็น"
|
||||
},
|
||||
"app.modal.install-to-play.server-requires-mods": {
|
||||
"message": "เซิร์ฟเวอร์ดังกล่าวจำเป็นต้องใช้ม็อดเพื่อเล่น โปรดติดตั้งและตั้งค่าไฟล์อื่นใดที่จำเป็นจาก Modrinth ก่อน จากนั้นถึงจะสามารถเข้าเล่นเซิร์ฟเวอร์ได้"
|
||||
"message": "เซิร์ฟเวอร์นี้จำเป็นต้องใช้มอดเพื่อเข้าเล่น คลิกติดตั้ง เพื่อตั้งค่าไฟล์ที่จำเป็นจาก Modrinth จากนั้นจึงจะเข้าสู่เซิร์ฟเวอร์โดยตรงได้"
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "โปรแกรมที่มีร่วมกัน"
|
||||
"message": "อินสแตนซ์ที่ใช้ร่วมกัน"
|
||||
},
|
||||
"app.modal.install-to-play.shared-server-instance": {
|
||||
"message": "เซิร์ฟเวอร์ของโปรแกรมที่มีร่วมกัน"
|
||||
"message": "อินสแตนซ์เซิร์ฟเวอร์ที่ใช้ร่วมกัน"
|
||||
},
|
||||
"app.modal.install-to-play.view-contents": {
|
||||
"message": "ดูเนื้อหา"
|
||||
@@ -291,13 +159,13 @@
|
||||
"message": "โหมดนักพัฒนาเปิดอยู่"
|
||||
},
|
||||
"app.settings.downloading": {
|
||||
"message": "ดาวน์โหลดเวอร์ชัน {version}"
|
||||
"message": "ดาวน์โหลด เวอร์ชั่น{version}"
|
||||
},
|
||||
"app.settings.tabs.appearance": {
|
||||
"message": "หน้าตา"
|
||||
},
|
||||
"app.settings.tabs.default-instance-options": {
|
||||
"message": "ตั้งค่าโปรแกรมเริ่มต้น"
|
||||
"message": "ตัวเลือกอินสแตนซ์เริ่มต้น"
|
||||
},
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "การจัดการ Java ที่ติดตั้ง"
|
||||
@@ -312,28 +180,22 @@
|
||||
"message": "การจัดการทรัพยากร"
|
||||
},
|
||||
"app.update-popup.body": {
|
||||
"message": "Modrinth v{version} พร้อมสำหรับการติดตั้งแล้ว! เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
|
||||
"message": "แอฟ Modrinth v{version} พร้อมสำหรับการติดตั้งแล้ว! รีโหลดแอปเพื่ออัปเดต หรือ อัปเดตอัตโนมัติเมื่อคุณปิดแอฟ Modrinth"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "ดาวน์โหลด Modrinth v{version} สำเร็จแล้ว เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
|
||||
"message": "แอฟ Modrinth v{version} ดาวน์โหลดเสร็จแล้ว รีโหลดแอปเพื่ออัปเดต หรือ อัปเดตอัตโนมัติเมื่อคุณปิดแอฟ Modrinth"
|
||||
},
|
||||
"app.update-popup.body.linux": {
|
||||
"message": "Modrinth v{version} พร้อมให้อัปเดตแล้ว โปรดไปที่การจัดการแพ็กเกจของคุณสำหรับการอัปเดตคุณสมบัติใหม่และการแก้ไขต่าง ๆ"
|
||||
"message": "Modrinth App v{version} พร้อมให้อัปเดตแล้ว โปรดใช้ตัวจัดการแพ็กเกจ ของคุณเพื่ออัปเดตฟีเจอร์ใหม่และตัวแก้ไขล่าสุด!"
|
||||
},
|
||||
"app.update-popup.body.metered": {
|
||||
"message": "Modrinth v{version} พร้อมให้บริการแล้ว! แต่เนื่องจากคุณเปิดโหมดจำกัดปริมาณการใช้ข้อมูลเครือข่าย เราจึงไม่ได้อัปเดตเนื้อหาโดยอัตโนมัติ"
|
||||
},
|
||||
"app.update-popup.changelog": {
|
||||
"message": "บันทึกการเปลี่ยนแปลง"
|
||||
"message": "แอฟ Modrinth v{version} พร้อมแล้ว! เนื่องจากคุณเปิดโหมดจำกัดปริมาณข้อมูล เราจึงไม่ได้อัปเดตอัตโนมัติ"
|
||||
},
|
||||
"app.update-popup.download": {
|
||||
"message": "ดาวโหลด ({size})"
|
||||
},
|
||||
"app.update-popup.download-complete": {
|
||||
"message": "การดาวน์โหลดเสร็จสมบูรณ์"
|
||||
},
|
||||
"app.update-popup.reload": {
|
||||
"message": "โหลดใหม่"
|
||||
"message": "ดาวน์โหลดเสร็จสมบูรณ์"
|
||||
},
|
||||
"app.update-popup.title": {
|
||||
"message": "มีการอัปเดตใหม่"
|
||||
@@ -345,13 +207,13 @@
|
||||
"message": "เวอร์ชั่น {version} ถูกติดตั้งแล้ว"
|
||||
},
|
||||
"app.update.download-update": {
|
||||
"message": "ดาวน์โหลดอัปเดต"
|
||||
"message": "ดาวน์โหลดอัพเดต"
|
||||
},
|
||||
"app.update.downloading-update": {
|
||||
"message": "ดาวน์โหลดอัปเดตไปแล้ว ({percent}%)"
|
||||
"message": "ดาวน์โหลดอัพเดตไปแล้ว ({percent}%)"
|
||||
},
|
||||
"app.update.reload-to-update": {
|
||||
"message": "รีโหลดเพื่อติดตั้งอัปเดต"
|
||||
"message": "รีโหลดเพื่อติดตั้งอัพเดต"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "example.modrinth.gg"
|
||||
@@ -360,10 +222,7 @@
|
||||
"message": "เลือกตัวเลือก"
|
||||
},
|
||||
"app.world.world-item.incompatible-version": {
|
||||
"message": "ไม่สามารถเข้ากับเวอร์ชัน {version} ได้"
|
||||
},
|
||||
"app.world.world-item.not-played-yet": {
|
||||
"message": "ยังไม่เคยเล่น"
|
||||
"message": "เวอร์ชัน {version} ไม่รองรับ"
|
||||
},
|
||||
"app.world.world-item.offline": {
|
||||
"message": "ออฟไลน์"
|
||||
@@ -374,9 +233,6 @@
|
||||
"friends.action.add-friend": {
|
||||
"message": "เพิ่มเพื่อน"
|
||||
},
|
||||
"friends.action.view-friend-requests": {
|
||||
"message": "คำขอเป็นเพื่อน {count} {count, plural, other {คน}}"
|
||||
},
|
||||
"friends.add-friend.submit": {
|
||||
"message": "ส่งคำขอเป็นเพื่อน"
|
||||
},
|
||||
@@ -384,13 +240,13 @@
|
||||
"message": "กำลังเพิ่มเพื่อน"
|
||||
},
|
||||
"friends.add-friend.username.description": {
|
||||
"message": "มันอาจแตกต่างจากชื่อในเกม Minecraft ของพวกเขา!"
|
||||
"message": "อาจจะแตกต่างกับชื่อใน Minecraft"
|
||||
},
|
||||
"friends.add-friend.username.placeholder": {
|
||||
"message": "กรอกชื่อผู้ใช้ Modrinth..."
|
||||
"message": "ใส่ชื่อบน Modrinth"
|
||||
},
|
||||
"friends.add-friend.username.title": {
|
||||
"message": "เพื่อนของคุณมีชื่อผู้ใช้ Modrinth ว่าอะไร?"
|
||||
"message": "เพื่อนของคุณมีชื่อบน Modrinth ว่าอะไร"
|
||||
},
|
||||
"friends.add-friends-to-share": {
|
||||
"message": "<link>เพิ่มเพื่อน</link> เพื่อเห็นว่าพวกเขากำลังเล่นอะไรอยู่!"
|
||||
@@ -417,13 +273,13 @@
|
||||
"message": "ออฟไลน์"
|
||||
},
|
||||
"friends.heading.online": {
|
||||
"message": "ออนไลน์"
|
||||
"message": "ออฟไลน์"
|
||||
},
|
||||
"friends.heading.pending": {
|
||||
"message": "กําลังดำเนินการ"
|
||||
},
|
||||
"friends.no-friends-match": {
|
||||
"message": "ไม่มีรายชื่อเพื่อนที่ตรงกับ \"{query}\""
|
||||
"message": "ไม่มีเพื่อนชื่อว่า \"{query}\""
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "ค้นหาเพื่อน"
|
||||
@@ -465,7 +321,7 @@
|
||||
"message": "โลก Minecraft"
|
||||
},
|
||||
"instance.edit-world.reset-icon": {
|
||||
"message": "รีเซ็ตรูปสัญลักษณ์"
|
||||
"message": "รีเซ็ตรูปตัวอย่าง"
|
||||
},
|
||||
"instance.edit-world.title": {
|
||||
"message": "แก้ไขโลก"
|
||||
@@ -492,13 +348,13 @@
|
||||
"message": "ทั่วไป"
|
||||
},
|
||||
"instance.settings.tabs.general.delete": {
|
||||
"message": "ลบโปรแกรม"
|
||||
"message": "ลบอินสแตนซ์"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.button": {
|
||||
"message": "ลบโปรแกรม"
|
||||
"message": "ลบอินสแตนซ์"
|
||||
},
|
||||
"instance.settings.tabs.general.delete.description": {
|
||||
"message": "การกระทำดังกล่าวจะลบโปรแกรมออกจากอุปกรณ์ของคุณอย่างถาวร รวมถึงโลก การตั้งค่า และเนื้อหาทั้งหมดที่ติดตั้งไว้ โปรดระมัดระวัง เนื่องจากเมื่อถูกลบแล้วจะไม่สามารถกู้คืนได้อีก"
|
||||
"message": "ลบอินสแตนซ์ออกจากอุปกรณ์ของคุณอย่างถาวร รวมถึงโลก การตั้งค่า และเนื้อหาทั้งหมดที่ติดตั้งไว้ โปรดระมัดระวัง เนื่องจากเมื่อถูกลบแล้วจะไม่สามารถกู้คืนได้อีก"
|
||||
},
|
||||
"instance.settings.tabs.general.deleting.button": {
|
||||
"message": "กำลังลบ..."
|
||||
@@ -510,31 +366,31 @@
|
||||
"message": "ไม่สามารถทำซ้ำได้ขณะติดตั้ง"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance": {
|
||||
"message": "สร้างสำเนาโปรแกรม"
|
||||
"message": "สร้างสำเนาอินสแตนซ์"
|
||||
},
|
||||
"instance.settings.tabs.general.duplicate-instance.description": {
|
||||
"message": "สร้างสำเนาของโปรแกรมนี้ รวมถึงโลก การตั้งค่า ม็อด และอื่น ๆ"
|
||||
"message": "สร้างสำเนาของอินสแตนซ์นี้ รวมถึงโลก การตั้งค่า ม็อด และอื่นๆ"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "แก้ไขสัญลักษณ์"
|
||||
"message": "แก้ไขไอคอน"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.remove": {
|
||||
"message": "ลบสัญลักษณ์"
|
||||
"message": "ลบไอคอน"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.replace": {
|
||||
"message": "แทนที่สัญลักษณ์"
|
||||
"message": "เปลี่ยนไอคอน"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.select": {
|
||||
"message": "เลือกรูปสัญลักษณ์"
|
||||
"message": "เลือกรูปไอคอน"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups": {
|
||||
"message": "จัดกลุ่ม"
|
||||
"message": "การจัดกลุ่ม"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.create": {
|
||||
"message": "สร้างกลุ่มใหม่"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.description": {
|
||||
"message": "การจัดกลุ่มจะช่วยจัดระเบียบโปรแกรมของคุณภายในหน้ารายการให้เป็นหมวดหมู่"
|
||||
"message": "กลุ่มไลบรารีช่วยให้คุณจัดระเบียบอินสแตนซ์ของคุณเป็นหมวดหมู่ต่างๆ ภายในไลบรารี"
|
||||
},
|
||||
"instance.settings.tabs.general.library-groups.enter-name": {
|
||||
"message": "ใส่ชื่อกลุ่ม"
|
||||
@@ -543,58 +399,43 @@
|
||||
"message": "ชื่อ"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Launch Hook"
|
||||
"message": "ตัวดักการปล่อย"
|
||||
},
|
||||
"instance.settings.tabs.hooks.custom-hooks": {
|
||||
"message": "กำหนด Launch Hook เอง"
|
||||
"message": "กำหนดเอง"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "Launch Hook จะช่วยให้ผู้เล่นที่ชำนาญสามารถใช้คำสั่งต่อระบบบางอย่างได้ ทั้งก่อนและหลังเกมถูกเปิด"
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit": {
|
||||
"message": "Post-exit"
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.description": {
|
||||
"message": "จะรันหลังจากที่ตัวเกมถูกปิด"
|
||||
"message": "ตัวดักจับช่วยให้ผู้ใช้ระดับสูงสามารถรันคำสั่งระบบบางอย่างได้ ทั้งก่อนและหลังการเปิดเกม"
|
||||
},
|
||||
"instance.settings.tabs.hooks.post-exit.enter": {
|
||||
"message": "ป้อนคำสั่ง..."
|
||||
"message": "ป้อนคำสั่งหลังจบการทำงาน..."
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch": {
|
||||
"message": "Pre-launch"
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.description": {
|
||||
"message": "จะรันก่อนที่โปรแกรมจะทำงาน"
|
||||
},
|
||||
"instance.settings.tabs.hooks.pre-launch.enter": {
|
||||
"message": "ป้อนคำสั่ง..."
|
||||
"message": "ก่อนรัน"
|
||||
},
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Game Launch Hook"
|
||||
"message": "ฮุกการเปิดเกม"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Wrapper"
|
||||
"message": "ตัวห่อหุ้ม"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.description": {
|
||||
"message": "คำสั่ง Wrapper สำหรับใช้ในการรันเปิด Minecraft"
|
||||
"message": "คำสั่งตัวห่อหุ้มสำหรับเปิด ไมน์คราฟต์"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper.enter": {
|
||||
"message": "ป้อนคำสั่ง Wrapper..."
|
||||
"message": "ป้อนตัวห่อหุ้มคำสั่ง..."
|
||||
},
|
||||
"instance.settings.tabs.installation": {
|
||||
"message": "การติดตั้ง"
|
||||
},
|
||||
"instance.settings.tabs.installation.loader-version": {
|
||||
"message": "เวอร์ชัน {loader}"
|
||||
"message": "เวอร์ชั่น {loader}"
|
||||
},
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java และ หน่วยความจำ"
|
||||
},
|
||||
"instance.settings.tabs.java.environment-variables": {
|
||||
"message": "Environment Variables"
|
||||
},
|
||||
"instance.settings.tabs.java.hooks": {
|
||||
"message": "Hook"
|
||||
"message": "ฮุก"
|
||||
},
|
||||
"instance.settings.tabs.java.java-arguments": {
|
||||
"message": "อาร์กิวเมนต์ Java"
|
||||
@@ -608,9 +449,6 @@
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "หน้าต่าง"
|
||||
},
|
||||
"instance.settings.tabs.window.custom-window-settings": {
|
||||
"message": "การตั้งค่าการปรับแต่งหน้าต่าง"
|
||||
},
|
||||
"instance.settings.tabs.window.fullscreen": {
|
||||
"message": "เต็มหน้าจอ"
|
||||
},
|
||||
@@ -636,7 +474,7 @@
|
||||
"message": "ป้อนความกว้าง..."
|
||||
},
|
||||
"instance.worlds.a_minecraft_server": {
|
||||
"message": "เซิร์ฟเวอร์ Minecraft"
|
||||
"message": "เซิร์ฟเวอร์ ไมน์คราฟต์"
|
||||
},
|
||||
"instance.worlds.cant_connect": {
|
||||
"message": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้"
|
||||
@@ -648,7 +486,7 @@
|
||||
"message": "ไม่ต้องแสดงในหน้าแรก"
|
||||
},
|
||||
"instance.worlds.game_already_open": {
|
||||
"message": "โปรแกรมถูกเปิดอยู่แล้ว"
|
||||
"message": "อินสแตนซ์ถูกเปิดอยู่แล้ว"
|
||||
},
|
||||
"instance.worlds.hardcore": {
|
||||
"message": "โหมดฮาร์ดคอร์"
|
||||
@@ -669,75 +507,24 @@
|
||||
"message": "คุณสามารถเข้าสู่โลกผู้เล่นคนเดียวได้โดยตรงบน Minecraft 1.20 ขึ้นไปเท่านั้น"
|
||||
},
|
||||
"instance.worlds.play_instance": {
|
||||
"message": "เริ่มรันโปรแกรม"
|
||||
"message": "เริ่มเล่นอินสแตนซ์"
|
||||
},
|
||||
"instance.worlds.view_instance": {
|
||||
"message": "ดูโปรแกรม"
|
||||
"message": "ดูอินสแตนซ์"
|
||||
},
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "โลกนี้กำลังถูกใช้งานอยู่"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "เพิ่มบัญชี"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "บัญชี Minecraft"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "ยังไม่ได้ลงชื่อเข้าใช้"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "ลบบัญชี"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "เลือกบัญชี"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "ลงชื่อเข้าใช้ Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "โปรแกรมเป็นผู้กำหนด"
|
||||
},
|
||||
"search.filter.locked.instance-game-version.title": {
|
||||
"message": "เวอร์ชันเกมถูกกำหนดไว้โดยโปรแกรมแล้ว"
|
||||
},
|
||||
"search.filter.locked.instance-loader.title": {
|
||||
"message": "ตัวรันถูกกำหนดไว้โดยโปรแกรมแล้ว"
|
||||
"message": "อินสแตนซ์เป็นผู้จัดเตรียม"
|
||||
},
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "เชื่อมต่อกับโปรแกรม"
|
||||
"message": "ซิงค์กับอินสแตนซ์"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "เซิร์ฟเวอร์เป็นผู้กำหนด"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "มีเพียงม็อดสำหรับฝั่งเครื่องของผู้เล่นเท่านั้นที่สามารถเพิ่มลงในโปรแกรมเซิร์ฟเวอร์ดังกล่าวได้"
|
||||
"message": "เซิร์ฟเวอร์เป็นผู้จัดเตรียม"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "เวอร์ชันเกมถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "ตัวรันถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
|
||||
},
|
||||
"unknown-pack-warning-modal.body": {
|
||||
"message": "ไฟล์จะได้รับการตรวจสอบโดยไม่คำนึงถึงประเภทของไฟล์ (รวมทั้ง .mrpack) เมื่อไฟล์ถูกอัปโหลดขึ้น Modrinth"
|
||||
},
|
||||
"unknown-pack-warning-modal.dont-show-again": {
|
||||
"message": "อย่าแสดงคำเตือนนี้อีก"
|
||||
},
|
||||
"unknown-pack-warning-modal.header": {
|
||||
"message": "ยืนยันการติดตั้ง"
|
||||
},
|
||||
"unknown-pack-warning-modal.install-anyway": {
|
||||
"message": "ดำเนินการติดตั้งต่อไป"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "มัลแวร์มักแฝงตัวมากับไฟล์แพ็กม็อดผ่านการแชร์ผ่านแพลตฟอร์มที่ไม่ใช่แพลตฟอร์มเฉพาะ เช่น ดิสคอร์ด"
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "เราไม่สามารถค้นหาไฟล์ดังกล่าวได้บน Modrinth พวกเราขอแนะนำอย่างมากกว่าควรติดตั้งไฟล์จากแหล่งที่น่าเชื่อถือเท่านั้น"
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.title": {
|
||||
"message": "แจ้งเตือนไฟล์ไม่รู้จัก"
|
||||
"message": "เซิร์ฟเวอร์เป็นผู้จัดเตรียมเวอร์ชันเกม"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Doğrulama sunucularına erişilemedi"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Sunucuyu kurulumu ekle"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Kurulumunuza sunucular ekleyin"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Kuruluma ekle"
|
||||
},
|
||||
@@ -125,9 +131,21 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Zaten eklenmiş sunucuları gizle"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Zaten yüklenmiş içerikleri gizle"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "İçeriği kuruluma yükle"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Mod paketleri"
|
||||
},
|
||||
"app.browse.server.install": {
|
||||
"message": "Yükle"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Yüklü"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Yükleniyor"
|
||||
},
|
||||
@@ -152,6 +170,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Modpaketi adı"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Pakete dahil edilecek dosya ve klasörleri seçin"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Sürüm numarası"
|
||||
},
|
||||
@@ -671,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Dünya şu anda kullanımda"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Hesap ekle"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraft hesabı"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Giriş yapılmadı"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Hesabı kaldır"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Hesap seçin"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Minecraft'a giriş yapın"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Kurulum tarafından sağlanan"
|
||||
},
|
||||
|
||||
@@ -6,49 +6,49 @@
|
||||
"message": "Завантаження"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Приховати більше запущених профілів"
|
||||
"message": "Приховати більше запущених екземплярів"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Зробити основним профілем"
|
||||
"message": "Зробити основним екземпляром"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "Немає запущених профілів"
|
||||
"message": "Немає запущених екземплярів"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "Поза мережею"
|
||||
"message": "Офлайн"
|
||||
},
|
||||
"app.action-bar.primary-instance": {
|
||||
"message": "Основний профіль"
|
||||
"message": "Основний екземпляр"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Показати більше запущених профілів"
|
||||
"message": "Показати більше запущених екземплярів"
|
||||
},
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": "Зупинити профіль"
|
||||
"message": "Зупинити екземпляр"
|
||||
},
|
||||
"app.action-bar.view-active-downloads": {
|
||||
"message": "Переглянути активні завантаження"
|
||||
},
|
||||
"app.action-bar.view-instance": {
|
||||
"message": "Переглянути профіль"
|
||||
"message": "Переглянути екземпляр"
|
||||
},
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Переглянути журнали"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Дозволяє розширене промальовування, як-от ефекти розмиття, які можуть спричиняти проблеми з продуктивністю без апаратно-прискореного промальовування."
|
||||
"message": "Дозволяє розширений рендеринг, такий як ефекти розмиття, які можуть спричиняти проблеми з продуктивністю без апаратно-прискореного рендерингу."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Розширене промальовування"
|
||||
"message": "Розширений рендеринг"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Виберіть бажану колірну тему для Modrinth App."
|
||||
"message": "Виберіть бажану колірну тему для програми Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Колірна тема"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.description": {
|
||||
"message": "Змініть сторінку, на якій відкривається запускач."
|
||||
"message": "Змініть сторінку, на якій відкривається панель запуску."
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Головна"
|
||||
@@ -57,46 +57,46 @@
|
||||
"message": "Бібліотека"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.title": {
|
||||
"message": "Усталена цільова сторінка"
|
||||
"message": "Цільова сторінка за замовчуванням"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Вимикає ім'я над вашим гравцем на сторінці скінів."
|
||||
"message": "Вимикає іменний бейдж над вашим гравцем на сторінці скінів."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Приховати ім'я"
|
||||
"message": "Приховати бейдж з іменем"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Уключає нещодавні світи в розділі «Повернутися» на головній сторінці."
|
||||
"message": "Включає нещодавні світи в розділі «Повернення» на головній сторінці."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Повернутися у світи"
|
||||
"message": "Стрибни назад у світи"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Згортає запускач під час запуску процесу Minecraft."
|
||||
"message": "Згорнути лаунчер під час запуску процесу Minecraft."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Мінімізувати запускач"
|
||||
"message": "Згорнути панель запуску"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Використовувати рамку системного вікна (потрібно перезапустити програму)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Системне оформлення"
|
||||
"message": "Рідні прикраси"
|
||||
},
|
||||
"app.appearance-settings.select-option": {
|
||||
"message": "Виберіть опцію"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Умикає можливість перемикання бокової панелі."
|
||||
"message": "Вмикає можливість перемикання бічної панелі."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Перемикання бокової панелі"
|
||||
"message": "Перемикання бічної панелі"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Якщо ви спробуєте встановити файл пакета Modrinth (.mrpack), який не розміщено на Modrinth, ми переконаємося, що ви розумієте ризики, перш ніж установлювати його."
|
||||
"message": "Якщо ви спробуєте встановити файл пакета Modrinth (.mrpack), який не розміщено на Modrinth, ми переконаємося, що ви розумієте ризики, перш ніж встановлювати його."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Попереджати мене перед установленням невідомих збірок"
|
||||
"message": "Попереджати мене перед встановленням невідомих модпаків"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Сервери автентифікації Minecraft можуть зараз не працювати. Перевірте з’єднання з інтернетом та спробуйте пізніше."
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Не вдається зв’язатися зі серверами автентифікації"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Додавання сервера до профілю"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Додати сервер до профілю"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "Додати до профілю"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Додати сервера до вашого профілю"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Додати до профілю"
|
||||
@@ -122,9 +122,6 @@
|
||||
"app.browse.already-added": {
|
||||
"message": "Уже додано"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Назад до профілю"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "Дослідити вміст"
|
||||
},
|
||||
@@ -132,25 +129,34 @@
|
||||
"message": "Дослідити сервера"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Сховати наявні сервери"
|
||||
"message": "Скрити наявні сервери"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "Скрити доданий контент"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Установити вміст в профіль"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Збірки"
|
||||
"message": "Модпаки"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Додавання вмісту може порушити сумісність при приєднанні до сервера. Усі додані матеріали також будуть утрачені під час оновлення вмісту профілю сервера."
|
||||
"app.browse.server.install": {
|
||||
"message": "Установлено"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "Встановлено"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Установлення"
|
||||
"message": "Встановлення"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.description": {
|
||||
"message": "{fileName}"
|
||||
},
|
||||
"app.creation-modal.installing-modpack.title": {
|
||||
"message": "Установлення збірки…"
|
||||
"message": "Встановлення модпаку..."
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Уведіть опис збірки…"
|
||||
"message": "Уведіть опис збірки..."
|
||||
},
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Експортувати"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "Експортувати збірку"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "Уключити «{file}»?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "Назва збірки"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "Назва збірки"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Змінити які файли додані до експорту"
|
||||
"message": "Виберіть файли й теки, щоб додати їх до збірки"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Номер версії"
|
||||
@@ -177,7 +180,7 @@
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Усі дані вашого профілю будуть видалені назавжди, включно з вашими світами, налаштуваннями та всім установленим умістом."
|
||||
"message": "Усі дані вашого профілю будуть видалені назавжди, включно з вашими світами, конфігураціями та усім встановленим контентом."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "Ця дія є незворотною"
|
||||
@@ -231,7 +234,7 @@
|
||||
"message": "Ви впевнені, що хочете назавжди видалити цей світ?"
|
||||
},
|
||||
"app.instance.worlds.filter-modded": {
|
||||
"message": "Модифіковано"
|
||||
"message": "Модифікований"
|
||||
},
|
||||
"app.instance.worlds.filter-offline": {
|
||||
"message": "Поза мережею"
|
||||
@@ -240,7 +243,7 @@
|
||||
"message": "У мережі"
|
||||
},
|
||||
"app.instance.worlds.filter-vanilla": {
|
||||
"message": "Стандартний"
|
||||
"message": "Ванільний"
|
||||
},
|
||||
"app.instance.worlds.no-worlds-description": {
|
||||
"message": "Додати сервер або переглянути, щоб почати"
|
||||
@@ -258,7 +261,7 @@
|
||||
"message": "Ви впевнені, що хочете видалити «{name}»?"
|
||||
},
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "Пошук {count} світів…"
|
||||
"message": "Пошук {count} світів..."
|
||||
},
|
||||
"app.instance.worlds.this-server": {
|
||||
"message": "цей сервер"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "«{name}» потребує оновлення, щоб грати. Будь ласка, оновіть гру до останньої версії, щоб запустити її."
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Проєкт уже встановлено"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Повернутися до дослідження"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Установити вміст до профілю"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Увімкнено режим розробника."
|
||||
},
|
||||
@@ -447,7 +441,7 @@
|
||||
"message": "Немає друзів, які збігаються з «{query}»"
|
||||
},
|
||||
"friends.search-friends-placeholder": {
|
||||
"message": "Пошук друзів…"
|
||||
"message": "Пошук друзів..."
|
||||
},
|
||||
"friends.section.heading": {
|
||||
"message": "{title} — {count}"
|
||||
@@ -495,7 +489,7 @@
|
||||
"message": "Додавання файлів ({completed}/{total})"
|
||||
},
|
||||
"instance.files.save-as": {
|
||||
"message": "Зберегти як…"
|
||||
"message": "Зберегти як..."
|
||||
},
|
||||
"instance.server-modal.address": {
|
||||
"message": "Адреса"
|
||||
@@ -698,24 +692,6 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Світ наразі використовується"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Додати обліковий запис"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Майнкрафт аккаунт"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Не ввійдено в систему"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Видалити обліковий запис"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Обрати обліковий запис"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Увійти в Майнкрафт"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Надано профілем"
|
||||
},
|
||||
@@ -753,7 +729,7 @@
|
||||
"message": "Усе одно інсталювати"
|
||||
},
|
||||
"unknown-pack-warning-modal.malware-statement": {
|
||||
"message": "Шкідливе програмне забезпечення часто поширюють через файли збірок, які публікуються на таких платформах, як Discord."
|
||||
"message": "Шкідливе програмне забезпечення часто поширюють через файли модпаків, які публікуються на таких платформах, як Discord."
|
||||
},
|
||||
"unknown-pack-warning-modal.warning.body": {
|
||||
"message": "Ми не змогли знайти цей файл на Modrinth. Ми рекомендуємо встановлювати файли лише з тих джерел яким ви довіряєте."
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Không thể kết nối đến máy chủ xác thực"
|
||||
},
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "Thêm máy chủ vào hồ sơ"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Thêm máy chủ vào hồ sơ của bạn"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "Thêm vào hồ sơ"
|
||||
},
|
||||
@@ -23,6 +29,9 @@
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Khám phá máy chủ"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "Tải nội dung này vào hồ sơ"
|
||||
},
|
||||
"app.export-modal.description-placeholder": {
|
||||
"message": "Thêm miêu tả cho gói modpack..."
|
||||
},
|
||||
@@ -38,6 +47,9 @@
|
||||
"app.export-modal.modpack-name-placeholder": {
|
||||
"message": "Tên modpack"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "Chọn tệp và gói để thêm vào gói tài nguyên"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "Phiên bản"
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"message": "设为主实例"
|
||||
},
|
||||
"app.action-bar.no-instances-running": {
|
||||
"message": "没有运行中的实例"
|
||||
"message": "没有实例正在运行"
|
||||
},
|
||||
"app.action-bar.offline": {
|
||||
"message": "离线"
|
||||
@@ -60,10 +60,10 @@
|
||||
"message": "默认起始页"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "在皮肤页面中禁用你玩家头顶上的名牌。"
|
||||
"message": "在皮肤页面中禁用你玩家头顶上的名称标签。"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "隐藏名牌"
|
||||
"message": "隐藏名称标签"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "在主页的“快速回到”部分包含最近的世界。"
|
||||
@@ -104,14 +104,14 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "无法连接到身份验证服务器"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "将服务器添加到实例"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "将服务器添加到实例实例"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "添加到实例"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "将服务器添加到你的实例"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "添加到实例"
|
||||
"message": "添加到实例实例"
|
||||
},
|
||||
"app.browse.add-to-instance-name": {
|
||||
"message": "添加到 {instanceName}"
|
||||
@@ -120,10 +120,7 @@
|
||||
"message": "已添加"
|
||||
},
|
||||
"app.browse.already-added": {
|
||||
"message": "已添加过"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "返回实例"
|
||||
"message": "已添加"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "发现内容"
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "隐藏已添加的服务器"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "隐藏已安装的内容"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "将内容安装到实例中"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "整合包"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "加入服务器时,添加内容可能会破坏兼容性。当更新服务器实例内容时任何添加的内容也将丢失。"
|
||||
"app.browse.server.install": {
|
||||
"message": "安装"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "已安装"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "安装中"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "导出整合包"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "包含 \"{file}\"?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "整合包名称"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "整合包名称"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "配置此导出中包含哪些文件"
|
||||
"message": "选择要包含在包中的文件和文件夹"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "版本号"
|
||||
@@ -299,15 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "需要更新至最新版本才能运行 {name}。请更新后启动游戏。"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "此项目已安装"
|
||||
},
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "返回发现页"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "将内容安装到实例中"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "开发者模式已启用。"
|
||||
},
|
||||
@@ -567,7 +561,7 @@
|
||||
"message": "启动 Hooks"
|
||||
},
|
||||
"instance.settings.tabs.hooks.custom-hooks": {
|
||||
"message": "自定义启动 Hooks"
|
||||
"message": "自定义启动Hooks"
|
||||
},
|
||||
"instance.settings.tabs.hooks.description": {
|
||||
"message": "Hooks允许高级用户在启动游戏前后运行特定的系统命令。"
|
||||
|
||||
@@ -104,11 +104,11 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "無法連線到驗證伺服器"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "將伺服器新增至實例"
|
||||
"app.browse.add-server-to-instance": {
|
||||
"message": "將伺服器新增到實例"
|
||||
},
|
||||
"app.browse.add-to-an-instance": {
|
||||
"message": "新增至實例"
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "將伺服器新增到實例"
|
||||
},
|
||||
"app.browse.add-to-instance": {
|
||||
"message": "新增至實例"
|
||||
@@ -120,10 +120,7 @@
|
||||
"message": "已新增"
|
||||
},
|
||||
"app.browse.already-added": {
|
||||
"message": "已經新增"
|
||||
},
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "回到實例"
|
||||
"message": "已新增"
|
||||
},
|
||||
"app.browse.discover-content": {
|
||||
"message": "探索內容"
|
||||
@@ -134,11 +131,20 @@
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "隱藏已新增的伺服器"
|
||||
},
|
||||
"app.browse.hide-installed-content": {
|
||||
"message": "隱藏已安裝的內容"
|
||||
},
|
||||
"app.browse.install-content-to-instance": {
|
||||
"message": "安裝至實例中"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "模組包"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "加入內容可能會導致加入伺服器時發生相容性問題。當你更新伺服器實例內容時,任何新增的內容也會遺失。"
|
||||
"app.browse.server.install": {
|
||||
"message": "安裝"
|
||||
},
|
||||
"app.browse.server.installed": {
|
||||
"message": "已安裝"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "安裝中"
|
||||
@@ -158,9 +164,6 @@
|
||||
"app.export-modal.header": {
|
||||
"message": "匯出模組包"
|
||||
},
|
||||
"app.export-modal.include-file-accessibility-label": {
|
||||
"message": "要包含「{file}」嗎?"
|
||||
},
|
||||
"app.export-modal.modpack-name-label": {
|
||||
"message": "模組包名稱"
|
||||
},
|
||||
@@ -168,7 +171,7 @@
|
||||
"message": "模組包名稱"
|
||||
},
|
||||
"app.export-modal.select-files-label": {
|
||||
"message": "設定要包含在此匯出檔案中的檔案"
|
||||
"message": "選擇要包含在模組包中的檔案與資料夾"
|
||||
},
|
||||
"app.export-modal.version-number-label": {
|
||||
"message": "版本號碼"
|
||||
@@ -195,7 +198,7 @@
|
||||
"message": "建立"
|
||||
},
|
||||
"app.instance.modpack-already-installed.header": {
|
||||
"message": "模組包已安裝"
|
||||
"message": "模組包已經安裝"
|
||||
},
|
||||
"app.instance.modpack-already-installed.instance": {
|
||||
"message": "實例"
|
||||
@@ -299,12 +302,6 @@
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "需要更新才能遊玩「{name}」。請更新至最新版本以啟動遊戲。"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "這個專案已安裝"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "將內容安裝至實例"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "開發人員模式已啟用。"
|
||||
},
|
||||
@@ -696,22 +693,22 @@
|
||||
"message": "世界正在使用中"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "新增帳號"
|
||||
"message": "添加賬號"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraft 帳號"
|
||||
"message": "Minecraft 賬號"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "未登入"
|
||||
"message": "未登錄"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "移除帳號"
|
||||
"message": "移除賬號"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "選擇帳號"
|
||||
"message": "選擇賬號"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "登入 Minecraft"
|
||||
"message": "登錄 Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "由該實例提供"
|
||||
|
||||
@@ -5,49 +5,46 @@ import {
|
||||
ClipboardCopyIcon,
|
||||
ExternalIcon,
|
||||
GlobeIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
SpinnerIcon,
|
||||
StopCircleIcon,
|
||||
} from '@modrinth/assets'
|
||||
import type { BrowseInstallContentType, CardAction, ProjectType, Tags } from '@modrinth/ui'
|
||||
import type { CardAction, ProjectType, Tags } from '@modrinth/ui'
|
||||
import {
|
||||
BrowsePageLayout,
|
||||
BrowseSidebar,
|
||||
commonMessages,
|
||||
CreationFlowModal,
|
||||
defineMessages,
|
||||
getLatestMatchingInstallVersion,
|
||||
getSelectedInstallPreferences,
|
||||
getTargetInstallPreferences,
|
||||
injectNotificationManager,
|
||||
preferencesDiffer,
|
||||
provideBrowseManager,
|
||||
requestInstall,
|
||||
useBrowseSearch,
|
||||
useDebugLogger,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
|
||||
import type { LocationQuery } from 'vue-router'
|
||||
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { useAppServerBrowse } from '@/composables/browse/use-app-server-browse'
|
||||
import {
|
||||
get_project,
|
||||
get_project_v3,
|
||||
get_search_results_v3,
|
||||
get_version_many,
|
||||
} from '@/helpers/cache.js'
|
||||
import { get_project_v3, get_search_results_v3 } from '@/helpers/cache.js'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import {
|
||||
get as getInstance,
|
||||
get_installed_project_ids as getInstalledProjectIds,
|
||||
kill,
|
||||
list as listInstances,
|
||||
} from '@/helpers/profile.js'
|
||||
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import { get_profile_worlds } from '@/helpers/worlds'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { add_server_to_profile, get_profile_worlds, getServerLatency } from '@/helpers/worlds'
|
||||
import { injectContentInstall } from '@/providers/content-install'
|
||||
import { injectServerInstall } from '@/providers/server-install'
|
||||
import {
|
||||
@@ -55,6 +52,7 @@ import {
|
||||
provideServerInstallContent,
|
||||
} from '@/providers/setup/server-install-content'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { getServerAddress } from '@/store/install.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -78,27 +76,15 @@ const {
|
||||
effectiveServerWorldId,
|
||||
serverContextServerData,
|
||||
serverContentProjectIds,
|
||||
queuedServerInstallProjectIds,
|
||||
queuedServerInstallCount,
|
||||
selectedServerInstallProjects,
|
||||
isInstallingQueuedServerInstalls,
|
||||
queuedInstallProgress,
|
||||
serverBackUrl,
|
||||
serverBackLabel,
|
||||
serverBrowseHeading,
|
||||
clearQueuedServerInstalls,
|
||||
removeQueuedServerInstall,
|
||||
flushQueuedServerInstalls,
|
||||
discardQueuedServerInstallsAndBack,
|
||||
installQueuedServerInstallsAndBack,
|
||||
initServerContext,
|
||||
watchServerContextChanges,
|
||||
searchServerModpacks,
|
||||
getServerProjectVersions,
|
||||
enforceSetupModpackRoute,
|
||||
getQueuedServerInstallPlans,
|
||||
setQueuedServerInstallPlans,
|
||||
openServerModpackInstallFlow,
|
||||
installProjectToServer,
|
||||
onServerFlowBack,
|
||||
handleServerModpackFlowCreate,
|
||||
markServerProjectInstalled,
|
||||
@@ -268,7 +254,6 @@ const instanceFilters = computed(() => {
|
||||
})
|
||||
|
||||
const serverHideInstalled = ref(false)
|
||||
const hideSelectedServerInstalls = ref(false)
|
||||
if (route.query.shi) {
|
||||
serverHideInstalled.value = route.query.shi === 'true'
|
||||
}
|
||||
@@ -306,12 +291,6 @@ const serverContextFilters = computed(() => {
|
||||
filters.push({ type: 'plugin_loader', option: platform })
|
||||
|
||||
if (pt === 'mod') filters.push({ type: 'environment', option: 'server' })
|
||||
|
||||
if (hideSelectedServerInstalls.value && queuedServerInstallProjectIds.value.size > 0) {
|
||||
for (const id of queuedServerInstallProjectIds.value) {
|
||||
filters.push({ type: 'project_id', option: `project_id:${id}`, negative: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pt === 'modpack') {
|
||||
@@ -334,24 +313,134 @@ const combinedProvidedFilters = computed(() =>
|
||||
isServerContext.value ? serverContextFilters.value : instanceFilters.value,
|
||||
)
|
||||
|
||||
const {
|
||||
serverPings,
|
||||
contextMenuRef,
|
||||
updateServerHits,
|
||||
getServerModpackContent,
|
||||
getServerCardActions,
|
||||
handleRightClick,
|
||||
handleOptionsClick,
|
||||
} = useAppServerBrowse({
|
||||
instance,
|
||||
isFromWorlds,
|
||||
allInstalledIds,
|
||||
newlyInstalled,
|
||||
installingServerProjects,
|
||||
playServerProject,
|
||||
showAddServerToInstanceModal,
|
||||
handleError,
|
||||
router,
|
||||
const serverPings = shallowRef<Record<string, number | undefined>>({})
|
||||
const serverPingCache = new Map<string, number | undefined>()
|
||||
const pendingServerPings = new Map<string, Promise<number | undefined>>()
|
||||
let serverPingCacheActive = true
|
||||
const runningServerProjects = ref<Record<string, string>>({})
|
||||
|
||||
async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
|
||||
debugLog('checkServerRunningStates', { hitCount: hits.length })
|
||||
const packs = await listInstances()
|
||||
const newRunning: Record<string, string> = {}
|
||||
for (const hit of hits) {
|
||||
const inst = packs.find((p: GameInstance) => p.linked_data?.project_id === hit.project_id)
|
||||
if (inst) {
|
||||
const processes = await get_by_profile_path(inst.path).catch(() => [])
|
||||
if (Array.isArray(processes) && processes.length > 0) {
|
||||
newRunning[hit.project_id] = inst.path
|
||||
}
|
||||
}
|
||||
}
|
||||
debugLog('runningServerProjects updated', newRunning)
|
||||
runningServerProjects.value = newRunning
|
||||
}
|
||||
|
||||
async function handleStopServerProject(projectId: string) {
|
||||
debugLog('handleStopServerProject', projectId)
|
||||
const instancePath = runningServerProjects.value[projectId]
|
||||
if (!instancePath) return
|
||||
await kill(instancePath).catch(() => {})
|
||||
const { [projectId]: _, ...rest } = runningServerProjects.value
|
||||
runningServerProjects.value = rest
|
||||
}
|
||||
|
||||
async function handlePlayServerProject(projectId: string) {
|
||||
debugLog('handlePlayServerProject', projectId)
|
||||
await playServerProject(projectId)
|
||||
checkServerRunningStates(lastServerHits.value)
|
||||
}
|
||||
|
||||
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
|
||||
|
||||
async function handleAddServerToInstance(project: Labrinth.Search.v3.ResultSearchProject) {
|
||||
debugLog('handleAddServerToInstance', { projectId: project.project_id, name: project.name })
|
||||
const address = getServerAddress(project.minecraft_java_server)
|
||||
if (!address) return
|
||||
|
||||
if (instance.value) {
|
||||
try {
|
||||
await add_server_to_profile(
|
||||
instance.value.path,
|
||||
project.name,
|
||||
address,
|
||||
'prompt',
|
||||
project.project_id,
|
||||
project.minecraft_java_server?.content?.kind,
|
||||
)
|
||||
newlyInstalled.value.push(project.project_id)
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
}
|
||||
} else {
|
||||
showAddServerToInstanceModal(project.name, address)
|
||||
}
|
||||
}
|
||||
|
||||
async function pingServerHits(hits: Labrinth.Search.v3.ResultSearchProject[]) {
|
||||
debugLog('pingServerHits', { hitCount: hits.length })
|
||||
const pingsToFetch = hits.flatMap((hit) => {
|
||||
const address = hit.minecraft_java_server?.address
|
||||
if (!address) return []
|
||||
return [{ hit, address }]
|
||||
})
|
||||
const nextPings = { ...serverPings.value }
|
||||
for (const { hit, address } of pingsToFetch) {
|
||||
if (serverPingCache.has(address)) {
|
||||
nextPings[hit.project_id] = serverPingCache.get(address)
|
||||
}
|
||||
}
|
||||
serverPings.value = nextPings
|
||||
|
||||
await Promise.all(
|
||||
pingsToFetch.map(async ({ hit, address }) => {
|
||||
if (serverPingCache.has(address)) return
|
||||
|
||||
let pending = pendingServerPings.get(address)
|
||||
if (!pending) {
|
||||
pending = getServerLatency(address)
|
||||
.then((latency) => {
|
||||
if (serverPingCacheActive) serverPingCache.set(address, latency)
|
||||
return latency
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failed to ping server ${address}:`, err)
|
||||
if (serverPingCacheActive) serverPingCache.set(address, undefined)
|
||||
return undefined
|
||||
})
|
||||
.finally(() => {
|
||||
pendingServerPings.delete(address)
|
||||
})
|
||||
pendingServerPings.set(address, pending)
|
||||
}
|
||||
|
||||
const latency = await pending
|
||||
if (!serverPingCacheActive) return
|
||||
serverPings.value = { ...serverPings.value, [hit.project_id]: latency }
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const unlistenProcesses = await process_listener(
|
||||
(e: { event: string; profile_path_id: string }) => {
|
||||
debugLog('process event', e)
|
||||
if (e.event === 'finished') {
|
||||
const projectId = Object.entries(runningServerProjects.value).find(
|
||||
([, path]) => path === e.profile_path_id,
|
||||
)?.[0]
|
||||
if (projectId) {
|
||||
const { [projectId]: _, ...rest } = runningServerProjects.value
|
||||
runningServerProjects.value = rest
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
serverPingCacheActive = false
|
||||
unlistenProcesses()
|
||||
serverPingCache.clear()
|
||||
pendingServerPings.clear()
|
||||
})
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
@@ -365,13 +454,29 @@ window.addEventListener('online', () => {
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
addServerToInstance: {
|
||||
id: 'app.browse.add-server-to-instance',
|
||||
defaultMessage: 'Add server to instance',
|
||||
},
|
||||
addServersToInstance: {
|
||||
id: 'app.browse.add-servers-to-instance',
|
||||
defaultMessage: 'Adding server to instance',
|
||||
defaultMessage: 'Add servers to your instance',
|
||||
},
|
||||
addToAnInstance: {
|
||||
id: 'app.browse.add-to-an-instance',
|
||||
defaultMessage: 'Add to an instance',
|
||||
addToInstance: {
|
||||
id: 'app.browse.add-to-instance',
|
||||
defaultMessage: 'Add to instance',
|
||||
},
|
||||
addToInstanceName: {
|
||||
id: 'app.browse.add-to-instance-name',
|
||||
defaultMessage: 'Add to {instanceName}',
|
||||
},
|
||||
added: {
|
||||
id: 'app.browse.added',
|
||||
defaultMessage: 'Added',
|
||||
},
|
||||
alreadyAdded: {
|
||||
id: 'app.browse.already-added',
|
||||
defaultMessage: 'Already added',
|
||||
},
|
||||
discoverContent: {
|
||||
id: 'app.browse.discover-content',
|
||||
@@ -397,19 +502,26 @@ const messages = defineMessages({
|
||||
id: 'app.browse.hide-added-servers',
|
||||
defaultMessage: 'Hide already added servers',
|
||||
},
|
||||
hideInstalledContent: {
|
||||
id: 'app.browse.hide-installed-content',
|
||||
defaultMessage: 'Hide already installed content',
|
||||
},
|
||||
installContentToInstance: {
|
||||
id: 'app.browse.install-content-to-instance',
|
||||
defaultMessage: 'Install content to instance',
|
||||
},
|
||||
installToServer: {
|
||||
id: 'app.browse.server.install',
|
||||
defaultMessage: 'Install',
|
||||
},
|
||||
installedToServer: {
|
||||
id: 'app.browse.server.installed',
|
||||
defaultMessage: 'Installed',
|
||||
},
|
||||
installingToServer: {
|
||||
id: 'app.browse.server.installing',
|
||||
defaultMessage: 'Installing',
|
||||
},
|
||||
backToInstance: {
|
||||
id: 'app.browse.back-to-instance',
|
||||
defaultMessage: 'Back to instance',
|
||||
},
|
||||
serverInstanceContentWarning: {
|
||||
id: 'app.browse.server-instance-content-warning',
|
||||
defaultMessage:
|
||||
'Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content.',
|
||||
},
|
||||
modLoaderProvidedByInstance: {
|
||||
id: 'search.filter.locked.instance-loader.title',
|
||||
defaultMessage: 'Loader is provided by the instance',
|
||||
@@ -542,6 +654,46 @@ const selectableProjectTypes = computed(() => {
|
||||
]
|
||||
})
|
||||
|
||||
const getServerModpackContent = (project: Labrinth.Search.v3.ResultSearchProject) => {
|
||||
const content = project.minecraft_java_server?.content
|
||||
if (content?.kind === 'modpack') {
|
||||
const { project_name, project_icon, project_id } = content
|
||||
if (!project_name) return undefined
|
||||
return {
|
||||
name: project_name,
|
||||
icon: project_icon ?? undefined,
|
||||
onclick:
|
||||
project_id !== project.project_id
|
||||
? () => {
|
||||
router.push(`/project/${project_id}`)
|
||||
}
|
||||
: undefined,
|
||||
showCustomModpackTooltip: project_id === project.project_id,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const contextMenuRef = ref(null)
|
||||
// @ts-expect-error - no event types
|
||||
const handleRightClick = (event, result) => {
|
||||
// @ts-ignore
|
||||
contextMenuRef.value?.showMenu(event, result, [{ name: 'open_link' }, { name: 'copy_link' }])
|
||||
}
|
||||
// @ts-expect-error - no event types
|
||||
const handleOptionsClick = (args) => {
|
||||
switch (args.option) {
|
||||
case 'open_link':
|
||||
openUrl(`https://modrinth.com/${args.item.project_types?.[0] ?? 'project'}/${args.item.slug}`)
|
||||
break
|
||||
case 'copy_link':
|
||||
navigator.clipboard.writeText(
|
||||
`https://modrinth.com/${args.item.project_types?.[0] ?? 'project'}/${args.item.slug}`,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const installContext = computed(() => {
|
||||
if (isServerContext.value && serverContextServerData.value) {
|
||||
return {
|
||||
@@ -555,15 +707,6 @@ const installContext = computed(() => {
|
||||
backUrl: serverBackUrl.value,
|
||||
backLabel: serverBackLabel.value,
|
||||
heading: serverBrowseHeading.value,
|
||||
queuedCount: queuedServerInstallCount.value,
|
||||
selectedProjects: selectedServerInstallProjects.value,
|
||||
isInstallingSelected: isInstallingQueuedServerInstalls.value,
|
||||
installProgress: queuedInstallProgress.value,
|
||||
clearQueued: clearQueuedServerInstalls,
|
||||
clearSelected: clearQueuedServerInstalls,
|
||||
onBack: flushQueuedServerInstalls,
|
||||
discardSelectedAndBack: discardQueuedServerInstallsAndBack,
|
||||
installSelected: installQueuedServerInstallsAndBack,
|
||||
}
|
||||
}
|
||||
if (instance.value) {
|
||||
@@ -573,13 +716,13 @@ const installContext = computed(() => {
|
||||
gameVersion: instance.value.game_version,
|
||||
iconSrc: instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : null,
|
||||
backUrl: `/instance/${encodeURIComponent(instance.value.path)}${isFromWorlds.value ? '/worlds' : ''}`,
|
||||
backLabel: formatMessage(messages.backToInstance),
|
||||
backLabel: 'Back to instance',
|
||||
heading: formatMessage(
|
||||
isFromWorlds.value ? messages.addServersToInstance : commonMessages.installingContentLabel,
|
||||
isFromWorlds.value ? messages.addServersToInstance : messages.installContentToInstance,
|
||||
),
|
||||
warning:
|
||||
isServerInstance.value && !isFromWorlds.value
|
||||
? formatMessage(messages.serverInstanceContentWarning)
|
||||
? 'Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content.'
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
@@ -598,82 +741,71 @@ function setProjectInstalling(projectId: string, installing: boolean) {
|
||||
installingProjectIds.value = next
|
||||
}
|
||||
|
||||
const serverInstallQueue = {
|
||||
get: getQueuedServerInstallPlans,
|
||||
set: setQueuedServerInstallPlans,
|
||||
}
|
||||
|
||||
function getCurrentSelectedInstallPreferences(projectTypeValue: string) {
|
||||
return getSelectedInstallPreferences({
|
||||
contentType: projectTypeValue,
|
||||
selectedFilters: searchState.currentFilters.value,
|
||||
providedFilters: combinedProvidedFilters.value,
|
||||
overriddenProvidedFilterTypes: searchState.overriddenProvidedFilterTypes.value,
|
||||
})
|
||||
}
|
||||
|
||||
function getServerInstallTargetPreferences(contentType: BrowseInstallContentType) {
|
||||
return getTargetInstallPreferences(
|
||||
{
|
||||
gameVersion: serverContextServerData.value?.mc_version,
|
||||
loader: serverContextServerData.value?.loader,
|
||||
},
|
||||
contentType,
|
||||
)
|
||||
}
|
||||
|
||||
function getInstanceInstallTargetPreferences(projectTypeValue: string) {
|
||||
return getTargetInstallPreferences(
|
||||
{
|
||||
gameVersion: instance.value?.game_version,
|
||||
loader: instance.value?.loader,
|
||||
},
|
||||
projectTypeValue,
|
||||
)
|
||||
}
|
||||
|
||||
async function getInstallProjectVersions(projectId: string) {
|
||||
const project = await get_project(projectId, 'must_revalidate')
|
||||
return (await get_version_many(
|
||||
project.versions,
|
||||
'must_revalidate',
|
||||
)) as Labrinth.Versions.v2.Version[]
|
||||
}
|
||||
|
||||
async function chooseInstanceInstallVersion(
|
||||
project: Labrinth.Search.v2.ResultSearchProject & Labrinth.Search.v3.ResultSearchProject,
|
||||
projectTypeValue: string,
|
||||
) {
|
||||
const targetInstance = instance.value
|
||||
if (!targetInstance) {
|
||||
return { versionId: null as string | null }
|
||||
}
|
||||
|
||||
const selectedPreferences = getCurrentSelectedInstallPreferences(projectTypeValue)
|
||||
const targetPreferences = getInstanceInstallTargetPreferences(projectTypeValue)
|
||||
if (!preferencesDiffer(selectedPreferences, targetPreferences)) {
|
||||
return { versionId: null as string | null }
|
||||
}
|
||||
|
||||
const selectedVersion = getLatestMatchingInstallVersion(
|
||||
await getInstallProjectVersions(project.project_id),
|
||||
selectedPreferences,
|
||||
projectTypeValue,
|
||||
)
|
||||
|
||||
if (!selectedVersion) {
|
||||
return { versionId: null as string | null }
|
||||
}
|
||||
|
||||
return { versionId: selectedVersion.id }
|
||||
}
|
||||
|
||||
function getCardActions(
|
||||
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
|
||||
currentProjectType: string,
|
||||
): CardAction[] {
|
||||
if (currentProjectType === 'server') {
|
||||
return getServerCardActions(result as Labrinth.Search.v3.ResultSearchProject)
|
||||
const serverResult = result as Labrinth.Search.v3.ResultSearchProject
|
||||
const isInstalled = allInstalledIds.value.has(serverResult.project_id)
|
||||
|
||||
if (isFromWorlds.value && instance.value) {
|
||||
return [
|
||||
{
|
||||
key: 'add-to-instance',
|
||||
label: formatMessage(isInstalled ? messages.added : messages.addToInstance),
|
||||
icon: isInstalled ? CheckIcon : PlusIcon,
|
||||
disabled: isInstalled,
|
||||
color: 'brand',
|
||||
type: 'outlined',
|
||||
onClick: () => handleAddServerToInstance(serverResult),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const actions: CardAction[] = []
|
||||
|
||||
actions.push({
|
||||
key: 'add',
|
||||
label: '',
|
||||
icon: isInstalled ? CheckIcon : PlusIcon,
|
||||
disabled: isInstalled,
|
||||
circular: true,
|
||||
tooltip: isInstalled
|
||||
? formatMessage(messages.alreadyAdded)
|
||||
: instance.value
|
||||
? formatMessage(messages.addToInstanceName, { instanceName: instance.value.name })
|
||||
: formatMessage(messages.addServerToInstance),
|
||||
onClick: () => handleAddServerToInstance(serverResult),
|
||||
})
|
||||
|
||||
if (runningServerProjects.value[serverResult.project_id]) {
|
||||
actions.push({
|
||||
key: 'stop',
|
||||
label: formatMessage(commonMessages.stopButton),
|
||||
icon: StopCircleIcon,
|
||||
color: 'red',
|
||||
type: 'outlined',
|
||||
onClick: () => handleStopServerProject(serverResult.project_id),
|
||||
})
|
||||
} else {
|
||||
const isInstalling = (installingServerProjects.value as string[]).includes(
|
||||
serverResult.project_id,
|
||||
)
|
||||
actions.push({
|
||||
key: 'play',
|
||||
label: formatMessage(
|
||||
isInstalling ? commonMessages.installingLabel : commonMessages.playButton,
|
||||
),
|
||||
icon: PlayIcon,
|
||||
disabled: isInstalling,
|
||||
color: 'brand',
|
||||
type: 'outlined',
|
||||
onClick: () => handlePlayServerProject(serverResult.project_id),
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
// Non-server project actions
|
||||
@@ -685,84 +817,39 @@ function getCardActions(
|
||||
const isInstalled =
|
||||
projectResult.installed ||
|
||||
allInstalledIds.value.has(projectResult.project_id || '') ||
|
||||
serverContentProjectIds.value.has(projectResult.project_id || '') ||
|
||||
serverContextServerData.value?.upstream?.project_id === projectResult.project_id
|
||||
serverContentProjectIds.value.has(projectResult.project_id || '')
|
||||
const isInstalling = installingProjectIds.value.has(projectResult.project_id)
|
||||
|
||||
if (
|
||||
isServerContext.value &&
|
||||
['modpack', 'mod', 'plugin', 'datapack'].includes(currentProjectType)
|
||||
) {
|
||||
const isQueued = queuedServerInstallProjectIds.value.has(projectResult.project_id)
|
||||
const isInstallingSelection = isInstallingQueuedServerInstalls.value
|
||||
const validatingInstall =
|
||||
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
|
||||
const installLabel = isInstalled
|
||||
? commonMessages.installedLabel
|
||||
: isQueued
|
||||
? isInstalling || isInstallingSelection
|
||||
? validatingInstall
|
||||
? commonMessages.validatingLabel
|
||||
: messages.installingToServer
|
||||
: commonMessages.selectedLabel
|
||||
: isInstalling || isInstallingSelection
|
||||
? validatingInstall
|
||||
? commonMessages.validatingLabel
|
||||
: messages.installingToServer
|
||||
: commonMessages.installButton
|
||||
return [
|
||||
{
|
||||
key: 'install',
|
||||
label: formatMessage(installLabel),
|
||||
icon:
|
||||
isInstalling || isInstallingSelection
|
||||
? SpinnerIcon
|
||||
: isQueued || isInstalled
|
||||
? CheckIcon
|
||||
: PlusIcon,
|
||||
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
|
||||
disabled: isInstalled || isInstalling || isInstallingSelection,
|
||||
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
|
||||
label: formatMessage(
|
||||
isInstalling
|
||||
? messages.installingToServer
|
||||
: isInstalled
|
||||
? messages.installedToServer
|
||||
: messages.installToServer,
|
||||
),
|
||||
icon: isInstalled ? CheckIcon : PlusIcon,
|
||||
iconClass: isInstalling ? 'animate-spin' : undefined,
|
||||
disabled: isInstalled || isInstalling,
|
||||
color: 'brand',
|
||||
type: 'outlined',
|
||||
onClick: async () => {
|
||||
if (isQueued) {
|
||||
removeQueuedServerInstall(projectResult.project_id)
|
||||
return
|
||||
}
|
||||
|
||||
const contentType = currentProjectType as BrowseInstallContentType
|
||||
const isModpack = contentType === 'modpack'
|
||||
const shouldShowInstalling = isModpack || !isQueued
|
||||
if (shouldShowInstalling) {
|
||||
setProjectInstalling(projectResult.project_id, true)
|
||||
}
|
||||
setProjectInstalling(projectResult.project_id, true)
|
||||
try {
|
||||
await requestInstall({
|
||||
project: projectResult,
|
||||
contentType,
|
||||
mode: isModpack ? 'immediate' : 'queue',
|
||||
selectedFilters: isModpack ? [] : searchState.currentFilters.value,
|
||||
providedFilters: isModpack ? [] : combinedProvidedFilters.value,
|
||||
overriddenProvidedFilterTypes: isModpack
|
||||
? []
|
||||
: searchState.overriddenProvidedFilterTypes.value,
|
||||
targetPreferences: getServerInstallTargetPreferences(contentType),
|
||||
getProjectVersions: getInstallProjectVersions,
|
||||
queue: serverInstallQueue,
|
||||
install: (plan) =>
|
||||
openServerModpackInstallFlow({
|
||||
projectId: plan.projectId,
|
||||
versionId: plan.versionId,
|
||||
name: plan.project.name,
|
||||
iconUrl: plan.project.icon_url ?? undefined,
|
||||
}),
|
||||
})
|
||||
const didInstall = await installProjectToServer(projectResult)
|
||||
if (didInstall !== false) {
|
||||
onSearchResultInstalled(projectResult.project_id)
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
} finally {
|
||||
if (shouldShowInstalling) {
|
||||
setProjectInstalling(projectResult.project_id, false)
|
||||
}
|
||||
setProjectInstalling(projectResult.project_id, false)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -775,15 +862,13 @@ function getCardActions(
|
||||
return [
|
||||
{
|
||||
key: 'install',
|
||||
label: formatMessage(
|
||||
isInstalling
|
||||
? messages.installingToServer
|
||||
: isInstalled
|
||||
? commonMessages.installedLabel
|
||||
: shouldUseInstallIcon
|
||||
? commonMessages.installButton
|
||||
: messages.addToAnInstance,
|
||||
),
|
||||
label: isInstalling
|
||||
? 'Installing'
|
||||
: isInstalled
|
||||
? 'Installed'
|
||||
: shouldUseInstallIcon
|
||||
? 'Install'
|
||||
: 'Add to an instance',
|
||||
icon: isInstalling ? SpinnerIcon : isInstalled ? CheckIcon : PlusIcon,
|
||||
iconClass: isInstalling ? 'animate-spin' : undefined,
|
||||
disabled: isInstalled || isInstalling,
|
||||
@@ -791,39 +876,28 @@ function getCardActions(
|
||||
type: 'outlined',
|
||||
onClick: async () => {
|
||||
setProjectInstalling(projectResult.project_id, true)
|
||||
try {
|
||||
const selectedInstall = instance.value
|
||||
? await chooseInstanceInstallVersion(projectResult, currentProjectType)
|
||||
: { versionId: null as string | null }
|
||||
if (selectedInstall === null) {
|
||||
await installVersion(
|
||||
projectResult.project_id,
|
||||
null,
|
||||
instance.value ? instance.value.path : null,
|
||||
'SearchCard',
|
||||
(versionId) => {
|
||||
setProjectInstalling(projectResult.project_id, false)
|
||||
return
|
||||
}
|
||||
const selectedPreferences = getCurrentSelectedInstallPreferences(currentProjectType)
|
||||
await installVersion(
|
||||
projectResult.project_id,
|
||||
selectedInstall.versionId,
|
||||
instance.value ? instance.value.path : null,
|
||||
'SearchCard',
|
||||
(versionId) => {
|
||||
setProjectInstalling(projectResult.project_id, false)
|
||||
if (versionId) {
|
||||
onSearchResultInstalled(projectResult.project_id)
|
||||
}
|
||||
},
|
||||
(profile) => {
|
||||
router.push(`/instance/${profile}`)
|
||||
},
|
||||
{
|
||||
preferredLoader: instance.value?.loader ?? selectedPreferences.loaders?.[0],
|
||||
preferredGameVersion:
|
||||
instance.value?.game_version ?? selectedPreferences.gameVersions?.[0],
|
||||
},
|
||||
)
|
||||
} catch (err) {
|
||||
if (versionId) {
|
||||
onSearchResultInstalled(projectResult.project_id)
|
||||
}
|
||||
},
|
||||
(profile) => {
|
||||
router.push(`/instance/${profile}`)
|
||||
},
|
||||
{
|
||||
preferredLoader: instance.value?.loader ?? undefined,
|
||||
preferredGameVersion: instance.value?.game_version ?? undefined,
|
||||
},
|
||||
).catch((err) => {
|
||||
setProjectInstalling(projectResult.project_id, false)
|
||||
handleError(err)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -863,7 +937,9 @@ async function search(requestParams: string) {
|
||||
|
||||
if (isServer) {
|
||||
const hits = rawResults.result.hits ?? []
|
||||
updateServerHits(hits)
|
||||
lastServerHits.value = hits
|
||||
pingServerHits(hits)
|
||||
checkServerRunningStates(hits)
|
||||
return {
|
||||
projectHits: [],
|
||||
serverHits: hits,
|
||||
@@ -948,12 +1024,6 @@ watch(
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(queuedServerInstallCount, (count) => {
|
||||
if (count === 0) {
|
||||
hideSelectedServerInstalls.value = false
|
||||
}
|
||||
})
|
||||
|
||||
if (instance.value?.game_version) {
|
||||
const gv = instance.value.game_version
|
||||
const alreadyHasGv = searchState.serverCurrentFilters.value.some(
|
||||
@@ -966,26 +1036,16 @@ if (instance.value?.game_version) {
|
||||
|
||||
await searchState.refreshSearch()
|
||||
|
||||
function getProjectBrowseQuery() {
|
||||
if (!installContext.value) return undefined
|
||||
return {
|
||||
...route.query,
|
||||
b: route.fullPath,
|
||||
}
|
||||
}
|
||||
|
||||
provideBrowseManager({
|
||||
tags,
|
||||
projectType,
|
||||
...searchState,
|
||||
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) => ({
|
||||
path: `/project/${result.project_id ?? result.slug}`,
|
||||
query: getProjectBrowseQuery(),
|
||||
}),
|
||||
getServerProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) => ({
|
||||
path: `/project/${result.slug ?? result.project_id}`,
|
||||
query: getProjectBrowseQuery(),
|
||||
query: instance.value ? { i: instance.value.path } : undefined,
|
||||
}),
|
||||
getServerProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) =>
|
||||
`/project/${result.slug ?? result.project_id}`,
|
||||
selectableProjectTypes,
|
||||
showProjectTypeTabs: computed(() => !isServerContext.value),
|
||||
variant: 'app',
|
||||
@@ -1008,18 +1068,8 @@ provideBrowseManager({
|
||||
() => (isServerContext.value && projectType.value !== 'modpack') || !!instance.value,
|
||||
),
|
||||
hideInstalledLabel: computed(() =>
|
||||
formatMessage(
|
||||
isFromWorlds.value ? messages.hideAddedServers : commonMessages.hideInstalledContentLabel,
|
||||
),
|
||||
formatMessage(isFromWorlds.value ? messages.hideAddedServers : messages.hideInstalledContent),
|
||||
),
|
||||
hideSelected: hideSelectedServerInstalls,
|
||||
showHideSelected: computed(
|
||||
() =>
|
||||
isServerContext.value &&
|
||||
projectType.value !== 'modpack' &&
|
||||
queuedServerInstallCount.value > 0,
|
||||
),
|
||||
hideSelectedLabel: computed(() => formatMessage(commonMessages.hideSelectedContentLabel)),
|
||||
onInstalled: onSearchResultInstalled,
|
||||
serverPings,
|
||||
getServerModpackContent,
|
||||
@@ -1034,12 +1084,8 @@ provideBrowseManager({
|
||||
<BrowsePageLayout>
|
||||
<template #after>
|
||||
<ContextMenu ref="contextMenuRef" @option-clicked="handleOptionsClick">
|
||||
<template #open_link>
|
||||
<GlobeIcon /> {{ formatMessage(commonMessages.openInModrinthButton) }} <ExternalIcon />
|
||||
</template>
|
||||
<template #copy_link>
|
||||
<ClipboardCopyIcon /> {{ formatMessage(commonMessages.copyLinkButton) }}
|
||||
</template>
|
||||
<template #open_link> <GlobeIcon /> Open in Modrinth <ExternalIcon /> </template>
|
||||
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
</BrowsePageLayout>
|
||||
|
||||
@@ -218,11 +218,7 @@
|
||||
:key="instance.path"
|
||||
>
|
||||
<template v-if="Component">
|
||||
<Suspense
|
||||
:key="instance.path"
|
||||
@pending="subpagePending = true"
|
||||
@resolve="subpagePending = false"
|
||||
>
|
||||
<Suspense :key="instance.path">
|
||||
<component
|
||||
:is="Component"
|
||||
:instance="instance"
|
||||
@@ -232,7 +228,6 @@
|
||||
:installed="instance.install_stage !== 'installed'"
|
||||
:is-server-instance="isServerInstance"
|
||||
:open-settings="() => settingsModal?.show(1)"
|
||||
v-bind="contentSubpageProps"
|
||||
@play="updatePlayState"
|
||||
@stop="() => stopInstance('InstanceSubpage')"
|
||||
></component>
|
||||
@@ -300,7 +295,6 @@ import {
|
||||
ServerPing,
|
||||
ServerRecentPlays,
|
||||
ServerRegion,
|
||||
useLoadingBarToken,
|
||||
} from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
@@ -318,7 +312,6 @@ import { useInstanceConsole } from '@/composables/useInstanceConsole'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_v3 } from '@/helpers/cache.js'
|
||||
import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { type InstanceContentData, loadInstanceContentData } from '@/helpers/instance-content'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
@@ -338,7 +331,6 @@ const route = useRoute()
|
||||
|
||||
const router = useRouter()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
const contentSubpageRouteNames = new Set(['Mods', 'ModsFilter'])
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
@@ -349,16 +341,12 @@ window.addEventListener('online', () => {
|
||||
})
|
||||
|
||||
const instance = ref<GameInstance>()
|
||||
const preloadedContent = ref<InstanceContentData | null>(null)
|
||||
const playing = ref(false)
|
||||
const loading = ref(false)
|
||||
const subpagePending = ref(false)
|
||||
const stopping = ref(false)
|
||||
const exportModal = ref<InstanceType<typeof ExportModal>>()
|
||||
const updateToPlayModal = ref<InstanceType<typeof UpdateToPlayModal>>()
|
||||
|
||||
useLoadingBarToken(subpagePending)
|
||||
|
||||
const isServerInstance = ref(false)
|
||||
const linkedProjectV3 = ref<Labrinth.Projects.v3.Project>()
|
||||
const selected = ref<unknown[]>([])
|
||||
@@ -373,55 +361,36 @@ const playersOnline = ref<number | undefined>(undefined)
|
||||
const ping = ref<number | undefined>(undefined)
|
||||
const loadingServerPing = ref(false)
|
||||
|
||||
function isContentSubpageRoute(routeName = route.name) {
|
||||
return typeof routeName === 'string' && contentSubpageRouteNames.has(routeName)
|
||||
}
|
||||
|
||||
async function fetchInstance() {
|
||||
isServerInstance.value = false
|
||||
linkedProjectV3.value = undefined
|
||||
preloadedContent.value = null
|
||||
ping.value = undefined
|
||||
playersOnline.value = undefined
|
||||
loadingServerPing.value = false
|
||||
|
||||
const nextInstance = await get(route.params.id as string).catch(handleError)
|
||||
let nextLinkedProjectV3: Labrinth.Projects.v3.Project | undefined
|
||||
let nextIsServerInstance = false
|
||||
instance.value = await get(route.params.id as string).catch(handleError)
|
||||
|
||||
const contentPreloadPromise =
|
||||
nextInstance && isContentSubpageRoute()
|
||||
? loadInstanceContentData(nextInstance.path, undefined, handleError)
|
||||
: Promise.resolve(null)
|
||||
|
||||
if (!offline.value && nextInstance?.linked_data && nextInstance.linked_data.project_id) {
|
||||
if (!offline.value && instance.value?.linked_data && instance.value.linked_data.project_id) {
|
||||
try {
|
||||
nextLinkedProjectV3 = await get_project_v3(
|
||||
nextInstance.linked_data.project_id,
|
||||
linkedProjectV3.value = await get_project_v3(
|
||||
instance.value.linked_data.project_id,
|
||||
'must_revalidate',
|
||||
)
|
||||
|
||||
if (nextLinkedProjectV3?.minecraft_server != null) {
|
||||
nextIsServerInstance = true
|
||||
if (linkedProjectV3.value?.minecraft_server != null) {
|
||||
isServerInstance.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error as Error)
|
||||
}
|
||||
}
|
||||
|
||||
const nextPreloadedContent = await contentPreloadPromise
|
||||
|
||||
instance.value = nextInstance ?? undefined
|
||||
linkedProjectV3.value = nextLinkedProjectV3
|
||||
isServerInstance.value = nextIsServerInstance
|
||||
preloadedContent.value = nextPreloadedContent
|
||||
|
||||
fetchDeferredData()
|
||||
|
||||
if (nextInstance) {
|
||||
if (instance.value) {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['worlds', nextInstance.path],
|
||||
queryFn: () => refreshWorlds(nextInstance.path),
|
||||
queryKey: ['worlds', instance.value.path],
|
||||
queryFn: () => refreshWorlds(instance.value!.path),
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
@@ -479,9 +448,6 @@ const renderMode = computed<'scroll' | 'fixed'>(() =>
|
||||
route.meta.renderMode === 'fixed' ? 'fixed' : 'scroll',
|
||||
)
|
||||
const isFixedRender = computed(() => renderMode.value === 'fixed')
|
||||
const contentSubpageProps = computed(() =>
|
||||
isContentSubpageRoute() ? { preloadedContent: preloadedContent.value } : {},
|
||||
)
|
||||
|
||||
const tabs = computed(() => [
|
||||
{
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
:modpack-name="linkedModpackProject?.title"
|
||||
:modpack-icon-url="linkedModpackProject?.icon_url ?? undefined"
|
||||
:enable-toggle="!props.isServerInstance"
|
||||
:busy="isBulkOperating"
|
||||
:get-overflow-options="getOverflowOptions"
|
||||
:switch-version="handleSwitchVersion"
|
||||
@update:enabled="handleModpackContentToggle"
|
||||
@@ -88,12 +87,12 @@ import {
|
||||
ReadyTransition,
|
||||
useDebugLogger,
|
||||
useVIntl,
|
||||
versionChangesGameVersion,
|
||||
} from '@modrinth/ui'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||
@@ -101,20 +100,22 @@ import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_versions, get_version } from '@/helpers/cache.js'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import { type InstanceContentData, loadInstanceContentData } from '@/helpers/instance-content'
|
||||
import {
|
||||
add_project_from_path,
|
||||
add_project_from_version,
|
||||
duplicate,
|
||||
edit,
|
||||
get,
|
||||
get_content_items,
|
||||
get_linked_modpack_content,
|
||||
get_linked_modpack_info,
|
||||
list,
|
||||
remove_project,
|
||||
toggle_disable_project,
|
||||
update_managed_modrinth_version,
|
||||
update_project,
|
||||
} from '@/helpers/profile'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import type { CacheBehaviour, GameInstance } from '@/helpers/types'
|
||||
import { highlightModInProfile } from '@/helpers/utils.js'
|
||||
import { injectContentInstall } from '@/providers/content-install'
|
||||
@@ -159,7 +160,6 @@ const props = defineProps<{
|
||||
instance: GameInstance
|
||||
isServerInstance?: boolean
|
||||
openSettings?: () => void
|
||||
preloadedContent?: InstanceContentData | null
|
||||
}>()
|
||||
|
||||
const loading = ref(true)
|
||||
@@ -212,7 +212,6 @@ const contentUpdaterModal = ref<InstanceType<typeof ContentUpdaterModal> | null>
|
||||
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal> | null>()
|
||||
const modpackUpdateConfirmModal = ref<InstanceType<typeof ConfirmModpackUpdateModal> | null>()
|
||||
|
||||
// TODO: Extract content operation and updater modal state into composables; this page currently owns file mutations, dependency installs, busy flags, and version selection flow.
|
||||
const updatingProject = ref<ContentItem | null>(null)
|
||||
const updatingProjectVersions = ref<Labrinth.Versions.v2.Version[]>([])
|
||||
const loadingVersions = ref(false)
|
||||
@@ -220,78 +219,6 @@ const loadingChangelog = ref(false)
|
||||
const updatingModpack = ref(false)
|
||||
const pendingModpackUpdateVersion = ref<Labrinth.Versions.v2.Version | null>(null)
|
||||
const isModpackUpdateDowngrade = ref(false)
|
||||
const activeContentOperationKeys = ref(new Set<string>())
|
||||
|
||||
let activeContentOperationCount = 0
|
||||
let updateRequestId = 0
|
||||
const activeUpdateRequestId = ref(0)
|
||||
|
||||
function fileNameFromPath(path: string) {
|
||||
return path.split('/').pop() ?? path
|
||||
}
|
||||
|
||||
function getContentOperationKeys(item: ContentItem) {
|
||||
return [item.id, item.file_path, item.file_name, item.project?.id, item.version?.id].filter(
|
||||
(key): key is string => !!key,
|
||||
)
|
||||
}
|
||||
|
||||
function hasContentOperation(item: ContentItem) {
|
||||
const keys = getContentOperationKeys(item)
|
||||
return keys.some((key) => activeContentOperationKeys.value.has(key))
|
||||
}
|
||||
|
||||
function setContentItemBusy(item: ContentItem, busy: boolean, originalFileName = item.file_name) {
|
||||
item.installing = busy
|
||||
modpackContentModal.value?.updateItem(originalFileName, {
|
||||
installing: busy,
|
||||
disabled: busy,
|
||||
})
|
||||
if (item.file_name !== originalFileName) {
|
||||
modpackContentModal.value?.updateItem(item.file_name, {
|
||||
installing: busy,
|
||||
disabled: busy,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function beginContentOperation(item: ContentItem) {
|
||||
if (hasContentOperation(item)) return null
|
||||
|
||||
const keys = getContentOperationKeys(item)
|
||||
activeContentOperationKeys.value = new Set([...activeContentOperationKeys.value, ...keys])
|
||||
activeContentOperationCount++
|
||||
isBulkOperating.value = true
|
||||
setContentItemBusy(item, true)
|
||||
|
||||
return { keys, originalFileName: item.file_name }
|
||||
}
|
||||
|
||||
function finishContentOperation(
|
||||
item: ContentItem,
|
||||
operation: { keys: string[]; originalFileName: string },
|
||||
) {
|
||||
const nextKeys = new Set(activeContentOperationKeys.value)
|
||||
for (const key of operation.keys) {
|
||||
nextKeys.delete(key)
|
||||
}
|
||||
activeContentOperationKeys.value = nextKeys
|
||||
activeContentOperationCount = Math.max(0, activeContentOperationCount - 1)
|
||||
setContentItemBusy(item, false, operation.originalFileName)
|
||||
if (activeContentOperationCount === 0) {
|
||||
isBulkOperating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function beginUpdateRequest() {
|
||||
updateRequestId++
|
||||
activeUpdateRequestId.value = updateRequestId
|
||||
return updateRequestId
|
||||
}
|
||||
|
||||
function isActiveUpdateRequest(requestId: number) {
|
||||
return activeUpdateRequestId.value === requestId
|
||||
}
|
||||
|
||||
async function handleBrowseContent() {
|
||||
if (!props.instance) return
|
||||
@@ -338,21 +265,9 @@ async function handleUploadFiles() {
|
||||
}
|
||||
|
||||
async function toggleDisableMod(mod: ContentItem) {
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
|
||||
try {
|
||||
const newPath = await toggle_disable_project(props.instance.path, mod.file_path)
|
||||
const newFileName = fileNameFromPath(newPath)
|
||||
mod.file_path = newPath
|
||||
mod.file_name = newFileName
|
||||
mod.file_path = await toggle_disable_project(props.instance.path, mod.file_path!)
|
||||
mod.enabled = !mod.enabled
|
||||
modpackContentModal.value?.updateItem(operation.originalFileName, {
|
||||
file_path: newPath,
|
||||
file_name: newFileName,
|
||||
enabled: mod.enabled,
|
||||
})
|
||||
|
||||
trackEvent('InstanceProjectDisable', {
|
||||
loader: props.instance.loader,
|
||||
@@ -364,48 +279,33 @@ async function toggleDisableMod(mod: ContentItem) {
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
} finally {
|
||||
finishContentOperation(mod, operation)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleDisableDebounced = toggleDisableMod
|
||||
const toggleDisableDebounced = useDebounceFn(toggleDisableMod, 20)
|
||||
|
||||
async function removeMod(mod: ContentItem) {
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
await remove_project(props.instance.path, mod.file_path!).catch(handleError)
|
||||
projects.value = projects.value.filter((x) => mod.file_path !== x.file_path)
|
||||
|
||||
try {
|
||||
const removedPath = mod.file_path
|
||||
await remove_project(props.instance.path, removedPath)
|
||||
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 {
|
||||
finishContentOperation(mod, operation)
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
async function updateProject(mod: ContentItem) {
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
|
||||
try {
|
||||
const updateVersionId = mod.update_version_id
|
||||
await update_project(props.instance.path, mod.file_path)
|
||||
const newPath = await update_project(props.instance.path, mod.file_path!)
|
||||
mod.file_path = newPath
|
||||
|
||||
if (updateVersionId) {
|
||||
const versionData = await get_version(updateVersionId, 'must_revalidate').catch(handleError)
|
||||
if (mod.update_version_id) {
|
||||
const versionData = await get_version(mod.update_version_id, 'must_revalidate').catch(
|
||||
handleError,
|
||||
)
|
||||
|
||||
if (versionData) {
|
||||
const profile = await get(props.instance.path).catch(handleError)
|
||||
@@ -416,6 +316,12 @@ async function updateProject(mod: ContentItem) {
|
||||
}
|
||||
}
|
||||
|
||||
mod.has_update = false
|
||||
if (mod.version && mod.update_version_id) {
|
||||
mod.version.id = mod.update_version_id
|
||||
}
|
||||
mod.update_version_id = null
|
||||
|
||||
trackEvent('InstanceProjectUpdate', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
@@ -425,55 +331,32 @@ async function updateProject(mod: ContentItem) {
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
} finally {
|
||||
await refreshContentState('must_revalidate')
|
||||
finishContentOperation(mod, operation)
|
||||
}
|
||||
}
|
||||
|
||||
async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions.v2.Version) {
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
|
||||
const oldPath = mod.file_path
|
||||
const wasDisabled = mod.enabled === false || oldPath.endsWith('.disabled')
|
||||
let newPath: string | null = null
|
||||
let shouldRemoveNewOnError = false
|
||||
|
||||
isBulkOperating.value = true
|
||||
mod.installing = true
|
||||
if (mod.version) {
|
||||
mod.version.id = version.id
|
||||
mod.version.version_number = version.version_number
|
||||
}
|
||||
try {
|
||||
newPath = await add_project_from_version(props.instance.path, version.id, 'update')
|
||||
shouldRemoveNewOnError = newPath !== oldPath
|
||||
|
||||
if (wasDisabled) {
|
||||
newPath = await toggle_disable_project(props.instance.path, newPath)
|
||||
}
|
||||
await remove_project(props.instance.path, mod.file_path!)
|
||||
const newPath = await add_project_from_version(props.instance.path, version.id, 'standalone')
|
||||
|
||||
const profile = await get(props.instance.path).catch(handleError)
|
||||
if (profile) {
|
||||
await installVersionDependencies(profile, version, 'update').catch(handleError)
|
||||
}
|
||||
|
||||
shouldRemoveNewOnError = false
|
||||
if (newPath !== oldPath) {
|
||||
await remove_project(props.instance.path, oldPath)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
mod.file_path = newPath
|
||||
} catch (err) {
|
||||
if (shouldRemoveNewOnError && newPath && newPath !== oldPath) {
|
||||
await remove_project(props.instance.path, newPath).catch(() => {})
|
||||
}
|
||||
handleError(err as Error)
|
||||
} finally {
|
||||
await refreshContentState('must_revalidate')
|
||||
finishContentOperation(mod, operation)
|
||||
mod.installing = false
|
||||
isBulkOperating.value = false
|
||||
await initProjects()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,8 +364,6 @@ async function handleUpdate(id: string) {
|
||||
const item = projects.value.find((p) => p.id === id)
|
||||
if (!item?.has_update || !item.project?.id || !item.version?.id) return
|
||||
|
||||
const requestId = beginUpdateRequest()
|
||||
|
||||
debug('handleUpdate triggered', {
|
||||
fileName: item.file_name,
|
||||
projectType: item.project_type,
|
||||
@@ -503,47 +384,12 @@ async function handleUpdate(id: string) {
|
||||
|
||||
await nextTick()
|
||||
|
||||
const initialVersionId = item.update_version_id ?? undefined
|
||||
debug('handleUpdate: opening content updater modal', {
|
||||
type: 'content',
|
||||
initialVersionId,
|
||||
item: {
|
||||
id: item.id,
|
||||
fileName: item.file_name,
|
||||
projectType: item.project_type,
|
||||
projectId: item.project.id,
|
||||
projectTitle: item.project.title,
|
||||
currentVersionId: item.version.id,
|
||||
currentVersionNumber: item.version.version_number,
|
||||
updateVersionId: item.update_version_id,
|
||||
},
|
||||
instance: {
|
||||
path: props.instance.path,
|
||||
name: props.instance.name,
|
||||
gameVersion: props.instance.game_version,
|
||||
loader: props.instance.loader,
|
||||
linkedData: props.instance.linked_data,
|
||||
},
|
||||
modalStateBeforeFetch: {
|
||||
updatingModpack: updatingModpack.value,
|
||||
updatingProjectId: updatingProject.value?.id,
|
||||
updatingProjectVersions: updatingProjectVersions.value.map((version) => ({
|
||||
id: version.id,
|
||||
versionNumber: version.version_number,
|
||||
gameVersions: version.game_versions,
|
||||
loaders: version.loaders,
|
||||
datePublished: version.date_published,
|
||||
})),
|
||||
},
|
||||
})
|
||||
contentUpdaterModal.value?.show(initialVersionId)
|
||||
contentUpdaterModal.value?.show(item.update_version_id ?? undefined)
|
||||
|
||||
const versions = (await get_project_versions(item.project.id).catch((e) => {
|
||||
return handleError(e)
|
||||
})) as Labrinth.Versions.v2.Version[] | null
|
||||
|
||||
if (!isActiveUpdateRequest(requestId) || updatingProject.value?.id !== item.id) return
|
||||
|
||||
loadingVersions.value = false
|
||||
|
||||
if (!versions) {
|
||||
@@ -568,25 +414,6 @@ async function handleUpdate(id: string) {
|
||||
versions.sort(
|
||||
(a, b) => new Date(b.date_published).getTime() - new Date(a.date_published).getTime(),
|
||||
)
|
||||
const preselectedVersion =
|
||||
versions.find((version) => version.id === initialVersionId) ?? versions[0] ?? null
|
||||
debug('handleUpdate: resolved content updater preselection', {
|
||||
type: 'content',
|
||||
initialVersionId,
|
||||
foundInitialVersion: versions.some((version) => version.id === initialVersionId),
|
||||
preselectedVersion: preselectedVersion
|
||||
? {
|
||||
id: preselectedVersion.id,
|
||||
versionNumber: preselectedVersion.version_number,
|
||||
gameVersions: preselectedVersion.game_versions,
|
||||
loaders: preselectedVersion.loaders,
|
||||
datePublished: preselectedVersion.date_published,
|
||||
}
|
||||
: null,
|
||||
versionCount: versions.length,
|
||||
currentVersionId: item.version.id,
|
||||
updateVersionId: item.update_version_id,
|
||||
})
|
||||
|
||||
updatingProjectVersions.value = versions
|
||||
}
|
||||
@@ -594,8 +421,6 @@ async function handleUpdate(id: string) {
|
||||
async function handleSwitchVersion(item: ContentItem) {
|
||||
if (!item.project?.id || !item.version?.id) return
|
||||
|
||||
const requestId = beginUpdateRequest()
|
||||
|
||||
updatingModpack.value = false
|
||||
updatingProject.value = item
|
||||
updatingProjectVersions.value = []
|
||||
@@ -610,8 +435,6 @@ async function handleSwitchVersion(item: ContentItem) {
|
||||
return handleError(e)
|
||||
})) as Labrinth.Versions.v2.Version[] | null
|
||||
|
||||
if (!isActiveUpdateRequest(requestId) || updatingProject.value?.id !== item.id) return
|
||||
|
||||
loadingVersions.value = false
|
||||
|
||||
if (!versions) return
|
||||
@@ -645,28 +468,9 @@ async function handleModpackContent() {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshModpackContentItems(cacheBehaviour?: CacheBehaviour) {
|
||||
if (!props.instance?.path) return
|
||||
|
||||
const contentItems = await get_linked_modpack_content(props.instance.path, cacheBehaviour).catch(
|
||||
handleError,
|
||||
)
|
||||
|
||||
if (contentItems) {
|
||||
modpackContentModal.value?.setItems(contentItems)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshContentState(cacheBehaviour?: CacheBehaviour) {
|
||||
await initProjects(cacheBehaviour)
|
||||
await refreshModpackContentItems(cacheBehaviour)
|
||||
}
|
||||
|
||||
async function handleModpackUpdate() {
|
||||
if (!props.instance?.linked_data?.project_id) return
|
||||
|
||||
const requestId = beginUpdateRequest()
|
||||
|
||||
updatingModpack.value = true
|
||||
updatingProject.value = null
|
||||
updatingProjectVersions.value = []
|
||||
@@ -675,42 +479,14 @@ async function handleModpackUpdate() {
|
||||
|
||||
await nextTick()
|
||||
|
||||
const initialVersionId =
|
||||
linkedModpackUpdateVersionId.value ?? props.instance?.linked_data?.version_id ?? undefined
|
||||
debug('handleModpackUpdate: opening modpack updater modal', {
|
||||
type: 'modpack',
|
||||
initialVersionId,
|
||||
linkedModpackUpdateVersionId: linkedModpackUpdateVersionId.value,
|
||||
linkedModpackProject: linkedModpackProject.value,
|
||||
linkedModpackVersion: linkedModpackVersion.value,
|
||||
linkedModpackHasUpdate: linkedModpackHasUpdate.value,
|
||||
instance: {
|
||||
path: props.instance.path,
|
||||
name: props.instance.name,
|
||||
gameVersion: props.instance.game_version,
|
||||
loader: props.instance.loader,
|
||||
linkedData: props.instance.linked_data,
|
||||
},
|
||||
modalStateBeforeFetch: {
|
||||
updatingModpack: updatingModpack.value,
|
||||
updatingProjectId: updatingProject.value?.id,
|
||||
updatingProjectVersions: updatingProjectVersions.value.map((version) => ({
|
||||
id: version.id,
|
||||
versionNumber: version.version_number,
|
||||
gameVersions: version.game_versions,
|
||||
loaders: version.loaders,
|
||||
datePublished: version.date_published,
|
||||
})),
|
||||
},
|
||||
})
|
||||
contentUpdaterModal.value?.show(initialVersionId)
|
||||
contentUpdaterModal.value?.show(
|
||||
linkedModpackUpdateVersionId.value ?? props.instance?.linked_data?.version_id ?? undefined,
|
||||
)
|
||||
|
||||
const versions = (await get_project_versions(props.instance.linked_data.project_id).catch(
|
||||
handleError,
|
||||
)) as Labrinth.Versions.v2.Version[] | null
|
||||
|
||||
if (!isActiveUpdateRequest(requestId) || !updatingModpack.value) return
|
||||
|
||||
loadingVersions.value = false
|
||||
|
||||
if (!versions) return
|
||||
@@ -718,25 +494,6 @@ async function handleModpackUpdate() {
|
||||
versions.sort(
|
||||
(a, b) => new Date(b.date_published).getTime() - new Date(a.date_published).getTime(),
|
||||
)
|
||||
const preselectedVersion =
|
||||
versions.find((version) => version.id === initialVersionId) ?? versions[0] ?? null
|
||||
debug('handleModpackUpdate: resolved modpack updater preselection', {
|
||||
type: 'modpack',
|
||||
initialVersionId,
|
||||
foundInitialVersion: versions.some((version) => version.id === initialVersionId),
|
||||
preselectedVersion: preselectedVersion
|
||||
? {
|
||||
id: preselectedVersion.id,
|
||||
versionNumber: preselectedVersion.version_number,
|
||||
gameVersions: preselectedVersion.game_versions,
|
||||
loaders: preselectedVersion.loaders,
|
||||
datePublished: preselectedVersion.date_published,
|
||||
}
|
||||
: null,
|
||||
versionCount: versions.length,
|
||||
linkedModpackUpdateVersionId: linkedModpackUpdateVersionId.value,
|
||||
currentLinkedVersionId: props.instance.linked_data.version_id,
|
||||
})
|
||||
|
||||
updatingProjectVersions.value = versions
|
||||
}
|
||||
@@ -745,12 +502,10 @@ async function fetchAndSpliceVersion(
|
||||
versionId: string,
|
||||
cacheBehaviour?: Parameters<typeof get_version>[1],
|
||||
onError?: (err: unknown) => void,
|
||||
requestId = activeUpdateRequestId.value,
|
||||
) {
|
||||
const fullVersion = (await get_version(versionId, cacheBehaviour).catch(
|
||||
onError ?? (() => null),
|
||||
)) as Labrinth.Versions.v2.Version | null
|
||||
if (!isActiveUpdateRequest(requestId)) return
|
||||
if (!fullVersion) return
|
||||
const index = updatingProjectVersions.value.findIndex((v) => v.id === versionId)
|
||||
if (index !== -1) {
|
||||
@@ -762,26 +517,17 @@ async function fetchAndSpliceVersion(
|
||||
|
||||
async function handleVersionSelect(version: Labrinth.Versions.v2.Version) {
|
||||
if (version.changelog != null) return
|
||||
const requestId = activeUpdateRequestId.value
|
||||
loadingChangelog.value = true
|
||||
await fetchAndSpliceVersion(
|
||||
version.id,
|
||||
'must_revalidate',
|
||||
handleError as (err: unknown) => void,
|
||||
requestId,
|
||||
)
|
||||
if (isActiveUpdateRequest(requestId)) {
|
||||
loadingChangelog.value = false
|
||||
}
|
||||
await fetchAndSpliceVersion(version.id, 'must_revalidate', handleError as (err: unknown) => void)
|
||||
loadingChangelog.value = false
|
||||
}
|
||||
|
||||
async function handleVersionHover(version: Labrinth.Versions.v2.Version) {
|
||||
if (version.changelog != null) return
|
||||
await fetchAndSpliceVersion(version.id, undefined, undefined, activeUpdateRequestId.value)
|
||||
await fetchAndSpliceVersion(version.id)
|
||||
}
|
||||
|
||||
function resetUpdateState() {
|
||||
activeUpdateRequestId.value = 0
|
||||
updatingModpack.value = false
|
||||
updatingProject.value = null
|
||||
updatingProjectVersions.value = []
|
||||
@@ -789,23 +535,13 @@ function resetUpdateState() {
|
||||
loadingChangelog.value = false
|
||||
}
|
||||
|
||||
async function handleModpackUpdateRequest(selectedVersion: Labrinth.Versions.v2.Version) {
|
||||
function handleModpackUpdateRequest(selectedVersion: Labrinth.Versions.v2.Version) {
|
||||
pendingModpackUpdateVersion.value = selectedVersion
|
||||
|
||||
const currentVersionId = props.instance?.linked_data?.version_id
|
||||
const currentVersion = updatingProjectVersions.value.find((v) => v.id === currentVersionId)
|
||||
isModpackUpdateDowngrade.value = currentVersion
|
||||
? new Date(selectedVersion.date_published) < new Date(currentVersion.date_published)
|
||||
: false
|
||||
const shouldShowWarning =
|
||||
isModpackUpdateDowngrade.value ||
|
||||
versionChangesGameVersion(selectedVersion, props.instance.game_version)
|
||||
|
||||
if (!shouldShowWarning) {
|
||||
await handleModpackUpdateConfirm()
|
||||
return
|
||||
}
|
||||
|
||||
modpackUpdateConfirmModal.value?.show()
|
||||
}
|
||||
|
||||
@@ -815,7 +551,6 @@ async function handleModpackUpdateConfirm() {
|
||||
const version = pendingModpackUpdateVersion.value
|
||||
pendingModpackUpdateVersion.value = null
|
||||
|
||||
contentUpdaterModal.value?.hide()
|
||||
isModpackUpdating.value = true
|
||||
try {
|
||||
await update_managed_modrinth_version(props.instance.path, version.id)
|
||||
@@ -839,7 +574,7 @@ async function handleModalUpdate(
|
||||
pendingModpackUpdateVersion.value = selectedVersion
|
||||
await handleModpackUpdateConfirm()
|
||||
} else {
|
||||
await handleModpackUpdateRequest(selectedVersion)
|
||||
handleModpackUpdateRequest(selectedVersion)
|
||||
}
|
||||
} else if (updatingProject.value) {
|
||||
const mod = updatingProject.value
|
||||
@@ -927,31 +662,51 @@ function getOverflowOptions(item: ContentItem): OverflowMenuOption[] {
|
||||
async function initProjects(cacheBehaviour?: CacheBehaviour) {
|
||||
if (!props.instance) return
|
||||
|
||||
const contentData = await loadInstanceContentData(
|
||||
props.instance.path,
|
||||
cacheBehaviour,
|
||||
handleError,
|
||||
)
|
||||
applyContentData(contentData)
|
||||
}
|
||||
const [contentItems, modpackInfo, allCategories] = await Promise.all([
|
||||
get_content_items(props.instance.path, cacheBehaviour).catch(handleError),
|
||||
get_linked_modpack_info(props.instance.path, cacheBehaviour).catch(handleError),
|
||||
get_categories().catch(handleError),
|
||||
])
|
||||
|
||||
function applyContentData(contentData: InstanceContentData) {
|
||||
if (contentData.path !== props.instance.path) return false
|
||||
|
||||
if (!contentData.contentItems) {
|
||||
if (!contentItems) {
|
||||
loading.value = false
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
projects.value = contentData.contentItems
|
||||
projects.value = contentItems
|
||||
|
||||
if (contentData.modpack) {
|
||||
linkedModpackProject.value = contentData.modpack.project
|
||||
linkedModpackVersion.value = contentData.modpack.version
|
||||
linkedModpackOwner.value = contentData.modpack.owner
|
||||
linkedModpackCategories.value = contentData.modpack.categories
|
||||
linkedModpackHasUpdate.value = contentData.modpack.hasUpdate
|
||||
linkedModpackUpdateVersionId.value = contentData.modpack.updateVersionId
|
||||
if (modpackInfo) {
|
||||
linkedModpackProject.value = {
|
||||
...modpackInfo.project,
|
||||
slug: modpackInfo.project.slug ?? modpackInfo.project.id,
|
||||
icon_url: modpackInfo.project.icon_url ?? undefined,
|
||||
}
|
||||
linkedModpackVersion.value = {
|
||||
...modpackInfo.version,
|
||||
date_published: modpackInfo.version.date_published.toString(),
|
||||
}
|
||||
linkedModpackOwner.value = modpackInfo.owner
|
||||
? {
|
||||
...modpackInfo.owner,
|
||||
avatar_url: modpackInfo.owner.avatar_url ?? undefined,
|
||||
}
|
||||
: null
|
||||
|
||||
linkedModpackHasUpdate.value = modpackInfo.has_update
|
||||
linkedModpackUpdateVersionId.value = modpackInfo.update_version_id
|
||||
|
||||
if (allCategories && modpackInfo.project.categories) {
|
||||
const seen = new Set<string>()
|
||||
linkedModpackCategories.value = allCategories.filter((cat: { name: string }) => {
|
||||
if (modpackInfo.project.categories.includes(cat.name) && !seen.has(cat.name)) {
|
||||
seen.add(cat.name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
} else {
|
||||
linkedModpackCategories.value = []
|
||||
}
|
||||
} else {
|
||||
linkedModpackProject.value = null
|
||||
linkedModpackVersion.value = null
|
||||
@@ -962,7 +717,6 @@ function applyContentData(contentData: InstanceContentData) {
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
return true
|
||||
}
|
||||
|
||||
provideAppBackup({
|
||||
@@ -1090,22 +844,10 @@ provideContentManager({
|
||||
filterPersistKey: props.instance.path,
|
||||
})
|
||||
|
||||
type UnlistenFn = () => void
|
||||
|
||||
const initialContentReady = loadInitialContent()
|
||||
void initialContentReady.then(restoreModpackContentModalState).catch(handleError)
|
||||
|
||||
function loadInitialContent() {
|
||||
if (props.preloadedContent && applyContentData(props.preloadedContent)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return initProjects()
|
||||
}
|
||||
|
||||
async function restoreModpackContentModalState() {
|
||||
if (!savedModalState) return
|
||||
await initProjects()
|
||||
|
||||
// Restore modpack content modal state if returning via back navigation
|
||||
if (savedModalState) {
|
||||
const stateToRestore = savedModalState
|
||||
savedModalState = null
|
||||
await nextTick()
|
||||
@@ -1118,32 +860,18 @@ const removeBeforeEach = router.beforeEach(() => {
|
||||
savedModalState = state ?? null
|
||||
})
|
||||
|
||||
let isUnmounted = false
|
||||
let unlistenDragDrop: UnlistenFn | null = null
|
||||
let unlistenProfiles: UnlistenFn | null = null
|
||||
const unlisten = await getCurrentWebview().onDragDropEvent(async (event) => {
|
||||
if (event.payload.type !== 'drop' || !props.instance) return
|
||||
|
||||
onMounted(() => {
|
||||
void getCurrentWebview()
|
||||
.onDragDropEvent(async (event) => {
|
||||
if (event.payload.type !== 'drop' || !props.instance) return
|
||||
for (const file of event.payload.paths) {
|
||||
if (file.endsWith('.mrpack')) continue
|
||||
await add_project_from_path(props.instance.path, file).catch(handleError)
|
||||
}
|
||||
await initProjects()
|
||||
})
|
||||
|
||||
for (const file of event.payload.paths) {
|
||||
if (file.endsWith('.mrpack')) continue
|
||||
await add_project_from_path(props.instance.path, file).catch(handleError)
|
||||
}
|
||||
await initProjects()
|
||||
})
|
||||
.then((unlisten) => {
|
||||
if (isUnmounted) {
|
||||
unlisten()
|
||||
return
|
||||
}
|
||||
|
||||
unlistenDragDrop = unlisten
|
||||
})
|
||||
.catch(handleError)
|
||||
|
||||
void profile_listener(async (event: { event: string; profile_path_id: string }) => {
|
||||
const unlistenProfiles = await profile_listener(
|
||||
async (event: { event: string; profile_path_id: string }) => {
|
||||
if (
|
||||
props.instance &&
|
||||
event.profile_path_id === props.instance.path &&
|
||||
@@ -1153,17 +881,8 @@ onMounted(() => {
|
||||
) {
|
||||
await initProjects()
|
||||
}
|
||||
})
|
||||
.then((unlisten) => {
|
||||
if (isUnmounted) {
|
||||
unlisten()
|
||||
return
|
||||
}
|
||||
|
||||
unlistenProfiles = unlisten
|
||||
})
|
||||
.catch(handleError)
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.instance?.install_stage,
|
||||
@@ -1186,9 +905,8 @@ watch(
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
isUnmounted = true
|
||||
removeBeforeEach()
|
||||
unlistenDragDrop?.()
|
||||
unlistenProfiles?.()
|
||||
unlisten()
|
||||
unlistenProfiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -45,13 +45,7 @@
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="flex flex-col gap-4 p-6">
|
||||
<div
|
||||
v-if="projectInstallContext"
|
||||
class="sticky top-0 z-20 -mx-6 -mt-6 rounded-tl-[--radius-xl] border-0 border-b border-solid bg-surface-1 p-3 border-surface-5"
|
||||
>
|
||||
<BrowseInstallHeader :install-context="projectInstallContext" />
|
||||
</div>
|
||||
<InstanceIndicator v-if="instance && !projectInstallContext" :instance="instance" />
|
||||
<InstanceIndicator v-if="instance" :instance="instance" />
|
||||
<template v-if="data">
|
||||
<Teleport
|
||||
v-if="themeStore.featureFlags.project_background"
|
||||
@@ -70,7 +64,7 @@
|
||||
<ButtonStyled v-if="serverPlaying" size="large" color="red">
|
||||
<button @click="handleStopServer">
|
||||
<StopCircleIcon />
|
||||
{{ formatMessage(commonMessages.stopButton) }}
|
||||
Stop
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else size="large" color="brand">
|
||||
@@ -79,18 +73,11 @@
|
||||
@click="handleClickPlay"
|
||||
>
|
||||
<PlayIcon />
|
||||
{{
|
||||
data && installingServerProjects.includes(data.id)
|
||||
? formatMessage(commonMessages.installingLabel)
|
||||
: formatMessage(commonMessages.playButton)
|
||||
}}
|
||||
{{ data && installingServerProjects.includes(data.id) ? 'Installing...' : 'Play' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled size="large" circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.addServerToInstanceButton)"
|
||||
@click="handleAddServerToInstance"
|
||||
>
|
||||
<button v-tooltip="'Add server to instance'" @click="handleAddServerToInstance">
|
||||
<PlusIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -124,17 +111,13 @@
|
||||
<template v-else #actions>
|
||||
<ButtonStyled size="large" color="brand">
|
||||
<button
|
||||
v-tooltip="installButtonTooltip"
|
||||
:disabled="installButtonDisabled"
|
||||
v-tooltip="installed ? `This project is already installed` : null"
|
||||
:disabled="installed || installing"
|
||||
@click="install(null)"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="installButtonLoading && !installButtonInstalled"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<DownloadIcon v-else-if="!installButtonInstalled && !serverProjectSelected" />
|
||||
<CheckIcon v-else />
|
||||
{{ installButtonLabel }}
|
||||
<DownloadIcon v-if="!installed && !installing" />
|
||||
<CheckIcon v-else-if="installed" />
|
||||
{{ installing ? 'Installing...' : installed ? 'Installed' : 'Install' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled size="large" circular type="transparent">
|
||||
@@ -183,7 +166,7 @@
|
||||
:links="[
|
||||
{
|
||||
label: 'Description',
|
||||
href: projectDescriptionHref,
|
||||
href: `/project/${$route.params.id}`,
|
||||
},
|
||||
{
|
||||
label: 'Versions',
|
||||
@@ -193,7 +176,7 @@
|
||||
},
|
||||
{
|
||||
label: 'Gallery',
|
||||
href: projectGalleryHref,
|
||||
href: `/project/${$route.params.id}/gallery`,
|
||||
shown: data.gallery.length > 0,
|
||||
},
|
||||
]"
|
||||
@@ -212,39 +195,11 @@
|
||||
</template>
|
||||
<template v-else> Project data couldn't not be loaded. </template>
|
||||
</div>
|
||||
<SelectedProjectsFloatingBar
|
||||
v-if="projectInstallContext"
|
||||
:install-context="projectInstallContext"
|
||||
/>
|
||||
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
|
||||
<template #install>
|
||||
<DownloadIcon /> {{ formatMessage(commonMessages.installButton) }}
|
||||
</template>
|
||||
<template #open_link>
|
||||
<GlobeIcon /> {{ formatMessage(commonMessages.openInModrinthButton) }} <ExternalIcon />
|
||||
</template>
|
||||
<template #copy_link>
|
||||
<ClipboardCopyIcon /> {{ formatMessage(commonMessages.copyLinkButton) }}
|
||||
</template>
|
||||
<template #install> <DownloadIcon /> Install </template>
|
||||
<template #open_link> <GlobeIcon /> Open in Modrinth <ExternalIcon /> </template>
|
||||
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
||||
</ContextMenu>
|
||||
<CreationFlowModal
|
||||
v-if="serverInstallContent.isServerContext.value && data?.project_type === 'modpack'"
|
||||
ref="serverSetupModalRef"
|
||||
:type="
|
||||
serverInstallContent.serverFlowFrom.value === 'reset-server'
|
||||
? 'reset-server'
|
||||
: 'server-onboarding'
|
||||
"
|
||||
:available-loaders="['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur']"
|
||||
:show-snapshot-toggle="true"
|
||||
:on-back="serverInstallContent.onServerFlowBack"
|
||||
:search-modpacks="serverInstallContent.searchServerModpacks"
|
||||
:get-project-versions="serverInstallContent.getServerProjectVersions"
|
||||
:get-loader-manifest="getLoaderManifest"
|
||||
@hide="() => {}"
|
||||
@browse-modpacks="() => {}"
|
||||
@create="serverInstallContent.handleServerModpackFlowCreate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -261,16 +216,10 @@ import {
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
ReportIcon,
|
||||
SpinnerIcon,
|
||||
StopCircleIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
BrowseInstallHeader,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
CreationFlowModal,
|
||||
defineMessages,
|
||||
getTargetInstallPreferences,
|
||||
injectNotificationManager,
|
||||
NavTabs,
|
||||
OverflowMenu,
|
||||
@@ -282,11 +231,7 @@ import {
|
||||
ProjectSidebarLinks,
|
||||
ProjectSidebarServerInfo,
|
||||
ProjectSidebarTags,
|
||||
requestInstall,
|
||||
SelectedProjectsFloatingBar,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
@@ -304,7 +249,6 @@ import {
|
||||
get_version_many,
|
||||
} from '@/helpers/cache.js'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import {
|
||||
get as getInstance,
|
||||
@@ -316,7 +260,6 @@ import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import { getServerLatency } from '@/helpers/worlds'
|
||||
import { injectContentInstall } from '@/providers/content-install'
|
||||
import { injectServerInstall } from '@/providers/server-install'
|
||||
import { createServerInstallContent } from '@/providers/setup/server-install-content'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { getServerAddress } from '@/store/install.js'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
@@ -329,22 +272,6 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
const themeStore = useTheming()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
backToBrowse: {
|
||||
id: 'app.project.install-context.back-to-browse',
|
||||
defaultMessage: 'Back to discover',
|
||||
},
|
||||
installContentToInstance: {
|
||||
id: 'app.project.install-context.install-content-to-instance',
|
||||
defaultMessage: 'Install content to instance',
|
||||
},
|
||||
alreadyInstalled: {
|
||||
id: 'app.project.install-button.already-installed',
|
||||
defaultMessage: 'This project is already installed',
|
||||
},
|
||||
})
|
||||
|
||||
const { installingServerProjects, playServerProject, showAddServerToInstanceModal } =
|
||||
injectServerInstall()
|
||||
@@ -369,11 +296,6 @@ const serverPing = ref(undefined)
|
||||
const serverStatusOnline = ref(false)
|
||||
const serverInstancePath = ref(null)
|
||||
const serverPlaying = ref(false)
|
||||
const serverSetupModalRef = ref(null)
|
||||
const serverInstallContent = createServerInstallContent({ serverSetupModalRef })
|
||||
|
||||
serverInstallContent.watchServerContextChanges()
|
||||
await serverInstallContent.initServerContext()
|
||||
|
||||
const instanceFilters = computed(() => {
|
||||
if (!instance.value) {
|
||||
@@ -393,9 +315,11 @@ const instanceFilters = computed(() => {
|
||||
return { l: loaders, g: instance.value.game_version }
|
||||
})
|
||||
|
||||
function buildProjectHref(path, extraQuery = {}) {
|
||||
const versionsHref = computed(() => {
|
||||
const base = `/project/${route.params.id}/versions`
|
||||
const filters = instanceFilters.value
|
||||
const params = new URLSearchParams()
|
||||
for (const [key, val] of Object.entries({ ...route.query, ...extraQuery })) {
|
||||
for (const [key, val] of Object.entries(filters)) {
|
||||
if (Array.isArray(val)) {
|
||||
for (const v of val) params.append(key, v)
|
||||
} else if (val) {
|
||||
@@ -403,102 +327,7 @@ function buildProjectHref(path, extraQuery = {}) {
|
||||
}
|
||||
}
|
||||
const qs = params.toString()
|
||||
return qs ? `${path}?${qs}` : path
|
||||
}
|
||||
|
||||
const projectDescriptionHref = computed(() => buildProjectHref(`/project/${route.params.id}`))
|
||||
const versionsHref = computed(() =>
|
||||
buildProjectHref(`/project/${route.params.id}/versions`, instanceFilters.value),
|
||||
)
|
||||
const projectGalleryHref = computed(() => buildProjectHref(`/project/${route.params.id}/gallery`))
|
||||
|
||||
const projectBrowseBackUrl = computed(() => {
|
||||
const browsePath = route.query.b
|
||||
if (typeof browsePath === 'string' && browsePath.startsWith('/browse/')) return browsePath
|
||||
const type = data.value?.project_type ? `${data.value.project_type}` : 'mod'
|
||||
return `/browse/${type}`
|
||||
})
|
||||
|
||||
const projectInstallContext = computed(() => {
|
||||
const serverData = serverInstallContent.serverContextServerData.value
|
||||
if (serverData) {
|
||||
return {
|
||||
name: serverData.name,
|
||||
loader: serverData.loader ?? '',
|
||||
gameVersion: serverData.mc_version ?? '',
|
||||
serverId: serverInstallContent.serverIdQuery.value,
|
||||
upstream: serverData.upstream,
|
||||
iconSrc: null,
|
||||
isMedal: serverData.is_medal,
|
||||
backUrl: projectBrowseBackUrl.value,
|
||||
backLabel: formatMessage(messages.backToBrowse),
|
||||
heading: serverInstallContent.serverBrowseHeading.value,
|
||||
queuedCount: serverInstallContent.queuedServerInstallCount.value,
|
||||
selectedProjects: serverInstallContent.selectedServerInstallProjects.value,
|
||||
isInstallingSelected: serverInstallContent.isInstallingQueuedServerInstalls.value,
|
||||
installProgress: serverInstallContent.queuedInstallProgress.value,
|
||||
clearQueued: serverInstallContent.clearQueuedServerInstalls,
|
||||
clearSelected: serverInstallContent.clearQueuedServerInstalls,
|
||||
discardSelectedAndBack: serverInstallContent.discardQueuedServerInstallsAndBack,
|
||||
installSelected: serverInstallContent.installQueuedServerInstallsAndBack,
|
||||
}
|
||||
}
|
||||
|
||||
if (instance.value) {
|
||||
return {
|
||||
name: instance.value.name,
|
||||
loader: instance.value.loader,
|
||||
gameVersion: instance.value.game_version,
|
||||
iconSrc: instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : null,
|
||||
backUrl: projectBrowseBackUrl.value,
|
||||
backLabel: formatMessage(messages.backToBrowse),
|
||||
heading: formatMessage(messages.installContentToInstance),
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
const serverProjectInstallContext = computed(
|
||||
() =>
|
||||
!!serverInstallContent.serverContextServerData.value &&
|
||||
['modpack', 'mod', 'plugin', 'datapack'].includes(data.value?.project_type),
|
||||
)
|
||||
const serverProjectSelected = computed(
|
||||
() => !!data.value && serverInstallContent.queuedServerInstallProjectIds.value.has(data.value.id),
|
||||
)
|
||||
const serverProjectInstalled = computed(
|
||||
() =>
|
||||
!!data.value &&
|
||||
(serverInstallContent.serverContentProjectIds.value.has(data.value.id) ||
|
||||
serverInstallContent.serverContextServerData.value?.upstream?.project_id === data.value.id),
|
||||
)
|
||||
const installButtonLoading = computed(
|
||||
() => installing.value || serverInstallContent.isInstallingQueuedServerInstalls.value,
|
||||
)
|
||||
const installButtonValidating = computed(
|
||||
() =>
|
||||
serverProjectInstallContext.value &&
|
||||
installing.value &&
|
||||
data.value?.project_type !== 'modpack' &&
|
||||
!serverInstallContent.isInstallingQueuedServerInstalls.value,
|
||||
)
|
||||
const installButtonInstalled = computed(() =>
|
||||
serverProjectInstallContext.value ? serverProjectInstalled.value : installed.value,
|
||||
)
|
||||
const installButtonDisabled = computed(
|
||||
() => installButtonInstalled.value || installButtonLoading.value,
|
||||
)
|
||||
const installButtonLabel = computed(() => {
|
||||
if (installButtonInstalled.value) return formatMessage(commonMessages.installedLabel)
|
||||
if (installButtonValidating.value) return formatMessage(commonMessages.validatingLabel)
|
||||
if (installButtonLoading.value) return formatMessage(commonMessages.installingLabel)
|
||||
if (serverProjectSelected.value) return formatMessage(commonMessages.selectedLabel)
|
||||
return formatMessage(commonMessages.installButton)
|
||||
})
|
||||
const installButtonTooltip = computed(() => {
|
||||
if (installButtonInstalled.value) return formatMessage(messages.alreadyInstalled)
|
||||
return null
|
||||
return qs ? `${base}?${qs}` : base
|
||||
})
|
||||
|
||||
const [allLoaders, allGameVersions] = await Promise.all([
|
||||
@@ -670,55 +499,6 @@ watch(
|
||||
)
|
||||
|
||||
async function install(version) {
|
||||
if (serverProjectInstallContext.value && data.value) {
|
||||
if (serverProjectSelected.value) {
|
||||
serverInstallContent.removeQueuedServerInstall(data.value.id)
|
||||
return
|
||||
}
|
||||
if (installButtonDisabled.value) return
|
||||
|
||||
installing.value = true
|
||||
try {
|
||||
const contentType = data.value.project_type
|
||||
await requestInstall({
|
||||
project: {
|
||||
...data.value,
|
||||
project_id: data.value.id,
|
||||
icon_url: data.value.icon_url,
|
||||
},
|
||||
contentType,
|
||||
mode: contentType === 'modpack' ? 'immediate' : 'queue',
|
||||
selectedFilters: [],
|
||||
providedFilters: [],
|
||||
overriddenProvidedFilterTypes: [],
|
||||
targetPreferences: getTargetInstallPreferences(
|
||||
{
|
||||
gameVersion: serverInstallContent.serverContextServerData.value?.mc_version,
|
||||
loader: serverInstallContent.serverContextServerData.value?.loader,
|
||||
},
|
||||
contentType,
|
||||
),
|
||||
getProjectVersions: async () => versions.value,
|
||||
queue: {
|
||||
get: serverInstallContent.getQueuedServerInstallPlans,
|
||||
set: serverInstallContent.setQueuedServerInstallPlans,
|
||||
},
|
||||
install: (plan) =>
|
||||
serverInstallContent.openServerModpackInstallFlow({
|
||||
projectId: plan.projectId,
|
||||
versionId: plan.versionId,
|
||||
name: plan.project.title ?? plan.project.name ?? data.value.title,
|
||||
iconUrl: plan.project.icon_url ?? undefined,
|
||||
}),
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err)
|
||||
} finally {
|
||||
installing.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
installing.value = true
|
||||
await installVersion(
|
||||
data.value.id,
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface InstanceSettingsContext {
|
||||
offline?: boolean
|
||||
isMinecraftServer: Ref<boolean>
|
||||
onUnlinked: () => void
|
||||
closeModal?: () => void
|
||||
}
|
||||
|
||||
export const [injectInstanceSettings, provideInstanceSettings] =
|
||||
|
||||
@@ -1,36 +1,22 @@
|
||||
import type { AbstractModrinthClient, Archon, Labrinth } from '@modrinth/api-client'
|
||||
import type { Archon, Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
addPendingServerContentInstalls,
|
||||
type BrowseInstallPlan,
|
||||
type BrowseSelectedProject,
|
||||
createContext,
|
||||
type CreationFlowContextValue,
|
||||
flushStoredServerAddonInstallQueue,
|
||||
getStoredServerAddonInstallQueue,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
type PendingServerContentInstall,
|
||||
type PendingServerContentInstallType,
|
||||
readPendingServerContentInstalls,
|
||||
readStoredServerInstallQueue,
|
||||
removePendingServerContentInstall,
|
||||
writePendingServerContentInstallBaseline,
|
||||
writeStoredServerInstallQueue,
|
||||
} from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, type ComputedRef, nextTick, type Ref, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
type ServerFlowFrom = 'onboarding' | 'reset-server'
|
||||
type ServerInstallableType = 'modpack' | 'mod' | 'plugin' | 'datapack'
|
||||
|
||||
type InstallableSearchResult = Labrinth.Search.v3.ResultSearchProject & {
|
||||
title?: string
|
||||
installing?: boolean
|
||||
installed?: boolean
|
||||
}
|
||||
type PendingServerContentInstallInput = Omit<PendingServerContentInstall, 'createdAt'>
|
||||
|
||||
export interface ServerModpackSelectionRequest {
|
||||
interface ServerModpackSelectionRequest {
|
||||
projectId: string
|
||||
versionId: string
|
||||
name: string
|
||||
@@ -54,19 +40,9 @@ export interface ServerInstallContentContext {
|
||||
effectiveServerWorldId: ComputedRef<string | null>
|
||||
serverContextServerData: Ref<Archon.Servers.v0.Server | null>
|
||||
serverContentProjectIds: Ref<Set<string>>
|
||||
queuedServerInstallProjectIds: ComputedRef<Set<string>>
|
||||
queuedServerInstallCount: ComputedRef<number>
|
||||
selectedServerInstallProjects: ComputedRef<BrowseSelectedProject[]>
|
||||
isInstallingQueuedServerInstalls: Ref<boolean>
|
||||
queuedInstallProgress: Ref<{ completed: number; total: number }>
|
||||
serverBackUrl: ComputedRef<string>
|
||||
serverBackLabel: ComputedRef<string>
|
||||
serverBrowseHeading: ComputedRef<string>
|
||||
clearQueuedServerInstalls: () => void
|
||||
removeQueuedServerInstall: (projectId: string) => void
|
||||
flushQueuedServerInstalls: () => Promise<boolean>
|
||||
discardQueuedServerInstallsAndBack: () => Promise<void>
|
||||
installQueuedServerInstallsAndBack: () => Promise<boolean>
|
||||
initServerContext: () => Promise<void>
|
||||
watchServerContextChanges: () => void
|
||||
searchServerModpacks: (
|
||||
@@ -75,11 +51,7 @@ export interface ServerInstallContentContext {
|
||||
) => Promise<Labrinth.Projects.v2.SearchResult>
|
||||
getServerProjectVersions: (projectId: string) => Promise<{ id: string }[]>
|
||||
enforceSetupModpackRoute: (currentProjectType: string | undefined) => void
|
||||
getQueuedServerInstallPlans: () => Map<string, BrowseInstallPlan<InstallableSearchResult>>
|
||||
setQueuedServerInstallPlans: (
|
||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||
) => void
|
||||
openServerModpackInstallFlow: (request: ServerModpackSelectionRequest) => Promise<void>
|
||||
installProjectToServer: (project: InstallableSearchResult) => Promise<boolean>
|
||||
onServerFlowBack: () => void
|
||||
handleServerModpackFlowCreate: (config: CreationFlowContextValue) => Promise<void>
|
||||
markServerProjectInstalled: (id: string) => void
|
||||
@@ -93,114 +65,6 @@ function readQueryString(value: unknown): string | null {
|
||||
return typeof value === 'string' && value.length > 0 ? value : null
|
||||
}
|
||||
|
||||
function getQueuedInstallOwnerFallback(project: InstallableSearchResult) {
|
||||
if (project.organization) {
|
||||
const ownerId = project.organization_id ?? project.organization
|
||||
return {
|
||||
id: ownerId,
|
||||
name: project.organization,
|
||||
type: 'organization' as const,
|
||||
link: `https://modrinth.com/organization/${ownerId}`,
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.author) return null
|
||||
|
||||
const ownerId = project.author_id ?? project.author
|
||||
return {
|
||||
id: ownerId,
|
||||
name: project.author,
|
||||
type: 'user' as const,
|
||||
link: `https://modrinth.com/user/${ownerId}`,
|
||||
}
|
||||
}
|
||||
|
||||
async function getQueuedInstallOwner(
|
||||
client: AbstractModrinthClient,
|
||||
project: InstallableSearchResult,
|
||||
) {
|
||||
const fallback = getQueuedInstallOwnerFallback(project)
|
||||
|
||||
try {
|
||||
if (project.organization) {
|
||||
const organization = await client.labrinth.projects_v3.getOrganization(project.project_id)
|
||||
if (organization) {
|
||||
return {
|
||||
id: organization.id,
|
||||
name: organization.name,
|
||||
type: 'organization' as const,
|
||||
avatar_url: organization.icon_url ?? undefined,
|
||||
link: `https://modrinth.com/organization/${organization.slug}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const members = await client.labrinth.projects_v3.getMembers(project.project_id)
|
||||
const owner =
|
||||
members.find((member) => member.user.id === project.author_id)?.user ??
|
||||
members.find((member) => member.is_owner || member.role === 'Owner')?.user ??
|
||||
members[0]?.user
|
||||
|
||||
if (owner) {
|
||||
return {
|
||||
id: owner.id,
|
||||
name: owner.username,
|
||||
type: 'user' as const,
|
||||
avatar_url: owner.avatar_url,
|
||||
link: `https://modrinth.com/user/${owner.username}`,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function getQueuedAddonInstallPlans(
|
||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||
) {
|
||||
return Array.from(plans.values()).filter((plan) => plan.contentType !== 'modpack')
|
||||
}
|
||||
|
||||
function getQueuedInstallPlaceholder(
|
||||
plan: BrowseInstallPlan<InstallableSearchResult>,
|
||||
owner: PendingServerContentInstallInput['owner'],
|
||||
): PendingServerContentInstallInput {
|
||||
const project = plan.project as InstallableSearchResult & { slug?: string | null }
|
||||
return {
|
||||
projectId: plan.projectId,
|
||||
versionId: plan.versionId,
|
||||
contentType: plan.contentType as PendingServerContentInstallType,
|
||||
title: project.title ?? project.name ?? 'Project',
|
||||
versionName: plan.versionName ?? null,
|
||||
versionNumber: plan.versionNumber ?? null,
|
||||
fileName: plan.fileName ?? null,
|
||||
owner,
|
||||
slug: project.slug ?? plan.projectId,
|
||||
iconUrl: project.icon_url ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
function getQueuedInstallPlaceholderFallbacks(
|
||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||
) {
|
||||
return getQueuedAddonInstallPlans(plans).map((plan) =>
|
||||
getQueuedInstallPlaceholder(plan, getQueuedInstallOwnerFallback(plan.project)),
|
||||
)
|
||||
}
|
||||
|
||||
async function getQueuedInstallPlaceholders(
|
||||
client: AbstractModrinthClient,
|
||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||
) {
|
||||
return Promise.all(
|
||||
getQueuedAddonInstallPlans(plans).map(async (plan) =>
|
||||
getQueuedInstallPlaceholder(plan, await getQueuedInstallOwner(client, plan.project)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function createServerInstallContent(opts: {
|
||||
serverSetupModalRef: Ref<ServerSetupModalHandle | null>
|
||||
}) {
|
||||
@@ -209,7 +73,6 @@ export function createServerInstallContent(opts: {
|
||||
const router = useRouter()
|
||||
const client = injectModrinthClient()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const serverIdQuery = computed(() => readQueryString(route.query.sid))
|
||||
const worldIdQuery = computed(() => readQueryString(route.query.wid))
|
||||
@@ -227,22 +90,8 @@ export function createServerInstallContent(opts: {
|
||||
const serverContextWorldId = ref<string | null>(worldIdQuery.value)
|
||||
const serverContextServerData = ref<Archon.Servers.v0.Server | null>(null)
|
||||
const serverContentProjectIds = ref<Set<string>>(new Set())
|
||||
const serverContentInstallKeys = ref<Set<string>>(new Set())
|
||||
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<InstallableSearchResult>>>(
|
||||
new Map(),
|
||||
)
|
||||
const queuedServerInstallProjectIds = computed(() => new Set(queuedServerInstalls.value.keys()))
|
||||
const queuedServerInstallCount = computed(() => queuedServerInstalls.value.size)
|
||||
const selectedServerInstallProjects = computed<BrowseSelectedProject[]>(() =>
|
||||
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
|
||||
id: plan.projectId,
|
||||
name: plan.project.title ?? plan.project.name ?? 'Project',
|
||||
iconUrl: plan.project.icon_url ?? null,
|
||||
})),
|
||||
)
|
||||
const isInstallingQueuedServerInstalls = ref(false)
|
||||
const queuedInstallProgress = ref({ completed: 0, total: 0 })
|
||||
const effectiveServerWorldId = computed(() => worldIdQuery.value ?? serverContextWorldId.value)
|
||||
|
||||
const serverBackUrl = computed(() => {
|
||||
const sid = serverIdQuery.value
|
||||
if (!sid) return '/hosting/manage'
|
||||
@@ -261,9 +110,9 @@ export function createServerInstallContent(opts: {
|
||||
})
|
||||
const serverBrowseHeading = computed(() => {
|
||||
if (serverFlowFrom.value === 'reset-server') {
|
||||
return 'Selecting modpack to install after reset'
|
||||
return 'Select modpack to install after reset'
|
||||
}
|
||||
return 'Installing content'
|
||||
return 'Install content to server'
|
||||
})
|
||||
|
||||
async function resolveServerContextWorldId(serverId: string) {
|
||||
@@ -285,11 +134,7 @@ export function createServerInstallContent(opts: {
|
||||
.map((addon) => addon.project_id)
|
||||
.filter((projectId): projectId is string => !!projectId),
|
||||
)
|
||||
const keys = new Set(
|
||||
(content.addons ?? []).map((addon) => addon.project_id ?? addon.filename),
|
||||
)
|
||||
serverContentProjectIds.value = ids
|
||||
serverContentInstallKeys.value = keys
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
}
|
||||
@@ -314,7 +159,6 @@ export function createServerInstallContent(opts: {
|
||||
}
|
||||
|
||||
if (resolvedWorldId) {
|
||||
queuedServerInstalls.value = readStoredServerInstallQueue(sid, resolvedWorldId)
|
||||
await refreshServerInstalledContent(sid, resolvedWorldId)
|
||||
}
|
||||
}
|
||||
@@ -324,15 +168,11 @@ export function createServerInstallContent(opts: {
|
||||
if (!sid) {
|
||||
serverContextServerData.value = null
|
||||
serverContentProjectIds.value = new Set()
|
||||
serverContentInstallKeys.value = new Set()
|
||||
setQueuedServerInstallPlans(new Map())
|
||||
return
|
||||
}
|
||||
|
||||
if (sid !== prevSid) {
|
||||
serverContentProjectIds.value = new Set()
|
||||
serverContentInstallKeys.value = new Set()
|
||||
queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid)
|
||||
try {
|
||||
serverContextServerData.value = await client.archon.servers_v0.get(sid)
|
||||
} catch (err) {
|
||||
@@ -340,16 +180,28 @@ export function createServerInstallContent(opts: {
|
||||
}
|
||||
}
|
||||
|
||||
if (wid !== prevWid) {
|
||||
queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid)
|
||||
}
|
||||
|
||||
if (wid && (sid !== prevSid || wid !== prevWid)) {
|
||||
await refreshServerInstalledContent(sid, wid)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeLoader(loader: string) {
|
||||
return loader.toLowerCase().replaceAll('_', '').replaceAll('-', '').replaceAll(' ', '')
|
||||
}
|
||||
|
||||
function getCompatibleLoaders(loader: string) {
|
||||
const normalized = normalizeLoader(loader)
|
||||
if (!normalized) return new Set<string>()
|
||||
if (normalized === 'paper' || normalized === 'purpur' || normalized === 'spigot') {
|
||||
return new Set(['paper', 'purpur', 'spigot', 'bukkit'])
|
||||
}
|
||||
if (normalized === 'neoforge' || normalized === 'neo') {
|
||||
return new Set(['neoforge', 'neo'])
|
||||
}
|
||||
return new Set([normalized])
|
||||
}
|
||||
|
||||
function enforceSetupModpackRoute(currentProjectType: string | undefined) {
|
||||
if (!isSetupServerContext.value || currentProjectType === 'modpack') return
|
||||
router.replace({
|
||||
@@ -396,139 +248,82 @@ export function createServerInstallContent(opts: {
|
||||
ctx.modal.value?.setStage('final-config')
|
||||
}
|
||||
|
||||
function clearQueuedServerInstalls() {
|
||||
setQueuedServerInstallPlans(new Map())
|
||||
}
|
||||
|
||||
function removeQueuedServerInstall(projectId: string) {
|
||||
const nextPlans = new Map(queuedServerInstalls.value)
|
||||
nextPlans.delete(projectId)
|
||||
setQueuedServerInstallPlans(nextPlans)
|
||||
}
|
||||
|
||||
function setStoredServerInstallPlans(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||
) {
|
||||
if (serverId === serverIdQuery.value && worldId === effectiveServerWorldId.value) {
|
||||
queuedServerInstalls.value = plans
|
||||
function getCurrentServerInstallType(): ServerInstallableType {
|
||||
const raw = Array.isArray(route.params.projectType)
|
||||
? route.params.projectType[0]
|
||||
: route.params.projectType
|
||||
if (raw === 'modpack' || raw === 'mod' || raw === 'plugin' || raw === 'datapack') {
|
||||
return raw
|
||||
}
|
||||
writeStoredServerInstallQueue(serverId, worldId, plans)
|
||||
throw new Error('This content type cannot be installed to a server from browse.')
|
||||
}
|
||||
|
||||
async function flushQueuedServerInstalls(
|
||||
serverId: string | null = serverIdQuery.value,
|
||||
worldId: string | null = effectiveServerWorldId.value,
|
||||
) {
|
||||
if (isInstallingQueuedServerInstalls.value) return false
|
||||
async function installProjectToServer(project: InstallableSearchResult) {
|
||||
const contentType = getCurrentServerInstallType()
|
||||
const sid = serverIdQuery.value
|
||||
const wid = effectiveServerWorldId.value
|
||||
if (!sid || !wid) {
|
||||
throw new Error('No server world is available for install.')
|
||||
}
|
||||
|
||||
if (!serverId || !worldId) {
|
||||
handleError(new Error('No server world is available for install.'))
|
||||
if (contentType === 'modpack') {
|
||||
const versions = await client.labrinth.versions_v2.getProjectVersions(project.project_id, {
|
||||
include_changelog: false,
|
||||
})
|
||||
const versionId = versions[0]?.id ?? project.version_id
|
||||
if (!versionId) {
|
||||
throw new Error('No version found for this modpack')
|
||||
}
|
||||
|
||||
await openServerModpackInstallFlow({
|
||||
projectId: project.project_id,
|
||||
versionId,
|
||||
name: project.name,
|
||||
iconUrl: project.icon_url ?? undefined,
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const queuedPlans = getStoredServerAddonInstallQueue<InstallableSearchResult>(serverId, worldId)
|
||||
if (queuedPlans.size === 0) return true
|
||||
const versions = await client.labrinth.versions_v2.getProjectVersions(project.project_id, {
|
||||
include_changelog: false,
|
||||
})
|
||||
const serverLoader = (serverContextServerData.value?.loader ?? '').toLowerCase()
|
||||
const serverGameVersion = (serverContextServerData.value?.mc_version ?? '').trim()
|
||||
const compatibleLoaders = getCompatibleLoaders(serverLoader)
|
||||
|
||||
isInstallingQueuedServerInstalls.value = true
|
||||
queuedInstallProgress.value = {
|
||||
completed: 0,
|
||||
total: queuedPlans.size,
|
||||
const hasGameVersionMatch = (version: Labrinth.Versions.v2.Version) =>
|
||||
!serverGameVersion || version.game_versions.includes(serverGameVersion)
|
||||
const hasLoaderMatch = (version: Labrinth.Versions.v2.Version) => {
|
||||
if (contentType === 'datapack') return true
|
||||
if (compatibleLoaders.size === 0) return true
|
||||
return version.loaders.some((loader) => compatibleLoaders.has(normalizeLoader(loader)))
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await flushStoredServerAddonInstallQueue({
|
||||
serverId,
|
||||
worldId,
|
||||
install: (plans) =>
|
||||
client.archon.content_v1.addAddons(
|
||||
serverId,
|
||||
worldId,
|
||||
plans.map((plan) => ({
|
||||
project_id: plan.projectId,
|
||||
version_id: plan.versionId,
|
||||
})),
|
||||
),
|
||||
onQueueChange: (plans) => setStoredServerInstallPlans(serverId, worldId, plans),
|
||||
})
|
||||
|
||||
if (!result.ok) {
|
||||
for (const plan of result.attemptedPlans) {
|
||||
removePendingServerContentInstall(serverId, worldId, plan.projectId)
|
||||
}
|
||||
handleError(result.error as Error)
|
||||
return false
|
||||
}
|
||||
|
||||
queuedInstallProgress.value = {
|
||||
completed: result.flushedPlans.length,
|
||||
total: result.flushedPlans.length,
|
||||
}
|
||||
serverContentProjectIds.value = new Set([
|
||||
...serverContentProjectIds.value,
|
||||
...result.flushedPlans.map((plan) => plan.projectId),
|
||||
])
|
||||
serverContentInstallKeys.value = new Set([
|
||||
...serverContentInstallKeys.value,
|
||||
...result.flushedPlans.map((plan) => plan.projectId),
|
||||
])
|
||||
if (result.flushedPlans.length > 0) {
|
||||
await queryClient.invalidateQueries({ queryKey: ['content', 'list', 'v1', serverId] })
|
||||
}
|
||||
|
||||
return true
|
||||
} finally {
|
||||
isInstallingQueuedServerInstalls.value = false
|
||||
queuedInstallProgress.value = { completed: 0, total: 0 }
|
||||
let matchingVersion = versions.find(
|
||||
(version) => hasGameVersionMatch(version) && hasLoaderMatch(version),
|
||||
)
|
||||
if (!matchingVersion) {
|
||||
matchingVersion = versions.find((version) => hasLoaderMatch(version))
|
||||
}
|
||||
}
|
||||
|
||||
async function discardQueuedServerInstallsAndBack() {
|
||||
clearQueuedServerInstalls()
|
||||
await router.push(serverBackUrl.value)
|
||||
}
|
||||
|
||||
async function installQueuedServerInstallsAndBack() {
|
||||
const sid = serverIdQuery.value
|
||||
const wid = effectiveServerWorldId.value
|
||||
const backUrl = serverBackUrl.value
|
||||
const plans = new Map(queuedServerInstalls.value)
|
||||
|
||||
if (sid && wid) {
|
||||
writeStoredServerInstallQueue(sid, wid, plans)
|
||||
writePendingServerContentInstallBaseline(sid, wid, serverContentInstallKeys.value)
|
||||
addPendingServerContentInstalls(sid, wid, getQueuedInstallPlaceholderFallbacks(plans))
|
||||
void getQueuedInstallPlaceholders(client, plans)
|
||||
.then((items) => {
|
||||
const pendingProjectIds = new Set(
|
||||
readPendingServerContentInstalls(sid, wid).map((item) => item.projectId),
|
||||
)
|
||||
addPendingServerContentInstalls(
|
||||
sid,
|
||||
wid,
|
||||
items.filter((item) => pendingProjectIds.has(item.projectId)),
|
||||
)
|
||||
})
|
||||
.catch((err) => handleError(err as Error))
|
||||
if (!matchingVersion) {
|
||||
matchingVersion = versions.find((version) => hasGameVersionMatch(version))
|
||||
}
|
||||
if (!matchingVersion) {
|
||||
matchingVersion = versions[0]
|
||||
}
|
||||
if (!matchingVersion) {
|
||||
throw new Error('No installable version was found for this project.')
|
||||
}
|
||||
await router.push(backUrl)
|
||||
void flushQueuedServerInstalls(sid, wid)
|
||||
|
||||
await client.archon.content_v1.addAddon(sid, wid, {
|
||||
project_id: matchingVersion.project_id,
|
||||
version_id: matchingVersion.id,
|
||||
})
|
||||
|
||||
serverContentProjectIds.value = new Set([...serverContentProjectIds.value, project.project_id])
|
||||
return true
|
||||
}
|
||||
|
||||
function getQueuedServerInstallPlans() {
|
||||
return queuedServerInstalls.value
|
||||
}
|
||||
|
||||
function setQueuedServerInstallPlans(
|
||||
plans: Map<string, BrowseInstallPlan<InstallableSearchResult>>,
|
||||
) {
|
||||
queuedServerInstalls.value = plans
|
||||
writeStoredServerInstallQueue(serverIdQuery.value, effectiveServerWorldId.value, plans)
|
||||
}
|
||||
|
||||
function onServerFlowBack() {
|
||||
serverSetupModalRef.value?.hide()
|
||||
}
|
||||
@@ -582,27 +377,15 @@ export function createServerInstallContent(opts: {
|
||||
effectiveServerWorldId,
|
||||
serverContextServerData,
|
||||
serverContentProjectIds,
|
||||
queuedServerInstallProjectIds,
|
||||
queuedServerInstallCount,
|
||||
selectedServerInstallProjects,
|
||||
isInstallingQueuedServerInstalls,
|
||||
queuedInstallProgress,
|
||||
serverBackUrl,
|
||||
serverBackLabel,
|
||||
serverBrowseHeading,
|
||||
clearQueuedServerInstalls,
|
||||
removeQueuedServerInstall,
|
||||
flushQueuedServerInstalls,
|
||||
discardQueuedServerInstallsAndBack,
|
||||
installQueuedServerInstallsAndBack,
|
||||
initServerContext,
|
||||
watchServerContextChanges,
|
||||
searchServerModpacks,
|
||||
getServerProjectVersions,
|
||||
enforceSetupModpackRoute,
|
||||
getQueuedServerInstallPlans,
|
||||
setQueuedServerInstallPlans,
|
||||
openServerModpackInstallFlow,
|
||||
installProjectToServer,
|
||||
onServerFlowBack,
|
||||
handleServerModpackFlowCreate,
|
||||
markServerProjectInstalled,
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"types": ["vite/client"],
|
||||
|
||||
"paths": {
|
||||
"@modrinth/api-client": ["../../packages/api-client/src/index.ts"],
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,7 +8,6 @@ import tauriConf from '../app/tauri.conf.json'
|
||||
|
||||
const projectRootDir = resolve(__dirname)
|
||||
const appLibEnvDir = resolve(projectRootDir, '../../packages/app-lib')
|
||||
const apiClientSource = resolve(projectRootDir, '../../packages/api-client/src/index.ts')
|
||||
|
||||
// Load .env from app-lib manually instead of using Vite's envDir, which would auto-load .env.local and override values
|
||||
const envFilePath = resolve(appLibEnvDir, '.env')
|
||||
@@ -38,10 +37,6 @@ export default defineConfig({
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: '@modrinth/api-client',
|
||||
replacement: apiClientSource,
|
||||
},
|
||||
{
|
||||
find: '@',
|
||||
replacement: resolve(projectRootDir, 'src'),
|
||||
|
||||
@@ -51,21 +51,8 @@ tauri-build = { workspace = true, features = ["codegen"] }
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tauri-plugin-updater = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation.workspace = true
|
||||
core-graphics.workspace = true
|
||||
objc2-app-kit = { workspace = true, features = [
|
||||
"NSWindow",
|
||||
] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
webview2-com.workspace = true
|
||||
windows = { workspace = true, features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
windows-core.workspace = true
|
||||
|
||||
[features]
|
||||
|
||||
+24
-368
@@ -1,6 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Manager, PhysicalPosition, PhysicalSize, Runtime};
|
||||
@@ -11,242 +9,11 @@ use tokio::sync::RwLock;
|
||||
pub struct AdsState {
|
||||
pub shown: bool,
|
||||
pub modal_shown: bool,
|
||||
pub occluded: bool,
|
||||
pub last_click: Option<Instant>,
|
||||
pub malicious_origins: HashSet<String>,
|
||||
}
|
||||
|
||||
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
pub(super) const OCCLUDED_AREA_THRESHOLD: f64 = 1.0;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
const ADS_USER_AGENT: &str = concat!(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ",
|
||||
"(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ",
|
||||
"ModrinthApp/",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
" (Modrinth App)",
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
fn ads_user_agent_override_params() -> String {
|
||||
serde_json::json!({
|
||||
"userAgent": ADS_USER_AGENT,
|
||||
"platform": "Win32",
|
||||
"userAgentMetadata": {
|
||||
"brands": [
|
||||
{ "brand": "Chromium", "version": "128" },
|
||||
{ "brand": "Google Chrome", "version": "128" },
|
||||
{ "brand": "Modrinth App", "version": env!("CARGO_PKG_VERSION") },
|
||||
{ "brand": "Not=A?Brand", "version": "99" },
|
||||
],
|
||||
"fullVersion": "128.0.0.0",
|
||||
"fullVersionList": [
|
||||
{ "brand": "Chromium", "version": "128.0.0.0" },
|
||||
{ "brand": "Google Chrome", "version": "128.0.0.0" },
|
||||
{ "brand": "Modrinth App", "version": env!("CARGO_PKG_VERSION") },
|
||||
{ "brand": "Not=A?Brand", "version": "99.0.0.0" },
|
||||
],
|
||||
"platform": "Windows",
|
||||
"platformVersion": "10.0.0",
|
||||
"architecture": "x86",
|
||||
"bitness": "64",
|
||||
"model": "",
|
||||
"mobile": false,
|
||||
},
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn configure_ads_cookie_settings(
|
||||
core_webview2: &webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
|
||||
) {
|
||||
use webview2_com::Microsoft::Web::WebView2::Win32::{
|
||||
COREWEBVIEW2_TRACKING_PREVENTION_LEVEL_NONE, ICoreWebView2_13,
|
||||
ICoreWebView2Profile3,
|
||||
};
|
||||
use windows_core::Interface;
|
||||
|
||||
match core_webview2
|
||||
.cast::<ICoreWebView2_13>()
|
||||
.and_then(|core_webview2| unsafe { core_webview2.Profile() })
|
||||
.and_then(|profile| profile.cast::<ICoreWebView2Profile3>())
|
||||
{
|
||||
Ok(profile) => {
|
||||
if let Err(error) = unsafe {
|
||||
profile.SetPreferredTrackingPreventionLevel(
|
||||
COREWEBVIEW2_TRACKING_PREVENTION_LEVEL_NONE,
|
||||
)
|
||||
} {
|
||||
tracing::warn!(
|
||||
?error,
|
||||
"Failed to disable ads WebView2 tracking prevention"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
?error,
|
||||
"Failed to access ads WebView2 profile tracking prevention settings"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_webview_visible<R: Runtime>(webview: &tauri::Webview<R>, visible: bool) {
|
||||
#[cfg(not(any(windows, target_os = "macos")))]
|
||||
{
|
||||
_ = visible;
|
||||
}
|
||||
|
||||
webview
|
||||
.with_webview(
|
||||
#[allow(unused_variables)]
|
||||
move |wv| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let controller = wv.controller();
|
||||
unsafe { controller.SetIsVisible(visible) }.ok();
|
||||
}
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if visible {
|
||||
webview.show().ok();
|
||||
} else {
|
||||
webview.hide().ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_webview_visible_for_window<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
webview: &tauri::Webview<R>,
|
||||
visible: bool,
|
||||
) {
|
||||
let is_minimized = app
|
||||
.get_window("main")
|
||||
.and_then(|window| window.is_minimized().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
let is_occluded = app
|
||||
.state::<RwLock<AdsState>>()
|
||||
.try_read()
|
||||
.map(|state| state.occluded)
|
||||
.unwrap_or(false);
|
||||
|
||||
set_webview_visible(webview, visible && !is_minimized && !is_occluded);
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
fn compute_ads_webview_occlusion<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
) -> Option<bool> {
|
||||
let main_window = app.get_window("main")?;
|
||||
let webviews = app.webviews();
|
||||
let webview = webviews.get("ads-window")?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let position = webview.position().ok()?;
|
||||
let size = webview.size().ok()?;
|
||||
|
||||
Some(
|
||||
crate::api::ads_occlusion_macos::is_ads_webview_occluded(
|
||||
&main_window,
|
||||
position.x,
|
||||
position.y,
|
||||
size.width,
|
||||
size.height,
|
||||
)
|
||||
.unwrap_or(false),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let position = webview.position().ok()?;
|
||||
let size = webview.size().ok()?;
|
||||
let hwnd = main_window.hwnd().ok()?;
|
||||
|
||||
Some(crate::api::ads_occlusion_windows::is_ads_webview_occluded(
|
||||
hwnd,
|
||||
position.x,
|
||||
position.y,
|
||||
size.width,
|
||||
size.height,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
async fn sync_ads_occlusion<R: Runtime>(app: &tauri::AppHandle<R>) {
|
||||
let Some(occluded) = compute_ads_webview_occlusion(app) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
if state.occluded == occluded {
|
||||
return;
|
||||
}
|
||||
|
||||
state.occluded = occluded;
|
||||
let visible = state.shown && !state.modal_shown;
|
||||
drop(state);
|
||||
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
set_webview_visible_for_window(app, webview, visible);
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_webview_visibility_for_main_window<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
main_window: &tauri::Window<R>,
|
||||
was_minimized: &AtomicBool,
|
||||
) {
|
||||
let is_minimized = main_window.is_minimized().unwrap_or(false);
|
||||
let was = was_minimized.load(Ordering::SeqCst);
|
||||
|
||||
if is_minimized == was {
|
||||
return;
|
||||
}
|
||||
|
||||
was_minimized.store(is_minimized, Ordering::SeqCst);
|
||||
|
||||
let ads_visible = if is_minimized {
|
||||
false
|
||||
} else {
|
||||
match app.state::<RwLock<AdsState>>().try_read() {
|
||||
Ok(state) => state.shown && !state.modal_shown && !state.occluded,
|
||||
Err(_) => false,
|
||||
}
|
||||
};
|
||||
|
||||
let mut webviews = Vec::new();
|
||||
let mut seen_webviews = HashSet::new();
|
||||
|
||||
for webview in main_window.webviews() {
|
||||
seen_webviews.insert(webview.label().to_string());
|
||||
webviews.push(webview);
|
||||
}
|
||||
|
||||
for webview in app.webviews().into_values() {
|
||||
if seen_webviews.insert(webview.label().to_string()) {
|
||||
webviews.push(webview);
|
||||
}
|
||||
}
|
||||
|
||||
for webview in webviews {
|
||||
let visible =
|
||||
!is_minimized && (webview.label() != "ads-window" || ads_visible);
|
||||
|
||||
set_webview_visible(&webview, visible);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("ads")
|
||||
@@ -254,28 +21,19 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
app.manage(RwLock::new(AdsState {
|
||||
shown: true,
|
||||
modal_shown: false,
|
||||
occluded: false,
|
||||
last_click: None,
|
||||
malicious_origins: HashSet::new(),
|
||||
}));
|
||||
|
||||
// We refresh the ads window periodically to mitigate memory leak issues.
|
||||
// Skip refreshes when app state has hidden the ads WebView. The refresh does
|
||||
// not reset the visibility state.
|
||||
let refresh_app = app.clone();
|
||||
// We refresh the ads window every 5 minutes to mitigate memory leak issues.
|
||||
// While this loop doesn't include explicit checks to see if the window is still
|
||||
// visible when we refresh, the Aditude wrapper will not make any ad requests
|
||||
// unless Chromium reports the page as visible. The refresh does not reset the
|
||||
// visibility state.
|
||||
let app = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
let should_refresh = refresh_app
|
||||
.state::<RwLock<AdsState>>()
|
||||
.try_read()
|
||||
.map(|state| {
|
||||
state.shown && !state.modal_shown && !state.occluded
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_refresh
|
||||
&& let Some(webview) =
|
||||
refresh_app.webviews().get_mut("ads-window")
|
||||
if let Some(webview) = app.webviews().get_mut("ads-window")
|
||||
{
|
||||
let _ = webview.navigate(AD_LINK.parse().unwrap());
|
||||
}
|
||||
@@ -285,47 +43,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(main_window) = app.get_window("main") {
|
||||
let app_handle = app.clone();
|
||||
let event_window = main_window.clone();
|
||||
let was_minimized = Arc::new(AtomicBool::new(false));
|
||||
|
||||
main_window.on_window_event(move |_| {
|
||||
sync_webview_visibility_for_main_window(
|
||||
&app_handle,
|
||||
&event_window,
|
||||
&was_minimized,
|
||||
);
|
||||
|
||||
let delayed_app_handle = app_handle.clone();
|
||||
let delayed_event_window = event_window.clone();
|
||||
let delayed_was_minimized = was_minimized.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
|
||||
sync_webview_visibility_for_main_window(
|
||||
&delayed_app_handle,
|
||||
&delayed_event_window,
|
||||
&delayed_was_minimized,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
{
|
||||
let app_handle = app.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
sync_ads_occlusion(&app_handle).await;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
@@ -386,38 +103,31 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
webview.show().ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.set_size(size).ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
} else {
|
||||
webview.hide().ok();
|
||||
webview
|
||||
.set_position(PhysicalPosition::new(-1000, -1000))
|
||||
.ok();
|
||||
set_webview_visible(webview, false);
|
||||
}
|
||||
|
||||
Some(webview.clone())
|
||||
} else if let Some(window) = app.get_window("main") {
|
||||
#[cfg(windows)]
|
||||
let webview_url =
|
||||
WebviewUrl::External("about:blank".parse().unwrap());
|
||||
#[cfg(not(windows))]
|
||||
let webview_url = WebviewUrl::External(AD_LINK.parse().unwrap());
|
||||
|
||||
let webview = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new("ads-window", webview_url)
|
||||
.initialization_script_for_all_frames(include_str!(
|
||||
"ads-init.js"
|
||||
))
|
||||
// We use a standard Chrome user agent for compatibility with our ad provider,
|
||||
// since Tauri is not recognized by ad providers by default.
|
||||
// Aditude has separately informed SSPs and IVT vendors that this traffic
|
||||
// originates from a desktop app.
|
||||
.user_agent(ADS_USER_AGENT)
|
||||
.zoom_hotkeys_enabled(false)
|
||||
.transparent(true)
|
||||
.on_new_window(|_, _| {
|
||||
tauri::webview::NewWindowResponse::Deny
|
||||
}),
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"ads-window",
|
||||
WebviewUrl::External(
|
||||
AD_LINK.parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.initialization_script_for_all_frames(include_str!("ads-init.js"))
|
||||
// We use a standard Chrome user agent for compatibility with our ad provider,
|
||||
// since Tauri is not recognized by ad providers by default.
|
||||
// Aditude has separately informed SSPs and IVT vendors that this traffic
|
||||
// originates from a desktop app.
|
||||
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
|
||||
.zoom_hotkeys_enabled(false)
|
||||
.transparent(true)
|
||||
.on_new_window(|_, _| tauri::webview::NewWindowResponse::Deny),
|
||||
// set both the `hide`/`show` state and `position`,
|
||||
// to ensure that the webview is actually shown/hidden
|
||||
if state.shown {
|
||||
@@ -430,68 +140,15 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
|
||||
if state.shown {
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, &webview, true);
|
||||
} else {
|
||||
webview.hide().ok();
|
||||
set_webview_visible(&webview, false);
|
||||
}
|
||||
|
||||
webview.with_webview(#[allow(unused_variables)] |webview2| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use webview2_com::CallDevToolsProtocolMethodCompletedHandler;
|
||||
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2_8;
|
||||
use windows_core::Interface;
|
||||
use windows_core::HSTRING;
|
||||
|
||||
let core_webview2 =
|
||||
unsafe { webview2.controller().CoreWebView2() };
|
||||
|
||||
if let Ok(core_webview2) = core_webview2 {
|
||||
configure_ads_cookie_settings(&core_webview2);
|
||||
|
||||
let navigate_webview = core_webview2.clone();
|
||||
let handler =
|
||||
CallDevToolsProtocolMethodCompletedHandler::create(
|
||||
Box::new(move |result: windows_core::Result<()>, _| {
|
||||
if let Err(error) = result {
|
||||
tracing::error!(
|
||||
?error,
|
||||
"Failed to override ads user-agent client hints"
|
||||
);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
navigate_webview
|
||||
.Navigate(&HSTRING::from(AD_LINK))
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}) as Box<_>,
|
||||
);
|
||||
|
||||
unsafe {
|
||||
if let Err(error) = core_webview2
|
||||
.CallDevToolsProtocolMethod(
|
||||
&HSTRING::from(
|
||||
"Emulation.setUserAgentOverride",
|
||||
),
|
||||
&HSTRING::from(
|
||||
ads_user_agent_override_params(),
|
||||
),
|
||||
&handler,
|
||||
)
|
||||
{
|
||||
tracing::error!(
|
||||
?error,
|
||||
"Failed to install ads user-agent client hints override"
|
||||
);
|
||||
|
||||
core_webview2.Navigate(&HSTRING::from(AD_LINK)).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let webview2_controller = webview2.controller();
|
||||
let Ok(webview2_8) = unsafe { webview2_controller.CoreWebView2() }
|
||||
@@ -509,9 +166,9 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
None
|
||||
};
|
||||
|
||||
if webview.is_none() {
|
||||
let Some(webview) = webview.clone() else {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// tauri::async_runtime::spawn(async move {
|
||||
// loop {
|
||||
@@ -592,7 +249,6 @@ pub async fn show_ads_window<R: Runtime>(
|
||||
webview.set_size(size).ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use core_foundation::array::CFArray;
|
||||
use core_foundation::base::{CFType, TCFType};
|
||||
use core_foundation::dictionary::CFDictionary;
|
||||
use core_foundation::number::CFNumber;
|
||||
use core_foundation::string::CFString;
|
||||
use core_graphics::display::CGDisplay;
|
||||
use core_graphics::geometry::{CGPoint, CGRect, CGSize};
|
||||
use core_graphics::window::{
|
||||
CGWindowID, kCGWindowAlpha, kCGWindowBounds, kCGWindowLayer,
|
||||
kCGWindowListExcludeDesktopElements,
|
||||
kCGWindowListOptionOnScreenAboveWindow, kCGWindowListOptionOnScreenOnly,
|
||||
kCGWindowNumber, kCGWindowOwnerName, kCGWindowOwnerPID,
|
||||
};
|
||||
use objc2_app_kit::NSWindow;
|
||||
|
||||
pub fn is_ads_webview_occluded<R: tauri::Runtime>(
|
||||
main_window: &tauri::Window<R>,
|
||||
ad_x: i32,
|
||||
ad_y: i32,
|
||||
ad_width: u32,
|
||||
ad_height: u32,
|
||||
) -> Option<bool> {
|
||||
let scale_factor = main_window.scale_factor().ok()?;
|
||||
let maybe_ns_window = main_window.ns_window().ok()?;
|
||||
let ns_window = NonNull::new(maybe_ns_window)?.cast::<NSWindow>();
|
||||
let ns_window = unsafe { ns_window.as_ref() };
|
||||
let window_number = ns_window.windowNumber();
|
||||
let main_window_id = if window_number > 0 {
|
||||
window_number as CGWindowID
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let window_infos = CGDisplay::window_list_info(
|
||||
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let window_infos = unsafe {
|
||||
CFArray::<CFDictionary<CFString, CFType>>::wrap_under_get_rule(
|
||||
window_infos.as_concrete_TypeRef(),
|
||||
)
|
||||
};
|
||||
let main_window_rect = window_infos
|
||||
.iter()
|
||||
.find(|window_info| window_id(window_info) == Some(main_window_id))
|
||||
.and_then(|window_info| window_rect(&window_info))?;
|
||||
let ad_rect = ad_rect_from_main_window(
|
||||
&main_window_rect,
|
||||
ad_x,
|
||||
ad_y,
|
||||
ad_width,
|
||||
ad_height,
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
if is_empty_rect(&ad_rect) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ad_area = rect_area(&ad_rect);
|
||||
|
||||
if ad_area == 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let app_process_id = std::process::id() as i32;
|
||||
let windows_above_main = CGDisplay::window_list_info(
|
||||
kCGWindowListOptionOnScreenAboveWindow
|
||||
| kCGWindowListExcludeDesktopElements,
|
||||
Some(main_window_id),
|
||||
)?;
|
||||
let windows_above_main = unsafe {
|
||||
CFArray::<CFDictionary<CFString, CFType>>::wrap_under_get_rule(
|
||||
windows_above_main.as_concrete_TypeRef(),
|
||||
)
|
||||
};
|
||||
let mut occluded_area = 0.0;
|
||||
|
||||
for window_info in windows_above_main.iter() {
|
||||
if window_id(&window_info) == Some(main_window_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if window_process_id(&window_info) == Some(app_process_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let owner_name = window_owner_name(&window_info);
|
||||
|
||||
if owner_name.as_deref().is_some_and(is_system_window_owner) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let layer = window_layer(&window_info);
|
||||
|
||||
if layer != Some(0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let alpha = window_alpha(&window_info);
|
||||
|
||||
if alpha.is_some_and(|alpha| alpha <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(rect) = window_rect(&window_info) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(intersection) = intersect_rects(&ad_rect, &rect) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
occluded_area += rect_area(&intersection);
|
||||
let occluded_ratio = occluded_area / ad_area;
|
||||
|
||||
if occluded_ratio >= super::ads::OCCLUDED_AREA_THRESHOLD {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn ad_rect_from_main_window(
|
||||
main_window_rect: &CGRect,
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
scale_factor: f64,
|
||||
) -> CGRect {
|
||||
let scale_factor = if scale_factor > 0.0 {
|
||||
scale_factor
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
CGRect::new(
|
||||
&CGPoint::new(
|
||||
main_window_rect.origin.x + x as f64 / scale_factor,
|
||||
main_window_rect.origin.y + y as f64 / scale_factor,
|
||||
),
|
||||
&CGSize::new(width as f64 / scale_factor, height as f64 / scale_factor),
|
||||
)
|
||||
}
|
||||
|
||||
fn window_id(
|
||||
window_info: &CFDictionary<CFString, CFType>,
|
||||
) -> Option<CGWindowID> {
|
||||
number_value(window_info, unsafe { kCGWindowNumber })
|
||||
.and_then(|value| u32::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn window_process_id(
|
||||
window_info: &CFDictionary<CFString, CFType>,
|
||||
) -> Option<i32> {
|
||||
number_value(window_info, unsafe { kCGWindowOwnerPID })
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn window_layer(window_info: &CFDictionary<CFString, CFType>) -> Option<i32> {
|
||||
number_value(window_info, unsafe { kCGWindowLayer })
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn window_alpha(window_info: &CFDictionary<CFString, CFType>) -> Option<f64> {
|
||||
let key = unsafe { CFString::wrap_under_get_rule(kCGWindowAlpha) };
|
||||
|
||||
window_info.find(&key)?.downcast::<CFNumber>()?.to_f64()
|
||||
}
|
||||
|
||||
fn window_owner_name(
|
||||
window_info: &CFDictionary<CFString, CFType>,
|
||||
) -> Option<String> {
|
||||
let key = unsafe { CFString::wrap_under_get_rule(kCGWindowOwnerName) };
|
||||
|
||||
Some(window_info.find(&key)?.downcast::<CFString>()?.to_string())
|
||||
}
|
||||
|
||||
fn is_system_window_owner(owner_name: &str) -> bool {
|
||||
matches!(owner_name, "WindowManager")
|
||||
}
|
||||
|
||||
fn number_value(
|
||||
window_info: &CFDictionary<CFString, CFType>,
|
||||
key: core_foundation::string::CFStringRef,
|
||||
) -> Option<i64> {
|
||||
let key = unsafe { CFString::wrap_under_get_rule(key) };
|
||||
|
||||
window_info.find(&key)?.downcast::<CFNumber>()?.to_i64()
|
||||
}
|
||||
|
||||
fn window_rect(window_info: &CFDictionary<CFString, CFType>) -> Option<CGRect> {
|
||||
let key = unsafe { CFString::wrap_under_get_rule(kCGWindowBounds) };
|
||||
let bounds = window_info.find(&key)?.downcast::<CFDictionary>()?;
|
||||
let rect = CGRect::from_dict_representation(&bounds)?;
|
||||
|
||||
if is_empty_rect(&rect) {
|
||||
None
|
||||
} else {
|
||||
Some(rect)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_rect(rect: &CGRect) -> bool {
|
||||
rect.size.width <= 0.0 || rect.size.height <= 0.0
|
||||
}
|
||||
|
||||
fn rect_area(rect: &CGRect) -> f64 {
|
||||
if is_empty_rect(rect) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
rect.size.width * rect.size.height
|
||||
}
|
||||
|
||||
fn intersect_rects(a: &CGRect, b: &CGRect) -> Option<CGRect> {
|
||||
let left = a.origin.x.max(b.origin.x);
|
||||
let top = a.origin.y.max(b.origin.y);
|
||||
let right = (a.origin.x + a.size.width).min(b.origin.x + b.size.width);
|
||||
let bottom = (a.origin.y + a.size.height).min(b.origin.y + b.size.height);
|
||||
let rect = CGRect::new(
|
||||
&CGPoint::new(left, top),
|
||||
&CGSize::new(right - left, bottom - top),
|
||||
);
|
||||
|
||||
if is_empty_rect(&rect) {
|
||||
None
|
||||
} else {
|
||||
Some(rect)
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
use windows::Win32::Foundation::{HWND, POINT, RECT};
|
||||
use windows::Win32::Graphics::Dwm::{
|
||||
DWMWA_CLOAKED, DWMWA_EXTENDED_FRAME_BOUNDS, DwmGetWindowAttribute,
|
||||
};
|
||||
use windows::Win32::Graphics::Gdi::ClientToScreen;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
GA_ROOT, GW_HWNDNEXT, GetAncestor, GetTopWindow, GetWindow, GetWindowRect,
|
||||
GetWindowThreadProcessId, IsIconic, IsWindowVisible,
|
||||
};
|
||||
|
||||
pub fn is_ads_webview_occluded(
|
||||
main_hwnd: HWND,
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> bool {
|
||||
let Some(ad_rect) = ad_rect_in_screen(main_hwnd, x, y, width, height)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if is_empty_rect(&ad_rect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ad_area = rect_area(&ad_rect);
|
||||
if ad_area == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut occluded_area = 0u64;
|
||||
let app_root = unsafe { GetAncestor(main_hwnd, GA_ROOT) };
|
||||
let app_process_id = std::process::id();
|
||||
let mut hwnd = match unsafe { GetTopWindow(None) } {
|
||||
Ok(hwnd) => hwnd,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
while !hwnd.is_invalid() {
|
||||
let window_root = unsafe { GetAncestor(hwnd, GA_ROOT) };
|
||||
|
||||
if window_root == app_root {
|
||||
return false;
|
||||
}
|
||||
|
||||
if window_process_id(hwnd) == Some(app_process_id) {
|
||||
hwnd = match unsafe { GetWindow(hwnd, GW_HWNDNEXT) } {
|
||||
Ok(hwnd) => hwnd,
|
||||
Err(_) => break,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
if window_counts_as_occluder(hwnd)
|
||||
&& let Some(occluder_rect) = window_rect(hwnd)
|
||||
&& let Some(intersection) =
|
||||
intersect_rects(&ad_rect, &occluder_rect)
|
||||
{
|
||||
occluded_area =
|
||||
occluded_area.saturating_add(rect_area(&intersection));
|
||||
|
||||
if (occluded_area as f64 / ad_area as f64)
|
||||
>= super::ads::OCCLUDED_AREA_THRESHOLD
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
hwnd = match unsafe { GetWindow(hwnd, GW_HWNDNEXT) } {
|
||||
Ok(hwnd) => hwnd,
|
||||
Err(_) => break,
|
||||
};
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn ad_rect_in_screen(
|
||||
main_hwnd: HWND,
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Option<RECT> {
|
||||
let mut origin = POINT { x: 0, y: 0 };
|
||||
|
||||
if !unsafe { ClientToScreen(main_hwnd, &mut origin).as_bool() } {
|
||||
return None;
|
||||
}
|
||||
|
||||
let left = origin.x.saturating_add(x);
|
||||
let top = origin.y.saturating_add(y);
|
||||
let right = left.saturating_add(width as i32);
|
||||
let bottom = top.saturating_add(height as i32);
|
||||
|
||||
Some(RECT {
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
})
|
||||
}
|
||||
|
||||
fn window_counts_as_occluder(hwnd: HWND) -> bool {
|
||||
if !unsafe { IsWindowVisible(hwnd).as_bool() } {
|
||||
return false;
|
||||
}
|
||||
|
||||
if unsafe { IsIconic(hwnd).as_bool() } {
|
||||
return false;
|
||||
}
|
||||
|
||||
if is_dwm_cloaked(hwnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn window_process_id(hwnd: HWND) -> Option<u32> {
|
||||
let mut process_id = 0u32;
|
||||
|
||||
unsafe {
|
||||
GetWindowThreadProcessId(hwnd, Some(&mut process_id));
|
||||
}
|
||||
|
||||
(process_id != 0).then_some(process_id)
|
||||
}
|
||||
|
||||
fn is_dwm_cloaked(hwnd: HWND) -> bool {
|
||||
let mut cloaked = 0u32;
|
||||
|
||||
unsafe {
|
||||
DwmGetWindowAttribute(
|
||||
hwnd,
|
||||
DWMWA_CLOAKED,
|
||||
&mut cloaked as *mut u32 as *mut _,
|
||||
std::mem::size_of::<u32>() as u32,
|
||||
)
|
||||
}
|
||||
.is_ok()
|
||||
&& cloaked != 0
|
||||
}
|
||||
|
||||
fn window_rect(hwnd: HWND) -> Option<RECT> {
|
||||
let mut rect = RECT::default();
|
||||
|
||||
if unsafe {
|
||||
DwmGetWindowAttribute(
|
||||
hwnd,
|
||||
DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||
&mut rect as *mut RECT as *mut _,
|
||||
std::mem::size_of::<RECT>() as u32,
|
||||
)
|
||||
}
|
||||
.is_err()
|
||||
&& unsafe { GetWindowRect(hwnd, &mut rect) }.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_empty_rect(&rect) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(rect)
|
||||
}
|
||||
|
||||
fn is_empty_rect(rect: &RECT) -> bool {
|
||||
rect.right <= rect.left || rect.bottom <= rect.top
|
||||
}
|
||||
|
||||
fn rect_area(rect: &RECT) -> u64 {
|
||||
if is_empty_rect(rect) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(rect.right - rect.left) as u64 * (rect.bottom - rect.top) as u64
|
||||
}
|
||||
|
||||
fn intersect_rects(a: &RECT, b: &RECT) -> Option<RECT> {
|
||||
let rect = RECT {
|
||||
left: a.left.max(b.left),
|
||||
top: a.top.max(b.top),
|
||||
right: a.right.min(b.right),
|
||||
bottom: a.bottom.min(b.bottom),
|
||||
};
|
||||
|
||||
(!is_empty_rect(&rect)).then_some(rect)
|
||||
}
|
||||
@@ -18,10 +18,6 @@ pub mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub mod ads;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod ads_occlusion_macos;
|
||||
#[cfg(windows)]
|
||||
mod ads_occlusion_windows;
|
||||
pub mod cache;
|
||||
pub mod files;
|
||||
pub mod friends;
|
||||
|
||||
+14
-52
@@ -6,7 +6,6 @@
|
||||
|
||||
use native_dialog::{DialogBuilder, MessageLevel};
|
||||
use std::env;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tauri::{Listener, Manager};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use theseus::prelude::*;
|
||||
@@ -97,17 +96,6 @@ fn restart_app(app: tauri::AppHandle) {
|
||||
app.restart();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn set_restart_after_pending_update(
|
||||
should_restart: bool,
|
||||
) -> api::Result<()> {
|
||||
let state = State::get().await?;
|
||||
state
|
||||
.restart_after_pending_update
|
||||
.store(should_restart, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
@@ -257,7 +245,6 @@ fn main() {
|
||||
get_update_size,
|
||||
enqueue_update_for_installation,
|
||||
remove_enqueued_update,
|
||||
set_restart_after_pending_update,
|
||||
toggle_decorations,
|
||||
show_window,
|
||||
restart_app,
|
||||
@@ -275,13 +262,7 @@ fn main() {
|
||||
#[cfg(feature = "updater")]
|
||||
if matches!(event, tauri::RunEvent::Exit) {
|
||||
let update_data = app.state::<PendingUpdateData>().inner();
|
||||
let should_restart = State::get_if_initialized()
|
||||
.map(|s| {
|
||||
s.restart_after_pending_update.load(Ordering::Relaxed)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if let Some((update, data)) = &*update_data.0.lock().unwrap()
|
||||
{
|
||||
if let Some((update, data)) = &*update_data.0.lock().unwrap() {
|
||||
fn set_changelog_toast(version: Option<String>) {
|
||||
let toast_result: theseus::Result<()> = tauri::async_runtime::block_on(async move {
|
||||
let mut settings = settings::get().await?;
|
||||
@@ -290,46 +271,27 @@ fn main() {
|
||||
Ok(())
|
||||
});
|
||||
if let Err(e) = toast_result {
|
||||
tracing::warn!(
|
||||
"Failed to set pending_update_toast: {e}"
|
||||
)
|
||||
tracing::warn!("Failed to set pending_update_toast: {e}")
|
||||
}
|
||||
}
|
||||
|
||||
set_changelog_toast(Some(update.version.clone()));
|
||||
match update.install(data) {
|
||||
Ok(()) => {
|
||||
if should_restart {
|
||||
tracing::info!(
|
||||
"Pending update installed successfully (version {}); restarting because user requested reload",
|
||||
update.version
|
||||
);
|
||||
app.restart();
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Pending update installed successfully (version {}); exiting without relaunch (user did not request reload)",
|
||||
update.version
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Pending update install failed (version {}): {e}",
|
||||
update.version
|
||||
);
|
||||
set_changelog_toast(None);
|
||||
if let Err(e) = update.install(data) {
|
||||
tracing::error!("Error while updating: {e}");
|
||||
set_changelog_toast(None);
|
||||
|
||||
DialogBuilder::message()
|
||||
.set_level(MessageLevel::Error)
|
||||
.set_title("Update error")
|
||||
.set_text(format!("Failed to install update due to an error:\n{e}"))
|
||||
.alert()
|
||||
.show()
|
||||
.unwrap();
|
||||
}
|
||||
DialogBuilder::message()
|
||||
.set_level(MessageLevel::Error)
|
||||
.set_title("Update error")
|
||||
.set_text(format!("Failed to install update due to an error:\n{e}"))
|
||||
.alert()
|
||||
.show()
|
||||
.unwrap();
|
||||
}
|
||||
app.restart();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let tauri::RunEvent::Opened { urls } = event {
|
||||
tracing::info!("Handling webview open {urls:?}");
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { GenericModrinthClient, type Labrinth } from '@modrinth/api-client'
|
||||
import serverSidedVue from '@vitejs/plugin-vue'
|
||||
import fs from 'fs/promises'
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
import { fileURLToPath } from 'url'
|
||||
import svgLoader from 'vite-svg-loader'
|
||||
|
||||
import { GenericModrinthClient, type Labrinth } from '../../packages/api-client/src/index.ts'
|
||||
|
||||
const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
|
||||
const API_CLIENT_SOURCE = fileURLToPath(
|
||||
new URL('../../packages/api-client/src/index.ts', import.meta.url),
|
||||
)
|
||||
|
||||
const preloadedFonts = [
|
||||
'inter/Inter-Regular.woff2',
|
||||
@@ -29,9 +24,6 @@ const STAGING_MODRINTH_URL = 'https://staging.modrinth.com'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
srcDir: 'src/',
|
||||
alias: {
|
||||
'@modrinth/api-client': API_CLIENT_SOURCE,
|
||||
},
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: {
|
||||
@@ -90,9 +82,6 @@ export default defineNuxtConfig({
|
||||
},
|
||||
cacheDir: '../../node_modules/.vite/apps/knossos',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@modrinth/api-client': API_CLIENT_SOURCE,
|
||||
},
|
||||
dedupe: ['vue'],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.4",
|
||||
"@intercom/messenger-js-sdk": "^0.0.14",
|
||||
"@ltd/j-toml": "^1.38.0",
|
||||
"@modrinth/api-client": "workspace:*",
|
||||
"@modrinth/assets": "workspace:*",
|
||||
@@ -46,7 +47,7 @@
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@sentry/nuxt": "^10.33.0",
|
||||
"@tanstack/vue-query": "5.90.7",
|
||||
"@tanstack/vue-query": "^5.90.7",
|
||||
"@types/three": "^0.172.0",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@vue-email/components": "^0.0.21",
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
BlueskyIcon,
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
MastodonIcon,
|
||||
ToggleRightIcon,
|
||||
TwitterIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { BlueskyIcon, DiscordIcon, GithubIcon, MastodonIcon, TwitterIcon } from '@modrinth/assets'
|
||||
import {
|
||||
AutoLink,
|
||||
ButtonStyled,
|
||||
@@ -17,7 +10,6 @@ import {
|
||||
type MessageDescriptor,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { commonSettingsMessages } from '@modrinth/ui/src/utils/common-messages.js'
|
||||
|
||||
import TextLogo from '~/components/brand/TextLogo.vue'
|
||||
|
||||
@@ -241,21 +233,11 @@ function developerModeIncrement() {
|
||||
role="region"
|
||||
:aria-label="formatMessage(messages.modrinthInformation)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<TextLogo
|
||||
aria-hidden="true"
|
||||
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
|
||||
@click="developerModeIncrement()"
|
||||
/>
|
||||
<ButtonStyled v-if="flags.developerMode" circular type="transparent" color="brand">
|
||||
<nuxt-link
|
||||
v-tooltip="formatMessage(commonSettingsMessages.featureFlags)"
|
||||
to="/settings/flags"
|
||||
>
|
||||
<ToggleRightIcon />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<TextLogo
|
||||
aria-hidden="true"
|
||||
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
|
||||
@click="developerModeIncrement()"
|
||||
/>
|
||||
<div class="flex flex-wrap justify-center gap-px sm:-mx-2">
|
||||
<ButtonStyled
|
||||
v-for="(social, index) in socialLinks"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { XCircleIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
|
||||
import { defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const tempIgnored = ref(false)
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
@@ -17,34 +13,16 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
"This deploy of Modrinth's frontend failed to generate state from the API. This may be due to an outage or an error in configuration. Rebuild when the API is available. Error codes: {errors}; Current API URL is: {url}",
|
||||
},
|
||||
ignoreErrors: {
|
||||
id: 'layout.banner.build-fail.ignore',
|
||||
defaultMessage: 'Ignore',
|
||||
},
|
||||
alwaysIgnore: {
|
||||
id: 'layout.banner.build-fail.always-ignore',
|
||||
defaultMessage: 'Always ignore',
|
||||
},
|
||||
})
|
||||
|
||||
defineProps<{
|
||||
errors: any[] | undefined
|
||||
apiUrl: string
|
||||
}>()
|
||||
|
||||
function alwaysIgnoreBanner() {
|
||||
flags.value.alwaysIgnoreErrorBanner = true
|
||||
saveFeatureFlags()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PagewideBanner
|
||||
v-if="
|
||||
flags.showAllBanners || (errors?.length && !tempIgnored && !flags.alwaysIgnoreErrorBanner)
|
||||
"
|
||||
variant="error"
|
||||
>
|
||||
<PagewideBanner v-if="errors?.length" variant="error">
|
||||
<template #title>
|
||||
<span>{{ formatMessage(messages.title) }}</span>
|
||||
</template>
|
||||
@@ -56,19 +34,5 @@ function alwaysIgnoreBanner() {
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<ButtonStyled color="red" type="transparent" hover-color-fill="background">
|
||||
<button @click="alwaysIgnoreBanner">
|
||||
<XCircleIcon />
|
||||
{{ formatMessage(messages.alwaysIgnore) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button @click="tempIgnored = true">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.ignoreErrors) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
const { formatMessage } = useVIntl()
|
||||
const flags = useFeatureFlags()
|
||||
const config = useRuntimeConfig()
|
||||
const route = useRoute()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
@@ -22,7 +21,7 @@ const messages = defineMessages({
|
||||
},
|
||||
description: {
|
||||
id: 'layout.banner.preview.description',
|
||||
defaultMessage: `If you meant to access the official Modrinth website, visit {url}. This preview deploy is used by Modrinth staff for testing purposes. It was built using <branch-link>{owner}/{branch}</branch-link> @ {commit}.`,
|
||||
defaultMessage: `If you meant to access the official Modrinth website, visit <link>https://modrinth.com</link>. This preview deploy is used by Modrinth staff for testing purposes. It was built using <branch-link>{owner}/{branch}</branch-link> @ {commit}.`,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -30,12 +29,10 @@ function hidePreviewBanner() {
|
||||
flags.value.hidePreviewBanner = true
|
||||
saveFeatureFlags()
|
||||
}
|
||||
|
||||
const url = computed(() => `https://modrinth.com${route.fullPath}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PagewideBanner v-if="!flags.hidePreviewBanner || flags.showAllBanners" variant="info">
|
||||
<PagewideBanner v-if="!flags.hidePreviewBanner" variant="info">
|
||||
<template #title>
|
||||
<span>{{ formatMessage(messages.title) }}</span>
|
||||
</template>
|
||||
@@ -48,9 +45,9 @@ const url = computed(() => `https://modrinth.com${route.fullPath}`)
|
||||
branch: config.public.branch,
|
||||
}"
|
||||
>
|
||||
<template #url>
|
||||
<a :href="url" target="_blank" rel="noopener" class="text-link">
|
||||
{{ url }}
|
||||
<template #link="{ children }">
|
||||
<a href="https://modrinth.com" target="_blank" rel="noopener" class="text-link">
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</a>
|
||||
</template>
|
||||
<template #branch-link="{ children }">
|
||||
@@ -78,7 +75,7 @@ const url = computed(() => `https://modrinth.com${route.fullPath}`)
|
||||
</IntlFormatted>
|
||||
</span>
|
||||
</template>
|
||||
<template #actions_top_right>
|
||||
<template #actions_right>
|
||||
<ButtonStyled type="transparent" circular>
|
||||
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hidePreviewBanner">
|
||||
<XIcon aria-hidden="true" />
|
||||
|
||||
@@ -47,7 +47,7 @@ function hideRussiaCensorshipBanner() {
|
||||
<span class="text-xs font-medium">(Перевод на русский)</span>
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled type="transparent" hover-color-fill="background">
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/news/article/standing-by-our-values">
|
||||
<BookTextIcon /> Read our full statement
|
||||
<span class="text-xs font-medium">(English)</span>
|
||||
@@ -55,7 +55,7 @@ function hideRussiaCensorshipBanner() {
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions_top_right>
|
||||
<template #actions_right>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.closeButton)"
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
@@ -30,14 +29,14 @@ function hideStagingBanner() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PagewideBanner v-if="flags.showAllBanners || !cosmetics.hideStagingBanner" variant="warning">
|
||||
<PagewideBanner v-if="!cosmetics.hideStagingBanner" variant="warning">
|
||||
<template #title>
|
||||
<span>{{ formatMessage(messages.title) }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ formatMessage(messages.description) }}
|
||||
</template>
|
||||
<template #actions_top_right>
|
||||
<template #actions_right>
|
||||
<ButtonStyled type="transparent" circular>
|
||||
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hideStagingBanner">
|
||||
<XIcon aria-hidden="true" />
|
||||
|
||||
@@ -29,8 +29,8 @@ const messages = defineMessages({
|
||||
<template #description>
|
||||
<span>{{ formatMessage(messages.description) }}</span>
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<ButtonStyled color="red">
|
||||
<template #actions>
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.action) }}
|
||||
|
||||
@@ -55,7 +55,7 @@ function openTaxForm(e: MouseEvent) {
|
||||
formatMessage(messages.description, { threshold: formatMoney(taxThreshold) })
|
||||
}}</span>
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<template #actions>
|
||||
<ButtonStyled color="orange">
|
||||
<button @click="openTaxForm"><FileTextIcon /> {{ formatMessage(messages.action) }}</button>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -29,7 +29,7 @@ const messages = defineMessages({
|
||||
<template #description>
|
||||
<span>{{ formatMessage(messages.description) }}</span>
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<template #actions>
|
||||
<div class="flex w-fit flex-row">
|
||||
<ButtonStyled color="red">
|
||||
<nuxt-link to="https://support.modrinth.com" target="_blank" rel="noopener">
|
||||
|
||||
@@ -96,12 +96,14 @@ async function handleResendEmailVerification() {
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<ButtonStyled color="orange">
|
||||
<button v-if="hasEmail" @click="handleResendEmailVerification">
|
||||
<template #actions>
|
||||
<ButtonStyled v-if="hasEmail">
|
||||
<button @click="handleResendEmailVerification">
|
||||
{{ formatMessage(verifyEmailBannerMessages.action) }}
|
||||
</button>
|
||||
<nuxt-link v-else to="/settings/account">
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<nuxt-link to="/settings/account">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(addEmailBannerMessages.action) }}
|
||||
</nuxt-link>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { PagewideBanner } from '@modrinth/ui'
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
const route = useRoute()
|
||||
|
||||
const url = computed(() => `https://modrinth.com${route.fullPath}`)
|
||||
|
||||
const bannerRoot = ref<HTMLElement | null>(null)
|
||||
|
||||
function onProdLinkClick(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
const el = bannerRoot.value
|
||||
if (el) {
|
||||
const { height } = el.getBoundingClientRect()
|
||||
window.scrollBy({ top: Math.ceil(height), behavior: 'auto' })
|
||||
}
|
||||
window.open(url.value, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="flags.showViewProdRouteBanner || flags.showAllBanners" ref="bannerRoot">
|
||||
<PagewideBanner variant="info" slim>
|
||||
<template #description>
|
||||
<span>
|
||||
View route on production:
|
||||
<a
|
||||
:href="url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-link"
|
||||
@click="onProdLinkClick"
|
||||
>
|
||||
{{ url }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
</div>
|
||||
</template>
|
||||
+14
-15
@@ -2,29 +2,28 @@
|
||||
<div
|
||||
class="flex h-11 items-center justify-between gap-2 rounded-xl bg-button-bg px-4 py-1 text-button-text"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center justify-start gap-2">
|
||||
<div class="grid max-w-[75%] grid-cols-[auto_1fr_auto] items-center gap-2">
|
||||
<Avatar v-if="icon" :src="icon" alt="dependency-icon" size="20px" :no-shadow="true" />
|
||||
|
||||
<span
|
||||
v-tooltip="name || projectId"
|
||||
class="min-w-0 max-w-fit flex-1 truncate font-semibold text-contrast"
|
||||
>
|
||||
<span v-tooltip="name || projectId" class="truncate font-semibold text-contrast">
|
||||
{{ name || 'Unknown Project' }}
|
||||
</span>
|
||||
<span
|
||||
v-if="versionNumber"
|
||||
v-tooltip="versionNumber"
|
||||
class="min-w-0 max-w-fit flex-1 truncate whitespace-nowrap text-sm font-medium"
|
||||
>
|
||||
{{ versionNumber }}
|
||||
</span>
|
||||
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5 capitalize">
|
||||
{{ dependencyType }}
|
||||
</TagItem>
|
||||
</div>
|
||||
|
||||
<div v-if="!hideRemove" class="flex shrink-0 items-center justify-end gap-1">
|
||||
<span
|
||||
v-if="versionName"
|
||||
v-tooltip="versionName"
|
||||
class="truncate whitespace-nowrap font-medium"
|
||||
:class="!hideRemove ? 'max-w-[35%]' : 'max-w-[50%]'"
|
||||
>
|
||||
{{ versionName }}
|
||||
</span>
|
||||
|
||||
<div v-if="!hideRemove" class="flex items-center justify-end gap-1">
|
||||
<ButtonStyled size="standard" :circular="true">
|
||||
<button aria-label="Remove file" class="-mr-2 !shadow-none" @click="emitRemove">
|
||||
<XIcon aria-hidden="true" />
|
||||
@@ -44,12 +43,12 @@ const emit = defineEmits<{
|
||||
(e: 'remove'): void
|
||||
}>()
|
||||
|
||||
const { projectId, name, icon, dependencyType, versionNumber, hideRemove } = defineProps<{
|
||||
const { projectId, name, icon, dependencyType, versionName, hideRemove } = defineProps<{
|
||||
projectId: string
|
||||
name?: string
|
||||
icon?: string
|
||||
dependencyType: Labrinth.Versions.v2.DependencyType
|
||||
versionNumber?: string
|
||||
versionName?: string
|
||||
hideRemove?: boolean
|
||||
}>()
|
||||
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
:name="dependency.name"
|
||||
:icon="dependency.icon"
|
||||
:dependency-type="dependency.dependencyType"
|
||||
:version-number="dependency.versionNumber"
|
||||
:version-name="dependency.versionName"
|
||||
:hide-remove="disableRemove"
|
||||
@remove="() => removeDependency(index)"
|
||||
/>
|
||||
@@ -35,7 +35,7 @@ const addedDependencies = computed(() =>
|
||||
if (!dep.project_id) return null
|
||||
|
||||
const dependencyProject = dependencyProjects.value[dep.project_id]
|
||||
const versionNumber = dependencyVersions.value[dep.version_id || '']?.version_number ?? ''
|
||||
const versionName = dependencyVersions.value[dep.version_id || '']?.name ?? ''
|
||||
|
||||
if (!dependencyProject && projectsFetchLoading.value) return null
|
||||
|
||||
@@ -44,7 +44,7 @@ const addedDependencies = computed(() =>
|
||||
name: dependencyProject?.name,
|
||||
icon: dependencyProject?.icon_url,
|
||||
dependencyType: dep.dependency_type,
|
||||
versionNumber,
|
||||
versionName,
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
|
||||
+23
-91
@@ -3,16 +3,11 @@
|
||||
v-model="projectId"
|
||||
placeholder="Select project"
|
||||
:options="options"
|
||||
:search-value="selectedProjectOption?.label"
|
||||
:searchable="true"
|
||||
search-placeholder="Search by name or paste ID..."
|
||||
:no-options-message="searchLoading ? 'Loading...' : 'No results found'"
|
||||
searchable
|
||||
disable-search-filter
|
||||
select-search-text-on-focus
|
||||
:show-chevron="false"
|
||||
:disable-search-filter="true"
|
||||
@search-input="(query) => handleSearch(query)"
|
||||
@search-blur="handleSearchBlur"
|
||||
@select="handleSelect"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -20,37 +15,15 @@
|
||||
import type { ComboboxOption } from '@modrinth/ui'
|
||||
import { Combobox, injectModrinthClient, injectNotificationManager } from '@modrinth/ui'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { defineAsyncComponent, h, markRaw, ref, watch } from 'vue'
|
||||
import { defineAsyncComponent, h } from 'vue'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const projectId = defineModel<string>()
|
||||
|
||||
const searchLoading = ref(false)
|
||||
const options = ref<ComboboxOption<string>[]>([])
|
||||
const selectedProjectOption = ref<ComboboxOption<string>>()
|
||||
const selectedProjectSearchQuery = ref('')
|
||||
|
||||
const { labrinth } = injectModrinthClient()
|
||||
let latestSearchQuery = ''
|
||||
|
||||
function hitToOption(hit: { title: string; project_id: string; icon_url?: string | null }) {
|
||||
return {
|
||||
label: hit.title,
|
||||
value: hit.project_id,
|
||||
icon: markRaw(
|
||||
defineAsyncComponent(() =>
|
||||
Promise.resolve({
|
||||
setup: () => () =>
|
||||
h('img', {
|
||||
src: hit.icon_url,
|
||||
alt: hit.title,
|
||||
class: 'h-5 w-5 rounded',
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const search = async (query: string) => {
|
||||
query = query.trim()
|
||||
@@ -80,77 +53,36 @@ const search = async (query: string) => {
|
||||
facets: [[`project_id:${query.replace(/[^a-zA-Z0-9]/g, '')}`]], // remove any non-alphanumeric characters
|
||||
})
|
||||
|
||||
if (query !== latestSearchQuery) return
|
||||
|
||||
options.value = [...resultsByProjectId.hits, ...results.hits].map(hitToOption)
|
||||
options.value = [...resultsByProjectId.hits, ...results.hits].map((hit) => ({
|
||||
label: hit.title,
|
||||
value: hit.project_id,
|
||||
icon: markRaw(
|
||||
defineAsyncComponent(() =>
|
||||
Promise.resolve({
|
||||
setup: () => () =>
|
||||
h('img', {
|
||||
src: hit.icon_url,
|
||||
alt: hit.title,
|
||||
class: 'h-5 w-5 rounded',
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}))
|
||||
} catch (error: any) {
|
||||
if (query !== latestSearchQuery) return
|
||||
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
text: error.data ? error.data.description : error,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
|
||||
if (query === latestSearchQuery) {
|
||||
searchLoading.value = false
|
||||
}
|
||||
searchLoading.value = false
|
||||
}
|
||||
|
||||
const throttledSearch = useDebounceFn(search, 250)
|
||||
|
||||
const runSearch = async (query: string, debounce: boolean) => {
|
||||
query = query.trim()
|
||||
latestSearchQuery = query
|
||||
|
||||
if (!query) {
|
||||
searchLoading.value = false
|
||||
options.value = []
|
||||
await throttledSearch(query)
|
||||
return
|
||||
}
|
||||
|
||||
searchLoading.value = true
|
||||
await (debounce ? throttledSearch(query) : search(query))
|
||||
}
|
||||
const throttledSearch = useDebounceFn(search, 500)
|
||||
|
||||
const handleSearch = async (query: string) => {
|
||||
await runSearch(query, true)
|
||||
searchLoading.value = true
|
||||
await throttledSearch(query)
|
||||
}
|
||||
|
||||
const handleSelect = (option: ComboboxOption<string>) => {
|
||||
selectedProjectOption.value = option
|
||||
selectedProjectSearchQuery.value = latestSearchQuery
|
||||
}
|
||||
|
||||
const handleSearchBlur = async () => {
|
||||
if (!projectId.value) return
|
||||
|
||||
const selectedOption =
|
||||
options.value.find((option) => option.value === projectId.value) ??
|
||||
(selectedProjectOption.value?.value === projectId.value
|
||||
? selectedProjectOption.value
|
||||
: undefined)
|
||||
if (!selectedOption) return
|
||||
|
||||
await runSearch(selectedProjectSearchQuery.value || selectedOption.label, false)
|
||||
|
||||
if (!options.value.some((option) => option.value === selectedOption.value)) {
|
||||
options.value = [selectedOption, ...options.value]
|
||||
}
|
||||
}
|
||||
|
||||
watch(projectId, (value) => {
|
||||
if (!value) {
|
||||
selectedProjectOption.value = undefined
|
||||
selectedProjectSearchQuery.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const option = options.value.find((option) => option.value === value)
|
||||
if (option) {
|
||||
selectedProjectOption.value = option
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
:name="dependency.name"
|
||||
:icon="dependency.icon"
|
||||
:dependency-type="dependency.dependency_type"
|
||||
:version-number="dependency.versionNumber"
|
||||
:version-name="dependency.versionName"
|
||||
@on-add-suggestion="
|
||||
() =>
|
||||
handleAddSuggestion({
|
||||
|
||||
+14
-17
@@ -1,31 +1,28 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex h-11 items-center justify-between gap-2 rounded-xl border-2 border-dashed border-surface-5 px-4 py-1 text-button-text"
|
||||
class="flex items-center justify-between gap-2 rounded-xl border-2 border-dashed border-surface-5 px-4 py-1 text-button-text"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center justify-start gap-2">
|
||||
<div class="grid max-w-[75%] grid-cols-[auto_1fr_auto] items-center gap-2">
|
||||
<Avatar v-if="icon" :src="icon" alt="dependency-icon" size="20px" :no-shadow="true" />
|
||||
|
||||
<span
|
||||
v-tooltip="name || 'Unknown Project'"
|
||||
class="min-w-0 max-w-fit flex-1 truncate font-semibold text-contrast"
|
||||
>
|
||||
<span v-tooltip="name || 'Unknown Project'" class="truncate font-semibold text-contrast">
|
||||
{{ name || 'Unknown Project' }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="versionNumber"
|
||||
v-tooltip="versionNumber"
|
||||
class="min-w-0 max-w-fit flex-1 truncate whitespace-nowrap text-sm font-medium"
|
||||
>
|
||||
{{ versionNumber }}
|
||||
</span>
|
||||
|
||||
<TagItem class="shrink-0 border !border-solid border-surface-5 capitalize">
|
||||
{{ dependencyType }}
|
||||
</TagItem>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center justify-end gap-1">
|
||||
<span
|
||||
v-if="versionName"
|
||||
v-tooltip="versionName"
|
||||
class="max-w-[35%] truncate whitespace-nowrap font-medium"
|
||||
>
|
||||
{{ versionName }}
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<ButtonStyled size="standard" :circular="true" type="transparent">
|
||||
<button aria-label="Add dependency" class="!shadow-none" @click="emitAddSuggestion">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
@@ -44,11 +41,11 @@ const emit = defineEmits<{
|
||||
(e: 'onAddSuggestion'): void
|
||||
}>()
|
||||
|
||||
const { name, icon, dependencyType, versionNumber } = defineProps<{
|
||||
const { name, icon, dependencyType, versionName } = defineProps<{
|
||||
name?: string
|
||||
icon?: string
|
||||
dependencyType: Labrinth.Versions.v2.DependencyType
|
||||
versionNumber?: string
|
||||
versionName?: string
|
||||
}>()
|
||||
|
||||
function emitAddSuggestion() {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<Tabs
|
||||
<NavTabs
|
||||
v-if="editingVersion"
|
||||
value="add-files"
|
||||
:tabs="editTabs"
|
||||
class="mb-5 border border-solid border-surface-5 !shadow-none !drop-shadow-none"
|
||||
@change="setEditTab"
|
||||
mode="local"
|
||||
:links="editTabLinks"
|
||||
:active-index="2"
|
||||
class="mb-4 border border-solid border-surface-5 shadow-none drop-shadow-none"
|
||||
@tab-click="setEditTab"
|
||||
/>
|
||||
<div class="flex w-full flex-col gap-4">
|
||||
<template
|
||||
@@ -98,8 +99,7 @@ import {
|
||||
defineMessages,
|
||||
DropzoneFileInput,
|
||||
injectProjectPageContext,
|
||||
Tabs,
|
||||
type TabsTab,
|
||||
NavTabs,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { acceptFileFromProjectType } from '@modrinth/utils'
|
||||
@@ -124,14 +124,18 @@ const {
|
||||
handleNewFiles,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const editTabs: TabsTab[] = [
|
||||
{ label: 'Metadata', value: 'metadata' },
|
||||
{ label: 'Details', value: 'add-details' },
|
||||
{ label: 'Files', value: 'add-files' },
|
||||
]
|
||||
const editTabs = [
|
||||
{ label: 'Metadata', href: 'metadata', stage: 'metadata' },
|
||||
{ label: 'Details', href: 'details', stage: 'add-details' },
|
||||
{ label: 'Files', href: 'files', stage: 'add-files' },
|
||||
] as const
|
||||
|
||||
function setEditTab(tab: TabsTab) {
|
||||
modal.value?.setStage(tab.value)
|
||||
const editTabLinks = editTabs.map(({ label, href }) => ({ label, href }))
|
||||
|
||||
function setEditTab(index: number) {
|
||||
const tab = editTabs[index]
|
||||
if (!tab) return
|
||||
modal.value?.setStage(tab.stage)
|
||||
}
|
||||
|
||||
function handleRemoveFile(index: number) {
|
||||
|
||||
+167
-43
@@ -1,58 +1,182 @@
|
||||
<template>
|
||||
<div class="flex w-full max-w-full flex-col gap-3">
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast">Project</span>
|
||||
<DependencySelect v-model="newDependencyProjectId" />
|
||||
<div class="flex w-full max-w-full flex-col gap-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Add dependency</span>
|
||||
<div class="flex flex-col gap-3 rounded-2xl border border-solid border-surface-5 p-4">
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast">Project</span>
|
||||
<DependencySelect v-model="newDependencyProjectId" />
|
||||
</div>
|
||||
|
||||
<template v-if="newDependencyProjectId">
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast"> Version </span>
|
||||
<Combobox
|
||||
v-model="newDependencyVersionId"
|
||||
placeholder="Select version"
|
||||
:options="[{ label: 'Any version', value: null }, ...newDependencyVersions]"
|
||||
:searchable="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast"> Dependency relation </span>
|
||||
<Combobox
|
||||
v-model="newDependencyType"
|
||||
placeholder="Select dependency type"
|
||||
:options="[
|
||||
{ label: 'Required', value: 'required' },
|
||||
{ label: 'Optional', value: 'optional' },
|
||||
{ label: 'Incompatible', value: 'incompatible' },
|
||||
{ label: 'Embedded', value: 'embedded' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ButtonStyled color="green">
|
||||
<button
|
||||
class="self-start"
|
||||
:disabled="!newDependencyProjectId"
|
||||
@click="
|
||||
() =>
|
||||
addDependency(
|
||||
toRaw({
|
||||
project_id: newDependencyProjectId,
|
||||
version_id: newDependencyVersionId || undefined,
|
||||
dependency_type: newDependencyType,
|
||||
}),
|
||||
)
|
||||
"
|
||||
>
|
||||
Add Dependency
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="newDependencyProjectId">
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast"> Version </span>
|
||||
<Combobox
|
||||
v-model="newDependencyVersionId"
|
||||
placeholder="Select version"
|
||||
:options="newDependencyVersionOptions"
|
||||
:search-value="selectedNewDependencyVersionLabel"
|
||||
:searchable="true"
|
||||
:select-search-text-on-focus="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Suggested dependencies</span>
|
||||
<SuggestedDependencies @on-add-suggestion="handleAddSuggestedDependency" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast"> Dependency relation </span>
|
||||
<Combobox
|
||||
v-model="newDependencyType"
|
||||
placeholder="Select dependency type"
|
||||
:options="[
|
||||
{ label: 'Required', value: 'required' },
|
||||
{ label: 'Optional', value: 'optional' },
|
||||
{ label: 'Incompatible', value: 'incompatible' },
|
||||
{ label: 'Embedded', value: 'embedded' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="addedDependencies.length" class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Added dependencies</span>
|
||||
<DependenciesList />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Combobox } from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Combobox,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
} from '@modrinth/ui'
|
||||
import type { ComboboxOption } from '@modrinth/ui/src/components/base/Combobox.vue'
|
||||
|
||||
import DependencySelect from '~/components/ui/create-project-version/components/DependencySelect.vue'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { newDependencyProjectId, newDependencyType, newDependencyVersionId, newDependencyVersions } =
|
||||
injectManageVersionContext()
|
||||
import DependenciesList from '../components/DependenciesList.vue'
|
||||
import SuggestedDependencies from '../components/SuggestedDependencies/SuggestedDependencies.vue'
|
||||
|
||||
const newDependencyVersionOptions = computed(() => [
|
||||
{ label: 'Any version', value: null },
|
||||
...newDependencyVersions.value,
|
||||
])
|
||||
const selectedNewDependencyVersionLabel = computed(
|
||||
() =>
|
||||
newDependencyVersionOptions.value.find(
|
||||
(option) => option.value === newDependencyVersionId.value,
|
||||
)?.label,
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { labrinth } = injectModrinthClient()
|
||||
|
||||
const {
|
||||
draftVersion,
|
||||
dependencyProjects,
|
||||
dependencyVersions,
|
||||
projectsFetchLoading,
|
||||
visibleSuggestedDependencies,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const errorNotification = (err: any) => {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
text: err.data ? err.data.description : err,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
|
||||
const newDependencyProjectId = ref<string>()
|
||||
const newDependencyType = ref<Labrinth.Versions.v2.DependencyType>('required')
|
||||
const newDependencyVersionId = ref<string | null>(null)
|
||||
|
||||
const newDependencyVersions = ref<ComboboxOption<string>[]>([])
|
||||
|
||||
// reset to defaults when select different project
|
||||
watch(newDependencyProjectId, async () => {
|
||||
newDependencyVersionId.value = null
|
||||
newDependencyType.value = 'required'
|
||||
|
||||
if (!newDependencyProjectId.value) {
|
||||
newDependencyVersions.value = []
|
||||
} else {
|
||||
try {
|
||||
const versions = await labrinth.versions_v3.getProjectVersions(newDependencyProjectId.value)
|
||||
newDependencyVersions.value = versions.map((version) => ({
|
||||
label: version.name,
|
||||
value: version.id,
|
||||
}))
|
||||
} catch (error: any) {
|
||||
errorNotification(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const addedDependencies = computed(() =>
|
||||
(draftVersion.value.dependencies || [])
|
||||
.map((dep) => {
|
||||
if (!dep.project_id) return null
|
||||
|
||||
const dependencyProject = dependencyProjects.value[dep.project_id]
|
||||
const versionName = dependencyVersions.value[dep.version_id || '']?.name ?? ''
|
||||
|
||||
if (!dependencyProject && projectsFetchLoading.value) return null
|
||||
|
||||
return {
|
||||
projectId: dep.project_id,
|
||||
name: dependencyProject?.name,
|
||||
icon: dependencyProject?.icon_url,
|
||||
dependencyType: dep.dependency_type,
|
||||
versionName,
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
)
|
||||
|
||||
const addDependency = (dependency: Labrinth.Versions.v3.Dependency) => {
|
||||
if (!draftVersion.value.dependencies) draftVersion.value.dependencies = []
|
||||
|
||||
const alreadyAdded = draftVersion.value.dependencies.some((existing) => {
|
||||
if (existing.project_id !== dependency.project_id) return false
|
||||
if (!existing.version_id && !dependency.version_id) return true
|
||||
return existing.version_id === dependency.version_id
|
||||
})
|
||||
|
||||
if (alreadyAdded) {
|
||||
addNotification({
|
||||
title: 'Dependency already added',
|
||||
text: 'You cannot add the same dependency twice.',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
projectsFetchLoading.value = true
|
||||
draftVersion.value.dependencies.push(dependency)
|
||||
newDependencyProjectId.value = undefined
|
||||
}
|
||||
|
||||
const handleAddSuggestedDependency = (dependency: Labrinth.Versions.v3.Dependency) => {
|
||||
draftVersion.value.dependencies?.push({
|
||||
project_id: dependency.project_id,
|
||||
version_id: dependency.version_id,
|
||||
dependency_type: dependency.dependency_type,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<Tabs
|
||||
<NavTabs
|
||||
v-if="editingVersion"
|
||||
value="add-details"
|
||||
:tabs="editTabs"
|
||||
class="mb-5 border border-solid border-surface-5 !shadow-none !drop-shadow-none"
|
||||
@change="setEditTab"
|
||||
mode="local"
|
||||
:links="editTabLinks"
|
||||
:active-index="1"
|
||||
class="mb-4 border border-solid border-surface-5 shadow-none drop-shadow-none"
|
||||
@tab-click="setEditTab"
|
||||
/>
|
||||
<div class="flex w-full flex-col gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
@@ -17,7 +18,6 @@
|
||||
:never-empty="true"
|
||||
:capitalize="true"
|
||||
:disabled="isUploading"
|
||||
hide-checkmark-icon
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
@@ -61,21 +61,25 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Chips, MarkdownEditor, StyledInput, Tabs, type TabsTab } from '@modrinth/ui'
|
||||
import { Chips, MarkdownEditor, NavTabs, StyledInput } from '@modrinth/ui'
|
||||
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { draftVersion, isUploading, editingVersion, modal } = injectManageVersionContext()
|
||||
|
||||
const editTabs: TabsTab[] = [
|
||||
{ label: 'Metadata', value: 'metadata' },
|
||||
{ label: 'Details', value: 'add-details' },
|
||||
{ label: 'Files', value: 'add-files' },
|
||||
]
|
||||
const editTabs = [
|
||||
{ label: 'Metadata', href: 'metadata', stage: 'metadata' },
|
||||
{ label: 'Details', href: 'details', stage: 'add-details' },
|
||||
{ label: 'Files', href: 'files', stage: 'add-files' },
|
||||
] as const
|
||||
|
||||
function setEditTab(tab: TabsTab) {
|
||||
modal.value?.setStage(tab.value)
|
||||
const editTabLinks = editTabs.map(({ label, href }) => ({ label, href }))
|
||||
|
||||
function setEditTab(index: number) {
|
||||
const tab = editTabs[index]
|
||||
if (!tab) return
|
||||
modal.value?.setStage(tab.stage)
|
||||
}
|
||||
|
||||
async function onImageUpload(file: File) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<Tabs
|
||||
<NavTabs
|
||||
v-if="editingVersion"
|
||||
value="metadata"
|
||||
:tabs="editTabs"
|
||||
class="mb-3 border border-solid border-surface-5 !shadow-none !drop-shadow-none"
|
||||
@change="setEditTab"
|
||||
mode="local"
|
||||
:links="editTabLinks"
|
||||
:active-index="0"
|
||||
class="mb-2 border border-solid border-surface-5 shadow-none drop-shadow-none"
|
||||
@tab-click="setEditTab"
|
||||
/>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div v-if="!editingVersion" class="flex flex-col gap-1">
|
||||
@@ -160,32 +161,40 @@
|
||||
</template>
|
||||
|
||||
<template v-if="!noDependenciesProject">
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Dependencies </span>
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Suggested dependencies </span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="addDependency">
|
||||
<PlusIcon />
|
||||
Add dependency
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editDependencies">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<SuggestedDependencies @on-add-suggestion="handleAddSuggestedDependency" />
|
||||
</div>
|
||||
|
||||
<div v-if="draftVersion.dependencies?.length" class="flex flex-col gap-4">
|
||||
<DependenciesList />
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-1.5 gap-y-4 rounded-xl bg-surface-2 p-3 py-4">
|
||||
<span class="text-sm font-medium">No dependencies added.</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!visibleSuggestedDependencies.length || draftVersion.dependencies?.length"
|
||||
class="flex flex-col gap-1"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Dependencies </span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editDependencies">
|
||||
<EditIcon />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-2.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-medium"> Suggested </span>
|
||||
</div>
|
||||
<SuggestedDependencies @on-add-suggestion="handleAddSuggestedDependency" />
|
||||
<div v-if="draftVersion.dependencies?.length" class="flex flex-col gap-4">
|
||||
<DependenciesList disable-remove />
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-1.5 gap-y-4 rounded-xl bg-surface-2 p-3 py-4">
|
||||
<span class="text-sm font-medium">No dependencies added.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -194,15 +203,14 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { EditIcon, getLoaderIcon, PlusIcon, UnknownIcon } from '@modrinth/assets'
|
||||
import { EditIcon, getLoaderIcon, UnknownIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
ENVIRONMENTS_COPY,
|
||||
FormattedTag,
|
||||
injectProjectPageContext,
|
||||
Tabs,
|
||||
type TabsTab,
|
||||
NavTabs,
|
||||
TagItem,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
@@ -231,14 +239,18 @@ const { projectV2 } = injectProjectPageContext()
|
||||
const generatedState = useGeneratedState()
|
||||
const loaders = computed(() => generatedState.value.loaders)
|
||||
|
||||
const editTabs: TabsTab[] = [
|
||||
{ label: 'Metadata', value: 'metadata' },
|
||||
{ label: 'Details', value: 'add-details' },
|
||||
{ label: 'Files', value: 'add-files' },
|
||||
]
|
||||
const editTabs = [
|
||||
{ label: 'Metadata', href: 'metadata', stage: 'metadata' },
|
||||
{ label: 'Details', href: 'details', stage: 'add-details' },
|
||||
{ label: 'Files', href: 'files', stage: 'add-files' },
|
||||
] as const
|
||||
|
||||
function setEditTab(tab: TabsTab) {
|
||||
modal.value?.setStage(tab.value)
|
||||
const editTabLinks = editTabs.map(({ label, href }) => ({ label, href }))
|
||||
|
||||
function setEditTab(index: number) {
|
||||
const tab = editTabs[index]
|
||||
if (!tab) return
|
||||
modal.value?.setStage(tab.stage)
|
||||
}
|
||||
|
||||
const isModpack = computed(() => projectType.value === 'modpack')
|
||||
@@ -267,7 +279,7 @@ const editEnvironment = () => {
|
||||
const editFiles = () => {
|
||||
modal.value?.setStage('from-details-files')
|
||||
}
|
||||
const addDependency = () => {
|
||||
const editDependencies = () => {
|
||||
modal.value?.setStage('from-details-dependencies')
|
||||
}
|
||||
|
||||
|
||||
@@ -131,9 +131,10 @@
|
||||
class="h-10"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
<StyledInput
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="formData[field.name]"
|
||||
type="date"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
|
||||
@@ -229,7 +230,6 @@ import {
|
||||
Admonition,
|
||||
Checkbox,
|
||||
Combobox,
|
||||
DatePicker,
|
||||
defineMessages,
|
||||
financialMessages,
|
||||
formFieldLabels,
|
||||
|
||||
@@ -95,11 +95,11 @@
|
||||
<span class="text-red">*</span>
|
||||
</span>
|
||||
</label>
|
||||
<DatePicker
|
||||
<StyledInput
|
||||
v-model="formData.dateOfBirth"
|
||||
:max-date="maxDate"
|
||||
type="date"
|
||||
:max="maxDate"
|
||||
autocomplete="bday"
|
||||
placeholder="Select date of birth"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
</div>
|
||||
@@ -213,7 +213,6 @@
|
||||
import {
|
||||
Chips,
|
||||
Combobox,
|
||||
DatePicker,
|
||||
defineMessages,
|
||||
formFieldLabels,
|
||||
formFieldPlaceholders,
|
||||
|
||||
@@ -233,7 +233,7 @@ const visibleNags = computed<Nag[]>(() => {
|
||||
link: {
|
||||
path: 'moderation',
|
||||
title: messages.visitModerationPage,
|
||||
shouldShow: () => props.routeName !== 'type-project-moderation',
|
||||
shouldShow: () => props.routeName !== 'type-id-moderation',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="shadow-card rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4">
|
||||
<div class="shadow-card rounded-2xl border border-solid border-surface-5 bg-surface-3 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<NuxtLink
|
||||
@@ -107,8 +107,8 @@
|
||||
<LinkIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular color="orange">
|
||||
<button v-tooltip="'Begin review'" @click="openProjectForReview">
|
||||
<ButtonStyled v-tooltip="'Begin review'" circular color="orange">
|
||||
<button @click="openProjectForReview">
|
||||
<ScaleIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
:expand-text="expandText"
|
||||
collapse-text="Collapse thread"
|
||||
>
|
||||
<div class="bg-surface-2 pt-2">
|
||||
<div class="bg-surface-2 p-4 pt-2">
|
||||
<ThreadView
|
||||
v-if="threadWithReportBody"
|
||||
ref="reportThread"
|
||||
|
||||
@@ -1075,7 +1075,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
mode="local"
|
||||
:links="navTabsLinks"
|
||||
:active-index="activeTabIndex"
|
||||
class="bg-surface-3! shadow-none!"
|
||||
@tab-click="handleTabClick"
|
||||
/>
|
||||
</div>
|
||||
@@ -1088,7 +1087,7 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
collapse-text="Collapse thread"
|
||||
class="border-x border-b border-solid border-surface-3"
|
||||
>
|
||||
<div class="bg-surface-2 pt-0">
|
||||
<div class="bg-surface-2 p-4 pt-0">
|
||||
<!-- DEV-531 -->
|
||||
<!-- @vue-expect-error TODO: will convert ThreadView to use api-client types at a later date -->
|
||||
<ThreadView
|
||||
|
||||
@@ -358,44 +358,26 @@
|
||||
|
||||
<div v-else-if="generatedMessage" class="flex items-center gap-2">
|
||||
<ButtonStyled>
|
||||
<button :disabled="loadingModerationDecision" @click="goBackToStages">
|
||||
<button @click="goBackToStages">
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button :disabled="loadingModerationDecision" @click="sendMessage('rejected')">
|
||||
<SpinnerIcon
|
||||
v-if="moderationDecision === 'rejected'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
<button @click="sendMessage('rejected')">
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="orange">
|
||||
<button :disabled="loadingModerationDecision" @click="sendMessage('withheld')">
|
||||
<SpinnerIcon
|
||||
v-if="moderationDecision === 'withheld'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<LinkIcon v-else aria-hidden="true" />
|
||||
<button @click="sendMessage('withheld')">
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="green">
|
||||
<button
|
||||
:disabled="loadingModerationDecision"
|
||||
@click="sendMessage(approveSendStatus)"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="moderationDecision === approveSendStatus"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
<button @click="sendMessage(projectV2.requested_status ?? 'approved')">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -446,15 +428,14 @@ import {
|
||||
BrushCleaningIcon,
|
||||
CheckIcon,
|
||||
DropdownIcon,
|
||||
EyeOffIcon,
|
||||
FileTextIcon,
|
||||
KeyboardIcon,
|
||||
LeftArrowIcon,
|
||||
LinkIcon,
|
||||
ListBulletedIcon,
|
||||
LockIcon,
|
||||
RightArrowIcon,
|
||||
ScaleIcon,
|
||||
SpinnerIcon,
|
||||
ToggleLeftIcon,
|
||||
ToggleRightIcon,
|
||||
XIcon,
|
||||
@@ -739,8 +720,8 @@ async function navigateToNextUnlockedProject(): Promise<boolean> {
|
||||
} else {
|
||||
// Fallback: use project ID (will trigger middleware redirect)
|
||||
navigateTo({
|
||||
name: 'type-project',
|
||||
params: { type: 'project', project: next.projectId },
|
||||
name: 'type-id',
|
||||
params: { type: 'project', id: next.projectId },
|
||||
state: { showChecklist: true },
|
||||
})
|
||||
}
|
||||
@@ -779,12 +760,6 @@ const message = ref(
|
||||
)
|
||||
const generatedMessage = ref(persistedGeneratedMessage.generated === true)
|
||||
const loadingMessage = ref(false)
|
||||
const moderationDecision = ref<ProjectStatus | null>(null)
|
||||
const loadingModerationDecision = computed(() => moderationDecision.value !== null)
|
||||
const approveSendStatus = computed<ProjectStatus>(() => {
|
||||
const requested = projectV2.value.requested_status
|
||||
return requested ?? 'approved'
|
||||
})
|
||||
const done = ref(false)
|
||||
|
||||
function persistGeneratedMessageState() {
|
||||
@@ -1067,8 +1042,8 @@ async function skipToNextProject() {
|
||||
} else {
|
||||
// Fallback: use project ID
|
||||
navigateTo({
|
||||
name: 'type-project',
|
||||
params: { type: 'project', project: id },
|
||||
name: 'type-id',
|
||||
params: { type: 'project', id },
|
||||
state: { showChecklist: true },
|
||||
})
|
||||
}
|
||||
@@ -1099,7 +1074,6 @@ function resetProgress() {
|
||||
done.value = false
|
||||
clearGeneratedMessageState()
|
||||
loadingMessage.value = false
|
||||
moderationDecision.value = null
|
||||
|
||||
localStorage.removeItem(`modpack-permissions-${projectV2.value.id}`)
|
||||
localStorage.removeItem(`modpack-permissions-index-${projectV2.value.id}`)
|
||||
@@ -1216,7 +1190,7 @@ function handleKeybinds(event: KeyboardEvent) {
|
||||
tryResetProgress: resetProgress,
|
||||
tryExitModeration: handleExit,
|
||||
|
||||
tryApprove: () => sendMessage(approveSendStatus.value),
|
||||
tryApprove: () => sendMessage(projectV2.value.requested_status ?? 'approved'),
|
||||
tryReject: () => sendMessage('rejected'),
|
||||
tryWithhold: () => sendMessage('withheld'),
|
||||
tryEditMessage: goBackToStages,
|
||||
@@ -2003,7 +1977,6 @@ async function sendMessage(status: ProjectStatus) {
|
||||
return
|
||||
}
|
||||
|
||||
moderationDecision.value = status
|
||||
try {
|
||||
await useBaseFetch(`project/${projectId}`, {
|
||||
method: 'PATCH',
|
||||
@@ -2053,8 +2026,6 @@ async function sendMessage(status: ProjectStatus) {
|
||||
text: 'Failed to submit moderation decision. Please try again.',
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
moderationDecision.value = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2127,8 +2098,8 @@ async function endChecklist(status?: string) {
|
||||
} else {
|
||||
// Fallback: use project ID
|
||||
navigateTo({
|
||||
name: 'type-project',
|
||||
params: { type: 'project', project: id },
|
||||
name: 'type-id',
|
||||
params: { type: 'project', id },
|
||||
state: { showChecklist: true },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<section>
|
||||
<section class="universal-card">
|
||||
<Breadcrumbs
|
||||
v-if="breadcrumbsStack"
|
||||
:current-title="`Report ${reportId}`"
|
||||
:link-stack="breadcrumbsStack"
|
||||
/>
|
||||
<h2>Report details</h2>
|
||||
<ReportInfo
|
||||
:report="report"
|
||||
:show-thread="false"
|
||||
:show-message="false"
|
||||
:auth="auth"
|
||||
class="card-shadow mb-4 rounded-2xl border border-solid border-surface-4 bg-surface-2 p-4"
|
||||
/>
|
||||
<ReportInfo :report="report" :show-thread="false" :show-message="false" :auth="auth" />
|
||||
</section>
|
||||
<section
|
||||
v-if="report && thread"
|
||||
class="card-shadow rounded-2xl border border-solid border-surface-4 bg-surface-3"
|
||||
>
|
||||
<h2 class="m-4 mb-2 text-xl font-semibold text-contrast">Messages with the moderators</h2>
|
||||
<p class="mx-4 mt-0">
|
||||
Make sure to include evidence of all claims you make, or your report may be closed without
|
||||
action.
|
||||
</p>
|
||||
<section v-if="report && thread" class="universal-card">
|
||||
<h2>Messages</h2>
|
||||
<ConversationThread
|
||||
class="overflow-clip rounded-b-2xl border-0 border-t border-solid border-surface-4 bg-surface-2"
|
||||
:thread="thread"
|
||||
:report="report"
|
||||
:auth="auth"
|
||||
|
||||
@@ -1,45 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<NewModal
|
||||
<Modal
|
||||
ref="modalSubmit"
|
||||
:header="
|
||||
formatMessage(
|
||||
isRejected(project)
|
||||
? messages.resubmitModalHeaderResubmitting
|
||||
: messages.resubmitModalHeaderSubmitting,
|
||||
)
|
||||
"
|
||||
:header="isRejected(project) ? 'Resubmit for review' : 'Submit for review'"
|
||||
>
|
||||
<div class="flex max-w-[35rem] flex-col gap-3">
|
||||
<p class="m-0">
|
||||
<IntlFormatted
|
||||
:message-id="messages.resubmitModalDescription"
|
||||
:message-values="{ projectTitle: project.title }"
|
||||
>
|
||||
<template #project-title="{ children }">
|
||||
<span class="font-semibold text-contrast">
|
||||
<component :is="() => children" />
|
||||
</span>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<p class="m-0">{{ formatMessage(messages.resubmitModalReminder) }}</p>
|
||||
<p class="m-0 font-semibold text-red">
|
||||
{{ formatMessage(messages.resubmitModalWarning) }}
|
||||
</p>
|
||||
<div class="modal-submit universal-body">
|
||||
<span>
|
||||
You're submitting <span class="project-title">{{ project.title }}</span> to be reviewed
|
||||
again by the moderators.
|
||||
</span>
|
||||
<span>
|
||||
Make sure you have addressed the comments from the moderation team.
|
||||
<span class="known-errors">
|
||||
Repeated submissions without addressing the moderators' comments may result in an
|
||||
account suspension.
|
||||
</span>
|
||||
</span>
|
||||
<Checkbox
|
||||
v-model="submissionConfirmation"
|
||||
:description="formatMessage(messages.resubmitModalConfirmationDescription)"
|
||||
description="Confirm I have addressed the messages from the moderators"
|
||||
>
|
||||
{{ formatMessage(messages.resubmitModalConfirmationLabel) }}
|
||||
I confirm that I have properly addressed the moderators' comments.
|
||||
</Checkbox>
|
||||
<div class="flex flex-wrap items-center justify-end gap-2">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="modalSubmit.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="input-group push-right">
|
||||
<ButtonStyled color="orange">
|
||||
<button
|
||||
:disabled="!submissionConfirmation || isLoading"
|
||||
@@ -51,37 +34,33 @@
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionResubmitForReview) }}
|
||||
Resubmit for review
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
<NewModal ref="modalReply" :header="formatMessage(messages.replyModalHeader)">
|
||||
<div class="flex max-w-[45rem] flex-col gap-3">
|
||||
<p class="m-0">{{ formatMessage(messages.replyModalDescription) }}</p>
|
||||
<p class="m-0">
|
||||
<IntlFormatted :message-id="messages.replyModalHelpCenterNote">
|
||||
<template #help-center-link="{ children }">
|
||||
<a class="text-link" href="https://support.modrinth.com" target="_blank">
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
</Modal>
|
||||
<Modal ref="modalReply" header="Reply to thread">
|
||||
<div class="modal-submit universal-body">
|
||||
<span>
|
||||
Your project is already approved. As such, the moderation team does not actively monitor
|
||||
this thread. However, they may still see your message if there is a problem with your
|
||||
project.
|
||||
</span>
|
||||
<span>
|
||||
If you need to get in contact with the moderation team, please use the
|
||||
<a class="text-link" href="https://support.modrinth.com" target="_blank">
|
||||
Modrinth Help Center
|
||||
</a>
|
||||
and click the green bubble to contact support.
|
||||
</span>
|
||||
<Checkbox
|
||||
v-model="replyConfirmation"
|
||||
:description="formatMessage(messages.replyModalConfirmationDescription)"
|
||||
description="Confirm moderators do not actively monitor this"
|
||||
>
|
||||
{{ formatMessage(messages.replyModalConfirmationLabel) }}
|
||||
I acknowledge that the moderators do not actively monitor the thread.
|
||||
</Checkbox>
|
||||
<div class="flex flex-wrap items-center justify-end gap-2">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="modalReply.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="input-group push-right">
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="!replyConfirmation || isLoading"
|
||||
@@ -93,318 +72,289 @@
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ReplyIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionReplyToThread) }}
|
||||
Reply to thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
<div v-if="flags.developerMode" class="mx-4 mb-3 font-semibold">
|
||||
</Modal>
|
||||
<div v-if="flags.developerMode" class="thread-id">
|
||||
Thread ID:
|
||||
<CopyCode :text="thread.id" />
|
||||
</div>
|
||||
<div v-bind="$attrs" class="flex flex-col">
|
||||
<div v-if="sortedMessages.length > 0" class="flex flex-col pt-2">
|
||||
<ThreadMessage
|
||||
v-for="message in sortedMessages"
|
||||
:key="'message-' + message.id"
|
||||
:thread="thread"
|
||||
:message="message"
|
||||
:members="members"
|
||||
:report="report"
|
||||
:auth="auth"
|
||||
raised
|
||||
@update-thread="() => updateThreadLocal()"
|
||||
<div v-if="sortedMessages.length > 0" class="messages universal-card recessed">
|
||||
<ThreadMessage
|
||||
v-for="message in sortedMessages"
|
||||
:key="'message-' + message.id"
|
||||
:thread="thread"
|
||||
:message="message"
|
||||
:members="members"
|
||||
:report="report"
|
||||
:auth="auth"
|
||||
raised
|
||||
@update-thread="() => updateThreadLocal()"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="report && report.closed">
|
||||
<p>This thread is closed and new messages cannot be sent to it.</p>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button :disabled="isLoading" @click="runBlockingAction('reopen', () => reopenReport())">
|
||||
<SpinnerIcon v-if="loadingAction === 'reopen'" class="animate-spin" aria-hidden="true" />
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
Reopen thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else-if="!report || !report.closed">
|
||||
<div class="markdown-editor-spacing">
|
||||
<MarkdownEditor
|
||||
v-model="replyBody"
|
||||
:placeholder="sortedMessages.length > 0 ? 'Reply to thread...' : 'Send a message...'"
|
||||
:on-image-upload="onUploadImage"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="report && report.closed">
|
||||
<p>{{ formatMessage(messages.closedThreadDescription) }}</p>
|
||||
<div class="input-group">
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
v-if="sortedMessages.length > 0"
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="
|
||||
isApproved(project) && !isStaff(auth.user)
|
||||
? openReplyModal()
|
||||
: runBlockingAction('reply', () => sendReply())
|
||||
"
|
||||
>
|
||||
<SpinnerIcon v-if="loadingAction === 'reply'" class="animate-spin" aria-hidden="true" />
|
||||
<ReplyIcon v-else aria-hidden="true" />
|
||||
Reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="
|
||||
isApproved(project) && !isStaff(auth.user)
|
||||
? openReplyModal()
|
||||
: runBlockingAction('send', () => sendReply())
|
||||
"
|
||||
>
|
||||
<SpinnerIcon v-if="loadingAction === 'send'" class="animate-spin" aria-hidden="true" />
|
||||
<SendIcon v-else aria-hidden="true" />
|
||||
Send
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button :disabled="isLoading" @click="runBlockingAction('reopen', () => reopenReport())">
|
||||
<button
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="runBlockingAction('private-note', () => sendReply(null, true))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reopen'"
|
||||
v-if="loadingAction === 'private-note'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionReopenThread) }}
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
Add private note
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else-if="!report || !report.closed">
|
||||
<div class="mx-4 mb-2 mt-2">
|
||||
<MarkdownEditor
|
||||
v-model="replyBody"
|
||||
:placeholder="
|
||||
formatMessage(
|
||||
sortedMessages.length > 0
|
||||
? messages.replyEditorPlaceholderReply
|
||||
: messages.replyEditorPlaceholderSend,
|
||||
)
|
||||
"
|
||||
:on-image-upload="onUploadImage"
|
||||
/>
|
||||
</div>
|
||||
<div class="m-4 mt-3 flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
v-if="sortedMessages.length > 0"
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="
|
||||
isApproved(project)
|
||||
? openReplyModal()
|
||||
: runBlockingAction('reply', () => sendReply())
|
||||
"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ReplyIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionReply) }}
|
||||
<template v-if="currentMember && !isStaff(auth.user)">
|
||||
<template v-if="isRejected(project)">
|
||||
<ButtonStyled color="orange">
|
||||
<button v-if="replyBody" :disabled="isLoading" @click="openResubmitModal(true)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review with reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="
|
||||
isApproved(project)
|
||||
? openReplyModal()
|
||||
: runBlockingAction('send', () => sendReply())
|
||||
"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'send'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<SendIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionSend) }}
|
||||
<button v-else :disabled="isLoading" @click="openResubmitModal(false)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button
|
||||
:disabled="!replyBody || isLoading"
|
||||
@click="runBlockingAction('private-note', () => sendReply(null, true))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'private-note'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionAddPrivateNote) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<template v-if="currentMember && !currentMember.staffOnly">
|
||||
<template v-if="isRejected(project)">
|
||||
<ButtonStyled color="orange">
|
||||
<button v-if="replyBody" :disabled="isLoading" @click="openResubmitModal(true)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionResubmitForReviewWithReply) }}
|
||||
</template>
|
||||
</template>
|
||||
<div class="spacer"></div>
|
||||
<div class="input-group extra-options">
|
||||
<template v-if="report">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
v-if="replyBody"
|
||||
:disabled="isLoading"
|
||||
@click="runBlockingAction('close-with-reply', () => closeReport(true))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'close-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
Close with reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="isLoading"
|
||||
@click="runBlockingAction('close', () => closeReport())"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'close'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
Close thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="project">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<ButtonStyled v-if="replyBody" color="green">
|
||||
<button
|
||||
:disabled="isApproved(project) || isLoading"
|
||||
@click="runBlockingAction('approve-with-reply', () => sendReply(requestedStatus))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'approve-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
Approve with reply
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="green">
|
||||
<button
|
||||
:disabled="isApproved(project) || isLoading"
|
||||
@click="runBlockingAction('approve', () => setStatus(requestedStatus))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'approve'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
Approve
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="joined-buttons">
|
||||
<ButtonStyled v-if="replyBody" color="red">
|
||||
<button
|
||||
:disabled="project.status === 'rejected' || isLoading"
|
||||
@click="runBlockingAction('reject-with-reply', () => sendReply('rejected'))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reject-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
Reject with reply
|
||||
</button>
|
||||
<button v-else :disabled="isLoading" @click="openResubmitModal(false)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionResubmitForReview) }}
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="red">
|
||||
<button
|
||||
:disabled="project.status === 'rejected' || isLoading"
|
||||
@click="runBlockingAction('reject', () => setStatus('rejected'))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reject'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
Reject
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<template v-if="report">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
v-if="replyBody"
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:disabled="isLoading"
|
||||
@click="runBlockingAction('close-with-reply', () => closeReport(true))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'close-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionCloseWithReply) }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="isLoading"
|
||||
@click="runBlockingAction('close', () => closeReport())"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'close'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionCloseThread) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="project">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<ButtonStyled v-if="replyBody" color="green">
|
||||
<button
|
||||
:disabled="isApproved(project) || isLoading"
|
||||
@click="
|
||||
runBlockingAction('approve-with-reply', () => sendReply(requestedStatus))
|
||||
:options="
|
||||
replyBody
|
||||
? [
|
||||
{
|
||||
id: 'withhold-reply',
|
||||
color: 'danger',
|
||||
action: () =>
|
||||
runBlockingAction('withhold-reply', () => sendReply('withheld')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft-reply',
|
||||
action: () =>
|
||||
runBlockingAction('set-to-draft-reply', () => sendReply('draft')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review-reply',
|
||||
action: () =>
|
||||
runBlockingAction('send-to-review-reply', () =>
|
||||
sendReply('processing', true),
|
||||
),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing' || isLoading,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'danger',
|
||||
action: () =>
|
||||
runBlockingAction('withhold', () => setStatus('withheld')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft',
|
||||
action: () =>
|
||||
runBlockingAction('set-to-draft', () => setStatus('draft')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () =>
|
||||
runBlockingAction('send-to-review', () => setStatus('processing')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing' || isLoading,
|
||||
},
|
||||
]
|
||||
"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'approve-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionApproveWithReply) }}
|
||||
</button>
|
||||
<SpinnerIcon v-if="isDropdownLoading" class="animate-spin" aria-hidden="true" />
|
||||
<DropdownIcon v-else aria-hidden="true" />
|
||||
<template #withhold-reply>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold with reply
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</template>
|
||||
<template #set-to-draft-reply>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
Set to draft with reply
|
||||
</template>
|
||||
<template #set-to-draft>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
Set to draft
|
||||
</template>
|
||||
<template #send-to-review-reply>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review with reply
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="green">
|
||||
<button
|
||||
:disabled="isApproved(project) || isLoading"
|
||||
@click="runBlockingAction('approve', () => setStatus(requestedStatus))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'approve'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionApprove) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="joined-buttons">
|
||||
<ButtonStyled v-if="replyBody" color="red">
|
||||
<button
|
||||
:disabled="project.status === 'rejected' || isLoading"
|
||||
@click="runBlockingAction('reject-with-reply', () => sendReply('rejected'))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reject-with-reply'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionRejectWithReply) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="red">
|
||||
<button
|
||||
:disabled="project.status === 'rejected' || isLoading"
|
||||
@click="runBlockingAction('reject', () => setStatus('rejected'))"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="loadingAction === 'reject'"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionReject) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:disabled="isLoading"
|
||||
:options="
|
||||
replyBody
|
||||
? [
|
||||
{
|
||||
id: 'withhold-reply',
|
||||
color: 'danger',
|
||||
action: () =>
|
||||
runBlockingAction('withhold-reply', () => sendReply('withheld')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft-reply',
|
||||
action: () =>
|
||||
runBlockingAction('set-to-draft-reply', () => sendReply('draft')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review-reply',
|
||||
action: () =>
|
||||
runBlockingAction('send-to-review-reply', () =>
|
||||
sendReply('processing', true),
|
||||
),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing' || isLoading,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'danger',
|
||||
action: () =>
|
||||
runBlockingAction('withhold', () => setStatus('withheld')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft',
|
||||
action: () =>
|
||||
runBlockingAction('set-to-draft', () => setStatus('draft')),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft' || isLoading,
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () =>
|
||||
runBlockingAction('send-to-review', () =>
|
||||
setStatus('processing'),
|
||||
),
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing' || isLoading,
|
||||
},
|
||||
]
|
||||
"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="isDropdownLoading"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<DropdownIcon v-else aria-hidden="true" />
|
||||
<template #withhold-reply>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionWithholdWithReply) }}
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionWithhold) }}
|
||||
</template>
|
||||
<template #set-to-draft-reply>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionSetToDraftWithReply) }}
|
||||
</template>
|
||||
<template #set-to-draft>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionSetToDraft) }}
|
||||
</template>
|
||||
<template #send-to-review-reply>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionSendToReviewWithReply) }}
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.actionSendToReview) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -424,179 +374,19 @@ import {
|
||||
import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
CopyCode,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
MarkdownEditor,
|
||||
NewModal,
|
||||
OverflowMenu,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import Modal from '~/components/ui/Modal.vue'
|
||||
import ThreadMessage from '~/components/ui/thread/ThreadMessage.vue'
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { isApproved, isRejected } from '~/helpers/projects.js'
|
||||
import { isStaff } from '~/helpers/users.js'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
resubmitModalHeaderResubmitting: {
|
||||
id: 'conversation-thread.resubmit-modal.header.resubmitting',
|
||||
defaultMessage: 'Resubmitting for review',
|
||||
},
|
||||
resubmitModalHeaderSubmitting: {
|
||||
id: 'conversation-thread.resubmit-modal.header.submitting',
|
||||
defaultMessage: 'Submitting for review',
|
||||
},
|
||||
resubmitModalDescription: {
|
||||
id: 'conversation-thread.resubmit-modal.description',
|
||||
defaultMessage:
|
||||
"You're submitting <project-title>{projectTitle}</project-title> to be reviewed again by the moderators.",
|
||||
},
|
||||
resubmitModalReminder: {
|
||||
id: 'conversation-thread.resubmit-modal.reminder',
|
||||
defaultMessage: 'Make sure you have addressed all the comments from the moderation team.',
|
||||
},
|
||||
resubmitModalWarning: {
|
||||
id: 'conversation-thread.resubmit-modal.warning',
|
||||
defaultMessage:
|
||||
"Repeated submissions without addressing the moderators' comments may result in an account suspension.",
|
||||
},
|
||||
resubmitModalConfirmationDescription: {
|
||||
id: 'conversation-thread.resubmit-modal.confirmation.description',
|
||||
defaultMessage: 'Confirm I have addressed the messages from the moderators',
|
||||
},
|
||||
resubmitModalConfirmationLabel: {
|
||||
id: 'conversation-thread.resubmit-modal.confirmation.label',
|
||||
defaultMessage: "I confirm that I have properly addressed the moderators' comments.",
|
||||
},
|
||||
replyModalHeader: {
|
||||
id: 'conversation-thread.reply-modal.header',
|
||||
defaultMessage: 'Reply to thread',
|
||||
},
|
||||
replyModalDescription: {
|
||||
id: 'conversation-thread.reply-modal.description',
|
||||
defaultMessage:
|
||||
'Your project is already approved. As such, the moderation team does not actively monitor this thread. However, they may still see your message if there is a problem with your project.',
|
||||
},
|
||||
replyModalHelpCenterNote: {
|
||||
id: 'conversation-thread.reply-modal.help-center-note',
|
||||
defaultMessage:
|
||||
'If you need to get in contact with the moderation team, please use the <help-center-link>Modrinth Help Center</help-center-link> and click the blue bubble in the bottom right corner to contact support.',
|
||||
},
|
||||
replyModalConfirmationDescription: {
|
||||
id: 'conversation-thread.reply-modal.confirmation.description',
|
||||
defaultMessage: 'Confirm moderators do not actively monitor this',
|
||||
},
|
||||
replyModalConfirmationLabel: {
|
||||
id: 'conversation-thread.reply-modal.confirmation.label',
|
||||
defaultMessage: 'I acknowledge that the moderators do not actively monitor the thread.',
|
||||
},
|
||||
closedThreadDescription: {
|
||||
id: 'conversation-thread.closed-thread.description',
|
||||
defaultMessage: 'This thread is closed and new messages cannot be sent to it.',
|
||||
},
|
||||
replyEditorPlaceholderReply: {
|
||||
id: 'conversation-thread.reply-editor.placeholder.reply',
|
||||
defaultMessage: 'Reply to thread...',
|
||||
},
|
||||
replyEditorPlaceholderSend: {
|
||||
id: 'conversation-thread.reply-editor.placeholder.send',
|
||||
defaultMessage: 'Send a message...',
|
||||
},
|
||||
actionResubmitForReview: {
|
||||
id: 'conversation-thread.action.resubmit-for-review',
|
||||
defaultMessage: 'Resubmit for review',
|
||||
},
|
||||
actionReplyToThread: {
|
||||
id: 'conversation-thread.action.reply-to-thread',
|
||||
defaultMessage: 'Reply to thread',
|
||||
},
|
||||
actionReopenThread: {
|
||||
id: 'conversation-thread.action.reopen-thread',
|
||||
defaultMessage: 'Reopen thread',
|
||||
},
|
||||
actionReply: {
|
||||
id: 'conversation-thread.action.reply',
|
||||
defaultMessage: 'Reply',
|
||||
},
|
||||
actionSend: {
|
||||
id: 'conversation-thread.action.send',
|
||||
defaultMessage: 'Send',
|
||||
},
|
||||
actionAddPrivateNote: {
|
||||
id: 'conversation-thread.action.add-private-note',
|
||||
defaultMessage: 'Add private note',
|
||||
},
|
||||
actionResubmitForReviewWithReply: {
|
||||
id: 'conversation-thread.action.resubmit-for-review-with-reply',
|
||||
defaultMessage: 'Resubmit for review with reply',
|
||||
},
|
||||
actionCloseWithReply: {
|
||||
id: 'conversation-thread.action.close-with-reply',
|
||||
defaultMessage: 'Close with reply',
|
||||
},
|
||||
actionCloseThread: {
|
||||
id: 'conversation-thread.action.close-thread',
|
||||
defaultMessage: 'Close thread',
|
||||
},
|
||||
actionApproveWithReply: {
|
||||
id: 'conversation-thread.action.approve-with-reply',
|
||||
defaultMessage: 'Approve with reply',
|
||||
},
|
||||
actionApprove: {
|
||||
id: 'conversation-thread.action.approve',
|
||||
defaultMessage: 'Approve',
|
||||
},
|
||||
actionRejectWithReply: {
|
||||
id: 'conversation-thread.action.reject-with-reply',
|
||||
defaultMessage: 'Reject with reply',
|
||||
},
|
||||
actionReject: {
|
||||
id: 'conversation-thread.action.reject',
|
||||
defaultMessage: 'Reject',
|
||||
},
|
||||
actionWithholdWithReply: {
|
||||
id: 'conversation-thread.action.withhold-with-reply',
|
||||
defaultMessage: 'Withhold with reply',
|
||||
},
|
||||
actionWithhold: {
|
||||
id: 'conversation-thread.action.withhold',
|
||||
defaultMessage: 'Withhold',
|
||||
},
|
||||
actionSetToDraftWithReply: {
|
||||
id: 'conversation-thread.action.set-to-draft-with-reply',
|
||||
defaultMessage: 'Set to draft with reply',
|
||||
},
|
||||
actionSetToDraft: {
|
||||
id: 'conversation-thread.action.set-to-draft',
|
||||
defaultMessage: 'Set to draft',
|
||||
},
|
||||
actionSendToReviewWithReply: {
|
||||
id: 'conversation-thread.action.send-to-review-with-reply',
|
||||
defaultMessage: 'Send to review with reply',
|
||||
},
|
||||
actionSendToReview: {
|
||||
id: 'conversation-thread.action.send-to-review',
|
||||
defaultMessage: 'Send to review',
|
||||
},
|
||||
errorSendingMessage: {
|
||||
id: 'conversation-thread.error.sending-message',
|
||||
defaultMessage: 'Error sending message',
|
||||
},
|
||||
errorClosingReport: {
|
||||
id: 'conversation-thread.error.closing-report',
|
||||
defaultMessage: 'Error closing report',
|
||||
},
|
||||
errorReopeningReport: {
|
||||
id: 'conversation-thread.error.reopening-report',
|
||||
defaultMessage: 'Error reopening report',
|
||||
},
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
thread: {
|
||||
@@ -742,7 +532,7 @@ async function sendReply(status = null, privateMessage = false) {
|
||||
}
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.errorSendingMessage),
|
||||
title: 'Error sending message',
|
||||
text: err.data ? err.data.description : err,
|
||||
type: 'error',
|
||||
})
|
||||
@@ -764,7 +554,7 @@ async function closeReport(reply) {
|
||||
await updateThreadLocal()
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.errorClosingReport),
|
||||
title: 'Error closing report',
|
||||
text: err.data ? err.data.description : err,
|
||||
type: 'error',
|
||||
})
|
||||
@@ -782,7 +572,7 @@ async function reopenReport() {
|
||||
await updateThreadLocal()
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.errorReopeningReport),
|
||||
title: 'Error reopening report',
|
||||
text: err.data ? err.data.description : err,
|
||||
type: 'error',
|
||||
})
|
||||
@@ -814,8 +604,44 @@ async function resubmit() {
|
||||
}
|
||||
|
||||
const requestedStatus = computed(() => props.project.requested_status ?? 'approved')
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.markdown-editor-spacing {
|
||||
margin-bottom: var(--gap-md);
|
||||
}
|
||||
|
||||
.messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--spacing-card-md);
|
||||
}
|
||||
|
||||
.thread-id {
|
||||
margin-bottom: var(--spacing-card-md);
|
||||
font-weight: bold;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.extra-options {
|
||||
flex-basis: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-submit {
|
||||
padding: var(--spacing-card-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-card-lg);
|
||||
|
||||
.project-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
class="message px-4 py-3"
|
||||
class="message"
|
||||
:class="{
|
||||
'has-body': message.body.type === 'text' && !forceCompact,
|
||||
'no-actions': noLinks,
|
||||
private: isPrivateMessage,
|
||||
'show-private-bg': flags.showModeratorPrivateMessageHighlight,
|
||||
}"
|
||||
>
|
||||
<template v-if="members[message.author_id]">
|
||||
@@ -23,6 +22,11 @@
|
||||
/>
|
||||
</AutoLink>
|
||||
<span :class="`message__author role-${members[message.author_id].role}`">
|
||||
<LockIcon
|
||||
v-if="isPrivateMessage"
|
||||
v-tooltip="'Only visible to moderators'"
|
||||
class="private-icon"
|
||||
/>
|
||||
<AutoLink :to="noLinks ? '' : `/user/${members[message.author_id].username}`">
|
||||
{{ members[message.author_id].username }}
|
||||
</AutoLink>
|
||||
@@ -31,11 +35,6 @@
|
||||
v-else-if="members[message.author_id].role === 'admin'"
|
||||
v-tooltip="'Modrinth Team'"
|
||||
/>
|
||||
<EyeOffIcon
|
||||
v-if="isPrivateMessage"
|
||||
v-tooltip="'Only visible to moderators'"
|
||||
class="ml-1 text-orange"
|
||||
/>
|
||||
<MicrophoneIcon
|
||||
v-if="report && message.author_id === report.reporter_user?.id"
|
||||
v-tooltip="'Reporter'"
|
||||
@@ -80,12 +79,6 @@
|
||||
<span v-if="message.body.new_status === 'processing'">
|
||||
submitted the project for review.
|
||||
</span>
|
||||
<span v-else-if="message.body.old_status === 'processing'">
|
||||
reviewed the project and set its status to <Badge :type="message.body.new_status" />.
|
||||
</span>
|
||||
<span v-else-if="message.body.new_status === 'draft'">
|
||||
reverted this project back to a <Badge :type="message.body.new_status" />.
|
||||
</span>
|
||||
<span v-else>
|
||||
changed the project's status from <Badge :type="message.body.old_status" /> to
|
||||
<Badge :type="message.body.new_status" />.
|
||||
@@ -133,7 +126,7 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
EyeOffIcon,
|
||||
LockIcon,
|
||||
MicrophoneIcon,
|
||||
ModrinthIcon,
|
||||
MoreHorizontalIcon,
|
||||
@@ -185,7 +178,6 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update-thread'])
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const formattedMessage = computed(() => {
|
||||
const body = renderString(props.message.body.body)
|
||||
@@ -230,13 +222,15 @@ async function deleteMessage() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.message {
|
||||
--gap-size: var(--spacing-card-xs);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-card-sm);
|
||||
gap: var(--gap-size);
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
border-radius: var(--size-rounded-card);
|
||||
padding: var(--spacing-card-md);
|
||||
word-break: break-word;
|
||||
position: relative;
|
||||
|
||||
.avatar,
|
||||
.backed-svg {
|
||||
@@ -244,12 +238,14 @@ async function deleteMessage() {
|
||||
}
|
||||
|
||||
&.has-body {
|
||||
--gap-size: var(--spacing-card-sm);
|
||||
display: grid;
|
||||
grid-template:
|
||||
'icon author actions'
|
||||
'icon body actions'
|
||||
'date date date';
|
||||
grid-template-columns: min-content auto 1fr;
|
||||
column-gap: var(--gap-size);
|
||||
row-gap: var(--spacing-card-xs);
|
||||
|
||||
.message__icon {
|
||||
@@ -264,22 +260,13 @@ async function deleteMessage() {
|
||||
|
||||
&:not(.no-actions):hover,
|
||||
&:not(.no-actions):focus-within {
|
||||
background-color: var(--surface-2-5);
|
||||
background-color: var(--color-table-alternate-row);
|
||||
|
||||
.message__actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.private.show-private-bg::before {
|
||||
content: '';
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
background-color: var(--color-orange);
|
||||
opacity: 0.05;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.no-actions {
|
||||
padding: 0;
|
||||
|
||||
@@ -359,6 +346,10 @@ a:active + .message__author a,
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
.private-icon {
|
||||
color: var(--color-gray);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.message {
|
||||
//grid-template:
|
||||
@@ -372,7 +363,6 @@ a:active + .message__author a,
|
||||
'icon body actions'
|
||||
'date date date';
|
||||
grid-template-columns: min-content auto 1fr;
|
||||
grid-template-rows: min-content 1fr auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,7 +377,7 @@ a:active + .message__author a,
|
||||
'icon author date actions'
|
||||
'icon body body actions';
|
||||
grid-template-columns: min-content auto 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
grid-template-rows: min-content 1fr auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="flags.developerMode" class="m-4 font-bold text-heading">
|
||||
<div v-if="flags.developerMode" class="mt-4 font-bold text-heading">
|
||||
Thread ID:
|
||||
<CopyCode :text="thread.id" />
|
||||
</div>
|
||||
|
||||
<div v-if="sortedMessages.length > 0" class="flex flex-col rounded-xl">
|
||||
<div v-if="sortedMessages.length > 0" class="flex flex-col space-y-4 rounded-xl p-3 sm:p-4">
|
||||
<ThreadMessage
|
||||
v-for="message in sortedMessages"
|
||||
:key="'message-' + message.id"
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="px-4 py-2">
|
||||
<div>
|
||||
<MarkdownEditor
|
||||
v-model="replyBody"
|
||||
:placeholder="sortedMessages.length > 0 ? 'Reply to thread...' : 'Send a message...'"
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-4 flex flex-col items-stretch justify-between gap-3 px-4 pb-4 sm:flex-row sm:items-center sm:gap-2"
|
||||
class="mt-4 flex flex-col items-stretch justify-between gap-3 sm:flex-row sm:items-center sm:gap-2"
|
||||
>
|
||||
<div class="flex flex-col items-stretch gap-2 sm:flex-row sm:items-center">
|
||||
<ButtonStyled v-if="sortedMessages.length > 0" color="brand">
|
||||
|
||||
@@ -49,11 +49,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
labrinthApiCanary: false,
|
||||
dismissedExternalProjectsInfo: false,
|
||||
modpackPermissionsPage: false,
|
||||
showAllBanners: false,
|
||||
alwaysIgnoreErrorBanner: false,
|
||||
showViewProdRouteBanner: false,
|
||||
showModeratorProjectMemberUi: false,
|
||||
showModeratorPrivateMessageHighlight: true,
|
||||
} as const)
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
|
||||
@@ -1,746 +0,0 @@
|
||||
import type { Archon, Labrinth } from '@modrinth/api-client'
|
||||
import type {
|
||||
BrowseInstallContentType,
|
||||
BrowseInstallPlan,
|
||||
BrowseSearchState,
|
||||
CreationFlowContextValue,
|
||||
FilterValue,
|
||||
PendingServerContentInstall,
|
||||
PendingServerContentInstallType,
|
||||
} from '@modrinth/ui'
|
||||
import {
|
||||
addPendingServerContentInstalls,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
flushStoredServerAddonInstallQueue,
|
||||
getStoredServerAddonInstallQueue,
|
||||
getTargetInstallPreferences,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
readPendingServerContentInstalls,
|
||||
readStoredServerInstallQueue,
|
||||
removePendingServerContentInstall,
|
||||
requestInstall,
|
||||
useVIntl,
|
||||
writePendingServerContentInstallBaseline,
|
||||
writeStoredServerInstallQueue,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
|
||||
import { navigateTo, useRoute } from '#app'
|
||||
import { queryAsString } from '~/utils/router'
|
||||
|
||||
type PendingServerContentInstallInput = Omit<PendingServerContentInstall, 'createdAt'>
|
||||
type ServerInstallBrowseSearchState = Pick<
|
||||
BrowseSearchState,
|
||||
'currentFilters' | 'overriddenProvidedFilterTypes'
|
||||
>
|
||||
type ServerInstallProjectType = {
|
||||
id?: string
|
||||
}
|
||||
type ServerInstallDebug = (...args: unknown[]) => void
|
||||
|
||||
export interface ServerInstallModalHandle {
|
||||
show: () => void | Promise<void>
|
||||
hide: () => void
|
||||
ctx?: CreationFlowContextValue | null
|
||||
}
|
||||
|
||||
export interface ServerInstallSearchResult extends Labrinth.Search.v2.ResultSearchProject {
|
||||
installed?: boolean
|
||||
}
|
||||
|
||||
export interface UseServerInstallContentOptions {
|
||||
projectType: ComputedRef<ServerInstallProjectType | undefined>
|
||||
onboardingModalRef: Ref<ServerInstallModalHandle | null>
|
||||
debug?: ServerInstallDebug
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
unsupportedContentType: {
|
||||
id: 'discover.install.error.unsupported-content-type',
|
||||
defaultMessage: 'This content type cannot be installed to a server from browse.',
|
||||
},
|
||||
noServerWorld: {
|
||||
id: 'discover.install.error.no-server-world',
|
||||
defaultMessage: 'No server world is available for install.',
|
||||
},
|
||||
backToSetup: {
|
||||
id: 'discover.install.back-to-setup',
|
||||
defaultMessage: 'Back to setup',
|
||||
},
|
||||
cancelReset: {
|
||||
id: 'discover.install.cancel-reset',
|
||||
defaultMessage: 'Cancel reset',
|
||||
},
|
||||
backToServer: {
|
||||
id: 'discover.install.back-to-server',
|
||||
defaultMessage: 'Back to server',
|
||||
},
|
||||
resetModpackHeading: {
|
||||
id: 'discover.install.heading.reset-modpack',
|
||||
defaultMessage: 'Selecting modpack to install after reset',
|
||||
},
|
||||
})
|
||||
|
||||
function getQueuedInstallOwnerFallback(project: ServerInstallSearchResult) {
|
||||
if (project.organization) {
|
||||
const ownerId = project.organization_id ?? project.organization
|
||||
return {
|
||||
id: ownerId,
|
||||
name: project.organization,
|
||||
type: 'organization' as const,
|
||||
link: `/organization/${ownerId}`,
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.author) return null
|
||||
|
||||
const ownerId = project.author_id ?? project.author
|
||||
return {
|
||||
id: ownerId,
|
||||
name: project.author,
|
||||
type: 'user' as const,
|
||||
link: `/user/${ownerId}`,
|
||||
}
|
||||
}
|
||||
|
||||
function getQueuedAddonInstallPlans(
|
||||
plans: Map<string, BrowseInstallPlan<ServerInstallSearchResult>>,
|
||||
) {
|
||||
return Array.from(plans.values()).filter((plan) => plan.contentType !== 'modpack')
|
||||
}
|
||||
|
||||
export function useServerInstallContent({
|
||||
projectType,
|
||||
onboardingModalRef,
|
||||
debug = () => {},
|
||||
}: UseServerInstallContentOptions) {
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const route = useRoute()
|
||||
const { handleError } = injectNotificationManager()
|
||||
let browseSearchState: ServerInstallBrowseSearchState | null = null
|
||||
|
||||
const currentServerId = computed(() => queryAsString(route.query.sid) || null)
|
||||
const fromContext = computed(() => queryAsString(route.query.from) || null)
|
||||
const currentWorldId = computed(() => queryAsString(route.query.wid) || null)
|
||||
|
||||
const {
|
||||
data: serverData,
|
||||
isLoading: serverDataLoading,
|
||||
error: serverDataError,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['servers', 'detail', currentServerId.value] as const),
|
||||
queryFn: () => {
|
||||
debug('serverData queryFn firing for:', currentServerId.value)
|
||||
return client.archon.servers_v0.get(currentServerId.value!)
|
||||
},
|
||||
enabled: computed(() => {
|
||||
const enabled = !!currentServerId.value
|
||||
debug('serverData enabled:', enabled)
|
||||
return enabled
|
||||
}),
|
||||
})
|
||||
|
||||
watch(serverData, (val) =>
|
||||
debug('serverData changed:', val?.server_id, val?.name, val?.loader, val?.mc_version),
|
||||
)
|
||||
watch(serverDataLoading, (val) => debug('serverData loading:', val))
|
||||
watch(serverDataError, (val) => {
|
||||
if (val) debug('serverData error:', val)
|
||||
})
|
||||
|
||||
const serverIcon = computed(() => {
|
||||
if (!currentServerId.value || !import.meta.client) return null
|
||||
return localStorage.getItem(`server-icon-${currentServerId.value}`)
|
||||
})
|
||||
|
||||
const serverHideInstalled = ref(false)
|
||||
const hideSelectedServerInstalls = ref(false)
|
||||
const installingProjectIds = ref<Set<string>>(new Set())
|
||||
const optimisticallyInstalledProjectIds = ref<Set<string>>(new Set())
|
||||
const hiddenInstalledProjectIds = ref<Set<string>>(new Set())
|
||||
const hiddenInstalledProjectIdsInitialized = ref(false)
|
||||
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<ServerInstallSearchResult>>>(
|
||||
readStoredServerInstallQueue(currentServerId.value, currentWorldId.value),
|
||||
)
|
||||
const queuedServerInstallProjectIds = computed(() => new Set(queuedServerInstalls.value.keys()))
|
||||
const queuedServerInstallCount = computed(() => queuedServerInstalls.value.size)
|
||||
const selectedServerInstallProjects = computed(() =>
|
||||
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
|
||||
id: plan.projectId,
|
||||
name: plan.project.title ?? formatMessage(commonMessages.projectLabel),
|
||||
iconUrl: plan.project.icon_url ?? null,
|
||||
})),
|
||||
)
|
||||
const isInstallingQueuedServerInstalls = ref(false)
|
||||
const queuedInstallProgress = ref({ completed: 0, total: 0 })
|
||||
const serverInstallQueue = {
|
||||
get: () => queuedServerInstalls.value,
|
||||
set: (plans: Map<string, BrowseInstallPlan<ServerInstallSearchResult>>) => {
|
||||
queuedServerInstalls.value = plans
|
||||
writeStoredServerInstallQueue(currentServerId.value, currentWorldId.value, plans)
|
||||
},
|
||||
}
|
||||
|
||||
const contentQueryKey = computed(() => ['content', 'list', currentServerId.value ?? ''] as const)
|
||||
const { data: serverContentData, error: serverContentError } = useQuery({
|
||||
queryKey: contentQueryKey,
|
||||
queryFn: () =>
|
||||
client.archon.content_v1.getAddons(currentServerId.value!, currentWorldId.value!),
|
||||
enabled: computed(() => !!currentServerId.value && !!currentWorldId.value),
|
||||
})
|
||||
|
||||
function setBrowseSearchState(state: ServerInstallBrowseSearchState) {
|
||||
browseSearchState = state
|
||||
}
|
||||
|
||||
function setStoredServerInstallPlans(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
plans: Map<string, BrowseInstallPlan<ServerInstallSearchResult>>,
|
||||
) {
|
||||
if (serverId === currentServerId.value && worldId === currentWorldId.value) {
|
||||
queuedServerInstalls.value = plans
|
||||
}
|
||||
writeStoredServerInstallQueue(serverId, worldId, plans)
|
||||
}
|
||||
|
||||
async function getQueuedInstallOwner(project: ServerInstallSearchResult) {
|
||||
const fallback = getQueuedInstallOwnerFallback(project)
|
||||
|
||||
try {
|
||||
if (project.organization) {
|
||||
const organization = await client.labrinth.projects_v3.getOrganization(project.project_id)
|
||||
if (organization) {
|
||||
return {
|
||||
id: organization.id,
|
||||
name: organization.name,
|
||||
type: 'organization' as const,
|
||||
avatar_url: organization.icon_url ?? undefined,
|
||||
link: `/organization/${organization.slug}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const members = await client.labrinth.projects_v3.getMembers(project.project_id)
|
||||
const owner =
|
||||
members.find((member) => member.user.id === project.author_id)?.user ??
|
||||
members.find((member) => member.is_owner || member.role === 'Owner')?.user ??
|
||||
members[0]?.user
|
||||
|
||||
if (owner) {
|
||||
return {
|
||||
id: owner.id,
|
||||
name: owner.username,
|
||||
type: 'user' as const,
|
||||
avatar_url: owner.avatar_url,
|
||||
link: `/user/${owner.username}`,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function getQueuedInstallPlaceholder(
|
||||
plan: BrowseInstallPlan<ServerInstallSearchResult>,
|
||||
owner: PendingServerContentInstallInput['owner'],
|
||||
): PendingServerContentInstallInput {
|
||||
return {
|
||||
projectId: plan.projectId,
|
||||
versionId: plan.versionId,
|
||||
contentType: plan.contentType as PendingServerContentInstallType,
|
||||
title: plan.project.title ?? formatMessage(commonMessages.projectLabel),
|
||||
versionName: plan.versionName ?? null,
|
||||
versionNumber: plan.versionNumber ?? null,
|
||||
fileName: plan.fileName ?? null,
|
||||
owner,
|
||||
slug: plan.project.slug ?? plan.projectId,
|
||||
iconUrl: plan.project.icon_url ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
function getQueuedInstallPlaceholderFallbacks(
|
||||
plans: Map<string, BrowseInstallPlan<ServerInstallSearchResult>>,
|
||||
) {
|
||||
return getQueuedAddonInstallPlans(plans).map((plan) =>
|
||||
getQueuedInstallPlaceholder(plan, getQueuedInstallOwnerFallback(plan.project)),
|
||||
)
|
||||
}
|
||||
|
||||
async function getQueuedInstallPlaceholders(
|
||||
plans: Map<string, BrowseInstallPlan<ServerInstallSearchResult>>,
|
||||
) {
|
||||
return Promise.all(
|
||||
getQueuedAddonInstallPlans(plans).map(async (plan) =>
|
||||
getQueuedInstallPlaceholder(plan, await getQueuedInstallOwner(plan.project)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function setProjectInstalling(projectId: string, installing: boolean) {
|
||||
const next = new Set(installingProjectIds.value)
|
||||
if (installing) {
|
||||
next.add(projectId)
|
||||
} else {
|
||||
next.delete(projectId)
|
||||
}
|
||||
installingProjectIds.value = next
|
||||
}
|
||||
|
||||
function markProjectInstalled(projectId: string) {
|
||||
optimisticallyInstalledProjectIds.value = new Set([
|
||||
...optimisticallyInstalledProjectIds.value,
|
||||
projectId,
|
||||
])
|
||||
}
|
||||
|
||||
function getServerInstalledProjectIds(data = serverContentData.value) {
|
||||
return new Set(
|
||||
(data?.addons ?? [])
|
||||
.map((addon) => addon.project_id)
|
||||
.filter((projectId): projectId is string => !!projectId),
|
||||
)
|
||||
}
|
||||
|
||||
function getServerInstalledContentKeys(data = serverContentData.value) {
|
||||
return new Set((data?.addons ?? []).map((addon) => addon.project_id ?? addon.filename))
|
||||
}
|
||||
|
||||
function syncHiddenInstalledProjectIds() {
|
||||
hiddenInstalledProjectIds.value = new Set([
|
||||
...getServerInstalledProjectIds(),
|
||||
...optimisticallyInstalledProjectIds.value,
|
||||
])
|
||||
hiddenInstalledProjectIdsInitialized.value = true
|
||||
}
|
||||
|
||||
const serverFilters = computed<FilterValue[]>(() => {
|
||||
debug(
|
||||
'serverFilters recomputing, serverData:',
|
||||
!!serverData.value,
|
||||
'projectType:',
|
||||
projectType.value?.id,
|
||||
)
|
||||
const filters: FilterValue[] = []
|
||||
if (serverData.value && projectType.value?.id !== 'modpack') {
|
||||
const gameVersion = serverData.value.mc_version
|
||||
if (gameVersion) {
|
||||
filters.push({ type: 'game_version', option: gameVersion })
|
||||
}
|
||||
|
||||
const platform = serverData.value.loader?.toLowerCase()
|
||||
|
||||
const modLoaders = ['fabric', 'forge', 'quilt', 'neoforge']
|
||||
if (platform && modLoaders.includes(platform)) {
|
||||
filters.push({ type: 'mod_loader', option: platform })
|
||||
}
|
||||
|
||||
const pluginLoaders = ['paper', 'purpur']
|
||||
if (platform && pluginLoaders.includes(platform)) {
|
||||
filters.push({ type: 'plugin_loader', option: platform })
|
||||
}
|
||||
|
||||
if (projectType.value?.id === 'mod') {
|
||||
filters.push({ type: 'environment', option: 'server' })
|
||||
}
|
||||
|
||||
if (serverHideInstalled.value && hiddenInstalledProjectIds.value.size > 0) {
|
||||
for (const x of hiddenInstalledProjectIds.value) {
|
||||
filters.push({
|
||||
type: 'project_id',
|
||||
option: `project_id:${x}`,
|
||||
negative: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (hideSelectedServerInstalls.value && queuedServerInstallProjectIds.value.size > 0) {
|
||||
for (const id of queuedServerInstallProjectIds.value) {
|
||||
filters.push({
|
||||
type: 'project_id',
|
||||
option: `project_id:${id}`,
|
||||
negative: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentServerId.value && projectType.value?.id === 'modpack') {
|
||||
filters.push(
|
||||
{ type: 'environment', option: 'client' },
|
||||
{ type: 'environment', option: 'server' },
|
||||
)
|
||||
}
|
||||
debug('serverFilters result:', filters)
|
||||
return filters
|
||||
})
|
||||
|
||||
function getCurrentServerInstallType(): BrowseInstallContentType {
|
||||
const type = projectType.value?.id
|
||||
if (type === 'modpack' || type === 'mod' || type === 'plugin' || type === 'datapack') {
|
||||
return type
|
||||
}
|
||||
throw new Error(formatMessage(messages.unsupportedContentType))
|
||||
}
|
||||
|
||||
function getServerInstallTargetPreferences(contentType: BrowseInstallContentType) {
|
||||
return getTargetInstallPreferences(
|
||||
{
|
||||
gameVersion: serverData.value?.mc_version,
|
||||
loader: serverData.value?.loader,
|
||||
},
|
||||
contentType,
|
||||
)
|
||||
}
|
||||
|
||||
function getInstallProjectVersions(projectId: string) {
|
||||
return client.labrinth.versions_v2.getProjectVersions(projectId, {
|
||||
include_changelog: false,
|
||||
})
|
||||
}
|
||||
|
||||
function clearQueuedServerInstalls() {
|
||||
serverInstallQueue.set(new Map())
|
||||
}
|
||||
|
||||
function removeQueuedServerInstall(projectId: string) {
|
||||
const nextPlans = new Map(queuedServerInstalls.value)
|
||||
nextPlans.delete(projectId)
|
||||
serverInstallQueue.set(nextPlans)
|
||||
}
|
||||
|
||||
async function flushQueuedServerInstalls(
|
||||
serverId: string | null = currentServerId.value,
|
||||
worldId: string | null = currentWorldId.value,
|
||||
) {
|
||||
if (isInstallingQueuedServerInstalls.value) return false
|
||||
|
||||
if (!serverId || !worldId) {
|
||||
handleError(new Error(formatMessage(messages.noServerWorld)))
|
||||
return false
|
||||
}
|
||||
|
||||
const queuedPlans = getStoredServerAddonInstallQueue<ServerInstallSearchResult>(
|
||||
serverId,
|
||||
worldId,
|
||||
)
|
||||
if (queuedPlans.size === 0) return true
|
||||
|
||||
isInstallingQueuedServerInstalls.value = true
|
||||
queuedInstallProgress.value = {
|
||||
completed: 0,
|
||||
total: queuedPlans.size,
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await flushStoredServerAddonInstallQueue({
|
||||
serverId,
|
||||
worldId,
|
||||
install: (plans) =>
|
||||
client.archon.content_v1.addAddons(
|
||||
serverId,
|
||||
worldId,
|
||||
plans.map((plan) => ({
|
||||
project_id: plan.projectId,
|
||||
version_id: plan.versionId,
|
||||
})),
|
||||
),
|
||||
onQueueChange: (plans) => setStoredServerInstallPlans(serverId, worldId, plans),
|
||||
})
|
||||
|
||||
if (!result.ok) {
|
||||
for (const plan of result.attemptedPlans) {
|
||||
removePendingServerContentInstall(serverId, worldId, plan.projectId)
|
||||
}
|
||||
handleError(result.error as Error)
|
||||
return false
|
||||
}
|
||||
|
||||
for (const plan of result.flushedPlans) {
|
||||
markProjectInstalled(plan.projectId)
|
||||
}
|
||||
queuedInstallProgress.value = {
|
||||
completed: result.flushedPlans.length,
|
||||
total: result.flushedPlans.length,
|
||||
}
|
||||
if (result.flushedPlans.length > 0) {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['content', 'list', 'v1', serverId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['content', 'list'] }),
|
||||
])
|
||||
}
|
||||
|
||||
return true
|
||||
} finally {
|
||||
isInstallingQueuedServerInstalls.value = false
|
||||
queuedInstallProgress.value = { completed: 0, total: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
async function discardQueuedServerInstallsAndBack() {
|
||||
clearQueuedServerInstalls()
|
||||
await navigateTo(serverBackUrl.value)
|
||||
}
|
||||
|
||||
async function installQueuedServerInstallsAndBack() {
|
||||
const sid = currentServerId.value
|
||||
const wid = currentWorldId.value
|
||||
const backUrl = serverBackUrl.value
|
||||
const plans = new Map(queuedServerInstalls.value)
|
||||
|
||||
if (sid && wid) {
|
||||
writeStoredServerInstallQueue(sid, wid, plans)
|
||||
writePendingServerContentInstallBaseline(sid, wid, [
|
||||
...getServerInstalledContentKeys(),
|
||||
...optimisticallyInstalledProjectIds.value,
|
||||
])
|
||||
addPendingServerContentInstalls(sid, wid, getQueuedInstallPlaceholderFallbacks(plans))
|
||||
void getQueuedInstallPlaceholders(plans)
|
||||
.then((items) => {
|
||||
const pendingProjectIds = new Set(
|
||||
readPendingServerContentInstalls(sid, wid).map((item) => item.projectId),
|
||||
)
|
||||
addPendingServerContentInstalls(
|
||||
sid,
|
||||
wid,
|
||||
items.filter((item) => pendingProjectIds.has(item.projectId)),
|
||||
)
|
||||
})
|
||||
.catch((err) => handleError(err as Error))
|
||||
}
|
||||
await navigateTo(backUrl)
|
||||
void flushQueuedServerInstalls(sid, wid)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function serverInstall(project: ServerInstallSearchResult) {
|
||||
if (!serverData.value || !currentServerId.value || !currentWorldId.value) {
|
||||
handleError(new Error('No server to install to.'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!browseSearchState) {
|
||||
handleError(new Error('Search state is not ready.'))
|
||||
return
|
||||
}
|
||||
|
||||
const contentType = getCurrentServerInstallType()
|
||||
const isModpack = contentType === 'modpack'
|
||||
|
||||
try {
|
||||
if (!isModpack && queuedServerInstallProjectIds.value.has(project.project_id)) {
|
||||
removeQueuedServerInstall(project.project_id)
|
||||
return
|
||||
}
|
||||
|
||||
if (isModpack || !queuedServerInstallProjectIds.value.has(project.project_id)) {
|
||||
setProjectInstalling(project.project_id, true)
|
||||
}
|
||||
|
||||
await requestInstall({
|
||||
project,
|
||||
contentType,
|
||||
mode: isModpack ? 'immediate' : 'queue',
|
||||
selectedFilters: isModpack ? [] : browseSearchState.currentFilters.value,
|
||||
providedFilters: isModpack ? [] : serverFilters.value,
|
||||
overriddenProvidedFilterTypes: isModpack
|
||||
? []
|
||||
: browseSearchState.overriddenProvidedFilterTypes.value,
|
||||
targetPreferences: getServerInstallTargetPreferences(contentType),
|
||||
getProjectVersions: getInstallProjectVersions,
|
||||
queue: serverInstallQueue,
|
||||
install: async (plan) => {
|
||||
const modalInstance = onboardingModalRef.value
|
||||
if (!modalInstance) {
|
||||
setProjectInstalling(plan.projectId, false)
|
||||
return
|
||||
}
|
||||
|
||||
onboardingInstallingProject.value = plan.project
|
||||
modalInstance.show()
|
||||
await nextTick()
|
||||
const ctx = modalInstance.ctx
|
||||
if (!ctx) return
|
||||
|
||||
ctx.setupType.value = 'modpack'
|
||||
ctx.modpackSelection.value = {
|
||||
projectId: plan.projectId,
|
||||
versionId: plan.versionId,
|
||||
name: plan.project.title,
|
||||
iconUrl: plan.project.icon_url ?? undefined,
|
||||
}
|
||||
ctx.modal.value?.setStage('final-config')
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
if (isModpack) {
|
||||
setProjectInstalling(project.project_id, false)
|
||||
}
|
||||
handleError(e instanceof Error ? e : new Error(`Error installing content ${e}`))
|
||||
} finally {
|
||||
if (!isModpack) {
|
||||
setProjectInstalling(project.project_id, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onboardingInstallingProject = ref<ServerInstallSearchResult | null>(null)
|
||||
|
||||
function onOnboardingHide() {
|
||||
if (onboardingInstallingProject.value) {
|
||||
setProjectInstalling(onboardingInstallingProject.value.project_id, false)
|
||||
onboardingInstallingProject.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function onOnboardingBack() {
|
||||
onboardingModalRef.value?.hide()
|
||||
}
|
||||
|
||||
async function onModpackFlowCreate(config: CreationFlowContextValue) {
|
||||
if (!currentServerId.value || !currentWorldId.value || !config.modpackSelection.value) return
|
||||
|
||||
try {
|
||||
await client.archon.content_v1.installContent(currentServerId.value, currentWorldId.value, {
|
||||
content_variant: 'modpack',
|
||||
spec: {
|
||||
platform: 'modrinth',
|
||||
project_id: config.modpackSelection.value.projectId,
|
||||
version_id: config.modpackSelection.value.versionId,
|
||||
},
|
||||
soft_override: false,
|
||||
properties: config.buildProperties(),
|
||||
} satisfies Archon.Content.v1.InstallWorldContent)
|
||||
|
||||
if (fromContext.value === 'onboarding') {
|
||||
await client.archon.servers_v1.endIntro(currentServerId.value)
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'detail', currentServerId.value] })
|
||||
navigateTo(`/hosting/manage/${currentServerId.value}/content`)
|
||||
} else {
|
||||
navigateTo(`/hosting/manage/${currentServerId.value}?openSettings=installation`)
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(new Error(`Error installing modpack: ${e}`))
|
||||
config.loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const serverBackUrl = computed(() => {
|
||||
if (!serverData.value) return ''
|
||||
const id = serverData.value.server_id
|
||||
if (fromContext.value === 'onboarding') return `/hosting/manage/${id}?resumeModal=setup-type`
|
||||
if (fromContext.value === 'reset-server')
|
||||
return `/hosting/manage/${id}?openSettings=installation`
|
||||
return `/hosting/manage/${id}/content`
|
||||
})
|
||||
|
||||
const serverBackLabel = computed(() => {
|
||||
if (fromContext.value === 'onboarding') return formatMessage(messages.backToSetup)
|
||||
if (fromContext.value === 'reset-server') return formatMessage(messages.cancelReset)
|
||||
return formatMessage(messages.backToServer)
|
||||
})
|
||||
|
||||
const serverBrowseHeading = computed(() =>
|
||||
fromContext.value === 'reset-server'
|
||||
? formatMessage(messages.resetModpackHeading)
|
||||
: formatMessage(commonMessages.installingContentLabel),
|
||||
)
|
||||
|
||||
const installContext = computed(() => {
|
||||
if (!serverData.value) return null
|
||||
return {
|
||||
name: serverData.value.name,
|
||||
loader: serverData.value.loader ?? '',
|
||||
gameVersion: serverData.value.mc_version ?? '',
|
||||
serverId: currentServerId.value,
|
||||
upstream: serverData.value.upstream,
|
||||
iconSrc: serverIcon.value,
|
||||
isMedal: serverData.value.is_medal,
|
||||
backUrl: serverBackUrl.value,
|
||||
backLabel: serverBackLabel.value,
|
||||
heading: serverBrowseHeading.value,
|
||||
queuedCount: queuedServerInstallCount.value,
|
||||
selectedProjects: selectedServerInstallProjects.value,
|
||||
isInstallingSelected: isInstallingQueuedServerInstalls.value,
|
||||
installProgress: queuedInstallProgress.value,
|
||||
clearQueued: clearQueuedServerInstalls,
|
||||
clearSelected: clearQueuedServerInstalls,
|
||||
onBack: flushQueuedServerInstalls,
|
||||
discardSelectedAndBack: discardQueuedServerInstallsAndBack,
|
||||
installSelected: installQueuedServerInstallsAndBack,
|
||||
}
|
||||
})
|
||||
|
||||
watch(serverContentError, (error) => {
|
||||
if (error) {
|
||||
console.error('Failed to load server content:', error)
|
||||
handleError(error)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
serverContentData,
|
||||
(data) => {
|
||||
if (!data) return
|
||||
if (!hiddenInstalledProjectIdsInitialized.value) {
|
||||
syncHiddenInstalledProjectIds()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
if (route.query.shi && projectType.value?.id !== 'modpack') {
|
||||
serverHideInstalled.value = route.query.shi === 'true'
|
||||
}
|
||||
|
||||
watch(serverHideInstalled, (hideInstalled) => {
|
||||
if (hideInstalled) {
|
||||
syncHiddenInstalledProjectIds()
|
||||
}
|
||||
})
|
||||
|
||||
watch([currentServerId, currentWorldId], ([serverId, worldId], [prevServerId, prevWorldId]) => {
|
||||
if (serverId !== prevServerId || worldId !== prevWorldId) {
|
||||
queuedServerInstalls.value = readStoredServerInstallQueue(serverId, worldId)
|
||||
}
|
||||
})
|
||||
|
||||
watch(queuedServerInstallCount, (count) => {
|
||||
if (count === 0) {
|
||||
hideSelectedServerInstalls.value = false
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
currentServerId,
|
||||
fromContext,
|
||||
currentWorldId,
|
||||
serverData,
|
||||
serverContentData,
|
||||
serverFilters,
|
||||
serverHideInstalled,
|
||||
hideSelectedServerInstalls,
|
||||
installingProjectIds,
|
||||
optimisticallyInstalledProjectIds,
|
||||
queuedServerInstallProjectIds,
|
||||
queuedServerInstallCount,
|
||||
isInstallingQueuedServerInstalls,
|
||||
installContext,
|
||||
setBrowseSearchState,
|
||||
syncHiddenInstalledProjectIds,
|
||||
serverInstall,
|
||||
onOnboardingHide,
|
||||
onOnboardingBack,
|
||||
onModpackFlowCreate,
|
||||
}
|
||||
}
|
||||
@@ -28,44 +28,26 @@ export function createLoaderParsers(
|
||||
if (metadata.dependencies) {
|
||||
const neoForgeDependency = Object.values(metadata.dependencies)
|
||||
.flat()
|
||||
.filter((dependency: any) => dependency.modId === 'neoforge')
|
||||
.map((dependency: any) => dependency.versionRange)
|
||||
.find((range) => range)
|
||||
const minecraftDependency = Object.values(metadata.dependencies)
|
||||
.flat()
|
||||
.filter((dependency: any) => dependency.modId === 'minecraft')
|
||||
.map((dependency: any) => dependency.versionRange)
|
||||
.find((range) => range)
|
||||
.find((dependency: any) => dependency.modId === 'neoforge')
|
||||
|
||||
if (minecraftDependency) {
|
||||
newGameVersions = getGameVersionsMatchingMavenRange(
|
||||
minecraftDependency,
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
} else if (neoForgeDependency) {
|
||||
// https://docs.neoforged.net/docs/gettingstarted/versioning/#neoforge
|
||||
// NeoForge's versioning changed after Mojang changed from 1.<major>.<minor> to <year>.<drop>.<patch>, so both cases need to be handled
|
||||
const neoPre26Regex = /^(?<mc_major>\d+)(?:\.(?<mc_minor>\d+))?(?:\.(?<neo>\d+)?)?$/
|
||||
const neoPost26Regex =
|
||||
/^(?<mc_year>\d+)(?:\.(?<mc_drop>\d+))?(?:\.(?<mc_patch>\d+)?)?(?:\.(\d+))?$/
|
||||
|
||||
newGameVersions = getGameVersionsMatchingMavenRange(
|
||||
neoForgeDependency.replace('-beta', ''),
|
||||
simplifiedGameVersions,
|
||||
(version) => {
|
||||
const matchPre26 = version.match(neoPre26Regex)
|
||||
if (matchPre26 && matchPre26.groups) {
|
||||
const { mc_major, mc_minor } = matchPre26.groups
|
||||
return mc_minor ? `1.${mc_major}.${mc_minor}` : `1.${mc_major}`
|
||||
}
|
||||
const matchPost26 = version.match(neoPost26Regex)
|
||||
if (matchPost26 && matchPost26.groups) {
|
||||
const { mc_year, mc_drop, mc_patch } = matchPost26.groups
|
||||
return `${mc_year}${mc_drop ? `.${mc_drop}` : ''}${mc_patch ? `.${mc_patch}` : ''}`
|
||||
}
|
||||
return version
|
||||
},
|
||||
)
|
||||
if (neoForgeDependency) {
|
||||
try {
|
||||
// https://docs.neoforged.net/docs/gettingstarted/versioning/#neoforge
|
||||
const mcVersionRange = (neoForgeDependency as any).versionRange
|
||||
.replace('-beta', '')
|
||||
.replace(
|
||||
/(\d+)(?:\.(\d+))?(?:\.(\d+)?)?/g,
|
||||
(_match: string, major: string, minor: string) => {
|
||||
return `1.${major}${minor ? '.' + minor : ''}`
|
||||
},
|
||||
)
|
||||
newGameVersions = getGameVersionsMatchingMavenRange(
|
||||
mcVersionRange,
|
||||
simplifiedGameVersions,
|
||||
)
|
||||
} catch {
|
||||
// Ignore parsing errors, just leave game_versions empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ export function getGameVersionsMatchingSemverRange(
|
||||
export function getGameVersionsMatchingMavenRange(
|
||||
range: string | undefined,
|
||||
gameVersions: string[],
|
||||
processor: (version: string) => string | null = (v) => v,
|
||||
): string[] {
|
||||
if (!range) {
|
||||
return []
|
||||
@@ -64,42 +63,24 @@ export function getGameVersionsMatchingMavenRange(
|
||||
|
||||
for (const range of ranges) {
|
||||
let result
|
||||
let version1
|
||||
let version2
|
||||
if ((result = range.match(LESS_THAN_EQUAL)) && (version1 = processor(result[1]))) {
|
||||
semverRanges.push(`<=${version1}`)
|
||||
} else if ((result = range.match(LESS_THAN)) && (version1 = processor(result[1]))) {
|
||||
semverRanges.push(`<${version1}`)
|
||||
} else if ((result = range.match(EQUAL)) && (version1 = processor(result[1]))) {
|
||||
semverRanges.push(`${version1}`)
|
||||
} else if ((result = range.match(GREATER_THAN_EQUAL)) && (version1 = processor(result[1]))) {
|
||||
semverRanges.push(`>=${version1}`)
|
||||
} else if ((result = range.match(GREATER_THAN)) && (version1 = processor(result[1]))) {
|
||||
semverRanges.push(`>${version1}`)
|
||||
} else if (
|
||||
(result = range.match(BETWEEN)) &&
|
||||
(version1 = processor(result[1])) &&
|
||||
(version2 = processor(result[2]))
|
||||
) {
|
||||
semverRanges.push(`>${version1} <${version2}`)
|
||||
} else if (
|
||||
(result = range.match(BETWEEN_EQUAL)) &&
|
||||
(version1 = processor(result[1])) &&
|
||||
(version2 = processor(result[2]))
|
||||
) {
|
||||
semverRanges.push(`>=${version1} <=${version2}`)
|
||||
} else if (
|
||||
(result = range.match(BETWEEN_LESS_THAN_EQUAL)) &&
|
||||
(version1 = processor(result[1])) &&
|
||||
(version2 = processor(result[2]))
|
||||
) {
|
||||
semverRanges.push(`>${version1} <=${version2}`)
|
||||
} else if (
|
||||
(result = range.match(BETWEEN_GREATER_THAN_EQUAL)) &&
|
||||
(version1 = processor(result[1])) &&
|
||||
(version2 = processor(result[2]))
|
||||
) {
|
||||
semverRanges.push(`>=${version1} <${version2}`)
|
||||
if ((result = range.match(LESS_THAN_EQUAL))) {
|
||||
semverRanges.push(`<=${result[1]}`)
|
||||
} else if ((result = range.match(LESS_THAN))) {
|
||||
semverRanges.push(`<${result[1]}`)
|
||||
} else if ((result = range.match(EQUAL))) {
|
||||
semverRanges.push(`${result[1]}`)
|
||||
} else if ((result = range.match(GREATER_THAN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]}`)
|
||||
} else if ((result = range.match(GREATER_THAN))) {
|
||||
semverRanges.push(`>${result[1]}`)
|
||||
} else if ((result = range.match(BETWEEN))) {
|
||||
semverRanges.push(`>${result[1]} <${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]} <=${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_LESS_THAN_EQUAL))) {
|
||||
semverRanges.push(`>${result[1]} <=${result[2]}`)
|
||||
} else if ((result = range.match(BETWEEN_GREATER_THAN_EQUAL))) {
|
||||
semverRanges.push(`>=${result[1]} <${result[2]}`)
|
||||
}
|
||||
}
|
||||
return getGameVersionsMatchingSemverRange(semverRanges, gameVersions)
|
||||
|
||||
@@ -34,39 +34,25 @@
|
||||
'modrinth-parent__no-modal-blurs': !cosmetics.advancedRendering,
|
||||
}"
|
||||
>
|
||||
<RussiaBanner v-if="flags.showAllBanners || isRussia" />
|
||||
<TaxIdMismatchBanner v-if="flags.showAllBanners || showTinMismatchBanner" />
|
||||
<TaxComplianceBanner v-if="flags.showAllBanners || showTaxComplianceBanner" />
|
||||
<RussiaBanner v-if="isRussia" />
|
||||
<TaxIdMismatchBanner v-if="showTinMismatchBanner" />
|
||||
<TaxComplianceBanner v-if="showTaxComplianceBanner" />
|
||||
<VerifyEmailBanner
|
||||
v-if="
|
||||
flags.showAllBanners ||
|
||||
(auth.user && !auth.user.email_verified && route.path !== '/auth/verify-email')
|
||||
"
|
||||
v-if="auth.user && !auth.user.email_verified && route.path !== '/auth/verify-email'"
|
||||
:has-email="!!auth?.user?.email"
|
||||
/>
|
||||
<SubscriptionPaymentFailedBanner
|
||||
v-if="
|
||||
flags.showAllBanners ||
|
||||
(user.subscriptions.some((x) => x.status === 'payment-failed') &&
|
||||
route.path !== '/settings/billing')
|
||||
"
|
||||
/>
|
||||
<PreviewBanner
|
||||
v-if="
|
||||
flags.showAllBanners || (config.public.buildEnv === 'production' && config.public.preview)
|
||||
"
|
||||
/>
|
||||
<StagingBanner
|
||||
v-if="
|
||||
flags.showAllBanners ||
|
||||
config.public.apiBaseUrl.startsWith('https://staging-api.modrinth.com')
|
||||
user.subscriptions.some((x) => x.status === 'payment-failed') &&
|
||||
route.path !== '/settings/billing'
|
||||
"
|
||||
/>
|
||||
<PreviewBanner v-if="config.public.buildEnv === 'production' && config.public.preview" />
|
||||
<StagingBanner v-if="config.public.apiBaseUrl.startsWith('https://staging-api.modrinth.com')" />
|
||||
<GeneratedStateErrorsBanner
|
||||
:errors="generatedStateErrors"
|
||||
:api-url="config.public.apiBaseUrl"
|
||||
/>
|
||||
<ViewOnModrinthBanner />
|
||||
<header
|
||||
class="desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
|
||||
>
|
||||
@@ -761,13 +747,9 @@ import {
|
||||
commonMessages,
|
||||
commonProjectTypeCategoryMessages,
|
||||
commonSettingsMessages,
|
||||
createHostingIntercomIdentityKey,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
injectPageContext,
|
||||
OverflowMenu,
|
||||
providePageContext,
|
||||
useHostingIntercom,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import TeleportOverflowMenu from '@modrinth/ui/src/components/base/TeleportOverflowMenu.vue'
|
||||
@@ -785,7 +767,6 @@ import SubscriptionPaymentFailedBanner from '~/components/ui/banner/Subscription
|
||||
import TaxComplianceBanner from '~/components/ui/banner/TaxComplianceBanner.vue'
|
||||
import TaxIdMismatchBanner from '~/components/ui/banner/TaxIdMismatchBanner.vue'
|
||||
import VerifyEmailBanner from '~/components/ui/banner/VerifyEmailBanner.vue'
|
||||
import ViewOnModrinthBanner from '~/components/ui/banner/ViewOnModrinthBanner.vue'
|
||||
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
||||
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
||||
import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
|
||||
@@ -812,25 +793,6 @@ const router = useNativeRouter()
|
||||
const signInRouteObj = computed(() => getSignInRouteObj(route))
|
||||
const link = config.public.siteUrl + route.path.replace(/\/+$/, '')
|
||||
const client = injectModrinthClient()
|
||||
const pageContext = injectPageContext()
|
||||
const hostingIntercomActive = computed(() => route.path.startsWith('/hosting') && !!auth.value.user)
|
||||
const hostingIntercomServerId = computed(() => {
|
||||
const rawId = route.params.id
|
||||
return Array.isArray(rawId) ? rawId[0] : rawId
|
||||
})
|
||||
const hostingIntercom = useHostingIntercom({
|
||||
enabled: hostingIntercomActive,
|
||||
appId: computed(() => config.public.intercomAppId),
|
||||
fetchToken: fetchIntercomToken,
|
||||
identityKey: computed(() =>
|
||||
createHostingIntercomIdentityKey(auth.value.user, hostingIntercomServerId.value),
|
||||
),
|
||||
})
|
||||
|
||||
providePageContext({
|
||||
...pageContext,
|
||||
intercomBubble: hostingIntercom.intercomBubble,
|
||||
})
|
||||
|
||||
const { data: payoutBalance } = useQuery({
|
||||
queryKey: ['payout', 'balance'],
|
||||
@@ -859,12 +821,6 @@ const showTinMismatchBanner = computed(() => {
|
||||
|
||||
const basePopoutId = useId()
|
||||
|
||||
async function fetchIntercomToken() {
|
||||
return $fetch('/api/intercom/messenger-jwt', {
|
||||
query: hostingIntercomServerId.value ? { server_id: hostingIntercomServerId.value } : {},
|
||||
})
|
||||
}
|
||||
|
||||
const navMenuMessages = defineMessages({
|
||||
home: {
|
||||
id: 'layout.nav.home',
|
||||
@@ -1177,7 +1133,7 @@ const isDiscovering = computed(
|
||||
)
|
||||
|
||||
const isDiscoveringSubpage = computed(
|
||||
() => route.name && route.name.startsWith('type-project') && !route.query.sid,
|
||||
() => route.name && route.name.startsWith('type-id') && !route.query.sid,
|
||||
)
|
||||
|
||||
const isRussia = computed(() => country.value === 'ru')
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
"app-marketing.download.options-title": {
|
||||
"message": "Stahovácí možnosti"
|
||||
},
|
||||
"app-marketing.download.terms": {
|
||||
"message": "Stažením aplikace Modrinth vyjadřujete souhlas s našimi <terms-link>Podmínkami</terms-link> a <privacy-link>Zásadami ochrany osobních údajů</privacy-link>."
|
||||
},
|
||||
"app-marketing.download.title": {
|
||||
"message": "Stáhnout Modrinth App (Beta)"
|
||||
},
|
||||
@@ -509,12 +506,6 @@
|
||||
"create.project.title": {
|
||||
"message": "Vytvořit projekt"
|
||||
},
|
||||
"create.project.type-project": {
|
||||
"message": "Projekt"
|
||||
},
|
||||
"create.project.type-server": {
|
||||
"message": "Server"
|
||||
},
|
||||
"create.project.url-label": {
|
||||
"message": "URL"
|
||||
},
|
||||
@@ -953,6 +944,9 @@
|
||||
"moderation.page.reports": {
|
||||
"message": "Hlášení"
|
||||
},
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Technické hodnocení"
|
||||
},
|
||||
"muralpay.country.at": {
|
||||
"message": "Rakousko"
|
||||
},
|
||||
|
||||
@@ -407,51 +407,6 @@
|
||||
"collection.title": {
|
||||
"message": "{name} - Kollektion"
|
||||
},
|
||||
"conversation-thread.action.add-private-note": {
|
||||
"message": "Private Notiz hinzufügen"
|
||||
},
|
||||
"conversation-thread.action.approve": {
|
||||
"message": "Genehmigen"
|
||||
},
|
||||
"conversation-thread.action.approve-with-reply": {
|
||||
"message": "Mit Antwort genehmigen"
|
||||
},
|
||||
"conversation-thread.action.close-thread": {
|
||||
"message": ""
|
||||
},
|
||||
"conversation-thread.action.reject": {
|
||||
"message": "Ablehnen"
|
||||
},
|
||||
"conversation-thread.action.reject-with-reply": {
|
||||
"message": "Mit Antwort ablehnen"
|
||||
},
|
||||
"conversation-thread.action.reply": {
|
||||
"message": "Antworten"
|
||||
},
|
||||
"conversation-thread.action.send": {
|
||||
"message": "Senden"
|
||||
},
|
||||
"conversation-thread.action.withhold": {
|
||||
"message": "Zurückhalten"
|
||||
},
|
||||
"conversation-thread.action.withhold-with-reply": {
|
||||
"message": "Mit Antwort zurückhalten"
|
||||
},
|
||||
"conversation-thread.reply-editor.placeholder.send": {
|
||||
"message": "Sende eine Nachricht..."
|
||||
},
|
||||
"conversation-thread.reply-modal.confirmation.label": {
|
||||
"message": "Mir ist bewusst, dass dieser Thread nicht aktiv von den Moderatoren überwacht wird."
|
||||
},
|
||||
"conversation-thread.resubmit-modal.description": {
|
||||
"message": "Du schickst <project-title>{projectTitle}</project-title> zur Wiederprüfung."
|
||||
},
|
||||
"conversation-thread.resubmit-modal.header.resubmitting": {
|
||||
"message": "Erneut zur Überprüfung einreichen"
|
||||
},
|
||||
"conversation-thread.resubmit-modal.header.submitting": {
|
||||
"message": "Erneut zur Überprüfung einreichen"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.admonition": {
|
||||
"message": "Ergänzende Dateien dienen zur unterstützung von Ressourcen wie Quellcode, nicht für alternative Versionen oder Varianten."
|
||||
},
|
||||
@@ -920,8 +875,23 @@
|
||||
"dashboard.head-title": {
|
||||
"message": "Dashboard"
|
||||
},
|
||||
"dashboard.notifications.button.mark-all-as-read": {
|
||||
"message": "Alle als gelesen markieren"
|
||||
},
|
||||
"dashboard.notifications.button.view-history": {
|
||||
"message": "Verlauf ansehen"
|
||||
},
|
||||
"dashboard.notifications.empty.no-unread": {
|
||||
"message": "Du hast keine ungelesenen Benachrichtigungen."
|
||||
"message": "Du hast keine ungelesenen Nachrichten."
|
||||
},
|
||||
"dashboard.notifications.error.loading": {
|
||||
"message": "Fehler beim laden von Benachrichtigungen:"
|
||||
},
|
||||
"dashboard.notifications.history.label": {
|
||||
"message": "Verlauf"
|
||||
},
|
||||
"dashboard.notifications.history.title": {
|
||||
"message": "Benachrichtigungsverlauf"
|
||||
},
|
||||
"dashboard.notifications.link.see-all": {
|
||||
"message": "Alle ansehen"
|
||||
@@ -932,6 +902,9 @@
|
||||
"dashboard.notifications.link.view-more": {
|
||||
"message": "Siehe {extraNotifs} weitere {extraNotifs, plural, one {Benachrichtigung} other {Benachrichtigungen}}"
|
||||
},
|
||||
"dashboard.notifications.loading": {
|
||||
"message": "Lade Benachrichtigungen..."
|
||||
},
|
||||
"dashboard.organizations.button.create": {
|
||||
"message": "Organisation erstellen"
|
||||
},
|
||||
@@ -947,27 +920,6 @@
|
||||
"dashboard.organizations.title": {
|
||||
"message": "Organisationen"
|
||||
},
|
||||
"dashboard.overview.notifications.button.mark-all-as-read": {
|
||||
"message": "Alle als gelesen markieren"
|
||||
},
|
||||
"dashboard.overview.notifications.button.view-history": {
|
||||
"message": "Verlauf ansehen"
|
||||
},
|
||||
"dashboard.overview.notifications.empty.no-unread": {
|
||||
"message": "Du hast keine ungelesenen Benachrichtigungen."
|
||||
},
|
||||
"dashboard.overview.notifications.error.loading": {
|
||||
"message": "Fehler beim Laden von Benachrichtigungen:"
|
||||
},
|
||||
"dashboard.overview.notifications.history.label": {
|
||||
"message": "Verlauf"
|
||||
},
|
||||
"dashboard.overview.notifications.history.title": {
|
||||
"message": "Benachrichtigungsverlauf"
|
||||
},
|
||||
"dashboard.overview.notifications.loading": {
|
||||
"message": "Benachrichtigungen werden geladen..."
|
||||
},
|
||||
"dashboard.projects.bulk-edit-hint": {
|
||||
"message": "Du kannst mehrere Projekte gleichzeitig bearbeiten, indem du sie unden auswählst."
|
||||
},
|
||||
@@ -1277,33 +1229,6 @@
|
||||
"dashboard.withdraw.error.tax-form.title": {
|
||||
"message": "Bitte Steuerformular ausfüllen"
|
||||
},
|
||||
"discover.install.back-to-server": {
|
||||
"message": "Zurück zum Server"
|
||||
},
|
||||
"discover.install.back-to-setup": {
|
||||
"message": "Zurück zur Einrichtung"
|
||||
},
|
||||
"discover.install.cancel-reset": {
|
||||
"message": "Reset abbrechen"
|
||||
},
|
||||
"discover.install.error.no-server-world": {
|
||||
"message": "Keine Serverwelt ist zur installation verfügbar."
|
||||
},
|
||||
"discover.install.error.unsupported-content-type": {
|
||||
"message": "Diese Art von Inhalt kann nicht vom durchstöbern zu einem Server hinzugefügt werden."
|
||||
},
|
||||
"discover.install.heading.reset-modpack": {
|
||||
"message": "Modpack zum installieren nach dem Reset auswählen"
|
||||
},
|
||||
"discover.seo.description": {
|
||||
"message": "Suche und durchstöbere tausende von Minecraft {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}} auf Modrinth sofortigen, genauen Suchergebnissen. Unsere Filter helfen dir beim schnellen finden der besten Minecraft {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}}."
|
||||
},
|
||||
"discover.seo.title": {
|
||||
"message": "Durchsuche {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}}"
|
||||
},
|
||||
"discover.seo.title-with-query": {
|
||||
"message": "Durchsuche {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}} | {query}"
|
||||
},
|
||||
"error.collection.404.list_item.1": {
|
||||
"message": "Du hast eventuell die Kollektions-URL falsch geschrieben."
|
||||
},
|
||||
@@ -1319,12 +1244,6 @@
|
||||
"error.collection.404.title": {
|
||||
"message": "Kollektion nicht gefunden"
|
||||
},
|
||||
"error.generic.401.signed-in-as": {
|
||||
"message": "Angemeldet als:"
|
||||
},
|
||||
"error.generic.401.title": {
|
||||
"message": "Du hast keinen Zugriff auf diese Seite"
|
||||
},
|
||||
"error.generic.404.subtitle": {
|
||||
"message": "Die Seite nach welcher du suchst, scheint nicht zu existieren."
|
||||
},
|
||||
@@ -1751,9 +1670,6 @@
|
||||
"layout.action.create-new": {
|
||||
"message": "Erstellen..."
|
||||
},
|
||||
"layout.action.external-projects": {
|
||||
"message": "Externe Projekte"
|
||||
},
|
||||
"layout.action.file-lookup": {
|
||||
"message": "Datei ansehen"
|
||||
},
|
||||
@@ -1802,20 +1718,14 @@
|
||||
"layout.banner.add-email.description": {
|
||||
"message": "Aus Sicherheitsgründen verlangt Modrinth, dass du eine E-Mail Adresse für dein Konto registrierst."
|
||||
},
|
||||
"layout.banner.build-fail.always-ignore": {
|
||||
"message": "Immer ignorieren"
|
||||
},
|
||||
"layout.banner.build-fail.description": {
|
||||
"message": "Diese Deployment von Modrinth's Frontend konnte keinen Status von der API generieren. Dies könnte aufgrund eines Ausfalls oder eines Konfigurationsfehlers sein. Erstelle neu wenn die API verfügbar ist. Fehlercodes: {errors}; Aktuelle API URL ist: {url}"
|
||||
},
|
||||
"layout.banner.build-fail.ignore": {
|
||||
"message": "Ignorieren"
|
||||
},
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Fehler beim generieren des Status von der API beim erstellen."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Falls du die offizielle Modrinth-Website aufrufen wolltest, besuche {url}. Diese Vorschauversion wird von Modrinth-Mitarbeitern zu Testzwecken genutzt. Sie wurde unter Verwendung von <branch-link>{owner}/{branch}</branch-link> @ {commit} erstellt."
|
||||
"message": "Falls du die offizielle Modrinth-Website aufrufen wolltest, besuch <link>https://modrinth.com</link>. Diese Vorschauversion wird von Modrinth-Mitarbeitern zu Testzwecken genutzt. Sie wurde unter Verwendung von <branch-link>{owner}/{branch}</branch-link> @ {commit} erstellt."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Dies ist eine Vorschauversion der Modrinth-Webseite."
|
||||
@@ -2012,9 +1922,6 @@
|
||||
"moderation.moderate": {
|
||||
"message": "Moderieren"
|
||||
},
|
||||
"moderation.page.external-projects": {
|
||||
"message": "Externe Projekte"
|
||||
},
|
||||
"moderation.page.projects": {
|
||||
"message": "Projekte"
|
||||
},
|
||||
@@ -2645,30 +2552,6 @@
|
||||
"project.license.title": {
|
||||
"message": "Lizenz"
|
||||
},
|
||||
"project.moderation.admonition.approved.body.private": {
|
||||
"message": "Dein Projekt ist privat, was bedeutet, dass nur du und von dir eingeladene Personen darauf zugreifen können."
|
||||
},
|
||||
"project.moderation.admonition.approved.header": {
|
||||
"message": "Projekt genehmigt"
|
||||
},
|
||||
"project.moderation.admonition.rejected.header": {
|
||||
"message": "Änderungen angefragt"
|
||||
},
|
||||
"project.moderation.admonition.under-review.body.1": {
|
||||
"message": "Dein Projekt ist in der Warteschlange, um von Modrinths Moderationsteam überprüft zu werden."
|
||||
},
|
||||
"project.moderation.admonition.under-review.header": {
|
||||
"message": "Projekt wird überprüft"
|
||||
},
|
||||
"project.moderation.admonition.withheld.header": {
|
||||
"message": "Von Mitarbeitern entlistet"
|
||||
},
|
||||
"project.moderation.error.unauthorized": {
|
||||
"message": "Nicht autorisiert"
|
||||
},
|
||||
"project.moderation.thread.title": {
|
||||
"message": "Moderationsnachrichten"
|
||||
},
|
||||
"project.moderation.title": {
|
||||
"message": "Moderation"
|
||||
},
|
||||
@@ -2732,45 +2615,6 @@
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.permissions.attention-needed.description.proj-approved": {
|
||||
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung fgolgender Dateien hast; alle zurückgehaltenen Versionen werden automatisch veröffentlicht."
|
||||
},
|
||||
"project.settings.permissions.attention-needed.description.proj-draft": {
|
||||
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung fgolgender Dateien hast, bevor du dein Projekt zur überprüfung einreichen kannst."
|
||||
},
|
||||
"project.settings.permissions.attention-needed.title": {
|
||||
"message": "Unbekannter eingebetteter Inhalt"
|
||||
},
|
||||
"project.settings.permissions.completed.description": {
|
||||
"message": "Alle externen Inhalte haben Quellenangaben."
|
||||
},
|
||||
"project.settings.permissions.completed.title": {
|
||||
"message": "Zuordnungen abgschlossen!"
|
||||
},
|
||||
"project.settings.permissions.empty-state.description": {
|
||||
"message": "Keine deiner Versionen enrhalten externe Inhalte, also musst du dir keine Sorgen über die Einholung von Berechtigungen machen."
|
||||
},
|
||||
"project.settings.permissions.empty-state.heading": {
|
||||
"message": "Du bist startklar!"
|
||||
},
|
||||
"project.settings.permissions.fail.description": {
|
||||
"message": "Du hast keine Berechtigung, einige der von dir hinzugefügten externen Inhalte weiterzuverbreiten. Um auf Modrinth veröffentlichen zu können, entferne bitte die urheberrechtswidrigen Inhalte."
|
||||
},
|
||||
"project.settings.permissions.fail.title": {
|
||||
"message": "Einige Inhalte können nicht enthalten sein"
|
||||
},
|
||||
"project.settings.permissions.info-banner.description": {
|
||||
"message": "Wenn du Inhalte einbindest, welche nicht auf Modrinth gehostet werden, musst du uns deren Quelle mitteilen und bestätigen, dass du die Berechtigung zur Verbreitung der Dateien hast. In <link>unserer Anleitung (Englisch)</link> lernst du, wie du dies am besten machst!"
|
||||
},
|
||||
"project.settings.permissions.info-banner.title": {
|
||||
"message": "Lerne wie Zuschreibungen funktionieren"
|
||||
},
|
||||
"project.settings.permissions.learn-more": {
|
||||
"message": "Mehr erfahren"
|
||||
},
|
||||
"project.settings.permissions.search-placeholder": {
|
||||
"message": "Durchsuche {count} {count, plural, one {externes Projekt} other {externe Projekte}}..."
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Einstellungen"
|
||||
},
|
||||
@@ -2789,15 +2633,6 @@
|
||||
"project.versions.title": {
|
||||
"message": "Versionen"
|
||||
},
|
||||
"project.versions.withheld-versions-warning.description": {
|
||||
"message": "{count, plural, one {Diese Version ist} other {Diese Versionen sind}} derzeit zurückgehalten und nicht öffentlich gelistet. Bitte stelle einen Nachweis bereit, dass du die Erlaubnis hast, bestimmte Dateien weiterzuverbreiten, die in {count, plural, one {der Modpack-Version} other {den Modpack-Versionen}} enthalten sind."
|
||||
},
|
||||
"project.versions.withheld-versions-warning.resolve-button": {
|
||||
"message": "Beheben"
|
||||
},
|
||||
"project.versions.withheld-versions-warning.title": {
|
||||
"message": "{count, plural, one {Version {version_name}} other {Versionen}} zurückgehalten aufgrund von unbekannten eingebetteten Inhalten"
|
||||
},
|
||||
"report.already-reported": {
|
||||
"message": "Du hast bereits {title} gemeldet"
|
||||
},
|
||||
@@ -3167,9 +3002,6 @@
|
||||
"search.filter.locked.server.sync": {
|
||||
"message": "Mit Server synchronisieren"
|
||||
},
|
||||
"servers.manage.content.title": {
|
||||
"message": "Inhalt - {serverName} - Modrinth"
|
||||
},
|
||||
"servers.notice.actions": {
|
||||
"message": "Aktionen"
|
||||
},
|
||||
|
||||
@@ -407,51 +407,6 @@
|
||||
"collection.title": {
|
||||
"message": "{name} - Kollektion"
|
||||
},
|
||||
"conversation-thread.action.add-private-note": {
|
||||
"message": "Private Notiz hinzufügen"
|
||||
},
|
||||
"conversation-thread.action.approve": {
|
||||
"message": "Genehmigen"
|
||||
},
|
||||
"conversation-thread.action.approve-with-reply": {
|
||||
"message": "Mit Antwort genehmigen"
|
||||
},
|
||||
"conversation-thread.action.close-thread": {
|
||||
"message": ""
|
||||
},
|
||||
"conversation-thread.action.reject": {
|
||||
"message": "Ablehnen"
|
||||
},
|
||||
"conversation-thread.action.reject-with-reply": {
|
||||
"message": "Mit Antwort ablehnen"
|
||||
},
|
||||
"conversation-thread.action.reply": {
|
||||
"message": "Antworten"
|
||||
},
|
||||
"conversation-thread.action.send": {
|
||||
"message": "Senden"
|
||||
},
|
||||
"conversation-thread.action.withhold": {
|
||||
"message": "Zurückhalten"
|
||||
},
|
||||
"conversation-thread.action.withhold-with-reply": {
|
||||
"message": "Mit Antwort zurückhalten"
|
||||
},
|
||||
"conversation-thread.reply-editor.placeholder.send": {
|
||||
"message": "Sende eine Nachricht..."
|
||||
},
|
||||
"conversation-thread.reply-modal.confirmation.label": {
|
||||
"message": "Mir ist bewusst, dass dieser Thread nicht aktiv von den Moderatoren überwacht wird."
|
||||
},
|
||||
"conversation-thread.resubmit-modal.description": {
|
||||
"message": "Du schickst <project-title>{projectTitle}</project-title> zur Wiederprüfung."
|
||||
},
|
||||
"conversation-thread.resubmit-modal.header.resubmitting": {
|
||||
"message": "Erneut zur Überprüfung einreichen"
|
||||
},
|
||||
"conversation-thread.resubmit-modal.header.submitting": {
|
||||
"message": "Erneut zur Überprüfung einreichen"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.admonition": {
|
||||
"message": "Ergänzende Dateien dienen zur Unterstützung von Ressourcen wie Quellcode, nicht für alternative Versionen oder Varianten."
|
||||
},
|
||||
@@ -920,9 +875,24 @@
|
||||
"dashboard.head-title": {
|
||||
"message": "Dashboard"
|
||||
},
|
||||
"dashboard.notifications.button.mark-all-as-read": {
|
||||
"message": "Alle als gelesen markieren"
|
||||
},
|
||||
"dashboard.notifications.button.view-history": {
|
||||
"message": "Verlauf ansehen"
|
||||
},
|
||||
"dashboard.notifications.empty.no-unread": {
|
||||
"message": "Du hast keine ungelesenen Benachrichtigungen."
|
||||
},
|
||||
"dashboard.notifications.error.loading": {
|
||||
"message": "Fehler beim Laden von Benachrichtigungen:"
|
||||
},
|
||||
"dashboard.notifications.history.label": {
|
||||
"message": "Verlauf"
|
||||
},
|
||||
"dashboard.notifications.history.title": {
|
||||
"message": "Benachrichtigungsverlauf"
|
||||
},
|
||||
"dashboard.notifications.link.see-all": {
|
||||
"message": "Alle ansehen"
|
||||
},
|
||||
@@ -932,6 +902,9 @@
|
||||
"dashboard.notifications.link.view-more": {
|
||||
"message": "Siehe {extraNotifs} weitere {extraNotifs, plural, one {Benachrichtigung} other {Benachrichtigungen}} an"
|
||||
},
|
||||
"dashboard.notifications.loading": {
|
||||
"message": "Benachrichtigungen werden geladen..."
|
||||
},
|
||||
"dashboard.organizations.button.create": {
|
||||
"message": "Organisation erstellen"
|
||||
},
|
||||
@@ -947,27 +920,6 @@
|
||||
"dashboard.organizations.title": {
|
||||
"message": "Organisationen"
|
||||
},
|
||||
"dashboard.overview.notifications.button.mark-all-as-read": {
|
||||
"message": "Alle als gelesen markieren"
|
||||
},
|
||||
"dashboard.overview.notifications.button.view-history": {
|
||||
"message": "Verlauf ansehen"
|
||||
},
|
||||
"dashboard.overview.notifications.empty.no-unread": {
|
||||
"message": "Du hast keine ungelesenen Benachrichtigungen."
|
||||
},
|
||||
"dashboard.overview.notifications.error.loading": {
|
||||
"message": "Fehler beim Laden von Benachrichtigungen:"
|
||||
},
|
||||
"dashboard.overview.notifications.history.label": {
|
||||
"message": "Verlauf"
|
||||
},
|
||||
"dashboard.overview.notifications.history.title": {
|
||||
"message": "Benachrichtigungsverlauf"
|
||||
},
|
||||
"dashboard.overview.notifications.loading": {
|
||||
"message": "Benachrichtigungen werden geladen..."
|
||||
},
|
||||
"dashboard.projects.bulk-edit-hint": {
|
||||
"message": "Du kannst mehrere Projekte gleichzeitig bearbeiten, indem du sie unten auswählst."
|
||||
},
|
||||
@@ -1277,33 +1229,6 @@
|
||||
"dashboard.withdraw.error.tax-form.title": {
|
||||
"message": "Bitte Steuerformular ausfüllen"
|
||||
},
|
||||
"discover.install.back-to-server": {
|
||||
"message": "Zurück zum Server"
|
||||
},
|
||||
"discover.install.back-to-setup": {
|
||||
"message": "Zurück zur Einrichtung"
|
||||
},
|
||||
"discover.install.cancel-reset": {
|
||||
"message": "Zurücksetzung abbrechen"
|
||||
},
|
||||
"discover.install.error.no-server-world": {
|
||||
"message": "Keine Serverwelt ist zur Installation verfügbar."
|
||||
},
|
||||
"discover.install.error.unsupported-content-type": {
|
||||
"message": "Dieser Inhaltstyp kann nicht vom Durchstöbern zu einem Server hinzugefügt werden."
|
||||
},
|
||||
"discover.install.heading.reset-modpack": {
|
||||
"message": "Modpack zur Installation nach dem Zurücksetzen auswählen"
|
||||
},
|
||||
"discover.seo.description": {
|
||||
"message": "Durchsuche und entdecke tausende Minecraft {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}} auf Modrinth mit sofortigen und präzisen Suchergebnissen. Unsere Filter helfen dir dabei, schnell die besten Minecraft {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}} zu finden."
|
||||
},
|
||||
"discover.seo.title": {
|
||||
"message": "Durchsuche {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}}"
|
||||
},
|
||||
"discover.seo.title-with-query": {
|
||||
"message": "Durchsuche {projectType, select, mod {Mods} modpack {Modpacks} resourcepack {Ressourcenpakete} shader {Shader} plugin {Plugins} datapack {Datenpakete} other {Projekte}} | {query}"
|
||||
},
|
||||
"error.collection.404.list_item.1": {
|
||||
"message": "Eventuell hast du die URL der Kollektion falsch eingegeben."
|
||||
},
|
||||
@@ -1319,12 +1244,6 @@
|
||||
"error.collection.404.title": {
|
||||
"message": "Kollektion nicht gefunden"
|
||||
},
|
||||
"error.generic.401.signed-in-as": {
|
||||
"message": "Du bist aktuell angemeldet als:"
|
||||
},
|
||||
"error.generic.401.title": {
|
||||
"message": "Du hast keinen Zugriff auf diese Seite"
|
||||
},
|
||||
"error.generic.404.subtitle": {
|
||||
"message": "Die von dir gesuchte Seite scheint nicht zu existieren."
|
||||
},
|
||||
@@ -1751,9 +1670,6 @@
|
||||
"layout.action.create-new": {
|
||||
"message": "Erstellen..."
|
||||
},
|
||||
"layout.action.external-projects": {
|
||||
"message": "Externe Projekte"
|
||||
},
|
||||
"layout.action.file-lookup": {
|
||||
"message": "Dateisuche"
|
||||
},
|
||||
@@ -1802,20 +1718,14 @@
|
||||
"layout.banner.add-email.description": {
|
||||
"message": "Aus Sicherheitsgründen verlangt Modrinth, dass du eine E-Mail-Adresse für dein Konto registrierst."
|
||||
},
|
||||
"layout.banner.build-fail.always-ignore": {
|
||||
"message": "Immer ignorieren"
|
||||
},
|
||||
"layout.banner.build-fail.description": {
|
||||
"message": "Dieses Deployment des Modrinth-Frontends konnte keinen Status aus der API generieren. Dies kann an einer Störung oder einem Fehler in der Konfiguration liegen. Erneut erstellen, wenn die API verfügbar ist. Fehlercodes: {errors}; Aktuelle API-URL lautet: {url}"
|
||||
},
|
||||
"layout.banner.build-fail.ignore": {
|
||||
"message": "Ignorieren"
|
||||
},
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Fehler beim Generieren des Status aus der API beim Erstellen."
|
||||
},
|
||||
"layout.banner.preview.description": {
|
||||
"message": "Falls du die offizielle Modrinth-Website aufrufen wolltest, besuche {url}. Diese Vorschauversion wird von Modrinth-Mitarbeitern zu Testzwecken genutzt. Sie wurde unter Verwendung von <branch-link>{owner}/{branch}</branch-link> @ {commit} erstellt."
|
||||
"message": "Falls du die offizielle Modrinth-Website aufrufen wolltest, besuch <link>https://modrinth.com</link>. Diese Vorschauversion wird von Modrinth-Mitarbeitern zu Testzwecken genutzt. Sie wurde unter Verwendung von <branch-link>{owner}/{branch}</branch-link> @ {commit} erstellt."
|
||||
},
|
||||
"layout.banner.preview.title": {
|
||||
"message": "Dies ist eine Vorschauversion der Modrinth-Website."
|
||||
@@ -2012,9 +1922,6 @@
|
||||
"moderation.moderate": {
|
||||
"message": "Moderieren"
|
||||
},
|
||||
"moderation.page.external-projects": {
|
||||
"message": "Externe Projekte"
|
||||
},
|
||||
"moderation.page.projects": {
|
||||
"message": "Projekte"
|
||||
},
|
||||
@@ -2645,30 +2552,6 @@
|
||||
"project.license.title": {
|
||||
"message": "Lizenz"
|
||||
},
|
||||
"project.moderation.admonition.approved.body.private": {
|
||||
"message": "Dein Projekt ist privat, was bedeutet, dass nur du und von dir eingeladene Personen darauf zugreifen können."
|
||||
},
|
||||
"project.moderation.admonition.approved.header": {
|
||||
"message": "Projekt genehmigt"
|
||||
},
|
||||
"project.moderation.admonition.rejected.header": {
|
||||
"message": "Änderungen angefragt"
|
||||
},
|
||||
"project.moderation.admonition.under-review.body.1": {
|
||||
"message": "Dein Projekt ist in der Warteschlange, um von Modrinths Moderationsteam überprüft zu werden."
|
||||
},
|
||||
"project.moderation.admonition.under-review.header": {
|
||||
"message": "Projekt wird überprüft"
|
||||
},
|
||||
"project.moderation.admonition.withheld.header": {
|
||||
"message": "Von Mitarbeitern entlistet"
|
||||
},
|
||||
"project.moderation.error.unauthorized": {
|
||||
"message": "Nicht autorisiert"
|
||||
},
|
||||
"project.moderation.thread.title": {
|
||||
"message": "Moderationsnachrichten"
|
||||
},
|
||||
"project.moderation.title": {
|
||||
"message": "Moderation"
|
||||
},
|
||||
@@ -2732,45 +2615,6 @@
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.permissions.attention-needed.description.proj-approved": {
|
||||
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung folgender Dateien hast; alle zurückgehaltenen Versionen werden automatisch veröffentlicht."
|
||||
},
|
||||
"project.settings.permissions.attention-needed.description.proj-draft": {
|
||||
"message": "Bitte lege Nachweise vor, dass du die Erlaubnis zur Weiterverbreitung aller folgenden Dateien hast, bevor du dein Projekt zur Überprüfung einreichen kannst."
|
||||
},
|
||||
"project.settings.permissions.attention-needed.title": {
|
||||
"message": "Unbekannter eingebetteter Inhalt"
|
||||
},
|
||||
"project.settings.permissions.completed.description": {
|
||||
"message": "Alle externen Inhalte haben Quellenangaben."
|
||||
},
|
||||
"project.settings.permissions.completed.title": {
|
||||
"message": "Quellenangaben vollständig!"
|
||||
},
|
||||
"project.settings.permissions.empty-state.description": {
|
||||
"message": "Keine deiner Versionen enthält externe Inhalte, du musst du dir keine Gedanken über die Einholung von Genehmigungen machen."
|
||||
},
|
||||
"project.settings.permissions.empty-state.heading": {
|
||||
"message": "Du bist startklar!"
|
||||
},
|
||||
"project.settings.permissions.fail.description": {
|
||||
"message": "Du hast keine Berechtigung, einige der von dir hinzugefügten externen Inhalte weiterzuverbreiten. Entferne die urheberrechtsverletzenden Inhalte, um auf Modrinth veröffentlichen zu können."
|
||||
},
|
||||
"project.settings.permissions.fail.title": {
|
||||
"message": "Bestimmte Inhalte können nicht enthalten sein"
|
||||
},
|
||||
"project.settings.permissions.info-banner.description": {
|
||||
"message": "Wenn du Inhalte einbindest, welche nicht auf Modrinth gehostet werden, musst du uns deren Quelle mitteilen und bestätigen, dass du die Berechtigung zur Verbreitung der Dateien hast. In <link>unserer Anleitung</link> lernst du, wie du dies am besten machst!"
|
||||
},
|
||||
"project.settings.permissions.info-banner.title": {
|
||||
"message": "So funktionieren Quellenangaben"
|
||||
},
|
||||
"project.settings.permissions.learn-more": {
|
||||
"message": "Mehr erfahren"
|
||||
},
|
||||
"project.settings.permissions.search-placeholder": {
|
||||
"message": "Durchsuche {count} {count, plural, one {externes Projekt} other {externe Projekte}}..."
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Einstellungen"
|
||||
},
|
||||
@@ -2789,15 +2633,6 @@
|
||||
"project.versions.title": {
|
||||
"message": "Versionen"
|
||||
},
|
||||
"project.versions.withheld-versions-warning.description": {
|
||||
"message": "{count, plural, one {Diese Version ist} other {Diese Versionen sind}} derzeit zurückgehalten und nicht öffentlich gelistet. Bitte stelle einen Nachweis bereit, dass du die Erlaubnis hast, bestimmte Dateien weiterzuverbreiten, die im Modpack in {count, plural, one {Version} other {Versionen}} enthalten sind."
|
||||
},
|
||||
"project.versions.withheld-versions-warning.resolve-button": {
|
||||
"message": "Beheben"
|
||||
},
|
||||
"project.versions.withheld-versions-warning.title": {
|
||||
"message": "{count, plural, one {Version {version_name} wurde} other {Versionen wurden}} aufgrund unbekannter eingebetteter Inhalte zurückgehalten"
|
||||
},
|
||||
"report.already-reported": {
|
||||
"message": "Du hast {title} bereits gemeldet"
|
||||
},
|
||||
@@ -3167,9 +3002,6 @@
|
||||
"search.filter.locked.server.sync": {
|
||||
"message": "Mit Server synchronisieren"
|
||||
},
|
||||
"servers.manage.content.title": {
|
||||
"message": "Inhalt - {serverName} - Modrinth"
|
||||
},
|
||||
"servers.notice.actions": {
|
||||
"message": "Aktionen"
|
||||
},
|
||||
@@ -3729,7 +3561,7 @@
|
||||
"message": "Methode aktualisieren"
|
||||
},
|
||||
"settings.billing.upgrade": {
|
||||
"message": "Upgraden"
|
||||
"message": "Verbessern"
|
||||
},
|
||||
"settings.display.banner.developer-mode.button": {
|
||||
"message": "Entwicklermodus deaktivieren"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user