mirror of
https://github.com/modrinth/code.git
synced 2026-08-29 11:04:48 +00:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a03ab1354a | ||
|
|
0748b8732e | ||
|
|
b0c77bb395 | ||
|
|
cba59f9848 | ||
|
|
08901e725f | ||
|
|
0fbd431169 | ||
|
|
fb39dabe43 | ||
|
|
bc50ed5f65 | ||
|
|
fe9ae94a5d | ||
|
|
871a161327 | ||
|
|
797ec2ec3c | ||
|
|
237963bb35 | ||
|
|
ba667921d1 | ||
|
|
56dba8ccc2 | ||
|
|
82d0184a85 | ||
|
|
5f9522b5cf | ||
|
|
75d2109be8 | ||
|
|
7e611c7678 | ||
|
|
0bd341371a | ||
|
|
32a43f2a22 | ||
|
|
2909aa49ca | ||
|
|
816830eea3 | ||
|
|
087752312b | ||
|
|
bcd7133251 | ||
|
|
a85b8eed3f | ||
|
|
52e055b20f | ||
|
|
5181370293 | ||
|
|
5d54cfa0dc | ||
|
|
5eb64cea3e | ||
|
|
6670e3184e | ||
|
|
12928edbcb | ||
|
|
c94034f4e2 | ||
|
|
8eb12d5d24 | ||
|
|
6a92ab0c2c | ||
|
|
d74211e827 | ||
|
|
49628a0ebc | ||
|
|
5b3177d27a | ||
|
|
a6043fc51f | ||
|
|
f08d977aff | ||
|
|
bee0c14b31 | ||
|
|
f39a602be3 | ||
|
|
3e07267e10 | ||
|
|
79fcf41316 | ||
|
|
10b1d7002a | ||
|
|
906ead762d | ||
|
|
e568efa2a1 | ||
|
|
c8816418ea | ||
|
|
12540a5fd7 | ||
|
|
fd7794c807 |
@@ -125,8 +125,7 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: ./apps/frontend
|
||||
run: pnpm build
|
||||
run: pnpm web:build
|
||||
env:
|
||||
CF_PAGES_BRANCH: ${{ inputs.head_ref || github.ref_name }}
|
||||
CF_PAGES_COMMIT_SHA: ${{ inputs.head_sha || github.sha }}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
name: Frontend Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- prod
|
||||
paths:
|
||||
- 'apps/frontend/**/*'
|
||||
- 'packages/ui/**/*'
|
||||
- 'packages/utils/**/*'
|
||||
- 'packages/assets/**/*'
|
||||
- 'packages/moderation/**/*'
|
||||
- 'packages/api-client/**/*'
|
||||
- 'packages/blog/**/*'
|
||||
- '**/pnpm-*.yaml'
|
||||
- .github/workflows/frontend-docker.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/prod' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build frontend
|
||||
runs-on: namespace-profile-modrinth-frontend
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Configure environment
|
||||
id: meta
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
if [ "$REF" = "refs/heads/main" ]; then
|
||||
echo "env=staging" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "env=production" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Set up caches
|
||||
uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1
|
||||
with:
|
||||
cache: |
|
||||
pnpm
|
||||
|
||||
- name: Inject build variables
|
||||
working-directory: ./apps/frontend
|
||||
run: |
|
||||
if [ "${{ steps.meta.outputs.env }}" == "staging" ]; then
|
||||
echo "Injecting staging variables from wrangler.jsonc..."
|
||||
jq -r '.env.staging.vars | to_entries[] | "\(.key)=\(.value)"' wrangler.jsonc >> $GITHUB_ENV
|
||||
else
|
||||
echo "Injecting production variables from wrangler.jsonc..."
|
||||
jq -r '.vars | to_entries[] | "\(.key)=\(.value)"' wrangler.jsonc >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./apps/frontend
|
||||
run: pnpm install
|
||||
|
||||
- name: Build frontend
|
||||
run: pnpm web:build
|
||||
env:
|
||||
NITRO_PRESET: node-server
|
||||
BUILD_ENV: ${{ steps.meta.outputs.env }}
|
||||
CF_PAGES_BRANCH: ${{ github.ref_name }}
|
||||
CF_PAGES_COMMIT_SHA: ${{ github.sha }}
|
||||
|
||||
- name: Stage Docker context
|
||||
run: |
|
||||
mkdir -p apps/frontend/docker-stage
|
||||
cp -r apps/frontend/.output apps/frontend/docker-stage/.output
|
||||
|
||||
- name: Upload Docker context
|
||||
uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3
|
||||
with:
|
||||
name: frontend-docker-context
|
||||
retention-days: 1
|
||||
path: apps/frontend/docker-stage
|
||||
|
||||
docker-build:
|
||||
needs: [build]
|
||||
uses: SparkUniverse/workflows/.github/workflows/docker-build.yaml@main
|
||||
with:
|
||||
image-name: frontend
|
||||
dockerfile-path: apps/frontend/Dockerfile
|
||||
artifacts-name: frontend-docker-context
|
||||
@@ -70,10 +70,9 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Build Storybook
|
||||
working-directory: ./packages/ui
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
run: pnpm run build-storybook
|
||||
run: pnpm build-storybook
|
||||
|
||||
- name: Configure short SHA
|
||||
id: meta
|
||||
|
||||
@@ -8,6 +8,10 @@ on:
|
||||
concurrency:
|
||||
group: i18n-management
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
pull_translations:
|
||||
name: 'Pull translations from Crowdin'
|
||||
@@ -38,23 +42,17 @@ jobs:
|
||||
flight_failure "CROWDIN_PERSONAL_TOKEN secret is not defined (required to push)"
|
||||
fi
|
||||
|
||||
if [ "$CROWDIN_GH_TOKEN_DEFINED" != true ]; then
|
||||
flight_failure "CROWDIN_GH_TOKEN secret is not defined (required to make pull requests)"
|
||||
fi
|
||||
|
||||
if [ "$PREFLIGHT_CHECK_RESULT" = false ]; then
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CROWDIN_PROJECT_ID_DEFINED: ${{ vars.CROWDIN_PROJECT_ID != '' }}
|
||||
CROWDIN_PERSONAL_TOKEN_DEFINED: ${{ secrets.CROWDIN_PERSONAL_TOKEN != '' }}
|
||||
CROWDIN_GH_TOKEN_DEFINED: ${{ secrets.CROWDIN_GH_TOKEN != '' }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
token: ${{ secrets.CROWDIN_GH_TOKEN }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
@@ -67,12 +65,6 @@ jobs:
|
||||
- name: Install script dependencies
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Configure Git author
|
||||
id: git-author
|
||||
uses: MarcoIeni/git-config@59144859caf016f8b817a2ac9b051578729173c4 # v0.1.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CROWDIN_GH_TOKEN }}
|
||||
|
||||
# # Because --all flag of Crowdin CLI is currently broken we need to create a fake source file
|
||||
# # so that the CLI won't omit translations for it. See https://github.com/crowdin/crowdin-cli/issues/724
|
||||
# - name: Write fake sources
|
||||
@@ -116,7 +108,4 @@ jobs:
|
||||
body-path: .github/templates/crowdin-pr.md
|
||||
commit-message: 'New translations from Crowdin (${{ steps.branch-name.outputs.branch_name }})'
|
||||
branch: crowdin-pull/${{ steps.branch-name.outputs.branch_name }}
|
||||
author: '${{ steps.git-author.outputs.name }} <${{ steps.git-author.outputs.email }}>'
|
||||
committer: '${{ steps.git-author.outputs.name }} <${{ steps.git-author.outputs.email }}>'
|
||||
labels: sync
|
||||
token: ${{ secrets.CROWDIN_GH_TOKEN }}
|
||||
|
||||
@@ -55,6 +55,7 @@ Thumbs.db
|
||||
|
||||
# frontend generated files
|
||||
apps/frontend/src/generated
|
||||
/packages/ui/src/layouts/wrapped/settings/language-settings/language-settings-coverage.generated.ts
|
||||
|
||||
.turbo
|
||||
target
|
||||
|
||||
Generated
+1
-13
@@ -2676,10 +2676,10 @@ dependencies = [
|
||||
"futures",
|
||||
"indexmap 2.11.4",
|
||||
"itertools 0.14.0",
|
||||
"quick-xml 0.38.3",
|
||||
"reqwest 0.12.24",
|
||||
"rust-s3",
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
"serde_json",
|
||||
"sha1_smol",
|
||||
"thiserror 2.0.17",
|
||||
@@ -9433,18 +9433,6 @@ dependencies = [
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-xml-rs"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53630160a98edebde0123eb4dfd0fce6adff091b2305db3154a9e920206eb510"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.19"
|
||||
|
||||
@@ -192,7 +192,6 @@ serde_cbor = "0.11.2"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.145"
|
||||
serde_with = "3.15.0"
|
||||
serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
||||
sha1 = "0.10.6"
|
||||
sha1_smol = { version = "1.0.1", features = ["std"] }
|
||||
sha2 = "0.10.9"
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"intl-messageformat": "^10.7.7",
|
||||
"ofetch": "^1.3.4",
|
||||
"overlayscrollbars": "^2.15.1",
|
||||
"pinia": "^3.0.0",
|
||||
"posthog-js": "^1.158.2",
|
||||
"three": "^0.172.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
|
||||
+101
-23
@@ -94,6 +94,9 @@ import SurveyPopup from '@/components/ui/SurveyPopup.vue'
|
||||
import WindowControls from '@/components/ui/WindowControls.vue'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { useError } from '@/composables/use-error.js'
|
||||
import { useTheme } from '@/composables/use-theme.ts'
|
||||
import { config } from '@/config'
|
||||
import {
|
||||
hide_ads_window,
|
||||
@@ -147,8 +150,7 @@ import { setupProviders } from '@/providers/setup'
|
||||
import { setupAppEventsProvider } from '@/providers/setup/app-events'
|
||||
import { setupAuthProvider } from '@/providers/setup/auth'
|
||||
import { setupLoadingStateProvider } from '@/providers/setup/loading-state'
|
||||
import { useError } from '@/store/error.js'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { setupAppUserPreferencesProvider } from '@/providers/setup/user-preferences.ts'
|
||||
import { appMessages } from '@/utils/app-messages'
|
||||
|
||||
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
|
||||
@@ -157,7 +159,8 @@ import { AppNotificationManager } from './providers/app-notifications'
|
||||
import { AppPopupNotificationManager } from './providers/app-popup-notifications'
|
||||
import { appSettingsModalOpenProfileKey } from './providers/app-settings-modal'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const appTheme = useTheme()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { channel: appEventChannel, events: appEvents } = setupAppEventsProvider()
|
||||
@@ -201,7 +204,7 @@ const credentials = ref()
|
||||
let credentialsRefreshId = 0
|
||||
const sidebarToggled = ref(true)
|
||||
watch(
|
||||
() => themeStore.toggleSidebar,
|
||||
() => appSettings.toggleSidebar,
|
||||
(toggleSidebar) => {
|
||||
sidebarToggled.value = !toggleSidebar
|
||||
},
|
||||
@@ -221,7 +224,7 @@ const hostingUpdateRequired = computed(
|
||||
appUpdateState.updatesEnabled.value,
|
||||
)
|
||||
const prideFundraiserEnabled = computed(
|
||||
() => themeStore.getFeatureFlag('pride_fundraiser') && Date.now() < PRIDE_FUNDRAISER_END_DATE,
|
||||
() => appSettings.getFeatureFlag('pride_fundraiser') && Date.now() < PRIDE_FUNDRAISER_END_DATE,
|
||||
)
|
||||
const hostingIntercomIdentityKey = computed(() => {
|
||||
const rawServerId = route.params.id
|
||||
@@ -324,13 +327,13 @@ providePageContext({
|
||||
intercomBubble: hostingIntercom.intercomBubble,
|
||||
featureFlags: {
|
||||
serverRamAsBytesAlwaysOn: computed(() =>
|
||||
themeStore.getFeatureFlag('server_ram_as_bytes_always_on'),
|
||||
appSettings.getFeatureFlag('server_ram_as_bytes_always_on'),
|
||||
),
|
||||
},
|
||||
openExternalUrl: (url) => void openUrl(url),
|
||||
})
|
||||
provideModalBehavior({
|
||||
noblur: computed(() => !themeStore.advancedRendering),
|
||||
noblur: computed(() => !appTheme.advancedRendering),
|
||||
onShow: () => take_ads_window_hold(),
|
||||
onHide: () => release_ads_window_hold(),
|
||||
})
|
||||
@@ -634,10 +637,11 @@ async function setupApp() {
|
||||
theme,
|
||||
locale,
|
||||
telemetry,
|
||||
collapsed_navigation,
|
||||
hide_nametag_skins_page,
|
||||
advanced_rendering,
|
||||
toggle_sidebar,
|
||||
sync_theme_across_devices,
|
||||
sync_behavior_across_devices,
|
||||
developer_mode,
|
||||
feature_flags,
|
||||
pending_update_toast_for_version,
|
||||
@@ -655,13 +659,14 @@ async function setupApp() {
|
||||
nativeDecorations.value = native_decorations
|
||||
if (os.value !== 'MacOS') await getCurrentWindow().setDecorations(native_decorations)
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
themeStore.advancedRendering = advanced_rendering
|
||||
themeStore.hideNametagSkinsPage = hide_nametag_skins_page
|
||||
themeStore.toggleSidebar = toggle_sidebar
|
||||
themeStore.devMode = developer_mode
|
||||
themeStore.featureFlags = feature_flags
|
||||
appTheme.preferred = theme
|
||||
appTheme.advancedRendering = advanced_rendering
|
||||
appTheme.syncAcrossDevices = sync_theme_across_devices
|
||||
appSettings.syncBehaviorAcrossDevices = sync_behavior_across_devices
|
||||
appSettings.hideNametagSkinsPage = hide_nametag_skins_page
|
||||
appSettings.toggleSidebar = toggle_sidebar
|
||||
appSettings.devMode = developer_mode
|
||||
Object.assign(appSettings.featureFlags, feature_flags)
|
||||
stateInitialized.value = true
|
||||
|
||||
isMaximized.value = await getCurrentWindow().isMaximized()
|
||||
@@ -959,7 +964,7 @@ watch(incompatibilityWarningModal, (modal) => {
|
||||
}
|
||||
})
|
||||
|
||||
setupAuthProvider(credentials, async (_redirectPath, flow, options) => {
|
||||
const authProvider = setupAuthProvider(credentials, async (_redirectPath, flow, options) => {
|
||||
if (options?.showModal === false) {
|
||||
await signIn(flow)
|
||||
} else {
|
||||
@@ -967,6 +972,80 @@ setupAuthProvider(credentials, async (_redirectPath, flow, options) => {
|
||||
}
|
||||
})
|
||||
|
||||
const userPreferences = setupAppUserPreferencesProvider(authProvider, notificationManager)
|
||||
let userPreferencesSync = Promise.resolve()
|
||||
|
||||
watch(
|
||||
[userPreferences.preferences, stateInitialized],
|
||||
([preferences, initialized]) => {
|
||||
if (!preferences || !initialized) return
|
||||
|
||||
userPreferencesSync = userPreferencesSync
|
||||
.then(async () => {
|
||||
const settings = await getSettings()
|
||||
const selectedTheme = preferences.appearance.auto ? 'system' : preferences.appearance.theme
|
||||
const locale = preferences.localization.locale
|
||||
const behavior = preferences.behavior
|
||||
let settingsChanged = false
|
||||
|
||||
if (appTheme.syncAcrossDevices && appTheme.preferred !== selectedTheme) {
|
||||
appTheme.preferred = selectedTheme
|
||||
}
|
||||
if (i18n.global.locale.value !== locale) {
|
||||
i18n.global.locale.value = locale
|
||||
}
|
||||
|
||||
if (appTheme.syncAcrossDevices && settings.theme !== selectedTheme) {
|
||||
settings.theme = selectedTheme
|
||||
settingsChanged = true
|
||||
}
|
||||
if (settings.locale !== locale) {
|
||||
settings.locale = locale
|
||||
settingsChanged = true
|
||||
}
|
||||
|
||||
if (behavior && appSettings.syncBehaviorAcrossDevices) {
|
||||
const behaviorFeatureFlags = {
|
||||
worlds_in_home: behavior.show_jump_in,
|
||||
compact_instance_cards: behavior.compact_instance_cards,
|
||||
show_instance_play_time: behavior.show_play_time,
|
||||
skip_unknown_pack_warning: !behavior.warn_on_unknown_modpacks,
|
||||
skip_non_essential_warnings: behavior.skip_non_essential_warnings,
|
||||
}
|
||||
|
||||
appSettings.toggleSidebar = behavior.hide_right_sidebar
|
||||
appSettings.hideNametagSkinsPage = behavior.hide_nametag
|
||||
Object.assign(appSettings.featureFlags, behaviorFeatureFlags)
|
||||
|
||||
if (settings.hide_on_process_start !== behavior.minimize_app) {
|
||||
settings.hide_on_process_start = behavior.minimize_app
|
||||
settingsChanged = true
|
||||
}
|
||||
if (settings.toggle_sidebar !== behavior.hide_right_sidebar) {
|
||||
settings.toggle_sidebar = behavior.hide_right_sidebar
|
||||
settingsChanged = true
|
||||
}
|
||||
if (settings.hide_nametag_skins_page !== behavior.hide_nametag) {
|
||||
settings.hide_nametag_skins_page = behavior.hide_nametag
|
||||
settingsChanged = true
|
||||
}
|
||||
for (const [flag, value] of Object.entries(behaviorFeatureFlags)) {
|
||||
if (settings.feature_flags[flag] !== value) {
|
||||
settings.feature_flags[flag] = value
|
||||
settingsChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsChanged) {
|
||||
await setSettings(settings)
|
||||
}
|
||||
})
|
||||
.catch(handleError)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
async function validateSession(sessionToken) {
|
||||
try {
|
||||
const response = await tauriFetch(`${config.labrinthBaseUrl}/v2/user`, {
|
||||
@@ -1523,9 +1602,8 @@ async function downloadUpdate(versionToDownload) {
|
||||
.then(() => {
|
||||
downloading.value = false
|
||||
finishedDownloading.value = true
|
||||
unlistenUpdateDownload?.().then(() => {
|
||||
unlistenUpdateDownload = null
|
||||
})
|
||||
unlistenUpdateDownload?.()
|
||||
unlistenUpdateDownload = null
|
||||
console.log('Finished downloading!')
|
||||
markAppUpdateActionable(versionToDownload.version, 'downloaded')
|
||||
scheduleDelayedUpdatePopup()
|
||||
@@ -1642,7 +1720,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<div
|
||||
v-if="stateInitialized"
|
||||
class="app-grid-layout relative"
|
||||
:class="{ 'disable-advanced-rendering': !themeStore.advancedRendering }"
|
||||
:class="{ 'disable-advanced-rendering': !appTheme.advancedRendering }"
|
||||
>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
@@ -1828,7 +1906,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
</div>
|
||||
<section data-tauri-drag-region class="flex shrink-0 ml-auto items-center">
|
||||
<IconButton
|
||||
v-if="!forceSidebar && themeStore.toggleSidebar"
|
||||
v-if="!forceSidebar && appSettings.toggleSidebar"
|
||||
:type="sidebarToggled ? 'base' : 'quiet'"
|
||||
:label="formatMessage(messages.nextImage)"
|
||||
class="mr-3 transition-transform"
|
||||
@@ -1851,7 +1929,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
class="app-contents"
|
||||
:class="{
|
||||
'sidebar-enabled': sidebarVisible,
|
||||
'disable-advanced-rendering': !themeStore.advancedRendering,
|
||||
'disable-advanced-rendering': !appTheme.advancedRendering,
|
||||
}"
|
||||
>
|
||||
<div class="app-viewport flex-grow router-view">
|
||||
@@ -1867,7 +1945,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<LoadingBar position="absolute" />
|
||||
</div>
|
||||
<div
|
||||
v-if="themeStore.featureFlags.page_path"
|
||||
v-if="appSettings.featureFlags.page_path"
|
||||
class="absolute bottom-0 left-0 m-2 bg-tooltip-bg text-tooltip-text font-semibold rounded-full px-2 py-1 text-xs z-50"
|
||||
>
|
||||
{{ route.fullPath }}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -20,6 +20,7 @@
|
||||
<div class="flex gap-2 w-full min-w-0">
|
||||
<Avatar
|
||||
size="36px"
|
||||
disable-conditional-icon-padding
|
||||
:src="
|
||||
selectedAccount
|
||||
? avatarUrl
|
||||
@@ -46,7 +47,11 @@
|
||||
class="w-5 h-5 text-brand shrink-0"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="w-5 h-5 text-secondary shrink-0" />
|
||||
<Avatar :src="getAccountAvatarUrl(account)" size="24px" />
|
||||
<Avatar
|
||||
:src="getAccountAvatarUrl(account)"
|
||||
size="24px"
|
||||
disable-conditional-icon-padding
|
||||
/>
|
||||
<p
|
||||
class="m-0 truncate min-w-0"
|
||||
:class="
|
||||
@@ -107,6 +112,7 @@ import type { Ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import {
|
||||
get_default_user,
|
||||
@@ -118,7 +124,6 @@ import {
|
||||
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import type { Skin } from '@/helpers/skins'
|
||||
import { get_available_skins } from '@/helpers/skins'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="shown"
|
||||
ref="contextMenu"
|
||||
class="context-menu"
|
||||
:style="{
|
||||
left: left,
|
||||
top: top,
|
||||
}"
|
||||
>
|
||||
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
|
||||
<hr v-if="option.type === 'divider'" class="divider" />
|
||||
<div
|
||||
v-else-if="!(isInstanceLink(item) && option.name === `add_content`)"
|
||||
class="item clickable"
|
||||
:class="[option.color ?? 'base']"
|
||||
>
|
||||
<slot :name="option.name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
const emit = defineEmits(['menu-closed', 'option-clicked'])
|
||||
|
||||
const item = ref(null)
|
||||
const contextMenu = ref(null)
|
||||
const options = ref([])
|
||||
const left = ref('0px')
|
||||
const top = ref('0px')
|
||||
const shown = ref(false)
|
||||
const contextMenuId = Symbol()
|
||||
const contextMenuOpenEvent = 'modrinth-context-menu-open'
|
||||
|
||||
const hideContextMenu = () => {
|
||||
shown.value = false
|
||||
emit('menu-closed')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showMenu: (event, passedItem, passedOptions) => {
|
||||
window.dispatchEvent(new CustomEvent(contextMenuOpenEvent, { detail: contextMenuId }))
|
||||
|
||||
item.value = passedItem
|
||||
options.value = passedOptions
|
||||
|
||||
// show to get dimensions
|
||||
shown.value = true
|
||||
|
||||
// then, adjust position if overflowing
|
||||
nextTick(() => {
|
||||
const menuWidth = contextMenu.value?.clientWidth || 200
|
||||
const menuHeight = contextMenu.value?.clientHeight || 100
|
||||
const minFromEdge = 10
|
||||
|
||||
if (event.pageX + menuWidth + minFromEdge >= window.innerWidth) {
|
||||
left.value = Math.max(minFromEdge, event.pageX - menuWidth - minFromEdge) + 'px'
|
||||
} else {
|
||||
left.value = event.pageX + minFromEdge + 'px'
|
||||
}
|
||||
|
||||
if (event.pageY + menuHeight + minFromEdge >= window.innerHeight) {
|
||||
top.value = Math.max(minFromEdge, event.pageY - menuHeight - minFromEdge) + 'px'
|
||||
} else {
|
||||
top.value = event.pageY + minFromEdge + 'px'
|
||||
}
|
||||
})
|
||||
},
|
||||
hideMenu: hideContextMenu,
|
||||
})
|
||||
|
||||
const isInstanceLink = (item) => {
|
||||
if (item.instance != undefined && item.instance.link) {
|
||||
return true
|
||||
} else if (item != undefined && item.link) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const optionClicked = (option) => {
|
||||
emit('option-clicked', {
|
||||
item: item.value,
|
||||
option: option,
|
||||
})
|
||||
hideContextMenu()
|
||||
}
|
||||
|
||||
const onEscKeyRelease = (event) => {
|
||||
if (event.keyCode === 27) {
|
||||
hideContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
const handleContextMenuOpen = (event) => {
|
||||
if (shown.value && event.detail !== contextMenuId) {
|
||||
hideContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||
if (
|
||||
contextMenu.value &&
|
||||
contextMenu.value.$el !== event.target &&
|
||||
!elements.includes(contextMenu.value.$el)
|
||||
) {
|
||||
hideContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener(contextMenuOpenEvent, handleContextMenuOpen)
|
||||
document.body.addEventListener('keyup', onEscKeyRelease)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener(contextMenuOpenEvent, handleContextMenuOpen)
|
||||
document.removeEventListener('keyup', onEscKeyRelease)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.context-menu {
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-floating);
|
||||
border: 1px solid var(--color-divider);
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
z-index: 1000000;
|
||||
overflow: hidden;
|
||||
padding: var(--gap-sm);
|
||||
|
||||
.item {
|
||||
align-items: center;
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
padding: var(--gap-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
:deep(svg) {
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
:deep(svg) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.base {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: var(--color-brand);
|
||||
color: var(--color-accent-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-accent-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.contrast {
|
||||
background-color: var(--color-orange);
|
||||
color: var(--color-accent-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: 1px solid var(--color-divider);
|
||||
margin: var(--gap-sm);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -20,11 +20,11 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { install_existing_instance } from '@/helpers/install'
|
||||
import { cancel_directory_change } from '@/helpers/settings.ts'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
|
||||
import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import { showInstanceInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getInstanceIconUrl, list } from '@/helpers/instance'
|
||||
import { instanceKeys } from '@/pages/instance/query-options'
|
||||
|
||||
const ITEM_SIZE = 52
|
||||
const APPROX_USED_VERTICAL_SPACE = 513 // doesn't need to be exact lol just close enough so there's a little gap and no overflow
|
||||
const APPROX_USED_VERTICAL_SPACE = 475 // doesn't need to be exact lol just close enough so there's a little gap and no overflow
|
||||
const STORAGE_KEY = 'modrinth-quick-instance-count'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
@@ -39,17 +39,17 @@ import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get as getSettings } from '@/helpers/settings.ts'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const nativeDecorations = ref(true)
|
||||
const isMaximized = ref(false)
|
||||
const os = ref('')
|
||||
|
||||
const alwaysShowAppControls = computed(() => themeStore.getFeatureFlag('always_show_app_controls'))
|
||||
const alwaysShowAppControls = computed(() => appSettings.getFeatureFlag('always_show_app_controls'))
|
||||
|
||||
const showControls = computed(
|
||||
() =>
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<Teleport to="#teleports">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="shown && !isMobileActiveSubmenu"
|
||||
ref="contextMenu"
|
||||
class="context-menu"
|
||||
:style="menuStyle"
|
||||
role="menu"
|
||||
@keydown="handleMenuKeydown"
|
||||
@mousemove="(event) => handleMenuMouseMove(event, 'menu')"
|
||||
>
|
||||
<template v-for="(option, index) in options" :key="index">
|
||||
<hr v-if="isDivider(option)" class="divider" />
|
||||
<button
|
||||
v-else-if="isOptionVisible(option)"
|
||||
:ref="(element) => setOptionButtonRef(index, element)"
|
||||
type="button"
|
||||
class="item clickable"
|
||||
:class="[
|
||||
option.color ?? 'base',
|
||||
{
|
||||
active: index === activeOptionIndex,
|
||||
'safe-triangle-hover': index === pendingOptionIndex,
|
||||
},
|
||||
]"
|
||||
role="menuitem"
|
||||
:aria-haspopup="hasChildren(option) ? 'menu' : undefined"
|
||||
:aria-expanded="hasChildren(option) ? index === activeOptionIndex : undefined"
|
||||
@click.stop="handleOptionClick(option, index)"
|
||||
@focus="handleOptionFocus(option, index)"
|
||||
@mouseenter="handleOptionMouseEnter(option, index)"
|
||||
>
|
||||
<span class="item-content"><slot :name="option.name" /></span>
|
||||
<ChevronRightIcon v-if="hasChildren(option)" class="submenu-chevron" />
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</transition>
|
||||
</Teleport>
|
||||
|
||||
<Teleport to="#teleports">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="shown && activeOption && hasSubmenuPosition"
|
||||
ref="submenu"
|
||||
class="context-menu submenu"
|
||||
:style="submenuStyle"
|
||||
role="menu"
|
||||
@keydown="handleSubmenuKeydown"
|
||||
@mouseenter="handleSubmenuMouseEnter"
|
||||
@mouseleave="isCursorInsideSubmenu = false"
|
||||
@mousemove="(event) => handleMenuMouseMove(event, 'submenu')"
|
||||
>
|
||||
<button
|
||||
v-if="isMobileSubmenuLayout"
|
||||
type="button"
|
||||
class="item clickable base mobile-back"
|
||||
@click.stop="returnToMenu"
|
||||
>
|
||||
<ChevronLeftIcon class="submenu-chevron" />
|
||||
<span class="item-content"><slot :name="activeOption.name" /></span>
|
||||
</button>
|
||||
<template v-for="(option, index) in activeOption.children" :key="index">
|
||||
<hr v-if="isDivider(option)" class="divider" />
|
||||
<button
|
||||
v-else-if="isOptionVisible(option)"
|
||||
:ref="(element) => setSubmenuButtonRef(index, element)"
|
||||
type="button"
|
||||
class="item clickable"
|
||||
:class="option.color ?? 'base'"
|
||||
role="menuitem"
|
||||
@click.stop="optionClicked(option.name)"
|
||||
>
|
||||
<span class="item-content"><slot :name="option.name" /></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ContextMenuEmit } from './types'
|
||||
import { useContextMenu } from './use-context-menu'
|
||||
import { hasChildren, isDivider } from './utils'
|
||||
|
||||
const emit = defineEmits<ContextMenuEmit>()
|
||||
|
||||
const {
|
||||
shown,
|
||||
contextMenu,
|
||||
submenu,
|
||||
options,
|
||||
menuStyle,
|
||||
submenuStyle,
|
||||
activeOption,
|
||||
activeOptionIndex,
|
||||
pendingOptionIndex,
|
||||
hasSubmenuPosition,
|
||||
isMobileSubmenuLayout,
|
||||
isMobileActiveSubmenu,
|
||||
isCursorInsideSubmenu,
|
||||
isOptionVisible,
|
||||
setOptionButtonRef,
|
||||
setSubmenuButtonRef,
|
||||
showMenu,
|
||||
hideMenu,
|
||||
handleOptionClick,
|
||||
optionClicked,
|
||||
handleOptionFocus,
|
||||
handleOptionMouseEnter,
|
||||
returnToMenu,
|
||||
handleSubmenuMouseEnter,
|
||||
handleMenuMouseMove,
|
||||
handleMenuKeydown,
|
||||
handleSubmenuKeydown,
|
||||
} = useContextMenu(emit)
|
||||
|
||||
defineExpose({ showMenu, hideMenu })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.context-menu {
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-floating);
|
||||
border: 1px solid var(--color-divider);
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
z-index: 1000000;
|
||||
overflow: hidden;
|
||||
padding: var(--gap-sm);
|
||||
min-width: 12rem;
|
||||
max-width: calc(100vw - 1.25rem);
|
||||
|
||||
&.submenu {
|
||||
z-index: 1000001;
|
||||
}
|
||||
|
||||
.item {
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: 500;
|
||||
gap: var(--gap-sm);
|
||||
justify-content: space-between;
|
||||
padding: var(--gap-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
.item-content {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.submenu-chevron {
|
||||
color: var(--color-text-secondary);
|
||||
flex-shrink: 0;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
margin-right: -0.25rem;
|
||||
}
|
||||
|
||||
:deep(svg) {
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
&:hover:not(.safe-triangle-hover),
|
||||
&:active,
|
||||
&:focus-visible,
|
||||
&.active {
|
||||
:deep(svg) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.base {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: var(--color-brand);
|
||||
color: var(--color-accent-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-accent-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.contrast {
|
||||
background-color: var(--color-orange);
|
||||
color: var(--color-accent-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-back {
|
||||
border-bottom: 1px solid var(--color-divider);
|
||||
border-radius: 0;
|
||||
margin: calc(var(--gap-sm) * -1) calc(var(--gap-sm) * -1) var(--gap-sm);
|
||||
width: calc(100% + var(--gap-sm) * 2);
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: 1px solid var(--color-divider);
|
||||
margin: var(--gap-sm);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
|
||||
export type ContextMenuDivider = {
|
||||
type: string
|
||||
}
|
||||
|
||||
export type ContextMenuAction = {
|
||||
name: string
|
||||
color?: string
|
||||
children?: ContextMenuOption[]
|
||||
}
|
||||
|
||||
export type ContextMenuParentAction = ContextMenuAction & {
|
||||
children: ContextMenuOption[]
|
||||
}
|
||||
|
||||
export type ContextMenuOption = ContextMenuDivider | ContextMenuAction
|
||||
|
||||
export type ContextMenuSelection = {
|
||||
item: unknown
|
||||
option: string
|
||||
}
|
||||
|
||||
export type ContextMenuEmit = {
|
||||
(event: 'menu-closed'): void
|
||||
(event: 'option-clicked', selection: ContextMenuSelection): void
|
||||
}
|
||||
|
||||
export type Point = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export type ViewportRect = {
|
||||
width: number
|
||||
height: number
|
||||
offsetTop: number
|
||||
offsetLeft: number
|
||||
}
|
||||
|
||||
export type ButtonRefElement = Element | ComponentPublicInstance | null
|
||||
@@ -0,0 +1,170 @@
|
||||
import type { CSSProperties, Ref } from 'vue'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
import type { ContextMenuParentAction, Point, ViewportRect } from './types'
|
||||
|
||||
const MENU_GAP = 8
|
||||
const VIEWPORT_MARGIN = 10
|
||||
|
||||
type ContextMenuPositionOptions = {
|
||||
shown: Ref<boolean>
|
||||
activeOption: Ref<ContextMenuParentAction | null>
|
||||
activeOptionIndex: Ref<number | null>
|
||||
isMobileSubmenuLayout: Ref<boolean>
|
||||
contextMenu: Ref<HTMLElement | null>
|
||||
submenu: Ref<HTMLElement | null>
|
||||
optionButtonRefs: Map<number, HTMLElement>
|
||||
}
|
||||
|
||||
export function useContextMenuPosition({
|
||||
shown,
|
||||
activeOption,
|
||||
activeOptionIndex,
|
||||
isMobileSubmenuLayout,
|
||||
contextMenu,
|
||||
submenu,
|
||||
optionButtonRefs,
|
||||
}: ContextMenuPositionOptions) {
|
||||
const menuStyle = ref<CSSProperties>({ left: '0px', top: '0px' })
|
||||
const menuAnchor = ref<Point>({ x: 0, y: 0 })
|
||||
const submenuPosition = ref<Point>({ x: 0, y: 0 })
|
||||
const hasSubmenuPosition = ref(false)
|
||||
let positionRafId: number | null = null
|
||||
|
||||
const submenuStyle = computed<CSSProperties>(() => {
|
||||
if (isMobileSubmenuLayout.value) return menuStyle.value
|
||||
|
||||
return {
|
||||
left: `${submenuPosition.value.x}px`,
|
||||
top: `${submenuPosition.value.y}px`,
|
||||
}
|
||||
})
|
||||
|
||||
function updateMenuPosition(x: number, y: number) {
|
||||
if (!contextMenu.value) return
|
||||
|
||||
const viewport = getViewportRect()
|
||||
const menuRect = contextMenu.value.getBoundingClientRect()
|
||||
const viewportLeft = viewport.offsetLeft
|
||||
const viewportTop = viewport.offsetTop
|
||||
const viewportRight = viewport.offsetLeft + viewport.width
|
||||
const viewportBottom = viewport.offsetTop + viewport.height
|
||||
const anchorX = x + viewport.offsetLeft
|
||||
const anchorY = y + viewport.offsetTop
|
||||
const left = Math.min(
|
||||
Math.max(viewportLeft + VIEWPORT_MARGIN, anchorX + MENU_GAP),
|
||||
Math.max(viewportLeft + VIEWPORT_MARGIN, viewportRight - menuRect.width - VIEWPORT_MARGIN),
|
||||
)
|
||||
const top = Math.min(
|
||||
Math.max(viewportTop + VIEWPORT_MARGIN, anchorY + MENU_GAP),
|
||||
Math.max(viewportTop + VIEWPORT_MARGIN, viewportBottom - menuRect.height - VIEWPORT_MARGIN),
|
||||
)
|
||||
|
||||
menuStyle.value = { left: `${left}px`, top: `${top}px` }
|
||||
if (activeOption.value) scheduleSubmenuPositionUpdate()
|
||||
}
|
||||
|
||||
function updateSubmenuPosition() {
|
||||
if (!activeOption.value || activeOptionIndex.value === null) return false
|
||||
|
||||
if (isMobileSubmenuLayout.value) {
|
||||
hasSubmenuPosition.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
const optionButton = optionButtonRefs.get(activeOptionIndex.value)
|
||||
if (!optionButton || !contextMenu.value) return false
|
||||
|
||||
const viewport = getViewportRect()
|
||||
const buttonRect = optionButton.getBoundingClientRect()
|
||||
const menuRect = contextMenu.value.getBoundingClientRect()
|
||||
const submenuRect = submenu.value?.getBoundingClientRect()
|
||||
const submenuWidth = submenuRect?.width ?? menuRect.width
|
||||
const submenuHeight = submenuRect?.height ?? 100
|
||||
const direction = getSubmenuOpenDirection(menuRect, submenuWidth, viewport)
|
||||
const preferredLeft =
|
||||
direction === 'right'
|
||||
? buttonRect.right + MENU_GAP
|
||||
: buttonRect.left - submenuWidth - MENU_GAP
|
||||
const minLeft = viewport.offsetLeft + VIEWPORT_MARGIN
|
||||
const maxLeft = Math.max(
|
||||
minLeft,
|
||||
viewport.offsetLeft + viewport.width - submenuWidth - VIEWPORT_MARGIN,
|
||||
)
|
||||
const minTop = viewport.offsetTop + VIEWPORT_MARGIN
|
||||
const maxTop = Math.max(
|
||||
minTop,
|
||||
viewport.offsetTop + viewport.height - submenuHeight - VIEWPORT_MARGIN,
|
||||
)
|
||||
|
||||
submenuPosition.value = {
|
||||
x: Math.min(Math.max(minLeft, preferredLeft), maxLeft),
|
||||
y: Math.min(Math.max(minTop, buttonRect.top), maxTop),
|
||||
}
|
||||
hasSubmenuPosition.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
function scheduleSubmenuPositionUpdate(retries = 8) {
|
||||
nextTick(() => {
|
||||
if (!shown.value || !activeOption.value) return
|
||||
|
||||
const hasRenderedSubmenu = submenu.value !== null
|
||||
if (updateSubmenuPosition()) {
|
||||
if (!hasRenderedSubmenu) nextTick(updateSubmenuPosition)
|
||||
return
|
||||
}
|
||||
|
||||
if (retries > 0) setTimeout(() => scheduleSubmenuPositionUpdate(retries - 1), 0)
|
||||
})
|
||||
}
|
||||
|
||||
function schedulePositionUpdate() {
|
||||
if (!shown.value || positionRafId !== null) return
|
||||
|
||||
positionRafId = window.requestAnimationFrame(() => {
|
||||
positionRafId = null
|
||||
updateMenuPosition(menuAnchor.value.x, menuAnchor.value.y)
|
||||
})
|
||||
}
|
||||
|
||||
function cancelPositionUpdate() {
|
||||
if (positionRafId !== null) window.cancelAnimationFrame(positionRafId)
|
||||
}
|
||||
|
||||
return {
|
||||
menuStyle,
|
||||
menuAnchor,
|
||||
submenuStyle,
|
||||
hasSubmenuPosition,
|
||||
updateMenuPosition,
|
||||
scheduleSubmenuPositionUpdate,
|
||||
schedulePositionUpdate,
|
||||
cancelPositionUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
function getViewportRect(): ViewportRect {
|
||||
const visualViewport = window.visualViewport
|
||||
return {
|
||||
width: visualViewport?.width ?? window.innerWidth,
|
||||
height: visualViewport?.height ?? window.innerHeight,
|
||||
offsetTop: visualViewport?.offsetTop ?? 0,
|
||||
offsetLeft: visualViewport?.offsetLeft ?? 0,
|
||||
}
|
||||
}
|
||||
|
||||
function getSubmenuOpenDirection(
|
||||
menuRect: DOMRect,
|
||||
submenuWidth: number,
|
||||
viewport: ViewportRect,
|
||||
): 'left' | 'right' {
|
||||
const viewportLeft = viewport.offsetLeft
|
||||
const viewportRight = viewport.offsetLeft + viewport.width
|
||||
const rightSpace = viewportRight - menuRect.right - MENU_GAP - VIEWPORT_MARGIN
|
||||
const leftSpace = menuRect.left - viewportLeft - MENU_GAP - VIEWPORT_MARGIN
|
||||
|
||||
if (rightSpace >= submenuWidth) return 'right'
|
||||
if (leftSpace >= submenuWidth) return 'left'
|
||||
return rightSpace >= leftSpace ? 'right' : 'left'
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import type {
|
||||
ButtonRefElement,
|
||||
ContextMenuAction,
|
||||
ContextMenuEmit,
|
||||
ContextMenuOption,
|
||||
ContextMenuParentAction,
|
||||
Point,
|
||||
} from './types'
|
||||
import { useContextMenuPosition } from './use-context-menu-position'
|
||||
import {
|
||||
focusRelativeButton,
|
||||
getFocusableButtons,
|
||||
hasChildren,
|
||||
isAction,
|
||||
isInstanceLink,
|
||||
isPointInTriangle,
|
||||
} from './utils'
|
||||
|
||||
const MOBILE_SUBMENU_LAYOUT_QUERY = '(pointer: coarse), (max-width: 800px)'
|
||||
const CONTEXT_MENU_OPEN_EVENT = 'modrinth-context-menu-open'
|
||||
|
||||
export function useContextMenu(emit: ContextMenuEmit) {
|
||||
const item = ref<unknown>(null)
|
||||
const contextMenu = ref<HTMLElement | null>(null)
|
||||
const submenu = ref<HTMLElement | null>(null)
|
||||
const options = ref<ContextMenuOption[]>([])
|
||||
const shown = ref(false)
|
||||
const activeOptionIndex = ref<number | null>(null)
|
||||
const pendingOptionIndex = ref<number | null>(null)
|
||||
const isCursorInsideSubmenu = ref(false)
|
||||
const isMobileSubmenuLayout = ref(false)
|
||||
const lastMousePosition = ref<Point | null>(null)
|
||||
const contextMenuId = Symbol()
|
||||
const optionButtonRefs = new Map<number, HTMLElement>()
|
||||
const submenuButtonRefs = new Map<number, HTMLElement>()
|
||||
let previousMousePosition: Point | null = null
|
||||
let pendingOptionTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
let mobileSubmenuMediaQuery: MediaQueryList | null = null
|
||||
|
||||
const activeOption = computed<ContextMenuParentAction | null>(() => {
|
||||
if (activeOptionIndex.value === null) return null
|
||||
const option = options.value[activeOptionIndex.value]
|
||||
return option && isAction(option) && hasChildren(option) ? option : null
|
||||
})
|
||||
|
||||
const {
|
||||
menuStyle,
|
||||
menuAnchor,
|
||||
submenuStyle,
|
||||
hasSubmenuPosition,
|
||||
updateMenuPosition,
|
||||
scheduleSubmenuPositionUpdate,
|
||||
schedulePositionUpdate,
|
||||
cancelPositionUpdate,
|
||||
} = useContextMenuPosition({
|
||||
shown,
|
||||
activeOption,
|
||||
activeOptionIndex,
|
||||
isMobileSubmenuLayout,
|
||||
contextMenu,
|
||||
submenu,
|
||||
optionButtonRefs,
|
||||
})
|
||||
|
||||
const isMobileActiveSubmenu = computed(
|
||||
() => isMobileSubmenuLayout.value && activeOption.value !== null && hasSubmenuPosition.value,
|
||||
)
|
||||
|
||||
function isOptionVisible(option: ContextMenuOption): option is ContextMenuAction {
|
||||
return isAction(option) && !(isInstanceLink(item.value) && option.name === 'add_content')
|
||||
}
|
||||
|
||||
function setOptionButtonRef(index: number, element: ButtonRefElement) {
|
||||
setButtonRef(optionButtonRefs, index, element)
|
||||
}
|
||||
|
||||
function setSubmenuButtonRef(index: number, element: ButtonRefElement) {
|
||||
setButtonRef(submenuButtonRefs, index, element)
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
if (!shown.value) return
|
||||
|
||||
shown.value = false
|
||||
deactivateSubmenu()
|
||||
emit('menu-closed')
|
||||
}
|
||||
|
||||
function showMenu(event: MouseEvent, passedItem: unknown, passedOptions: ContextMenuOption[]) {
|
||||
window.dispatchEvent(new CustomEvent(CONTEXT_MENU_OPEN_EVENT, { detail: contextMenuId }))
|
||||
|
||||
item.value = passedItem
|
||||
options.value = passedOptions
|
||||
menuAnchor.value = { x: event.clientX, y: event.clientY }
|
||||
shown.value = true
|
||||
deactivateSubmenu()
|
||||
syncMobileSubmenuLayout()
|
||||
nextTick(() => updateMenuPosition(event.clientX, event.clientY))
|
||||
}
|
||||
|
||||
function handleOptionClick(option: ContextMenuAction, index: number) {
|
||||
if (hasChildren(option)) {
|
||||
activateSubmenu(index)
|
||||
return
|
||||
}
|
||||
|
||||
optionClicked(option.name)
|
||||
}
|
||||
|
||||
function optionClicked(option: string) {
|
||||
emit('option-clicked', { item: item.value, option })
|
||||
hideMenu()
|
||||
}
|
||||
|
||||
function handleOptionFocus(option: ContextMenuAction, index: number) {
|
||||
if (hasChildren(option) && !isMobileSubmenuLayout.value) {
|
||||
activateSubmenu(index)
|
||||
} else if (!hasChildren(option)) {
|
||||
deactivateSubmenu()
|
||||
}
|
||||
}
|
||||
|
||||
function handleOptionMouseEnter(option: ContextMenuAction, index: number) {
|
||||
if (isMobileSubmenuLayout.value) return
|
||||
|
||||
if (activeOptionIndex.value === null) {
|
||||
if (hasChildren(option)) activateSubmenu(index)
|
||||
return
|
||||
}
|
||||
|
||||
if (activeOptionIndex.value === index) return
|
||||
|
||||
if (!isCursorAimingAtSubmenu(lastMousePosition.value, previousMousePosition)) {
|
||||
commitHoveredOption(index)
|
||||
return
|
||||
}
|
||||
|
||||
pendingOptionIndex.value = index
|
||||
clearPendingOptionTimeout()
|
||||
pendingOptionTimeout = setTimeout(() => {
|
||||
if (pendingOptionIndex.value !== index) return
|
||||
if (isCursorInsideSubmenu.value) {
|
||||
pendingOptionIndex.value = null
|
||||
return
|
||||
}
|
||||
|
||||
commitHoveredOption(index)
|
||||
}, 180)
|
||||
}
|
||||
|
||||
function commitHoveredOption(index: number) {
|
||||
const option = options.value[index]
|
||||
if (option && hasChildren(option)) {
|
||||
activateSubmenu(index)
|
||||
} else {
|
||||
deactivateSubmenu()
|
||||
}
|
||||
}
|
||||
|
||||
function activateSubmenu(index: number) {
|
||||
clearPendingOptionTimeout()
|
||||
pendingOptionIndex.value = null
|
||||
activeOptionIndex.value = index
|
||||
hasSubmenuPosition.value = false
|
||||
scheduleSubmenuPositionUpdate()
|
||||
}
|
||||
|
||||
function deactivateSubmenu() {
|
||||
clearPendingOptionTimeout()
|
||||
activeOptionIndex.value = null
|
||||
pendingOptionIndex.value = null
|
||||
hasSubmenuPosition.value = false
|
||||
isCursorInsideSubmenu.value = false
|
||||
lastMousePosition.value = null
|
||||
previousMousePosition = null
|
||||
}
|
||||
|
||||
function returnToMenu() {
|
||||
const previousIndex = activeOptionIndex.value
|
||||
deactivateSubmenu()
|
||||
nextTick(() => {
|
||||
if (previousIndex !== null) optionButtonRefs.get(previousIndex)?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
function handleSubmenuMouseEnter() {
|
||||
isCursorInsideSubmenu.value = true
|
||||
clearPendingOptionTimeout()
|
||||
pendingOptionIndex.value = null
|
||||
}
|
||||
|
||||
function handleMenuMouseMove(event: MouseEvent, source: 'menu' | 'submenu') {
|
||||
previousMousePosition = lastMousePosition.value
|
||||
lastMousePosition.value = { x: event.clientX, y: event.clientY }
|
||||
|
||||
if (
|
||||
source === 'menu' &&
|
||||
pendingOptionIndex.value !== null &&
|
||||
!isCursorAimingAtSubmenu(lastMousePosition.value, previousMousePosition)
|
||||
) {
|
||||
commitHoveredOption(pendingOptionIndex.value)
|
||||
}
|
||||
}
|
||||
|
||||
function isCursorAimingAtSubmenu(cursor: Point | null, origin: Point | null) {
|
||||
const submenuRect = submenu.value?.getBoundingClientRect()
|
||||
if (!submenuRect || !cursor || !origin) return false
|
||||
|
||||
const submenuTargetX =
|
||||
origin.x <= submenuRect.left
|
||||
? submenuRect.left
|
||||
: origin.x >= submenuRect.right
|
||||
? submenuRect.right
|
||||
: cursor.x <= submenuRect.left
|
||||
? submenuRect.left
|
||||
: submenuRect.right
|
||||
const upperTarget = { x: submenuTargetX, y: submenuRect.top - 20 }
|
||||
const lowerTarget = { x: submenuTargetX, y: submenuRect.bottom + 20 }
|
||||
|
||||
return isPointInTriangle(cursor, origin, upperTarget, lowerTarget)
|
||||
}
|
||||
|
||||
function handleMenuKeydown(event: KeyboardEvent) {
|
||||
const buttons = getFocusableButtons(optionButtonRefs)
|
||||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
focusRelativeButton(buttons, event.key === 'ArrowDown' ? 1 : -1)
|
||||
} else if (event.key === 'Home' || event.key === 'End') {
|
||||
event.preventDefault()
|
||||
buttons[event.key === 'Home' ? 0 : buttons.length - 1]?.focus()
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
const focusedIndex = [...optionButtonRefs.entries()].find(
|
||||
([, button]) => button === document.activeElement,
|
||||
)?.[0]
|
||||
const focusedOption = focusedIndex === undefined ? undefined : options.value[focusedIndex]
|
||||
if (focusedIndex !== undefined && focusedOption && hasChildren(focusedOption)) {
|
||||
event.preventDefault()
|
||||
activateSubmenu(focusedIndex)
|
||||
nextTick(() => getFocusableButtons(submenuButtonRefs)[0]?.focus())
|
||||
}
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
hideMenu()
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmenuKeydown(event: KeyboardEvent) {
|
||||
const buttons = getFocusableButtons(submenuButtonRefs)
|
||||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
focusRelativeButton(buttons, event.key === 'ArrowDown' ? 1 : -1)
|
||||
} else if (event.key === 'Home' || event.key === 'End') {
|
||||
event.preventDefault()
|
||||
buttons[event.key === 'Home' ? 0 : buttons.length - 1]?.focus()
|
||||
} else if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault()
|
||||
returnToMenu()
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
hideMenu()
|
||||
}
|
||||
}
|
||||
|
||||
function syncMobileSubmenuLayout(event?: MediaQueryListEvent) {
|
||||
isMobileSubmenuLayout.value = event?.matches ?? mobileSubmenuMediaQuery?.matches ?? false
|
||||
}
|
||||
|
||||
function handleDocumentKeydown(event: KeyboardEvent) {
|
||||
if (shown.value && event.key === 'Escape') hideMenu()
|
||||
}
|
||||
|
||||
function handleContextMenuOpen(event: Event) {
|
||||
if (shown.value && event instanceof CustomEvent && event.detail !== contextMenuId) hideMenu()
|
||||
}
|
||||
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const target = event.target
|
||||
if (!(target instanceof Node)) return
|
||||
if (!contextMenu.value?.contains(target) && !submenu.value?.contains(target)) hideMenu()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mobileSubmenuMediaQuery = window.matchMedia(MOBILE_SUBMENU_LAYOUT_QUERY)
|
||||
syncMobileSubmenuLayout()
|
||||
mobileSubmenuMediaQuery.addEventListener('change', syncMobileSubmenuLayout)
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
window.addEventListener('resize', schedulePositionUpdate)
|
||||
window.addEventListener('scroll', schedulePositionUpdate, true)
|
||||
window.visualViewport?.addEventListener('scroll', schedulePositionUpdate)
|
||||
window.visualViewport?.addEventListener('resize', schedulePositionUpdate)
|
||||
window.addEventListener(CONTEXT_MENU_OPEN_EVENT, handleContextMenuOpen)
|
||||
document.addEventListener('keydown', handleDocumentKeydown)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearPendingOptionTimeout()
|
||||
cancelPositionUpdate()
|
||||
mobileSubmenuMediaQuery?.removeEventListener('change', syncMobileSubmenuLayout)
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
window.removeEventListener('resize', schedulePositionUpdate)
|
||||
window.removeEventListener('scroll', schedulePositionUpdate, true)
|
||||
window.visualViewport?.removeEventListener('scroll', schedulePositionUpdate)
|
||||
window.visualViewport?.removeEventListener('resize', schedulePositionUpdate)
|
||||
window.removeEventListener(CONTEXT_MENU_OPEN_EVENT, handleContextMenuOpen)
|
||||
document.removeEventListener('keydown', handleDocumentKeydown)
|
||||
})
|
||||
|
||||
function clearPendingOptionTimeout() {
|
||||
if (!pendingOptionTimeout) return
|
||||
clearTimeout(pendingOptionTimeout)
|
||||
pendingOptionTimeout = null
|
||||
}
|
||||
|
||||
return {
|
||||
shown,
|
||||
contextMenu,
|
||||
submenu,
|
||||
options,
|
||||
menuStyle,
|
||||
submenuStyle,
|
||||
activeOption,
|
||||
activeOptionIndex,
|
||||
pendingOptionIndex,
|
||||
hasSubmenuPosition,
|
||||
isMobileSubmenuLayout,
|
||||
isMobileActiveSubmenu,
|
||||
isCursorInsideSubmenu,
|
||||
isOptionVisible,
|
||||
setOptionButtonRef,
|
||||
setSubmenuButtonRef,
|
||||
showMenu,
|
||||
hideMenu,
|
||||
handleOptionClick,
|
||||
optionClicked,
|
||||
handleOptionFocus,
|
||||
handleOptionMouseEnter,
|
||||
returnToMenu,
|
||||
handleSubmenuMouseEnter,
|
||||
handleMenuMouseMove,
|
||||
handleMenuKeydown,
|
||||
handleSubmenuKeydown,
|
||||
}
|
||||
}
|
||||
|
||||
function setButtonRef(
|
||||
buttonRefs: Map<number, HTMLElement>,
|
||||
index: number,
|
||||
element: ButtonRefElement,
|
||||
) {
|
||||
if (element instanceof HTMLElement) {
|
||||
buttonRefs.set(index, element)
|
||||
} else {
|
||||
buttonRefs.delete(index)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import type {
|
||||
ContextMenuAction,
|
||||
ContextMenuDivider,
|
||||
ContextMenuOption,
|
||||
ContextMenuParentAction,
|
||||
Point,
|
||||
} from './types'
|
||||
|
||||
export function isAction(option: ContextMenuOption): option is ContextMenuAction {
|
||||
return 'name' in option
|
||||
}
|
||||
|
||||
export function isDivider(option: ContextMenuOption): option is ContextMenuDivider {
|
||||
return 'type' in option && option.type === 'divider'
|
||||
}
|
||||
|
||||
export function hasChildren(option: ContextMenuOption): option is ContextMenuParentAction {
|
||||
return isAction(option) && Boolean(option.children?.length)
|
||||
}
|
||||
|
||||
export function isInstanceLink(value: unknown) {
|
||||
if (!value || typeof value !== 'object') return false
|
||||
|
||||
if ('instance' in value) {
|
||||
const instance = value.instance
|
||||
return Boolean(instance && typeof instance === 'object' && 'link' in instance && instance.link)
|
||||
}
|
||||
|
||||
return 'link' in value && Boolean(value.link)
|
||||
}
|
||||
|
||||
export function getFocusableButtons(buttonRefs: Map<number, HTMLElement>) {
|
||||
return [...buttonRefs.entries()]
|
||||
.sort(([left], [right]) => left - right)
|
||||
.map(([, button]) => button)
|
||||
.filter((button) => button.offsetParent !== null)
|
||||
}
|
||||
|
||||
export function focusRelativeButton(buttons: HTMLElement[], direction: 1 | -1) {
|
||||
if (!buttons.length) return
|
||||
|
||||
const currentIndex = buttons.indexOf(document.activeElement as HTMLElement)
|
||||
const nextIndex =
|
||||
currentIndex === -1 ? (direction === 1 ? 0 : buttons.length - 1) : currentIndex + direction
|
||||
buttons[(nextIndex + buttons.length) % buttons.length]?.focus()
|
||||
}
|
||||
|
||||
export function isPointInTriangle(point: Point, a: Point, b: Point, c: Point) {
|
||||
const area = triangleArea(a, b, c)
|
||||
const area1 = triangleArea(point, b, c)
|
||||
const area2 = triangleArea(a, point, c)
|
||||
const area3 = triangleArea(a, b, point)
|
||||
|
||||
return Math.abs(area - (area1 + area2 + area3)) < 0.5
|
||||
}
|
||||
|
||||
function triangleArea(a: Point, b: Point, c: Point) {
|
||||
return Math.abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2)
|
||||
}
|
||||
@@ -15,17 +15,17 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import FriendsSection from '@/components/ui/friends/FriendsSection.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { useFriends } from '@/composables/use-friends'
|
||||
import type { FriendWithUserData } from '@/helpers/friends.ts'
|
||||
import type { ModrinthCredentials } from '@/helpers/mr_auth'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const props = defineProps<{
|
||||
credentials: ModrinthCredentials | null
|
||||
@@ -39,11 +39,11 @@ type FriendsSectionCollapsedFlag =
|
||||
| 'friends_pending_collapsed'
|
||||
|
||||
function isFriendsSectionCollapsed(flag: FriendsSectionCollapsedFlag) {
|
||||
return themeStore.getFeatureFlag(flag)
|
||||
return appSettings.getFeatureFlag(flag)
|
||||
}
|
||||
|
||||
function setFriendsSectionCollapsed(flag: FriendsSectionCollapsedFlag, collapsed: boolean) {
|
||||
themeStore.featureFlags[flag] = collapsed
|
||||
appSettings.featureFlags[flag] = collapsed
|
||||
getSettings()
|
||||
.then((settings) => {
|
||||
settings.feature_flags[flag] = collapsed
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import type { FriendWithUserData } from '@/helpers/friends.ts'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -13,11 +13,10 @@
|
||||
import { UnknownFileWarningModal } from '@modrinth/ui'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
|
||||
import { type FeatureFlag, useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { FeatureFlag } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const skipUnknownPackWarningFeatureFlag = 'skip_unknown_pack_warning' as FeatureFlag
|
||||
|
||||
const modal = useTemplateRef('modal')
|
||||
@@ -34,7 +33,7 @@ function show(
|
||||
fileName.value = selectedFileName
|
||||
externalFilesInModpack.value = selectedExternalFiles
|
||||
|
||||
if (themeStore.getFeatureFlag(skipUnknownPackWarningFeatureFlag)) {
|
||||
if (appSettings.getFeatureFlag(skipUnknownPackWarningFeatureFlag)) {
|
||||
void createInstance()
|
||||
return
|
||||
}
|
||||
@@ -50,7 +49,7 @@ function reset() {
|
||||
|
||||
async function proceed(dontShowAgain: boolean) {
|
||||
if (dontShowAgain) {
|
||||
themeStore.featureFlags[skipUnknownPackWarningFeatureFlag] = true
|
||||
appSettings.featureFlags[skipUnknownPackWarningFeatureFlag] = true
|
||||
const settings = await getSettings()
|
||||
settings.feature_flags[skipUnknownPackWarningFeatureFlag] = true
|
||||
await setSettings(settings)
|
||||
|
||||
+51
-2
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@modrinth/ui'
|
||||
import { defineMessages, type MessageDescriptor } from '@modrinth/ui'
|
||||
|
||||
import backpack from '@/assets/instance-icons/backpack.png'
|
||||
import beacon from '@/assets/instance-icons/beacon.png'
|
||||
@@ -18,6 +18,8 @@ import enchantingTable from '@/assets/instance-icons/enchanting-table.png'
|
||||
import enderChest from '@/assets/instance-icons/ender-chest.png'
|
||||
import enderDragon from '@/assets/instance-icons/ender-dragon.png'
|
||||
import engine from '@/assets/instance-icons/engine.png'
|
||||
import fabric from '@/assets/instance-icons/fabric.png'
|
||||
import forge from '@/assets/instance-icons/forge.png'
|
||||
import furnace from '@/assets/instance-icons/furnace.png'
|
||||
import gizmo from '@/assets/instance-icons/gizmo.png'
|
||||
import globe from '@/assets/instance-icons/globe.png'
|
||||
@@ -25,11 +27,13 @@ import grassBlock from '@/assets/instance-icons/grass-block.png'
|
||||
import lantern from '@/assets/instance-icons/lantern.png'
|
||||
import moobloom from '@/assets/instance-icons/moobloom.png'
|
||||
import mrPack from '@/assets/instance-icons/mr-pack.png'
|
||||
import neoForge from '@/assets/instance-icons/neoforge.png'
|
||||
import orb from '@/assets/instance-icons/orb.png'
|
||||
import oxygenDistributor from '@/assets/instance-icons/oxygen-distributor.png'
|
||||
import pancakes from '@/assets/instance-icons/pancakes.png'
|
||||
import pickaxe from '@/assets/instance-icons/pickaxe.png'
|
||||
import pokeBall from '@/assets/instance-icons/poke-ball.png'
|
||||
import quilt from '@/assets/instance-icons/quilt.png'
|
||||
import redstoneBlock from '@/assets/instance-icons/redstone-block.png'
|
||||
import sculkSensor from '@/assets/instance-icons/sculk-sensor.png'
|
||||
import skeleton from '@/assets/instance-icons/skeleton.png'
|
||||
@@ -197,8 +201,20 @@ const names = defineMessages({
|
||||
defaultMessage: 'Modrinth Wrench',
|
||||
},
|
||||
zombie: { id: 'instance.icon-editor.symbol.zombie', defaultMessage: 'Zombie' },
|
||||
fabric: { id: 'instance.icon-editor.symbol.fabric', defaultMessage: 'Fabric' },
|
||||
forge: { id: 'instance.icon-editor.symbol.forge', defaultMessage: 'Forge' },
|
||||
neoForge: { id: 'instance.icon-editor.symbol.neoforge', defaultMessage: 'NeoForge' },
|
||||
quilt: { id: 'instance.icon-editor.symbol.quilt', defaultMessage: 'Quilt' },
|
||||
})
|
||||
|
||||
export interface SymbolOption {
|
||||
id: string
|
||||
name: MessageDescriptor
|
||||
asset: string
|
||||
category: 'loader' | 'modded' | 'vanilla'
|
||||
excludeFromRandomization?: boolean
|
||||
}
|
||||
|
||||
export const backgroundOptions = [
|
||||
{
|
||||
id: 'rose',
|
||||
@@ -339,6 +355,39 @@ export const backgroundOptions = [
|
||||
] as const
|
||||
|
||||
export const symbolOptions = [
|
||||
/////////////////////////
|
||||
// loaders
|
||||
/////////////////////////
|
||||
|
||||
{
|
||||
id: 'fabric',
|
||||
name: names.fabric,
|
||||
asset: fabric,
|
||||
category: 'loader',
|
||||
excludeFromRandomization: true,
|
||||
},
|
||||
{
|
||||
id: 'forge',
|
||||
name: names.forge,
|
||||
asset: forge,
|
||||
category: 'loader',
|
||||
excludeFromRandomization: true,
|
||||
},
|
||||
{
|
||||
id: 'neoforge',
|
||||
name: names.neoForge,
|
||||
asset: neoForge,
|
||||
category: 'loader',
|
||||
excludeFromRandomization: true,
|
||||
},
|
||||
{
|
||||
id: 'quilt',
|
||||
name: names.quilt,
|
||||
asset: quilt,
|
||||
category: 'loader',
|
||||
excludeFromRandomization: true,
|
||||
},
|
||||
|
||||
// Cobblemon: Poké Ball
|
||||
{ id: 'poke_ball', name: names.pokeBall, asset: pokeBall, category: 'modded' },
|
||||
|
||||
@@ -438,7 +487,7 @@ export const symbolOptions = [
|
||||
{ id: 'lantern', name: names.lantern, asset: lantern, category: 'vanilla' },
|
||||
{ id: 'tnt', name: names.tnt, asset: tnt, category: 'vanilla' },
|
||||
{ id: 'command_block', name: names.commandBlock, asset: commandBlock, category: 'vanilla' },
|
||||
] as const
|
||||
] as const satisfies readonly SymbolOption[]
|
||||
|
||||
export type BackgroundId = (typeof backgroundOptions)[number]['id']
|
||||
export type SymbolId = (typeof symbolOptions)[number]['id']
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon, InfoIcon, RefreshCwIcon, SaveIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
DEFAULT_SYMBOL_ID,
|
||||
RANDOM_CONFIG_BLACKLIST,
|
||||
type SymbolId,
|
||||
type SymbolOption,
|
||||
symbolOptions,
|
||||
} from './editor-catalog'
|
||||
|
||||
@@ -201,7 +203,8 @@ function surpriseMe() {
|
||||
const configurations = backgroundOptions.flatMap((background) =>
|
||||
symbolOptions
|
||||
.filter(
|
||||
(symbol) =>
|
||||
(symbol: SymbolOption) =>
|
||||
!symbol.excludeFromRandomization &&
|
||||
background.id !== selectedBackground.value &&
|
||||
symbol.id !== selectedSymbol.value &&
|
||||
!RANDOM_CONFIG_BLACKLIST.some(
|
||||
@@ -342,37 +345,37 @@ const messages = defineMessages({
|
||||
class="flex w-[244px] shrink-0 flex-col gap-4 overflow-y-auto border-0 border-r border-solid border-surface-5 p-6"
|
||||
>
|
||||
<div
|
||||
class="flex w-full flex-col items-center gap-3 rounded-[20px] border border-solid border-surface-4 bg-surface-2 p-4"
|
||||
class="flex w-full flex-col items-center gap-3.5 rounded-[20px] border border-solid border-surface-4 bg-surface-2 p-5"
|
||||
>
|
||||
<div
|
||||
class="icon-outline relative size-[132px] overflow-hidden rounded-[20px]"
|
||||
<Avatar
|
||||
:src="selectedSymbolOption.asset"
|
||||
size="132px"
|
||||
:style="backgroundStyle(selectedBackgroundOption.background)"
|
||||
>
|
||||
<img :src="selectedSymbolOption.asset" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
no-shadow
|
||||
/>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div
|
||||
class="icon-outline relative size-12 overflow-hidden rounded-2xl"
|
||||
<Avatar
|
||||
:src="selectedSymbolOption.asset"
|
||||
size="40px"
|
||||
:style="backgroundStyle(selectedBackgroundOption.background)"
|
||||
>
|
||||
<img :src="selectedSymbolOption.asset" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
<div
|
||||
class="icon-outline relative size-8 overflow-hidden rounded-[10px]"
|
||||
no-shadow
|
||||
/>
|
||||
<Avatar
|
||||
:src="selectedSymbolOption.asset"
|
||||
size="30px"
|
||||
:style="backgroundStyle(selectedBackgroundOption.background)"
|
||||
>
|
||||
<img :src="selectedSymbolOption.asset" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
<div
|
||||
class="icon-outline relative size-4 overflow-hidden rounded-[5px]"
|
||||
no-shadow
|
||||
/>
|
||||
<Avatar
|
||||
:src="selectedSymbolOption.asset"
|
||||
size="20px"
|
||||
:style="backgroundStyle(selectedBackgroundOption.background)"
|
||||
>
|
||||
<img :src="selectedSymbolOption.asset" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
no-shadow
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button class="w-full !shadow-none" @click="surpriseMe">
|
||||
<Button class="w-full" @click="surpriseMe">
|
||||
<RefreshCwIcon />
|
||||
{{ formatMessage(messages.surpriseMe) }}
|
||||
</Button>
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
<span class="text-2xl font-semibold text-contrast">
|
||||
{{
|
||||
formatMessage(messages.title, {
|
||||
groupName: groupInstancesModalGroup?.name ?? '',
|
||||
groupName:
|
||||
groupInstancesModalGroup?.id === 'group:none'
|
||||
? formatMessage(messages.ungrouped)
|
||||
: (groupInstancesModalGroup?.name ?? ''),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
@@ -53,6 +56,10 @@
|
||||
</div>
|
||||
<Button
|
||||
:type="selectedGroupInstanceIds.has(instance.id) ? 'outlined' : 'base'"
|
||||
:disabled="
|
||||
groupInstancesModalGroup?.id === 'group:none' &&
|
||||
selectedGroupInstanceIds.has(instance.id)
|
||||
"
|
||||
@click="toggleGroupInstance(instance.id)"
|
||||
>
|
||||
<CheckIcon v-if="selectedGroupInstanceIds.has(instance.id)" />
|
||||
@@ -100,6 +107,10 @@ import { getInstanceIconUrl } from '@/helpers/instance'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
ungrouped: {
|
||||
id: 'app.library.group.ungrouped',
|
||||
defaultMessage: 'Ungrouped',
|
||||
},
|
||||
title: {
|
||||
id: 'app.library.group.instances-modal.title',
|
||||
defaultMessage: 'Add instances to "{groupName}"',
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ClipboardCopyIcon,
|
||||
EditIcon,
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
MinusIcon,
|
||||
PaletteIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
StarIcon,
|
||||
StopCircleIcon,
|
||||
TrashIcon,
|
||||
UploadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, nextTick, onDeactivated, onUnmounted, ref, toRef, watch } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import IconEditorModal from '@/components/ui/instance_settings/icon-editor-modal/index.vue'
|
||||
import GroupInstancesModal from '@/components/ui/library/group-instances-modal.vue'
|
||||
import InstanceGroup from '@/components/ui/library/instance-group/index.vue'
|
||||
import InstanceGroupDnd from '@/components/ui/library/instance-group/instance-group-dnd.vue'
|
||||
@@ -55,6 +59,30 @@ const messages = defineMessages({
|
||||
id: 'app.library.instance.action.view-instance',
|
||||
defaultMessage: 'View instance',
|
||||
},
|
||||
editIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon',
|
||||
defaultMessage: 'Edit icon',
|
||||
},
|
||||
selectIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.select',
|
||||
defaultMessage: 'Select icon',
|
||||
},
|
||||
replaceIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.replace',
|
||||
defaultMessage: 'Replace icon',
|
||||
},
|
||||
createIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.create',
|
||||
defaultMessage: 'Create an icon',
|
||||
},
|
||||
editCreatedIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.edit-created',
|
||||
defaultMessage: 'Edit icon',
|
||||
},
|
||||
removeIcon: {
|
||||
id: 'instance.settings.tabs.general.edit-icon.remove',
|
||||
defaultMessage: 'Remove icon',
|
||||
},
|
||||
duplicateInstance: {
|
||||
id: 'app.library.instance.action.duplicate',
|
||||
defaultMessage: 'Duplicate instance',
|
||||
@@ -78,10 +106,13 @@ const {
|
||||
reorderGroups,
|
||||
instanceOptions,
|
||||
confirmDeleteModal,
|
||||
iconEditorModal,
|
||||
currentIconEditorInstance,
|
||||
currentDeleteInstances,
|
||||
clearLibraryInstanceSelection,
|
||||
deleteInstance,
|
||||
handleInstanceOption,
|
||||
handleInstanceIconSaved,
|
||||
selectedLibraryInstances,
|
||||
setSelectedLibraryInstances,
|
||||
toggleLibraryInstanceSelection,
|
||||
@@ -100,40 +131,35 @@ const visibleInstanceGroups = computed(() =>
|
||||
),
|
||||
)
|
||||
|
||||
const visibleCustomGroups = computed(() =>
|
||||
const visibleReorderableGroups = computed(() =>
|
||||
displayState.value.group === 'Group'
|
||||
? visibleInstanceGroups.value.filter(
|
||||
(group) => group.id !== FAVORITES_GROUP_ID && group.id !== 'group:none',
|
||||
)
|
||||
? visibleInstanceGroups.value.filter((group) => group.id !== FAVORITES_GROUP_ID)
|
||||
: [],
|
||||
)
|
||||
const visibleFavoritesGroup = computed(() =>
|
||||
visibleInstanceGroups.value.find((group) => group.id === FAVORITES_GROUP_ID),
|
||||
)
|
||||
const visibleUngroupedGroup = computed(() =>
|
||||
visibleInstanceGroups.value.find((group) => group.id === 'group:none'),
|
||||
)
|
||||
const draggableCustomGroups = ref<InstanceGroupType[]>([])
|
||||
const draggableGroups = ref<InstanceGroupType[]>([])
|
||||
const libraryGroupsContainer = ref<HTMLElement>()
|
||||
const isDraggingGroup = ref(false)
|
||||
const GROUP_REORDERING_CLASS = 'instance-group-reordering'
|
||||
const canDragReorderGroups = computed(
|
||||
() => !reorderingGroups.value && draggableCustomGroups.value.length > 1,
|
||||
() => !reorderingGroups.value && draggableGroups.value.length > 1,
|
||||
)
|
||||
|
||||
watch(
|
||||
visibleCustomGroups,
|
||||
visibleReorderableGroups,
|
||||
(groups) => {
|
||||
if (!isDraggingGroup.value) {
|
||||
const previousGroupTops = getCustomGroupTops()
|
||||
draggableCustomGroups.value = [...groups]
|
||||
void nextTick(() => animateCustomGroupReorder(previousGroupTops))
|
||||
const previousGroupTops = getReorderableGroupTops()
|
||||
draggableGroups.value = [...groups]
|
||||
void nextTick(() => animateGroupReorder(previousGroupTops))
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function getCustomGroupTops() {
|
||||
function getReorderableGroupTops() {
|
||||
const groupTops = new Map<string, number>()
|
||||
const groupElements = libraryGroupsContainer.value?.querySelectorAll<HTMLElement>(
|
||||
'[data-instance-group-reorder-id]',
|
||||
@@ -149,7 +175,7 @@ function getCustomGroupTops() {
|
||||
return groupTops
|
||||
}
|
||||
|
||||
function animateCustomGroupReorder(previousGroupTops: Map<string, number>) {
|
||||
function animateGroupReorder(previousGroupTops: Map<string, number>) {
|
||||
if (
|
||||
previousGroupTops.size === 0 ||
|
||||
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
@@ -185,10 +211,10 @@ function onGroupDragEnd() {
|
||||
isDraggingGroup.value = false
|
||||
document.documentElement.classList.remove(GROUP_REORDERING_CLASS)
|
||||
|
||||
const currentGroupIds = visibleCustomGroups.value.map((group) => group.id)
|
||||
const orderedGroupIds = draggableCustomGroups.value.map((group) => group.id)
|
||||
const currentGroupIds = visibleReorderableGroups.value.map((group) => group.id)
|
||||
const orderedGroupIds = draggableGroups.value.map((group) => group.id)
|
||||
if (orderedGroupIds.every((groupId, index) => groupId === currentGroupIds[index])) {
|
||||
draggableCustomGroups.value = [...visibleCustomGroups.value]
|
||||
draggableGroups.value = [...visibleReorderableGroups.value]
|
||||
return
|
||||
}
|
||||
|
||||
@@ -259,6 +285,10 @@ function setConfirmDeleteModal(component: unknown) {
|
||||
confirmDeleteModal.value = component as InstanceType<typeof ConfirmDeleteInstanceModal> | null
|
||||
}
|
||||
|
||||
function setIconEditorModal(component: unknown) {
|
||||
iconEditorModal.value = component as InstanceType<typeof IconEditorModal> | null
|
||||
}
|
||||
|
||||
watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
if (selectedInstances.size === 0) {
|
||||
anchorInstance.value = null
|
||||
@@ -313,7 +343,7 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
</div>
|
||||
|
||||
<Draggable
|
||||
:list="draggableCustomGroups"
|
||||
:list="draggableGroups"
|
||||
class="flex flex-col"
|
||||
item-key="id"
|
||||
:disabled="!canDragReorderGroups"
|
||||
@@ -341,6 +371,7 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
>
|
||||
<InstanceGroup
|
||||
:can-drag-reorder="canDragReorderGroups"
|
||||
:hide-header="visibleInstanceGroups.length === 1"
|
||||
:instance-group="instanceGroup"
|
||||
:selection-anchor-instance-id="
|
||||
anchorInstance?.groupId === instanceGroup.id ? anchorInstance?.instanceId : null
|
||||
@@ -353,20 +384,6 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
|
||||
<div v-if="visibleUngroupedGroup" class="min-w-0">
|
||||
<InstanceGroup
|
||||
:hide-header="visibleInstanceGroups.length === 1"
|
||||
:instance-group="visibleUngroupedGroup"
|
||||
:selection-anchor-instance-id="
|
||||
anchorInstance?.groupId === 'group:none' ? anchorInstance.instanceId : null
|
||||
"
|
||||
@toggle-selection="
|
||||
(instanceId: string, shiftKey: boolean) =>
|
||||
handleToggleInstance('group:none', instanceId, shiftKey)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TransitionGroup
|
||||
@@ -407,6 +424,12 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
:instances="currentDeleteInstances"
|
||||
@delete="deleteInstance"
|
||||
/>
|
||||
<IconEditorModal
|
||||
:ref="setIconEditorModal"
|
||||
:instance-id="currentIconEditorInstance?.id"
|
||||
:config="currentIconEditorInstance?.icon_config"
|
||||
@saved="handleInstanceIconSaved"
|
||||
/>
|
||||
<ContextMenu :ref="setInstanceOptions" @option-clicked="handleInstanceOption">
|
||||
<template #play> <PlayIcon /> {{ formatMessage(messages.play) }} </template>
|
||||
<template #stop> <StopCircleIcon /> {{ formatMessage(messages.stop) }} </template>
|
||||
@@ -419,6 +442,14 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
</template>
|
||||
<template #add_content> <PlusIcon /> {{ formatMessage(messages.addContent) }} </template>
|
||||
<template #edit> <EyeIcon /> {{ formatMessage(messages.viewInstance) }} </template>
|
||||
<template #edit_icon> <EditIcon /> {{ formatMessage(messages.editIcon) }} </template>
|
||||
<template #select_icon> <UploadIcon /> {{ formatMessage(messages.selectIcon) }} </template>
|
||||
<template #replace_icon> <UploadIcon /> {{ formatMessage(messages.replaceIcon) }} </template>
|
||||
<template #create_icon> <PaletteIcon /> {{ formatMessage(messages.createIcon) }} </template>
|
||||
<template #edit_created_icon>
|
||||
<PaletteIcon /> {{ formatMessage(messages.editCreatedIcon) }}
|
||||
</template>
|
||||
<template #remove_icon> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
|
||||
<template #duplicate>
|
||||
<ClipboardCopyIcon /> {{ formatMessage(messages.duplicateInstance) }}
|
||||
</template>
|
||||
|
||||
+21
-10
@@ -2,16 +2,25 @@
|
||||
import { ArrowDownIcon, ArrowUpIcon, EditIcon, SquarePlusIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { defineMessages, IconButton, useVIntl } from '@modrinth/ui'
|
||||
|
||||
defineProps<{
|
||||
deleting?: boolean
|
||||
canMoveDown: boolean
|
||||
canMoveUp: boolean
|
||||
onAddToGroup: () => void
|
||||
onDeleteGroup: () => void
|
||||
onEditGroupName: () => void
|
||||
onMoveDown: () => void
|
||||
onMoveUp: () => void
|
||||
}>()
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
deleting?: boolean
|
||||
canMoveDown: boolean
|
||||
canMoveUp: boolean
|
||||
onAddToGroup: () => void
|
||||
onDeleteGroup: () => void
|
||||
onEditGroupName: () => void
|
||||
onMoveDown: () => void
|
||||
onMoveUp: () => void
|
||||
showDelete?: boolean
|
||||
showEdit?: boolean
|
||||
}>(),
|
||||
{
|
||||
deleting: false,
|
||||
showDelete: true,
|
||||
showEdit: true,
|
||||
},
|
||||
)
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -64,6 +73,7 @@ const messages = defineMessages({
|
||||
<ArrowDownIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
v-if="showEdit"
|
||||
v-tooltip="formatMessage(messages.editGroupName)"
|
||||
:label="formatMessage(messages.editGroupName)"
|
||||
type="quiet"
|
||||
@@ -82,6 +92,7 @@ const messages = defineMessages({
|
||||
<SquarePlusIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
v-if="showDelete"
|
||||
v-tooltip="formatMessage(messages.deleteGroup)"
|
||||
:label="formatMessage(messages.deleteGroup)"
|
||||
type="quiet"
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { computed, inject, nextTick, onActivated, onDeactivated, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import GroupActionButtons from '@/components/ui/library/instance-group/group-action-buttons.vue'
|
||||
import InstanceCard from '@/components/ui/library/instance-group/instance-card.vue'
|
||||
import type {
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
InstanceGroup as InstanceGroupType,
|
||||
} from '@/components/ui/library/use-library'
|
||||
import { useLibrary } from '@/components/ui/library/use-library'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { FAVORITES_GROUP_ID, MAX_INSTANCE_GROUP_NAME_LENGTH } from '@/helpers/instance-groups'
|
||||
|
||||
const INSTANCE_GRID_OBSERVER_ACTIVATION_DELAY = 500
|
||||
@@ -48,6 +49,8 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const appSettings = useAppSettings()
|
||||
const compactMode = computed(() => appSettings.getFeatureFlag('compact_instance_cards'))
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const {
|
||||
isSectionCollapsed,
|
||||
@@ -84,6 +87,9 @@ const isFavorites = computed(() => props.instanceGroup.id === FAVORITES_GROUP_ID
|
||||
const isCustomGroup = computed(
|
||||
() => displayState.value.group === 'Group' && !isUngrouped.value && !isFavorites.value,
|
||||
)
|
||||
const isReorderableGroup = computed(
|
||||
() => displayState.value.group === 'Group' && !isFavorites.value,
|
||||
)
|
||||
const groupContextMenuOpen = ref(false)
|
||||
const isGroupToggleBlocked = computed(
|
||||
() => isSearching.value || groupContextMenuOpen.value || Boolean(groupNameInput.value?.isEditing),
|
||||
@@ -383,7 +389,7 @@ onMounted(startInstanceGridResizeObserver)
|
||||
v-if="!hideHeader"
|
||||
class="group/header h-10 flex w-full items-center gap-2 border-0 border-b border-solid border-b-surface-5"
|
||||
:class="{
|
||||
'instance-group-reorder-handle': isCustomGroup && canDragReorder,
|
||||
'instance-group-reorder-handle': isReorderableGroup && canDragReorder,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
@@ -442,7 +448,7 @@ onMounted(startInstanceGridResizeObserver)
|
||||
</div>
|
||||
<div class="min-w-0 flex-1" />
|
||||
<GroupActionButtons
|
||||
v-if="isCustomGroup"
|
||||
v-if="isCustomGroup || isUngrouped"
|
||||
:can-move-down="canMoveGroupDown(instanceGroup.id)"
|
||||
:can-move-up="canMoveGroupUp(instanceGroup.id)"
|
||||
:deleting="deletingGroup"
|
||||
@@ -451,6 +457,8 @@ onMounted(startInstanceGridResizeObserver)
|
||||
:on-edit-group-name="() => groupNameInput?.startEditing()"
|
||||
:on-move-down="() => moveGroup(instanceGroup.id, 1)"
|
||||
:on-move-up="() => moveGroup(instanceGroup.id, -1)"
|
||||
:show-delete="!isUngrouped"
|
||||
:show-edit="!isUngrouped"
|
||||
/>
|
||||
</div>
|
||||
<Accordion
|
||||
@@ -470,7 +478,12 @@ onMounted(startInstanceGridResizeObserver)
|
||||
<div ref="instanceGridContent">
|
||||
<TransitionGroup
|
||||
tag="section"
|
||||
class="grid min-h-[45px] w-full grid-cols-[repeat(auto-fill,minmax(min(10rem,100%),1fr))] max-xl:grid-cols-[repeat(auto-fill,minmax(min(8rem,100%),1fr))] gap-3 overflow-y-auto scroll-smooth"
|
||||
class="grid min-h-[45px] w-full gap-3 overflow-y-auto scroll-smooth"
|
||||
:class="
|
||||
compactMode
|
||||
? 'grid-cols-[repeat(auto-fill,minmax(min(15rem,100%),1fr))]'
|
||||
: 'grid-cols-[repeat(auto-fill,minmax(min(10rem,100%),1fr))] max-xl:grid-cols-[repeat(auto-fill,minmax(min(8rem,100%),1fr))]'
|
||||
"
|
||||
move-class="transition-transform duration-200 ease-out motion-reduce:transition-none"
|
||||
enter-active-class="transition-[opacity,transform] duration-[150ms] ease-out motion-reduce:transition-none"
|
||||
enter-from-class="opacity-0"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Avatar, truncatedTooltip } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { getInstanceIconUrl } from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
@@ -16,6 +17,8 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const iconSrc = computed(() => getInstanceIconUrl(props.instance.icon_path))
|
||||
const appSettings = useAppSettings()
|
||||
const compactMode = computed(() => appSettings.getFeatureFlag('compact_instance_cards'))
|
||||
|
||||
const nameRef = ref<HTMLElement | null>(null)
|
||||
const versionRef = ref<HTMLElement | null>(null)
|
||||
@@ -23,30 +26,40 @@ const versionRef = ref<HTMLElement | null>(null)
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative flex w-full min-w-0 select-none flex-col items-start justify-end gap-3 overflow-clip rounded-[20px] border border-solid bg-surface-3 p-3 text-left transition-all"
|
||||
class="relative flex w-full min-w-0 select-none overflow-clip border border-solid bg-surface-3 text-left transition-all"
|
||||
:class="{
|
||||
'flex-row items-center justify-start gap-2.5 rounded-xl p-2.5': compactMode,
|
||||
'flex-col items-start justify-end gap-3 rounded-[20px] p-3': !compactMode,
|
||||
'[border-color:color-mix(in_srgb,var(--color-text-primary)_40%,transparent)] brightness-110':
|
||||
selected,
|
||||
'border-surface-4': !selected,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="relative flex aspect-square min-w-full shrink-0 items-center overflow-clip rounded-2xl"
|
||||
class="relative flex shrink-0 items-center overflow-clip"
|
||||
:class="compactMode ? 'size-10 rounded-lg' : 'aspect-square min-w-full rounded-2xl'"
|
||||
>
|
||||
<Avatar
|
||||
class="pointer-events-none !rounded-2xl outline-none"
|
||||
class="pointer-events-none outline-none"
|
||||
:class="compactMode ? '!rounded-lg' : '!rounded-2xl'"
|
||||
size="100%"
|
||||
:src="iconSrc"
|
||||
:tint-by="instance.id"
|
||||
alt=""
|
||||
no-shadow
|
||||
/>
|
||||
<slot name="loading" />
|
||||
<div class="absolute bottom-1.5 right-1.5 z-[1] flex size-12 items-center justify-center">
|
||||
<slot name="leading" />
|
||||
<slot name="loading" :compact="compactMode" />
|
||||
<div
|
||||
class="absolute z-[1] flex items-center justify-center"
|
||||
:class="compactMode ? 'inset-0' : 'bottom-1.5 right-1.5 size-12'"
|
||||
>
|
||||
<slot name="leading" :compact="compactMode" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex min-w-0 w-full flex-col items-start justify-center gap-1 px-0.5">
|
||||
<div
|
||||
class="flex min-w-0 w-full flex-col items-start justify-center gap-1 px-0.5"
|
||||
:class="{ 'pr-10': compactMode }"
|
||||
>
|
||||
<p
|
||||
ref="nameRef"
|
||||
v-tooltip="truncatedTooltip(nameRef, instance.name)"
|
||||
@@ -62,6 +75,6 @@ const versionRef = ref<HTMLElement | null>(null)
|
||||
{{ instance.loader }} {{ instance.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
<slot name="overlay" />
|
||||
<slot name="overlay" :compact="compactMode" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -9,13 +9,13 @@ import { useRouter } from 'vue-router'
|
||||
import InstanceCardView from '@/components/ui/library/instance-group/instance-card-view.vue'
|
||||
import { getLibraryInstanceSelectionKey, useLibrary } from '@/components/ui/library/use-library'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { showInstanceInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
type ProcessEvent = 'installing' | 'launched' | 'finished'
|
||||
|
||||
@@ -285,7 +285,7 @@ onMounted(() => {
|
||||
@mouseenter="checkProcess"
|
||||
@pointerdown="handlePointerDown"
|
||||
>
|
||||
<template #loading>
|
||||
<template #loading="{ compact }">
|
||||
<div
|
||||
v-if="loadingIndicatorVisible"
|
||||
class="pointer-events-none absolute inset-0 z-[1] flex items-center justify-center"
|
||||
@@ -293,13 +293,17 @@ onMounted(() => {
|
||||
<div class="absolute inset-0 bg-surface-1 opacity-30" />
|
||||
<SpinnerIcon
|
||||
v-tooltip="formatMessage(modLoading ? messages.loading : messages.installing)"
|
||||
class="relative size-[30%] animate-spin text-contrast"
|
||||
class="relative animate-spin text-contrast"
|
||||
:class="compact ? 'size-5' : 'size-[30%]'"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #leading>
|
||||
<div class="relative flex size-12 shrink-0 items-center justify-center">
|
||||
<template #leading="{ compact }">
|
||||
<div
|
||||
class="relative flex shrink-0 items-center justify-center"
|
||||
:class="compact ? 'size-10' : 'size-12'"
|
||||
>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<IconButton
|
||||
v-if="playing"
|
||||
@@ -307,7 +311,7 @@ onMounted(() => {
|
||||
:label="formatMessage(messages.stop)"
|
||||
type="colored"
|
||||
color="red"
|
||||
size="lg"
|
||||
:size="compact ? 'md' : 'lg'"
|
||||
@click="(e) => stop(e, 'InstanceCard')"
|
||||
@mouseenter="checkProcess"
|
||||
>
|
||||
@@ -325,7 +329,7 @@ onMounted(() => {
|
||||
:label="formatMessage(messages.repair)"
|
||||
type="colored"
|
||||
color="brand"
|
||||
size="lg"
|
||||
:size="compact ? 'md' : 'lg'"
|
||||
class="origin-bottom scale-75 opacity-0 transition-opacity group-hover/card:scale-100 group-hover/card:opacity-100"
|
||||
@click="(e) => repair(e)"
|
||||
>
|
||||
@@ -342,7 +346,7 @@ onMounted(() => {
|
||||
:label="formatMessage(messages.play)"
|
||||
type="colored"
|
||||
color="brand"
|
||||
size="lg"
|
||||
:size="compact ? 'md' : 'lg'"
|
||||
class="origin-bottom scale-75 opacity-0 transition-opacity group-hover/card:scale-100 group-hover/card:opacity-100"
|
||||
@click="(e) => play(e, 'InstanceCard')"
|
||||
@mouseenter="checkProcess"
|
||||
@@ -352,18 +356,21 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #overlay>
|
||||
<template #overlay="{ compact }">
|
||||
<button
|
||||
type="button"
|
||||
class="selection-button group/selection absolute right-2 top-1.5 z-[2] flex size-[50px] cursor-pointer items-start pt-4 justify-center border-0 bg-transparent p-0"
|
||||
class="selection-button group/selection absolute z-[2] flex size-[50px] cursor-pointer items-start pt-4 justify-center border-0 bg-transparent p-0"
|
||||
:class="compact ? '-right-1 -top-1' : 'right-2 top-1.5'"
|
||||
:aria-label="formatMessage(selected ? messages.deselect : messages.select)"
|
||||
:aria-pressed="selected"
|
||||
@click.stop="toggleSelection"
|
||||
>
|
||||
<span
|
||||
v-tooltip="formatMessage(selected ? messages.deselect : messages.select)"
|
||||
class="relative flex size-[24px] items-center justify-center rounded-full opacity-0 transition-opacity duration-200 ease-out group-hover/card:opacity-100 group-hover/selection:brightness-125"
|
||||
class="relative flex items-center justify-center rounded-full opacity-0 transition-opacity duration-200 ease-out group-hover/card:opacity-100 group-hover/selection:brightness-125"
|
||||
:class="{
|
||||
'size-[20px]': compact,
|
||||
'size-[24px]': !compact,
|
||||
'border-0 !opacity-100': selected,
|
||||
'border-2 border-solid border-primary bg-transparent': !selected,
|
||||
'[outline:3px_solid_var(--color-purple)] outline-offset-1':
|
||||
|
||||
@@ -15,6 +15,8 @@ const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
name: { id: 'app.library.sort.name', defaultMessage: 'Name' },
|
||||
loaderSort: { id: 'app.library.sort.loader', defaultMessage: 'Loader' },
|
||||
gameVersionSort: { id: 'app.library.sort.game-version', defaultMessage: 'Game version' },
|
||||
lastPlayed: { id: 'app.library.sort.last-played', defaultMessage: 'Last played' },
|
||||
hoursPlayed: { id: 'app.library.sort.hours-played', defaultMessage: 'Hours played' },
|
||||
dateCreated: { id: 'app.library.sort.date-created', defaultMessage: 'Date created' },
|
||||
@@ -34,6 +36,8 @@ const sortLabels = {
|
||||
'Hours played': messages.hoursPlayed,
|
||||
'Date created': messages.dateCreated,
|
||||
'Date modified': messages.dateModified,
|
||||
Loader: messages.loaderSort,
|
||||
'Game version': messages.gameVersionSort,
|
||||
}
|
||||
|
||||
const groupLabels = {
|
||||
@@ -60,6 +64,7 @@ const groupOptions: ComboboxOption<LibraryGroupBy>[] = libraryGroupOptions.map((
|
||||
class="w-max"
|
||||
:options="sortOptions"
|
||||
:show-icon-in-selected="false"
|
||||
:max-height="320"
|
||||
dropdown-min-width="160px"
|
||||
>
|
||||
<template #prefix>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
||||
import { useEventListener, useStorage } from '@vueuse/core'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
computed,
|
||||
inject,
|
||||
type InjectionKey,
|
||||
nextTick,
|
||||
provide,
|
||||
type Ref,
|
||||
ref,
|
||||
@@ -13,10 +15,11 @@ import {
|
||||
watchEffect,
|
||||
} from 'vue'
|
||||
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_v3_many } from '@/helpers/cache.js'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import { install_duplicate_instance } from '@/helpers/install'
|
||||
import { edit, remove } from '@/helpers/instance'
|
||||
import { edit, edit_icon, remove } from '@/helpers/instance'
|
||||
import {
|
||||
create_group as createInstanceGroup,
|
||||
delete_group as deleteInstanceGroup,
|
||||
@@ -28,7 +31,7 @@ import {
|
||||
set_group_memberships as setInstanceGroupMemberships,
|
||||
set_group_order as setInstanceGroupOrder,
|
||||
} from '@/helpers/instance-groups'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import type { GameInstance, InstanceIconConfig } from '@/helpers/types'
|
||||
|
||||
export const librarySortOptions = [
|
||||
'Name',
|
||||
@@ -36,6 +39,8 @@ export const librarySortOptions = [
|
||||
'Hours played',
|
||||
'Date created',
|
||||
'Date modified',
|
||||
'Loader',
|
||||
'Game version',
|
||||
] as const
|
||||
|
||||
export const libraryGroupOptions = [
|
||||
@@ -100,6 +105,10 @@ type ConfirmDeleteModal = {
|
||||
show: () => void
|
||||
}
|
||||
|
||||
type IconEditorModal = {
|
||||
show: () => void
|
||||
}
|
||||
|
||||
type ContextMenuSelection = {
|
||||
option: string
|
||||
item: InstanceCard
|
||||
@@ -153,17 +162,25 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
)
|
||||
const currentContextGroupId = ref<string | null>(null)
|
||||
const confirmDeleteModal = ref<ConfirmDeleteModal | null>(null)
|
||||
const iconEditorModal = ref<IconEditorModal | null>(null)
|
||||
const currentIconEditorInstanceId = ref<string | null>(null)
|
||||
const currentIconEditorInstance = computed(
|
||||
() =>
|
||||
instances.value.find((instance) => instance.id === currentIconEditorInstanceId.value) ?? null,
|
||||
)
|
||||
|
||||
const displayState = useStorage<{
|
||||
group: LibraryGroupBy
|
||||
sortBy: LibrarySort
|
||||
collapsedGroups: string[]
|
||||
ungroupedGroupPosition: number
|
||||
}>(
|
||||
'Instances-grid-display-state',
|
||||
{
|
||||
group: 'Group',
|
||||
sortBy: 'Last played',
|
||||
collapsedGroups: [],
|
||||
ungroupedGroupPosition: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
localStorage,
|
||||
{ mergeDefaults: true },
|
||||
@@ -209,10 +226,15 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
})
|
||||
const groupInstancesModalGroup = computed(
|
||||
() =>
|
||||
libraryGroups.value.find((group) => group.id === groupInstancesModalGroupId.value) ?? null,
|
||||
)
|
||||
const groupInstancesModalGroup = computed(() => {
|
||||
if (groupInstancesModalGroupId.value === 'group:none') {
|
||||
return { id: 'group:none', name: 'None' }
|
||||
}
|
||||
|
||||
return (
|
||||
libraryGroups.value.find((group) => group.id === groupInstancesModalGroupId.value) ?? null
|
||||
)
|
||||
})
|
||||
const groupInstances = computed(() => {
|
||||
const query = groupInstancesSearch.value.trim().toLowerCase()
|
||||
|
||||
@@ -227,8 +249,23 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const customLibraryGroups = computed(() =>
|
||||
libraryGroups.value.filter((group) => group.id !== FAVORITES_GROUP_ID),
|
||||
)
|
||||
const customGroupOrder = computed(
|
||||
() => new Map(customLibraryGroups.value.map((group, index) => [group.id, index])),
|
||||
const orderedLibraryGroupIds = computed(() => {
|
||||
const groupIds = customLibraryGroups.value.map((group) => group.id)
|
||||
const storedUngroupedGroupPosition = displayState.value.ungroupedGroupPosition
|
||||
const ungroupedGroupPosition = Math.min(
|
||||
Math.max(
|
||||
Number.isFinite(storedUngroupedGroupPosition)
|
||||
? Math.trunc(storedUngroupedGroupPosition)
|
||||
: Number.MAX_SAFE_INTEGER,
|
||||
0,
|
||||
),
|
||||
groupIds.length,
|
||||
)
|
||||
groupIds.splice(ungroupedGroupPosition, 0, 'group:none')
|
||||
return groupIds
|
||||
})
|
||||
const libraryGroupOrder = computed(
|
||||
() => new Map(orderedLibraryGroupIds.value.map((groupId, index) => [groupId, index])),
|
||||
)
|
||||
|
||||
const refreshGroups = async () => {
|
||||
@@ -328,6 +365,18 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
case 'Name':
|
||||
visibleInstances.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
case 'Loader':
|
||||
visibleInstances.sort((a, b) =>
|
||||
formatLoader(formatMessage, a.loader).localeCompare(
|
||||
formatLoader(formatMessage, b.loader),
|
||||
),
|
||||
)
|
||||
break
|
||||
case 'Game version':
|
||||
visibleInstances.sort((a, b) =>
|
||||
a.game_version.localeCompare(b.game_version, undefined, { numeric: true }),
|
||||
)
|
||||
break
|
||||
case 'Last played':
|
||||
visibleInstances.sort((a, b) => dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0)))
|
||||
break
|
||||
@@ -436,11 +485,9 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
if (a.id === b.id) return 0
|
||||
if (a.id === FAVORITES_GROUP_ID) return -1
|
||||
if (b.id === FAVORITES_GROUP_ID) return 1
|
||||
if (a.id === 'group:none') return 1
|
||||
if (b.id === 'group:none') return -1
|
||||
|
||||
const aOrder = customGroupOrder.value.get(a.id) ?? Number.MAX_SAFE_INTEGER
|
||||
const bOrder = customGroupOrder.value.get(b.id) ?? Number.MAX_SAFE_INTEGER
|
||||
const aOrder = libraryGroupOrder.value.get(a.id) ?? Number.MAX_SAFE_INTEGER
|
||||
const bOrder = libraryGroupOrder.value.get(b.id) ?? Number.MAX_SAFE_INTEGER
|
||||
return aOrder - bOrder || a.key.localeCompare(b.key) || a.id.localeCompare(b.id)
|
||||
})
|
||||
}
|
||||
@@ -701,13 +748,17 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
|
||||
const openGroupInstancesModal = (groupId: string) => {
|
||||
const group = libraryGroups.value.find((candidate) => candidate.id === groupId)
|
||||
if (!group) return
|
||||
if (!group && groupId !== 'group:none') return
|
||||
|
||||
groupInstancesModalGroupId.value = groupId
|
||||
groupInstancesSearch.value = ''
|
||||
selectedGroupInstanceIds.value = new Set(
|
||||
instances.value
|
||||
.filter((instance) => instance.group_ids.includes(groupId))
|
||||
.filter((instance) =>
|
||||
groupId === 'group:none'
|
||||
? instance.group_ids.length === 0
|
||||
: instance.group_ids.includes(groupId),
|
||||
)
|
||||
.map((instance) => instance.id),
|
||||
)
|
||||
isGroupInstancesModalOpen.value = true
|
||||
@@ -722,6 +773,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const selectedIds = new Set(selectedGroupInstanceIds.value)
|
||||
|
||||
if (selectedIds.has(instanceId)) {
|
||||
if (groupInstancesModalGroupId.value === 'group:none') return
|
||||
selectedIds.delete(instanceId)
|
||||
} else {
|
||||
selectedIds.add(instanceId)
|
||||
@@ -734,15 +786,20 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const groupId = groupInstancesModalGroupId.value
|
||||
if (!groupId || savingGroupInstances.value) return false
|
||||
|
||||
const changedInstances = instances.value.filter(
|
||||
(instance) =>
|
||||
instance.group_ids.includes(groupId) !== selectedGroupInstanceIds.value.has(instance.id),
|
||||
)
|
||||
const isUngrouped = groupId === 'group:none'
|
||||
const changedInstances = instances.value.filter((instance) => {
|
||||
const isSelected = selectedGroupInstanceIds.value.has(instance.id)
|
||||
return isUngrouped
|
||||
? isSelected && instance.group_ids.length > 0
|
||||
: instance.group_ids.includes(groupId) !== isSelected
|
||||
})
|
||||
const operations = changedInstances.map((instance) => {
|
||||
const shouldIncludeGroup = selectedGroupInstanceIds.value.has(instance.id)
|
||||
const nextGroupIds = shouldIncludeGroup
|
||||
? [...instance.group_ids, groupId]
|
||||
: instance.group_ids.filter((instanceGroupId) => instanceGroupId !== groupId)
|
||||
const nextGroupIds = isUngrouped
|
||||
? []
|
||||
: shouldIncludeGroup
|
||||
? [...instance.group_ids, groupId]
|
||||
: instance.group_ids.filter((instanceGroupId) => instanceGroupId !== groupId)
|
||||
|
||||
return {
|
||||
instance,
|
||||
@@ -964,14 +1021,16 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
|
||||
const canMoveGroupUp = (groupId: string) =>
|
||||
!reorderingGroups.value &&
|
||||
customLibraryGroups.value.findIndex((group) => group.id === groupId) > 0
|
||||
orderedLibraryGroupIds.value.findIndex((orderedGroupId) => orderedGroupId === groupId) > 0
|
||||
|
||||
const canMoveGroupDown = (groupId: string) => {
|
||||
const groupIndex = customLibraryGroups.value.findIndex((group) => group.id === groupId)
|
||||
const groupIndex = orderedLibraryGroupIds.value.findIndex(
|
||||
(orderedGroupId) => orderedGroupId === groupId,
|
||||
)
|
||||
return (
|
||||
!reorderingGroups.value &&
|
||||
groupIndex >= 0 &&
|
||||
groupIndex < customLibraryGroups.value.length - 1
|
||||
groupIndex < orderedLibraryGroupIds.value.length - 1
|
||||
)
|
||||
}
|
||||
|
||||
@@ -979,35 +1038,48 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
if (reorderingGroups.value) return false
|
||||
|
||||
const previousGroups = libraryGroups.value
|
||||
const previousUngroupedGroupPosition = displayState.value.ungroupedGroupPosition
|
||||
const customGroupsById = new Map(customLibraryGroups.value.map((group) => [group.id, group]))
|
||||
const reorderableGroupIds = new Set([...customGroupsById.keys(), 'group:none'])
|
||||
const orderedGroupIdSet = new Set(orderedGroupIds)
|
||||
|
||||
if (
|
||||
orderedGroupIdSet.size !== orderedGroupIds.length ||
|
||||
orderedGroupIds.some((groupId) => !customGroupsById.has(groupId))
|
||||
orderedGroupIds.some((groupId) => !reorderableGroupIds.has(groupId))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
const orderedGroups = orderedGroupIds.map((groupId) => customGroupsById.get(groupId)!)
|
||||
let orderedGroupIndex = 0
|
||||
const reorderedCustomGroups = customLibraryGroups.value.map((group) =>
|
||||
orderedGroupIdSet.has(group.id) ? orderedGroups[orderedGroupIndex++] : group,
|
||||
const reorderedGroupIds = orderedLibraryGroupIds.value.map((groupId) =>
|
||||
orderedGroupIdSet.has(groupId) ? orderedGroupIds[orderedGroupIndex++] : groupId,
|
||||
)
|
||||
|
||||
if (reorderedCustomGroups.every((group, index) => group === customLibraryGroups.value[index])) {
|
||||
if (
|
||||
reorderedGroupIds.every((groupId, index) => groupId === orderedLibraryGroupIds.value[index])
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
const reorderedCustomGroups = reorderedGroupIds
|
||||
.filter((groupId) => groupId !== 'group:none')
|
||||
.map((groupId) => customGroupsById.get(groupId)!)
|
||||
const customGroupOrderChanged = reorderedCustomGroups.some(
|
||||
(group, index) => group !== customLibraryGroups.value[index],
|
||||
)
|
||||
const favoriteGroups = previousGroups.filter((group) => group.id === FAVORITES_GROUP_ID)
|
||||
libraryGroups.value = [...favoriteGroups, ...reorderedCustomGroups]
|
||||
displayState.value.ungroupedGroupPosition = reorderedGroupIds.indexOf('group:none')
|
||||
reorderingGroups.value = true
|
||||
|
||||
try {
|
||||
await setInstanceGroupOrder(reorderedCustomGroups.map((group) => group.id))
|
||||
if (customGroupOrderChanged) {
|
||||
await setInstanceGroupOrder(reorderedCustomGroups.map((group) => group.id))
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
libraryGroups.value = previousGroups
|
||||
displayState.value.ungroupedGroupPosition = previousUngroupedGroupPosition
|
||||
handleError(toError(error))
|
||||
await refreshGroups()
|
||||
return false
|
||||
@@ -1017,7 +1089,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
}
|
||||
|
||||
const moveGroup = async (groupId: string, direction: -1 | 1) => {
|
||||
const orderedGroupIds = customLibraryGroups.value.map((group) => group.id)
|
||||
const orderedGroupIds = [...orderedLibraryGroupIds.value]
|
||||
const groupIndex = orderedGroupIds.indexOf(groupId)
|
||||
const targetIndex = groupIndex + direction
|
||||
|
||||
@@ -1043,6 +1115,47 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
await install_duplicate_instance(instanceId).catch((error) => handleError(toError(error)))
|
||||
}
|
||||
|
||||
const selectInstanceIcon = async (item: InstanceCard) => {
|
||||
const iconPath = await openDialog({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg', 'svg', 'webp', 'gif', 'jpg'],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (!iconPath) return
|
||||
|
||||
try {
|
||||
await edit_icon(item.instance.id, iconPath)
|
||||
trackEvent('InstanceSetIcon')
|
||||
} catch (error) {
|
||||
handleError(toError(error))
|
||||
}
|
||||
}
|
||||
|
||||
const removeInstanceIcon = async (item: InstanceCard) => {
|
||||
try {
|
||||
await edit_icon(item.instance.id, null)
|
||||
trackEvent('InstanceRemoveIcon')
|
||||
} catch (error) {
|
||||
handleError(toError(error))
|
||||
}
|
||||
}
|
||||
|
||||
const openInstanceIconEditor = async (item: InstanceCard) => {
|
||||
currentIconEditorInstanceId.value = item.instance.id
|
||||
await nextTick()
|
||||
iconEditorModal.value?.show()
|
||||
trackEvent(item.instance.icon_config ? 'InstanceEditCreatedIcon' : 'InstanceCreateIcon')
|
||||
}
|
||||
|
||||
const handleInstanceIconSaved = (_iconPath: string, _config: InstanceIconConfig) => {
|
||||
trackEvent('InstanceSaveCreatedIcon')
|
||||
}
|
||||
|
||||
const handleInstanceContextMenu = (
|
||||
event: MouseEvent,
|
||||
item: InstanceCard,
|
||||
@@ -1069,6 +1182,14 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
{ name: 'duplicate' },
|
||||
{ name: 'open' },
|
||||
{ name: 'copy' },
|
||||
{
|
||||
name: 'edit_icon',
|
||||
children: [
|
||||
{ name: item.instance.icon_path ? 'replace_icon' : 'select_icon' },
|
||||
{ name: item.instance.icon_config ? 'edit_created_icon' : 'create_icon' },
|
||||
...(item.instance.icon_path ? [{ name: 'remove_icon' }] : []),
|
||||
],
|
||||
},
|
||||
...(currentContextGroupId.value
|
||||
? [{ name: 'remove_from_group' }, { type: 'divider' }]
|
||||
: [{ type: 'divider' }]),
|
||||
@@ -1113,6 +1234,17 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
case 'edit':
|
||||
await item.seeInstance()
|
||||
break
|
||||
case 'select_icon':
|
||||
case 'replace_icon':
|
||||
await selectInstanceIcon(item)
|
||||
break
|
||||
case 'create_icon':
|
||||
case 'edit_created_icon':
|
||||
await openInstanceIconEditor(item)
|
||||
break
|
||||
case 'remove_icon':
|
||||
await removeInstanceIcon(item)
|
||||
break
|
||||
case 'duplicate':
|
||||
if (item.instance.install_stage === 'installed') {
|
||||
await duplicateInstance(item.instance.id)
|
||||
@@ -1175,6 +1307,8 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
canCreateGroup,
|
||||
instanceOptions,
|
||||
confirmDeleteModal,
|
||||
iconEditorModal,
|
||||
currentIconEditorInstance,
|
||||
currentDeleteInstances,
|
||||
isSectionCollapsed,
|
||||
setSectionCollapsed,
|
||||
@@ -1206,6 +1340,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
deleteInstance,
|
||||
handleInstanceContextMenu,
|
||||
handleInstanceOption,
|
||||
handleInstanceIconSaved,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -10,8 +10,8 @@ import {
|
||||
import { Admonition, Button, ButtonLink, Collapsible, IconButton, NewModal } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
import { findMinecraftAuthError, type MinecraftAuthError } from './minecraft-auth-errors'
|
||||
|
||||
|
||||
+1
-1
@@ -63,9 +63,9 @@ import { inject, type Ref, ref } from 'vue'
|
||||
|
||||
import steveImage from '@/assets/steve-look-up-left.webp'
|
||||
import type AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { login as loginFlow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const accountsCard = inject('accountsCard') as Ref<InstanceType<typeof AccountsCard> | null>
|
||||
|
||||
@@ -36,16 +36,16 @@ import LanguageSettings from '@/components/ui/settings/display/LanguageSettings.
|
||||
import DefaultInstanceSettings from '@/components/ui/settings/instances/DefaultInstanceSettings.vue'
|
||||
import JavaSettings from '@/components/ui/settings/instances/JavaSettings.vue'
|
||||
import ResourceManagementSettings from '@/components/ui/settings/instances/ResourceManagementSettings.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import {
|
||||
appSettingsModalContextKey,
|
||||
type UnsavedChangesController,
|
||||
} from '@/providers/app-settings-modal'
|
||||
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
// TODO: Apply COMPONENT_STRUCTURE.md here and extract out common setting option components
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -157,7 +157,9 @@ const tabs = [
|
||||
},
|
||||
]
|
||||
|
||||
const availableTabs = computed(() => tabs.filter((tab) => !tab.developerOnly || themeStore.devMode))
|
||||
const availableTabs = computed(() =>
|
||||
tabs.filter((tab) => !tab.developerOnly || appSettings.devMode),
|
||||
)
|
||||
|
||||
const modal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
const unsavedChangesPopup = ref<{ nudge: () => void } | null>(null)
|
||||
@@ -170,10 +172,19 @@ const modifiedUnsavedChangesState = computed(
|
||||
() => unsavedChangesController.value?.getModified() ?? emptyUnsavedChangesState,
|
||||
)
|
||||
const savingUnsavedChanges = computed(() => unsavedChangesController.value?.isSaving() ?? false)
|
||||
const hasUnsavedChanges = computed(() => unsavedChangesController.value?.hasChanges() ?? false)
|
||||
const hasUnsavedChanges = computed(
|
||||
() =>
|
||||
(unsavedChangesController.value?.hasChanges() ?? false) ||
|
||||
(unsavedChangesController.value?.isSaving() ?? false),
|
||||
)
|
||||
|
||||
function canLeaveCurrentTab(): boolean {
|
||||
if (!unsavedChangesController.value?.hasChanges()) return true
|
||||
if (
|
||||
!unsavedChangesController.value?.hasChanges() &&
|
||||
!unsavedChangesController.value?.isSaving()
|
||||
) {
|
||||
return true
|
||||
}
|
||||
unsavedChangesPopup.value?.nudge()
|
||||
return false
|
||||
}
|
||||
@@ -233,8 +244,8 @@ function devModeCount() {
|
||||
if (devModeCounter.value > 5) {
|
||||
const selectedTab = modal.value ? availableTabs.value[modal.value.selectedTab] : undefined
|
||||
|
||||
themeStore.devMode = !themeStore.devMode
|
||||
settings.value.developer_mode = !!themeStore.devMode
|
||||
appSettings.devMode = !appSettings.devMode
|
||||
settings.value.developer_mode = !!appSettings.devMode
|
||||
devModeCounter.value = 0
|
||||
|
||||
if (modal.value) {
|
||||
@@ -298,7 +309,7 @@ const messages = defineMessages({
|
||||
<ProgressBar :progress="progress" />
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="themeStore.devMode" class="text-brand font-semibold m-0 mb-2">
|
||||
<p v-if="appSettings.devMode" class="text-brand font-semibold m-0 mb-2">
|
||||
{{ formatMessage(developerModeEnabled) }}
|
||||
</p>
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -306,8 +317,8 @@ const messages = defineMessages({
|
||||
:aria-label="formatMessage(messages.developerModeButtonLabel)"
|
||||
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
|
||||
:class="{
|
||||
'text-brand': themeStore.devMode,
|
||||
'text-secondary': !themeStore.devMode,
|
||||
'text-brand': appSettings.devMode,
|
||||
'text-secondary': !appSettings.devMode,
|
||||
}"
|
||||
@click="devModeCount"
|
||||
>
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ defineExpose({ show, hide })
|
||||
:disable-close="applying"
|
||||
class="!overflow-hidden !rounded-[20px]"
|
||||
>
|
||||
<div class="grid h-[384px] w-[768px] max-w-full grid-cols-2">
|
||||
<div class="grid w-[768px] max-w-full grid-cols-2">
|
||||
<section class="flex min-w-0 flex-col gap-6 bg-surface-3 p-8">
|
||||
<div
|
||||
class="flex h-8 w-fit items-center rounded-full border border-solid border-brand bg-brand-highlight px-2.5 text-sm font-base leading-5 text-brand"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<AccountSocialSettings
|
||||
ref="socialSettings"
|
||||
:get-blocked-users="get_blocked_users"
|
||||
:get-users="getUsers"
|
||||
:unblock-user="unblock_user"
|
||||
@@ -9,10 +10,29 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { AccountSocialSettings, injectModrinthClient } from '@modrinth/ui'
|
||||
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { get_blocked_users, unblock_user } from '@/helpers/users'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
const socialSettings = ref<InstanceType<typeof AccountSocialSettings> | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => socialSettings.value?.hasChanges ?? false,
|
||||
getOriginal: () => socialSettings.value?.originalState ?? {},
|
||||
getModified: () => socialSettings.value?.modifiedState ?? {},
|
||||
isSaving: () => socialSettings.value?.saving ?? false,
|
||||
reset: () => socialSettings.value?.reset(),
|
||||
save: () => socialSettings.value?.save(),
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
|
||||
function getUsers(userIds: string[]): Promise<Labrinth.Users.v2.User[]> {
|
||||
return client.labrinth.users_v2.getMultiple(userIds)
|
||||
|
||||
@@ -1,108 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import { defineMessages, ThemeSelector, Toggle, useVIntl } from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import {
|
||||
AppearanceSettingsLayout,
|
||||
injectAuth,
|
||||
injectUserPreferences,
|
||||
provideAppearanceSettings,
|
||||
useSavable,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { type ColorTheme, useTheme } from '@/composables/use-theme.ts'
|
||||
import { type AppSettings, get, set } from '@/helpers/settings.ts'
|
||||
import { getOS } from '@/helpers/utils'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { ColorTheme } from '@/store/theme.ts'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
colorThemeTitle: {
|
||||
id: 'app.appearance-settings.color-theme.title',
|
||||
defaultMessage: 'Color theme',
|
||||
},
|
||||
colorThemeDescription: {
|
||||
id: 'app.appearance-settings.color-theme.description',
|
||||
defaultMessage: 'Choose the color theme used by Modrinth App.',
|
||||
},
|
||||
advancedRenderingTitle: {
|
||||
id: 'app.appearance-settings.advanced-rendering.title',
|
||||
defaultMessage: 'Advanced rendering',
|
||||
},
|
||||
advancedRenderingDescription: {
|
||||
id: 'app.appearance-settings.advanced-rendering.description',
|
||||
defaultMessage:
|
||||
'Enable visual effects such as background blur. This may reduce performance without hardware acceleration.',
|
||||
},
|
||||
nativeDecorationsTitle: {
|
||||
id: 'app.appearance-settings.native-decorations.title',
|
||||
defaultMessage: 'System window frame',
|
||||
},
|
||||
nativeDecorationsDescription: {
|
||||
id: 'app.appearance-settings.native-decorations.description',
|
||||
defaultMessage:
|
||||
"Use your operating system's title bar and window controls. Requires an app restart.",
|
||||
},
|
||||
})
|
||||
|
||||
const os = ref(await getOS())
|
||||
const theme = useTheme()
|
||||
const auth = injectAuth()
|
||||
const { updatePreferences } = injectUserPreferences()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
const os = await getOS()
|
||||
const settings = ref(await get())
|
||||
const themeOptions = computed(() =>
|
||||
themeStore
|
||||
.getThemeOptions()
|
||||
.filter((theme) => theme !== 'retro' || themeStore.devMode || settings.value.theme === 'retro'),
|
||||
|
||||
type AppearanceSettingsState = {
|
||||
theme: ColorTheme
|
||||
syncAcrossDevices: boolean
|
||||
advancedRendering: boolean
|
||||
nativeDecorations: boolean
|
||||
}
|
||||
|
||||
function getAppearanceSettingsState(settings: AppSettings): AppearanceSettingsState {
|
||||
return {
|
||||
theme: settings.theme,
|
||||
syncAcrossDevices: settings.sync_theme_across_devices,
|
||||
advancedRendering: settings.advanced_rendering,
|
||||
nativeDecorations: settings.native_decorations,
|
||||
}
|
||||
}
|
||||
|
||||
const { saved, current, changes, saving, hasChanges, reset, save } = useSavable(
|
||||
() => getAppearanceSettingsState(settings.value),
|
||||
async (appearanceChanges) => {
|
||||
const value = current.value
|
||||
if (
|
||||
value.syncAcrossDevices &&
|
||||
auth.user.value &&
|
||||
(appearanceChanges.theme !== undefined || appearanceChanges.syncAcrossDevices !== undefined)
|
||||
) {
|
||||
await updatePreferences({
|
||||
appearance: value.theme === 'system' ? { auto: true } : { auto: false, theme: value.theme },
|
||||
})
|
||||
}
|
||||
|
||||
const nextSettings: AppSettings = {
|
||||
...settings.value,
|
||||
theme: value.theme,
|
||||
sync_theme_across_devices: value.syncAcrossDevices,
|
||||
advanced_rendering: value.advancedRendering,
|
||||
native_decorations: value.nativeDecorations,
|
||||
}
|
||||
|
||||
await set(nextSettings)
|
||||
settings.value = nextSettings
|
||||
theme.preferred = value.theme
|
||||
theme.syncAcrossDevices = value.syncAcrossDevices
|
||||
theme.advancedRendering = value.advancedRendering
|
||||
},
|
||||
)
|
||||
|
||||
const themeOptions = computed(() =>
|
||||
theme.options.filter(
|
||||
(option) =>
|
||||
option !== 'retro' || settings.value.developer_mode || current.value.theme === 'retro',
|
||||
),
|
||||
)
|
||||
|
||||
function setTheme(value: ColorTheme): void {
|
||||
current.value.theme = value
|
||||
}
|
||||
|
||||
function setSyncAcrossDevices(enabled: boolean): void {
|
||||
current.value.syncAcrossDevices = enabled
|
||||
}
|
||||
|
||||
function setAdvancedRendering(enabled: boolean): void {
|
||||
current.value.advancedRendering = enabled
|
||||
}
|
||||
|
||||
function setNativeDecorations(enabled: boolean): void {
|
||||
current.value.nativeDecorations = enabled
|
||||
}
|
||||
|
||||
watch(
|
||||
settings,
|
||||
async () => {
|
||||
await set(settings.value)
|
||||
[() => current.value.theme, () => saved.value.theme],
|
||||
([selectedTheme, savedTheme]) => {
|
||||
theme.preview = selectedTheme === savedTheme ? null : selectedTheme
|
||||
},
|
||||
{ deep: true },
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.colorThemeTitle) }}
|
||||
</h2>
|
||||
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.colorThemeDescription) }}</p>
|
||||
async function saveAppearanceSettings(): Promise<void> {
|
||||
try {
|
||||
await save()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
<ThemeSelector
|
||||
:update-color-theme="
|
||||
(theme: ColorTheme) => {
|
||||
themeStore.setThemeState(theme)
|
||||
settings.theme = theme
|
||||
}
|
||||
"
|
||||
:current-theme="settings.theme"
|
||||
:theme-options="themeOptions"
|
||||
system-theme-color="system"
|
||||
/>
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => hasChanges.value,
|
||||
getOriginal: () => saved.value,
|
||||
getModified: () => changes.value,
|
||||
isSaving: () => saving.value,
|
||||
reset,
|
||||
save: saveAppearanceSettings,
|
||||
})
|
||||
})
|
||||
|
||||
<div class="mt-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.advancedRenderingTitle) }}
|
||||
</h2>
|
||||
<p class="m-0 mt-1">
|
||||
{{ formatMessage(messages.advancedRenderingDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
onBeforeUnmount(() => {
|
||||
theme.preview = null
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
|
||||
<Toggle
|
||||
id="advanced-rendering"
|
||||
:model-value="themeStore.advancedRendering"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
themeStore.advancedRendering = !!e
|
||||
settings.advanced_rendering = themeStore.advancedRendering
|
||||
provideAppearanceSettings({
|
||||
deferPersistence: true,
|
||||
theme: {
|
||||
current: computed(() => current.value.theme),
|
||||
options: themeOptions,
|
||||
system: computed(() => theme.native),
|
||||
set: setTheme,
|
||||
syncAcrossDevices: {
|
||||
value: computed(() => current.value.syncAcrossDevices),
|
||||
set: setSyncAcrossDevices,
|
||||
},
|
||||
syncDisabled: computed(() => !auth.user.value),
|
||||
},
|
||||
advancedRendering: {
|
||||
value: computed(() => current.value.advancedRendering),
|
||||
set: setAdvancedRendering,
|
||||
},
|
||||
nativeDecorations:
|
||||
os !== 'MacOS'
|
||||
? {
|
||||
value: computed(() => current.value.nativeDecorations),
|
||||
set: setNativeDecorations,
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
: undefined,
|
||||
updatePreferences,
|
||||
})
|
||||
</script>
|
||||
|
||||
<div v-if="os !== 'MacOS'" class="mt-6 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.nativeDecorationsTitle) }}
|
||||
</h2>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.nativeDecorationsDescription) }}</p>
|
||||
</div>
|
||||
<Toggle id="native-decorations" v-model="settings.native_decorations" />
|
||||
</div>
|
||||
<template>
|
||||
<AppearanceSettingsLayout />
|
||||
</template>
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { defineMessages, Toggle, useVIntl } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import {
|
||||
defineMessages,
|
||||
injectAuth,
|
||||
injectUserPreferences,
|
||||
Toggle,
|
||||
useSavable,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { FeatureFlag } from '@/store/theme.ts'
|
||||
import {
|
||||
DEFAULT_FEATURE_FLAGS,
|
||||
type FeatureFlag,
|
||||
useAppSettings,
|
||||
} from '@/composables/use-app-settings.ts'
|
||||
import { type AppSettings, get, set } from '@/helpers/settings.ts'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const { formatMessage } = useVIntl()
|
||||
const auth = injectAuth()
|
||||
const { updatePreferences } = injectUserPreferences()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
|
||||
const worldsInHomeFlag: FeatureFlag = 'worlds_in_home'
|
||||
const compactInstanceCardsFlag: FeatureFlag = 'compact_instance_cards'
|
||||
const skipNonEssentialWarningsFlag: FeatureFlag = 'skip_non_essential_warnings'
|
||||
const skipUnknownPackWarningFlag: FeatureFlag = 'skip_unknown_pack_warning'
|
||||
const showPlayTimeFlag: FeatureFlag = 'show_instance_play_time'
|
||||
|
||||
const messages = defineMessages({
|
||||
syncAcrossDevicesTitle: {
|
||||
id: 'app.behavior-settings.sync-across-devices.title',
|
||||
defaultMessage: 'Sync behavior across devices',
|
||||
},
|
||||
syncAcrossDevicesDescription: {
|
||||
id: 'app.behavior-settings.sync-across-devices.description',
|
||||
defaultMessage:
|
||||
"Use these behavior settings everywhere you're signed in. Turn this off to keep separate settings on this device.",
|
||||
},
|
||||
syncAcrossDevicesSignedOutTooltip: {
|
||||
id: 'app.behavior-settings.sync-across-devices.signed-out-tooltip',
|
||||
defaultMessage: 'Sign into a Modrinth account to sync settings.',
|
||||
},
|
||||
startupAndNavigationTitle: {
|
||||
id: 'app.behavior-settings.startup-and-navigation.title',
|
||||
defaultMessage: 'Startup and navigation',
|
||||
@@ -60,6 +88,14 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'Show recently played worlds or instances in the "Jump in" section on the Home page.',
|
||||
},
|
||||
compactModeTitle: {
|
||||
id: 'app.appearance-settings.compact-mode.title',
|
||||
defaultMessage: 'Compact mode',
|
||||
},
|
||||
compactModeDescription: {
|
||||
id: 'app.appearance-settings.compact-mode.description',
|
||||
defaultMessage: 'Display library instances in a compact row layout.',
|
||||
},
|
||||
showPlayTimeTitle: {
|
||||
id: 'app.appearance-settings.show-play-time.title',
|
||||
defaultMessage: 'Show play time',
|
||||
@@ -96,18 +132,143 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const settings = ref(await get())
|
||||
type BehaviorSettingsState = {
|
||||
syncBehaviorAcrossDevices: boolean
|
||||
minimizeApp: boolean
|
||||
hideRightSidebar: boolean
|
||||
showJumpIn: boolean
|
||||
compactInstanceCards: boolean
|
||||
showPlayTime: boolean
|
||||
hideNametag: boolean
|
||||
warnOnUnknownModpacks: boolean
|
||||
skipNonEssentialWarnings: boolean
|
||||
}
|
||||
|
||||
watch(
|
||||
settings,
|
||||
const persistedSettings = ref(await get())
|
||||
|
||||
function getBehaviorSettingsState(settings: AppSettings): BehaviorSettingsState {
|
||||
return {
|
||||
syncBehaviorAcrossDevices: settings.sync_behavior_across_devices,
|
||||
minimizeApp: settings.hide_on_process_start,
|
||||
hideRightSidebar: settings.toggle_sidebar,
|
||||
showJumpIn: settings.feature_flags[worldsInHomeFlag] ?? DEFAULT_FEATURE_FLAGS[worldsInHomeFlag],
|
||||
compactInstanceCards:
|
||||
settings.feature_flags[compactInstanceCardsFlag] ??
|
||||
DEFAULT_FEATURE_FLAGS[compactInstanceCardsFlag],
|
||||
showPlayTime:
|
||||
settings.feature_flags[showPlayTimeFlag] ?? DEFAULT_FEATURE_FLAGS[showPlayTimeFlag],
|
||||
hideNametag: settings.hide_nametag_skins_page,
|
||||
warnOnUnknownModpacks: !(
|
||||
settings.feature_flags[skipUnknownPackWarningFlag] ??
|
||||
DEFAULT_FEATURE_FLAGS[skipUnknownPackWarningFlag]
|
||||
),
|
||||
skipNonEssentialWarnings:
|
||||
settings.feature_flags[skipNonEssentialWarningsFlag] ??
|
||||
DEFAULT_FEATURE_FLAGS[skipNonEssentialWarningsFlag],
|
||||
}
|
||||
}
|
||||
|
||||
const { saved, current, changes, saving, hasChanges, reset, save } = useSavable(
|
||||
() => getBehaviorSettingsState(persistedSettings.value),
|
||||
async () => {
|
||||
await set(settings.value)
|
||||
const value = current.value
|
||||
|
||||
if (value.syncBehaviorAcrossDevices && auth.user.value) {
|
||||
await updatePreferences({
|
||||
behavior: {
|
||||
minimize_app: value.minimizeApp,
|
||||
hide_right_sidebar: value.hideRightSidebar,
|
||||
show_jump_in: value.showJumpIn,
|
||||
compact_instance_cards: value.compactInstanceCards,
|
||||
show_play_time: value.showPlayTime,
|
||||
hide_nametag: value.hideNametag,
|
||||
warn_on_unknown_modpacks: value.warnOnUnknownModpacks,
|
||||
skip_non_essential_warnings: value.skipNonEssentialWarnings,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const nextSettings: AppSettings = {
|
||||
...persistedSettings.value,
|
||||
sync_behavior_across_devices: value.syncBehaviorAcrossDevices,
|
||||
hide_on_process_start: value.minimizeApp,
|
||||
toggle_sidebar: value.hideRightSidebar,
|
||||
hide_nametag_skins_page: value.hideNametag,
|
||||
feature_flags: {
|
||||
...persistedSettings.value.feature_flags,
|
||||
[worldsInHomeFlag]: value.showJumpIn,
|
||||
[compactInstanceCardsFlag]: value.compactInstanceCards,
|
||||
[showPlayTimeFlag]: value.showPlayTime,
|
||||
[skipUnknownPackWarningFlag]: !value.warnOnUnknownModpacks,
|
||||
[skipNonEssentialWarningsFlag]: value.skipNonEssentialWarnings,
|
||||
},
|
||||
}
|
||||
|
||||
await set(nextSettings)
|
||||
persistedSettings.value = nextSettings
|
||||
appSettings.setBehaviorSyncAcrossDevices(value.syncBehaviorAcrossDevices)
|
||||
appSettings.toggleSidebar = value.hideRightSidebar
|
||||
appSettings.hideNametagSkinsPage = value.hideNametag
|
||||
appSettings.featureFlags[worldsInHomeFlag] = value.showJumpIn
|
||||
appSettings.featureFlags[compactInstanceCardsFlag] = value.compactInstanceCards
|
||||
appSettings.featureFlags[showPlayTimeFlag] = value.showPlayTime
|
||||
appSettings.featureFlags[skipUnknownPackWarningFlag] = !value.warnOnUnknownModpacks
|
||||
appSettings.featureFlags[skipNonEssentialWarningsFlag] = value.skipNonEssentialWarnings
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
async function saveBehaviorSettings(): Promise<void> {
|
||||
try {
|
||||
await save()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => hasChanges.value,
|
||||
getOriginal: () => saved.value,
|
||||
getModified: () => changes.value,
|
||||
isSaving: () => saving.value,
|
||||
reset,
|
||||
save: saveBehaviorSettings,
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<section>
|
||||
<section class="border-0 border-b border-solid border-divider pb-6">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 id="sync-behavior-across-devices-label" class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.syncAcrossDevicesTitle) }}
|
||||
</h2>
|
||||
<p class="m-0 mt-1 text-secondary">
|
||||
{{ formatMessage(messages.syncAcrossDevicesDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
v-tooltip="
|
||||
!auth.user.value ? formatMessage(messages.syncAcrossDevicesSignedOutTooltip) : undefined
|
||||
"
|
||||
class="inline-flex shrink-0"
|
||||
>
|
||||
<Toggle
|
||||
id="sync-behavior-across-devices"
|
||||
:model-value="Boolean(auth.user.value) && current.syncBehaviorAcrossDevices"
|
||||
:disabled="!auth.user.value"
|
||||
aria-labelledby="sync-behavior-across-devices-label"
|
||||
@update:model-value="current.syncBehaviorAcrossDevices = $event"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-6">
|
||||
<h2 class="m-0 text-xl font-semibold text-contrast">
|
||||
{{ formatMessage(messages.startupAndNavigationTitle) }}
|
||||
</h2>
|
||||
@@ -121,7 +282,7 @@ watch(
|
||||
{{ formatMessage(messages.minimizeLauncherDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle id="minimize-launcher" v-model="settings.hide_on_process_start" />
|
||||
<Toggle id="minimize-launcher" v-model="current.minimizeApp" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
@@ -131,16 +292,7 @@ watch(
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.toggleSidebarDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="toggle-sidebar"
|
||||
:model-value="settings.toggle_sidebar"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
settings.toggle_sidebar = !!e
|
||||
themeStore.toggleSidebar = settings.toggle_sidebar
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="toggle-sidebar" v-model="current.hideRightSidebar" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -159,17 +311,17 @@ watch(
|
||||
{{ formatMessage(messages.jumpBackIntoWorldsDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="jump-back-into-worlds"
|
||||
:model-value="themeStore.getFeatureFlag(worldsInHomeFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(worldsInHomeFlag)
|
||||
themeStore.featureFlags[worldsInHomeFlag] = newValue
|
||||
settings.feature_flags[worldsInHomeFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="jump-back-into-worlds" v-model="current.showJumpIn" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.compactModeTitle) }}
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.compactModeDescription) }}</p>
|
||||
</div>
|
||||
<Toggle id="compact-mode" v-model="current.compactInstanceCards" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
@@ -179,17 +331,7 @@ watch(
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.showPlayTimeDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="show-play-time"
|
||||
:model-value="themeStore.getFeatureFlag(showPlayTimeFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(showPlayTimeFlag)
|
||||
themeStore.featureFlags[showPlayTimeFlag] = newValue
|
||||
settings.feature_flags[showPlayTimeFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="show-play-time" v-model="current.showPlayTime" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
@@ -199,16 +341,7 @@ watch(
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.hideNametagDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="hide-nametag-skins-page"
|
||||
:model-value="themeStore.hideNametagSkinsPage"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
themeStore.hideNametagSkinsPage = !!e
|
||||
settings.hide_nametag_skins_page = themeStore.hideNametagSkinsPage
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="hide-nametag-skins-page" v-model="current.hideNametag" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -229,15 +362,7 @@ watch(
|
||||
</div>
|
||||
<Toggle
|
||||
id="warn-before-installing-unknown-modpacks"
|
||||
:model-value="!themeStore.getFeatureFlag(skipUnknownPackWarningFlag)"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
const warnBeforeUnknownPackInstall = !!e
|
||||
const skipUnknownPackWarning = !warnBeforeUnknownPackInstall
|
||||
themeStore.featureFlags[skipUnknownPackWarningFlag] = skipUnknownPackWarning
|
||||
settings.feature_flags[skipUnknownPackWarningFlag] = skipUnknownPackWarning
|
||||
}
|
||||
"
|
||||
v-model="current.warnOnUnknownModpacks"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -250,17 +375,7 @@ watch(
|
||||
{{ formatMessage(messages.skipNonEssentialWarningsDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="skip-non-essential-warnings"
|
||||
:model-value="themeStore.getFeatureFlag(skipNonEssentialWarningsFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(skipNonEssentialWarningsFlag)
|
||||
themeStore.featureFlags[skipNonEssentialWarningsFlag] = newValue
|
||||
settings.feature_flags[skipNonEssentialWarningsFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="skip-non-essential-warnings" v-model="current.skipNonEssentialWarnings" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
import { Button, Toggle } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
DEFAULT_FEATURE_FLAGS,
|
||||
type FeatureFlag,
|
||||
useAppSettings,
|
||||
} from '@/composables/use-app-settings.ts'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const settings = ref(await getSettings())
|
||||
const options = ref<FeatureFlag[]>(Object.keys(DEFAULT_FEATURE_FLAGS))
|
||||
const options = ref(Object.keys(DEFAULT_FEATURE_FLAGS) as FeatureFlag[])
|
||||
|
||||
function setFeatureFlag(key: string, value: boolean) {
|
||||
themeStore.featureFlags[key] = value
|
||||
function setFeatureFlag(key: FeatureFlag, value: boolean) {
|
||||
appSettings.featureFlags[key] = value
|
||||
settings.value.feature_flags[key] = value
|
||||
}
|
||||
|
||||
@@ -35,15 +38,15 @@ watch(
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
type="quiet"
|
||||
:disabled="themeStore.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
|
||||
:disabled="appSettings.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
|
||||
@click="setFeatureFlag(option, DEFAULT_FEATURE_FLAGS[option])"
|
||||
>
|
||||
Reset to default
|
||||
</Button>
|
||||
<Toggle
|
||||
id="advanced-rendering"
|
||||
:model-value="themeStore.getFeatureFlag(option)"
|
||||
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
|
||||
:model-value="appSettings.getFeatureFlag(option)"
|
||||
@update:model-value="() => setFeatureFlag(option, !appSettings.getFeatureFlag(option))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,74 +1,35 @@
|
||||
<template>
|
||||
<SharedLanguageSettings ref="languageSettings" product="app" :persist-locale="persistLocale" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Admonition,
|
||||
AutoLink,
|
||||
commonSettingsMessages,
|
||||
IntlFormatted,
|
||||
LanguageSelector,
|
||||
languageSelectorMessages,
|
||||
LOCALES,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { LanguageSettings as SharedLanguageSettings } from '@modrinth/ui'
|
||||
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import i18n from '@/i18n.config'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
const languageSettings = ref<InstanceType<typeof SharedLanguageSettings> | null>(null)
|
||||
|
||||
const platform = computed(() => formatMessage(languageSelectorMessages.platformApp))
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => languageSettings.value?.hasChanges ?? false,
|
||||
getOriginal: () => languageSettings.value?.originalState ?? {},
|
||||
getModified: () => languageSettings.value?.modifiedState ?? {},
|
||||
isSaving: () => languageSettings.value?.saving ?? false,
|
||||
reset: () => languageSettings.value?.reset(),
|
||||
save: () => languageSettings.value?.save(),
|
||||
})
|
||||
})
|
||||
|
||||
const settings = ref(await get())
|
||||
onBeforeUnmount(() => {
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
|
||||
watch(
|
||||
settings,
|
||||
async () => {
|
||||
await set(settings.value)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const $isChanging = ref(false)
|
||||
|
||||
async function onLocaleChange(newLocale: string) {
|
||||
if (settings.value.locale === newLocale) return
|
||||
|
||||
$isChanging.value = true
|
||||
try {
|
||||
i18n.global.locale.value = newLocale
|
||||
settings.value.locale = newLocale
|
||||
} finally {
|
||||
$isChanging.value = false
|
||||
}
|
||||
async function persistLocale(locale: string): Promise<void> {
|
||||
const settings = await get()
|
||||
if (settings.locale === locale) return
|
||||
await set({ ...settings, locale })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(commonSettingsMessages.language) }}
|
||||
</h2>
|
||||
|
||||
<Admonition type="warning" class="mt-2 mb-4">
|
||||
{{ formatMessage(languageSelectorMessages.languageWarning, { platform }) }}
|
||||
</Admonition>
|
||||
|
||||
<p class="m-0 mb-4">
|
||||
<IntlFormatted
|
||||
:message-id="languageSelectorMessages.languagesDescription"
|
||||
:values="{ platform }"
|
||||
>
|
||||
<template #~crowdin-link="{ children }">
|
||||
<AutoLink to="https://translate.modrinth.com">
|
||||
<component :is="() => children" />
|
||||
</AutoLink>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
|
||||
<LanguageSelector
|
||||
:current-locale="settings.locale"
|
||||
:locales="LOCALES"
|
||||
:on-locale-change="onLocaleChange"
|
||||
:is-changing="$isChanging"
|
||||
/>
|
||||
</template>
|
||||
|
||||
+6
-6
@@ -14,14 +14,14 @@ import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { purge_cache_types } from '@/helpers/cache.js'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { showAppDbBackupsFolder } from '@/helpers/utils.js'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const settings = ref(await get())
|
||||
const purgeCacheConfirmModal = ref(null)
|
||||
const alwaysShowCopyDetailsFlag = 'always_show_copy_details'
|
||||
@@ -145,7 +145,7 @@ async function purgeCache() {
|
||||
}
|
||||
|
||||
function handlePurgeCacheClick() {
|
||||
if (themeStore.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
if (appSettings.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
void purgeCache()
|
||||
return
|
||||
}
|
||||
@@ -210,11 +210,11 @@ async function findLauncherDir() {
|
||||
</div>
|
||||
<Toggle
|
||||
id="always-show-copy-details"
|
||||
:model-value="themeStore.getFeatureFlag(alwaysShowCopyDetailsFlag)"
|
||||
:model-value="appSettings.getFeatureFlag(alwaysShowCopyDetailsFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(alwaysShowCopyDetailsFlag)
|
||||
themeStore.featureFlags[alwaysShowCopyDetailsFlag] = newValue
|
||||
const newValue = !appSettings.getFeatureFlag(alwaysShowCopyDetailsFlag)
|
||||
appSettings.featureFlags[alwaysShowCopyDetailsFlag] = newValue
|
||||
settings.feature_flags[alwaysShowCopyDetailsFlag] = newValue
|
||||
}
|
||||
"
|
||||
|
||||
+3
-3
@@ -9,6 +9,7 @@ import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { type Ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get_user } from '@/helpers/cache'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import {
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
} from '@/helpers/install'
|
||||
import { list } from '@/helpers/instance'
|
||||
import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
import { parseSharedInstanceInviteNotification } from './shared-instance-invite-parser'
|
||||
import type { AppNotification, SharedInstanceInvite } from './shared-instance-invite-types'
|
||||
@@ -58,7 +58,7 @@ export function useSharedInstanceInviteHandler(
|
||||
const popupNotificationManager = injectPopupNotificationManager()
|
||||
const queryClient = useQueryClient()
|
||||
const router = useRouter()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const displayedNotifications = new Set<string | number>()
|
||||
const displayedNotificationKeys = new Set<string>()
|
||||
const popupNotificationIds = new Set<string | number>()
|
||||
@@ -121,7 +121,7 @@ export function useSharedInstanceInviteHandler(
|
||||
(instance) => instance.shared_instance?.id === sharedInstanceId,
|
||||
)
|
||||
|
||||
if (!existingInstance || themeStore.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
if (!existingInstance || appSettings.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
showInstall(preview, install, creator)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { showInstanceInFolder } from '@/helpers/utils'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { LoaderCircleIcon } from '@modrinth/assets'
|
||||
import type { GameVersion } from '@modrinth/ui'
|
||||
import { defineMessages, GAME_MODES, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import {
|
||||
Accordion,
|
||||
defineMessages,
|
||||
GAME_MODES,
|
||||
type GameVersion,
|
||||
injectNotificationManager,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
||||
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_all } from '@/helpers/process'
|
||||
@@ -29,20 +37,19 @@ import {
|
||||
start_join_singleplayer_world,
|
||||
type WorldWithInstance,
|
||||
} from '@/helpers/worlds.ts'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
jumpIn: { id: 'app.home.jump-back-in.title', defaultMessage: 'Jump in' },
|
||||
resize: { id: 'app.home.jump-back-in.resize', defaultMessage: 'Drag to resize' },
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
recentInstances: GameInstance[]
|
||||
}>()
|
||||
|
||||
const theme = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const jumpBackInItems = ref<JumpBackInItem[]>([])
|
||||
const loading = ref(true)
|
||||
@@ -54,6 +61,100 @@ const gameVersions = ref<GameVersion[]>(await get_game_versions().catch(() => []
|
||||
const MAX_JUMP_BACK_IN = 5
|
||||
const MAX_NEW_INSTANCES = 3
|
||||
const MAX_LINUX_POPULATES = 3
|
||||
const ITEM_DRAG_DISTANCE = 92
|
||||
const STORAGE_KEY = 'modrinth-jump-back-in-count'
|
||||
|
||||
const storedVisibleCount = Number.parseInt(localStorage.getItem(STORAGE_KEY) ?? '', 10)
|
||||
const visibleItemLimit = ref(
|
||||
Number.isFinite(storedVisibleCount)
|
||||
? Math.max(1, Math.min(storedVisibleCount, MAX_JUMP_BACK_IN))
|
||||
: MAX_JUMP_BACK_IN,
|
||||
)
|
||||
const maxVisibleItems = computed(() => Math.min(jumpBackInItems.value.length, MAX_JUMP_BACK_IN))
|
||||
const visibleItemCount = computed(() => Math.min(visibleItemLimit.value, maxVisibleItems.value))
|
||||
const visibleJumpBackInItems = computed(() =>
|
||||
jumpBackInItems.value.slice(0, visibleItemCount.value),
|
||||
)
|
||||
const canResize = computed(() => maxVisibleItems.value > 1)
|
||||
const resizing = ref(false)
|
||||
const showOverdrag = ref(false)
|
||||
|
||||
function setVisibleItemLimit(count: number) {
|
||||
const clamped = Math.max(1, Math.min(count, maxVisibleItems.value))
|
||||
if (clamped >= maxVisibleItems.value) {
|
||||
visibleItemLimit.value = MAX_JUMP_BACK_IN
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
} else {
|
||||
visibleItemLimit.value = clamped
|
||||
localStorage.setItem(STORAGE_KEY, String(clamped))
|
||||
}
|
||||
}
|
||||
|
||||
function adjustVisibleItemLimit(delta: number) {
|
||||
const target = visibleItemCount.value + delta
|
||||
if (target < 1 || target > maxVisibleItems.value) {
|
||||
flashOverdrag()
|
||||
}
|
||||
setVisibleItemLimit(target)
|
||||
}
|
||||
|
||||
let dragStartY = 0
|
||||
let dragStartCount = 0
|
||||
let wasOverdragging = false
|
||||
let overdragTimeout: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
function clearOverdragFlash() {
|
||||
showOverdrag.value = false
|
||||
if (overdragTimeout) {
|
||||
clearTimeout(overdragTimeout)
|
||||
overdragTimeout = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function flashOverdrag() {
|
||||
showOverdrag.value = true
|
||||
if (overdragTimeout) clearTimeout(overdragTimeout)
|
||||
overdragTimeout = setTimeout(() => {
|
||||
showOverdrag.value = false
|
||||
overdragTimeout = undefined
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function onResizePointerDown(event: PointerEvent) {
|
||||
if (!canResize.value) return
|
||||
event.preventDefault()
|
||||
resizing.value = true
|
||||
wasOverdragging = false
|
||||
clearOverdragFlash()
|
||||
dragStartY = event.clientY
|
||||
dragStartCount = visibleItemCount.value
|
||||
document.body.classList.add('recent-worlds-resizing')
|
||||
const handle = event.currentTarget as HTMLElement
|
||||
handle.setPointerCapture(event.pointerId)
|
||||
}
|
||||
|
||||
function onResizePointerMove(event: PointerEvent) {
|
||||
if (!resizing.value) return
|
||||
const target = dragStartCount + Math.round((event.clientY - dragStartY) / ITEM_DRAG_DISTANCE)
|
||||
const isOverdragging = target < 1 || target > maxVisibleItems.value
|
||||
if (isOverdragging && !wasOverdragging) {
|
||||
flashOverdrag()
|
||||
}
|
||||
wasOverdragging = isOverdragging
|
||||
setVisibleItemLimit(target)
|
||||
}
|
||||
|
||||
function endResize(event?: PointerEvent) {
|
||||
if (!resizing.value) return
|
||||
resizing.value = false
|
||||
wasOverdragging = false
|
||||
clearOverdragFlash()
|
||||
document.body.classList.remove('recent-worlds-resizing')
|
||||
const target = event?.currentTarget as HTMLElement | undefined
|
||||
if (event && target?.hasPointerCapture(event.pointerId)) {
|
||||
target.releasePointerCapture(event.pointerId)
|
||||
}
|
||||
}
|
||||
|
||||
// Track populate calls on Linux to prevent server ping spam
|
||||
const isLinux = platform() === 'linux'
|
||||
@@ -76,7 +177,7 @@ type WorldJumpBackInItem = BaseJumpBackInItem & {
|
||||
|
||||
type JumpBackInItem = InstanceJumpBackInItem | WorldJumpBackInItem
|
||||
|
||||
const showWorlds = computed(() => theme.getFeatureFlag('worlds_in_home'))
|
||||
const showWorlds = computed(() => appSettings.getFeatureFlag('worlds_in_home'))
|
||||
|
||||
watch([() => props.recentInstances, () => showWorlds.value], async () => {
|
||||
await populateJumpBackIn().catch(() => {
|
||||
@@ -276,100 +377,179 @@ onMounted(() => {
|
||||
checkProcesses()
|
||||
linuxPopulateCount.value = 0
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.classList.remove('recent-worlds-resizing')
|
||||
clearOverdragFlash()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="loading" class="flex flex-col gap-2">
|
||||
<span
|
||||
class="flex mt-1 mb-3 leading-none items-center gap-1 text-2xl font-semibold text-contrast"
|
||||
>
|
||||
{{ formatMessage(messages.jumpIn) }}
|
||||
</span>
|
||||
<div class="text-center py-4">
|
||||
<Accordion
|
||||
v-if="loading || jumpBackInItems.length > 0"
|
||||
open-by-default
|
||||
button-class="group mt-1 mb-3 flex w-fit cursor-pointer items-center border-0 bg-transparent p-0 text-left"
|
||||
>
|
||||
<template #title>
|
||||
<span class="flex items-center gap-1 text-2xl font-semibold leading-none text-contrast mr-1">
|
||||
{{ formatMessage(messages.jumpIn) }}
|
||||
</span>
|
||||
</template>
|
||||
<div v-if="loading" class="text-center py-4">
|
||||
<LoaderCircleIcon class="mx-auto size-8 animate-spin text-contrast" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="jumpBackInItems.length > 0" class="flex flex-col gap-2">
|
||||
<span
|
||||
class="flex mt-1 mb-3 leading-none items-center gap-1 text-2xl font-semibold text-contrast"
|
||||
>
|
||||
{{ formatMessage(messages.jumpIn) }}
|
||||
</span>
|
||||
<div class="grid-when-huge flex flex-col w-full gap-3">
|
||||
<template
|
||||
v-for="item in jumpBackInItems"
|
||||
:key="`${item.instance.id}-${item.type === 'world' ? getWorldIdentifier(item.world) : 'instance'}`"
|
||||
>
|
||||
<WorldItem
|
||||
v-if="item.type === 'world'"
|
||||
:world="item.world"
|
||||
:playing-instance="runningInstances.includes(item.instance.id)"
|
||||
:playing-world="
|
||||
currentInstance === item.instance.id && currentWorld === getWorldIdentifier(item.world)
|
||||
"
|
||||
:refreshing="
|
||||
item.world.type === 'server'
|
||||
? serverData[item.world.address].refreshing && !serverData[item.world.address].status
|
||||
: undefined
|
||||
"
|
||||
:supports-server-quick-play="
|
||||
item.world.type === 'server' &&
|
||||
hasServerQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||
"
|
||||
:supports-world-quick-play="
|
||||
item.world.type === 'singleplayer' &&
|
||||
hasWorldQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||
"
|
||||
:quarantined="item.instance.quarantined"
|
||||
:server-status="
|
||||
item.world.type === 'server' ? serverData[item.world.address].status : undefined
|
||||
"
|
||||
:rendered-motd="
|
||||
item.world.type === 'server' ? serverData[item.world.address].renderedMotd : undefined
|
||||
"
|
||||
:current-protocol="protocolVersions[item.instance.id]"
|
||||
:game-mode="
|
||||
item.world.type === 'singleplayer' ? GAME_MODES[item.world.game_mode] : undefined
|
||||
"
|
||||
:instance-id="item.instance.id"
|
||||
:instance-name="item.instance.name"
|
||||
:instance-icon="item.instance.icon_path"
|
||||
@refresh="
|
||||
() =>
|
||||
<div v-else class="grid-when-huge relative flex w-full flex-col gap-3">
|
||||
<TransitionGroup name="jump-back-in-item">
|
||||
<div
|
||||
v-for="item in visibleJumpBackInItems"
|
||||
:key="`${item.instance.id}-${item.type === 'world' ? getWorldIdentifier(item.world) : 'instance'}`"
|
||||
class="jump-back-in-item min-w-0"
|
||||
>
|
||||
<WorldItem
|
||||
v-if="item.type === 'world'"
|
||||
:world="item.world"
|
||||
:playing-instance="runningInstances.includes(item.instance.id)"
|
||||
:playing-world="
|
||||
currentInstance === item.instance.id &&
|
||||
currentWorld === getWorldIdentifier(item.world)
|
||||
"
|
||||
:refreshing="
|
||||
item.world.type === 'server'
|
||||
? refreshServer(item.world.address, item.instance.id)
|
||||
: {}
|
||||
"
|
||||
@update="() => populateJumpBackIn()"
|
||||
@play="
|
||||
() => {
|
||||
currentInstance = item.instance.id
|
||||
currentWorld = getWorldIdentifier(item.world)
|
||||
joinWorld(item.world, item.instance)
|
||||
}
|
||||
"
|
||||
@play-instance="
|
||||
() => {
|
||||
currentInstance = item.instance.id
|
||||
playInstance(item.instance)
|
||||
}
|
||||
"
|
||||
@stop="() => stopInstance(item.instance.id)"
|
||||
/>
|
||||
<InstanceItem
|
||||
v-else
|
||||
:instance="item.instance"
|
||||
:last_played="item.sort_time"
|
||||
:newly-added="item.newly_added"
|
||||
@play="() => markInstancePlayed(item)"
|
||||
/>
|
||||
</template>
|
||||
? serverData[item.world.address].refreshing &&
|
||||
!serverData[item.world.address].status
|
||||
: undefined
|
||||
"
|
||||
:supports-server-quick-play="
|
||||
item.world.type === 'server' &&
|
||||
hasServerQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||
"
|
||||
:supports-world-quick-play="
|
||||
item.world.type === 'singleplayer' &&
|
||||
hasWorldQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||
"
|
||||
:quarantined="item.instance.quarantined"
|
||||
:server-status="
|
||||
item.world.type === 'server' ? serverData[item.world.address].status : undefined
|
||||
"
|
||||
:rendered-motd="
|
||||
item.world.type === 'server' ? serverData[item.world.address].renderedMotd : undefined
|
||||
"
|
||||
:current-protocol="protocolVersions[item.instance.id]"
|
||||
:game-mode="
|
||||
item.world.type === 'singleplayer' ? GAME_MODES[item.world.game_mode] : undefined
|
||||
"
|
||||
:instance-id="item.instance.id"
|
||||
:instance-name="item.instance.name"
|
||||
:instance-icon="item.instance.icon_path"
|
||||
@refresh="
|
||||
() =>
|
||||
item.world.type === 'server'
|
||||
? refreshServer(item.world.address, item.instance.id)
|
||||
: {}
|
||||
"
|
||||
@update="() => populateJumpBackIn()"
|
||||
@play="
|
||||
() => {
|
||||
currentInstance = item.instance.id
|
||||
currentWorld = getWorldIdentifier(item.world)
|
||||
joinWorld(item.world, item.instance)
|
||||
}
|
||||
"
|
||||
@play-instance="
|
||||
() => {
|
||||
currentInstance = item.instance.id
|
||||
playInstance(item.instance)
|
||||
}
|
||||
"
|
||||
@stop="() => stopInstance(item.instance.id)"
|
||||
/>
|
||||
<InstanceItem
|
||||
v-else
|
||||
:instance="item.instance"
|
||||
:last_played="item.sort_time"
|
||||
:newly-added="item.newly_added"
|
||||
@play="() => markInstancePlayed(item)"
|
||||
/>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
<div
|
||||
v-if="canResize"
|
||||
v-tooltip="resizing ? null : formatMessage(messages.resize)"
|
||||
role="separator"
|
||||
tabindex="0"
|
||||
aria-orientation="horizontal"
|
||||
:aria-label="formatMessage(messages.resize)"
|
||||
:aria-valuemin="1"
|
||||
:aria-valuemax="maxVisibleItems"
|
||||
:aria-valuenow="visibleItemCount"
|
||||
class="group/resize relative inset-x-0 bottom-0 flex h-6 -mt-3 w-1/3 mx-auto cursor-ns-resize touch-none select-none items-center justify-center"
|
||||
@pointerdown="onResizePointerDown"
|
||||
@pointermove="onResizePointerMove"
|
||||
@pointerup="endResize"
|
||||
@pointercancel="endResize"
|
||||
@keydown.up.prevent="adjustVisibleItemLimit(-1)"
|
||||
@keydown.down.prevent="adjustVisibleItemLimit(1)"
|
||||
@keydown.home.prevent="setVisibleItemLimit(1)"
|
||||
@keydown.end.prevent="setVisibleItemLimit(maxVisibleItems)"
|
||||
>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span
|
||||
class="h-px w-6 transition-colors"
|
||||
:class="showOverdrag ? 'bg-red' : 'bg-secondary'"
|
||||
/>
|
||||
<span
|
||||
class="h-px w-6 transition-colors"
|
||||
:class="showOverdrag ? 'bg-red' : 'bg-secondary'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.grid-when-huge {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(670px, 1fr));
|
||||
}
|
||||
|
||||
.jump-back-in-item {
|
||||
height: 5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jump-back-in-item-enter-active,
|
||||
.jump-back-in-item-leave-active {
|
||||
transition:
|
||||
opacity 0.25s ease,
|
||||
transform 0.25s ease,
|
||||
height 0.25s ease;
|
||||
}
|
||||
|
||||
.jump-back-in-item-enter-from,
|
||||
.jump-back-in-item-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.jump-back-in-item-enter-active,
|
||||
.jump-back-in-item-leave-active {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.jump-back-in-item-enter-from,
|
||||
.jump-back-in-item-leave-to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
height: 5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
body.recent-worlds-resizing,
|
||||
body.recent-worlds-resizing * {
|
||||
cursor: ns-resize !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MoreVerticalIcon,
|
||||
NoSignalIcon,
|
||||
PlayIcon,
|
||||
SignalIcon,
|
||||
SkullIcon,
|
||||
SpinnerIcon,
|
||||
StopCircleIcon,
|
||||
@@ -25,15 +26,17 @@ import {
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
ServerOnlinePlayers,
|
||||
SmartClickable,
|
||||
TagItem,
|
||||
TeleportOverflowMenu,
|
||||
useFormatDateTime,
|
||||
useFormatNumber,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { getPingLevel } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import type { Component } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -53,6 +56,7 @@ import { LockIcon } from '../../../../../../packages/assets/generated-icons'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatNumber = useFormatNumber()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
@@ -121,6 +125,9 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const playingOtherWorld = computed(() => props.playingInstance && !props.playingWorld)
|
||||
const hasPlayersTooltip = computed(
|
||||
() => !!props.serverStatus?.players?.sample && props.serverStatus.players.sample.length > 0,
|
||||
)
|
||||
const serverIncompatible = computed(
|
||||
() =>
|
||||
!!props.serverStatus &&
|
||||
@@ -238,6 +245,10 @@ const messages = defineMessages({
|
||||
id: 'app.world.world-item.incompatible-version',
|
||||
defaultMessage: 'Incompatible version {version}',
|
||||
},
|
||||
playersOnline: {
|
||||
id: 'app.world.world-item.players-online',
|
||||
defaultMessage: '{count} online',
|
||||
},
|
||||
offline: {
|
||||
id: 'app.world.world-item.offline',
|
||||
defaultMessage: 'Offline',
|
||||
@@ -315,13 +326,34 @@ const messages = defineMessages({
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<div v-else class="flex items-center gap-2">
|
||||
<ServerOnlinePlayers
|
||||
:online="serverStatus.players?.online ?? 0"
|
||||
status-online
|
||||
hide-label
|
||||
<template v-else>
|
||||
<SignalIcon
|
||||
v-tooltip="`${serverStatus.ping}ms`"
|
||||
aria-hidden="true"
|
||||
:style="`--_signal-${getPingLevel(serverStatus.ping ?? 0)}: var(--color-green)`"
|
||||
stroke-width="3px"
|
||||
class="shrink-0 smart-clickable:allow-pointer-events"
|
||||
/>
|
||||
</div>
|
||||
<Tooltip :disabled="!hasPlayersTooltip">
|
||||
<span
|
||||
class="smart-clickable:allow-pointer-events"
|
||||
:class="{ 'cursor-help': hasPlayersTooltip }"
|
||||
>
|
||||
{{
|
||||
formatMessage(messages.playersOnline, {
|
||||
count: formatNumber(serverStatus.players?.online ?? 0),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<template #popper>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-for="player in serverStatus.players?.sample" :key="player.id">
|
||||
{{ player.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" />
|
||||
|
||||
@@ -10,6 +10,7 @@ import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import {
|
||||
install_job_dismiss,
|
||||
install_job_list,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
} from '@/helpers/install'
|
||||
import { get_many as getInstances } from '@/helpers/instance'
|
||||
import { injectAppEvents } from '@/providers/app-events'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const messages = defineMessages({
|
||||
installs: {
|
||||
@@ -236,7 +236,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
}) {
|
||||
const appEvents = injectAppEvents()
|
||||
const { formatMessage } = useVIntl()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const jobs = ref<InstallJobSnapshot[]>([])
|
||||
const iconUrls = ref<Record<string, string | null>>({})
|
||||
const instanceNames = ref<Record<string, string>>({})
|
||||
@@ -421,7 +421,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
}
|
||||
|
||||
function shouldShowCopyDetails(job: InstallJobSnapshot): boolean {
|
||||
return isTerminalJob(job) || themeStore.getFeatureFlag('always_show_copy_details')
|
||||
return isTerminalJob(job) || appSettings.getFeatureFlag('always_show_copy_details')
|
||||
}
|
||||
|
||||
function isCopied(job: InstallJobSnapshot): boolean {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
export const DEFAULT_FEATURE_FLAGS = {
|
||||
project_background: false,
|
||||
page_path: false,
|
||||
worlds_in_home: true,
|
||||
server_project_qa: false,
|
||||
show_version_environment_column: false,
|
||||
server_ram_as_bytes_always_on: false,
|
||||
always_show_app_controls: false,
|
||||
skip_non_essential_warnings: false,
|
||||
skip_unknown_pack_warning: false,
|
||||
pride_fundraiser: true,
|
||||
i18n_debug: false,
|
||||
show_instance_play_time: true,
|
||||
compact_instance_cards: false,
|
||||
advanced_filters_collapsed: true,
|
||||
always_show_copy_details: false,
|
||||
hide_installed_modpacks: false,
|
||||
friends_active_collapsed: false,
|
||||
friends_online_collapsed: false,
|
||||
friends_offline_collapsed: true,
|
||||
friends_pending_collapsed: true,
|
||||
dismissed_photosensitivity_filter_warning: false,
|
||||
}
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
type FeatureFlags = Record<FeatureFlag, boolean>
|
||||
|
||||
const syncBehaviorAcrossDevices = ref(false)
|
||||
const featureFlags = reactive<FeatureFlags>({ ...DEFAULT_FEATURE_FLAGS })
|
||||
|
||||
function setBehaviorSyncAcrossDevices(enabled: boolean): void {
|
||||
syncBehaviorAcrossDevices.value = enabled
|
||||
}
|
||||
|
||||
function getFeatureFlag(key: FeatureFlag): boolean {
|
||||
return featureFlags[key] ?? DEFAULT_FEATURE_FLAGS[key]
|
||||
}
|
||||
|
||||
const appSettings = reactive({
|
||||
syncBehaviorAcrossDevices,
|
||||
hideNametagSkinsPage: false,
|
||||
toggleSidebar: false,
|
||||
devMode: false,
|
||||
featureFlags,
|
||||
setBehaviorSyncAcrossDevices,
|
||||
getFeatureFlag,
|
||||
})
|
||||
|
||||
export function useAppSettings() {
|
||||
return appSettings
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { findMinecraftAuthError } from '@/components/ui/minecraft-auth-error-modal/minecraft-auth-errors'
|
||||
|
||||
const errorState = reactive({
|
||||
errorModal: null,
|
||||
minecraftAuthErrorModal: null,
|
||||
minecraftRequiredModal: null,
|
||||
setErrorModal(ref) {
|
||||
this.errorModal = ref
|
||||
},
|
||||
setMinecraftAuthErrorModal(ref) {
|
||||
this.minecraftAuthErrorModal = ref
|
||||
},
|
||||
setMinecraftRequiredModal(ref) {
|
||||
this.minecraftRequiredModal = ref
|
||||
},
|
||||
showError(error, context, closable = true, source = null) {
|
||||
const errorMessage = error.message?.toLowerCase()
|
||||
if (
|
||||
(errorMessage?.includes('user is not logged in') ||
|
||||
errorMessage?.includes('cannot play instance since minecraft is required')) &&
|
||||
this.minecraftRequiredModal
|
||||
) {
|
||||
this.minecraftRequiredModal.show()
|
||||
return
|
||||
}
|
||||
if (
|
||||
error.message &&
|
||||
(error.message.includes('Minecraft authentication error:') ||
|
||||
findMinecraftAuthError(error.message)) &&
|
||||
this.minecraftAuthErrorModal
|
||||
) {
|
||||
this.minecraftAuthErrorModal.show(error)
|
||||
return
|
||||
}
|
||||
this.errorModal.show(error, context, closable, source)
|
||||
},
|
||||
})
|
||||
|
||||
export function useError() {
|
||||
return errorState
|
||||
}
|
||||
|
||||
export const handleSevereError = (error, context) => {
|
||||
useError().showError(error, context)
|
||||
console.error(error)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'retro', 'system'] as const
|
||||
|
||||
export type ColorTheme = (typeof THEME_OPTIONS)[number]
|
||||
type Theme = Exclude<ColorTheme, 'system'>
|
||||
|
||||
const preferred = ref<ColorTheme>('dark')
|
||||
const preview = ref<ColorTheme | null>(null)
|
||||
const advancedRendering = ref(true)
|
||||
const syncAcrossDevices = ref(false)
|
||||
const nativeThemeQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const native = ref<Theme>(nativeThemeQuery.matches ? 'dark' : 'light')
|
||||
const active = computed<Theme>(() => {
|
||||
const selectedTheme = preview.value ?? preferred.value
|
||||
return selectedTheme === 'system' ? native.value : selectedTheme
|
||||
})
|
||||
|
||||
nativeThemeQuery.addEventListener('change', (event) => {
|
||||
native.value = event.matches ? 'dark' : 'light'
|
||||
})
|
||||
|
||||
watch(
|
||||
active,
|
||||
(theme) => {
|
||||
const html = document.documentElement
|
||||
for (const option of THEME_OPTIONS) {
|
||||
html.classList.remove(`${option}-mode`)
|
||||
}
|
||||
html.classList.add(`${theme}-mode`)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const theme = reactive({
|
||||
preferred,
|
||||
preview,
|
||||
active,
|
||||
native,
|
||||
syncAcrossDevices,
|
||||
advancedRendering,
|
||||
options: THEME_OPTIONS,
|
||||
})
|
||||
|
||||
export function useTheme() {
|
||||
return theme
|
||||
}
|
||||
@@ -5,8 +5,9 @@
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import type { FeatureFlag } from '@/composables/use-app-settings.ts'
|
||||
import type { ColorTheme } from '@/composables/use-theme.ts'
|
||||
import type { Hooks, MemorySettings, WindowSize } from '@/helpers/types'
|
||||
import type { ColorTheme, FeatureFlag } from '@/store/theme.ts'
|
||||
|
||||
// Settings object
|
||||
/*
|
||||
@@ -43,6 +44,8 @@ export type AppSettings = {
|
||||
advanced_rendering: boolean
|
||||
native_decorations: boolean
|
||||
toggle_sidebar: boolean
|
||||
sync_theme_across_devices: boolean
|
||||
sync_behavior_across_devices: boolean
|
||||
|
||||
telemetry: boolean
|
||||
discord_rpc: boolean
|
||||
|
||||
+2
@@ -213,6 +213,8 @@ type AppSettings = {
|
||||
advanced_rendering: boolean
|
||||
native_decorations: boolean
|
||||
worlds_in_home: boolean
|
||||
sync_theme_across_devices: boolean
|
||||
sync_behavior_across_devices: boolean
|
||||
|
||||
telemetry: boolean
|
||||
discord_rpc: boolean
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { UserPreferencesContext } from '@modrinth/ui'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
type UserPreferences = NonNullable<UserPreferencesContext['preferences']['value']>
|
||||
type PartialUserPreferences = Parameters<UserPreferencesContext['updatePreferences']>[0]
|
||||
|
||||
export async function get_user_preferences(userId: string): Promise<UserPreferences> {
|
||||
return await invoke<UserPreferences>('plugin:users|get_user_preferences', { userId })
|
||||
}
|
||||
|
||||
export async function patch_user_preferences(
|
||||
userId: string,
|
||||
preferences: PartialUserPreferences,
|
||||
): Promise<UserPreferences> {
|
||||
return await invoke<UserPreferences>('plugin:users|patch_user_preferences', {
|
||||
userId,
|
||||
preferences,
|
||||
})
|
||||
}
|
||||
@@ -25,8 +25,8 @@ export async function get_user_profile(userId: string): Promise<Labrinth.Users.v
|
||||
return await invoke<Labrinth.Users.v3.User>('plugin:users|get_user_profile', { userId })
|
||||
}
|
||||
|
||||
export async function get_user_projects(userId: string): Promise<Labrinth.Projects.v2.Project[]> {
|
||||
return await invoke<Labrinth.Projects.v2.Project[]>('plugin:users|get_user_projects', {
|
||||
export async function get_user_projects(userId: string): Promise<Labrinth.Projects.v3.Project[]> {
|
||||
return await invoke<Labrinth.Projects.v3.Project[]>('plugin:users|get_user_projects', {
|
||||
userId,
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -137,33 +137,18 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Dit privatliv og hvordan reklamer hjælper Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Avanceret rendering"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Farvetema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Hjem"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Bibliotek"
|
||||
},
|
||||
"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.title": {
|
||||
"message": "Hop tilbage ind i verdener"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimer app"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Vis hvor meget tid du har brugt på at spille en instance."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Vis tid spillet"
|
||||
},
|
||||
@@ -221,9 +206,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Ved at tilføje indhold kan der opstå kompatibilitetsproblemer, når du tilslutter dig serveren. Alt tilføjet indhold går desuden tabt, når du opdaterer indholdet på din server instance."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installere"
|
||||
},
|
||||
@@ -647,9 +629,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Log Ind"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Skin vælger"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -149,17 +149,11 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Your privacy and how ads support Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Enable visual effects such as background blur. This may reduce performance without hardware acceleration."
|
||||
"app.appearance-settings.compact-mode.description": {
|
||||
"message": "Display library instances in a compact row layout."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Advanced rendering"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Choose the color theme used by Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Color theme"
|
||||
"app.appearance-settings.compact-mode.title": {
|
||||
"message": "Compact mode"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Home"
|
||||
@@ -185,12 +179,6 @@
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimize app"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Use your operating system's title bar and window controls. Requires an app restart."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "System window frame"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Show how long you've played each instance."
|
||||
},
|
||||
@@ -230,6 +218,15 @@
|
||||
"app.behavior-settings.startup-and-navigation.title": {
|
||||
"message": "Startup and navigation"
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.description": {
|
||||
"message": "Use these behavior settings everywhere you're signed in. Turn this off to keep separate settings on this device."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.signed-out-tooltip": {
|
||||
"message": "Sign into a Modrinth account to sync settings."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.title": {
|
||||
"message": "Sync behavior across devices"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Adding server to instance"
|
||||
},
|
||||
@@ -332,6 +329,9 @@
|
||||
"app.home.jump-back-in.new-instance": {
|
||||
"message": "New instance"
|
||||
},
|
||||
"app.home.jump-back-in.resize": {
|
||||
"message": "Drag to resize"
|
||||
},
|
||||
"app.home.jump-back-in.title": {
|
||||
"message": "Jump in"
|
||||
},
|
||||
@@ -1082,6 +1082,9 @@
|
||||
"app.library.sort.date-modified": {
|
||||
"message": "Date modified"
|
||||
},
|
||||
"app.library.sort.game-version": {
|
||||
"message": "Game version"
|
||||
},
|
||||
"app.library.sort.hours-played": {
|
||||
"message": "Hours played"
|
||||
},
|
||||
@@ -1091,6 +1094,9 @@
|
||||
"app.library.sort.last-played": {
|
||||
"message": "Last played"
|
||||
},
|
||||
"app.library.sort.loader": {
|
||||
"message": "Loader"
|
||||
},
|
||||
"app.library.sort.name": {
|
||||
"message": "Name"
|
||||
},
|
||||
@@ -1742,6 +1748,9 @@
|
||||
"app.world.world-item.offline": {
|
||||
"message": "Offline"
|
||||
},
|
||||
"app.world.world-item.players-online": {
|
||||
"message": "{count} online"
|
||||
},
|
||||
"content.shared-instance.change-version-body": {
|
||||
"message": "Changing the version only changes your local copy. Future shared instance updates may restore or change it again."
|
||||
},
|
||||
@@ -2069,6 +2078,12 @@
|
||||
"instance.icon-editor.symbol.engine": {
|
||||
"message": "Engine"
|
||||
},
|
||||
"instance.icon-editor.symbol.fabric": {
|
||||
"message": "Fabric"
|
||||
},
|
||||
"instance.icon-editor.symbol.forge": {
|
||||
"message": "Forge"
|
||||
},
|
||||
"instance.icon-editor.symbol.furnace": {
|
||||
"message": "Furnace"
|
||||
},
|
||||
@@ -2090,6 +2105,9 @@
|
||||
"instance.icon-editor.symbol.mr-pack": {
|
||||
"message": "Mr Pack"
|
||||
},
|
||||
"instance.icon-editor.symbol.neoforge": {
|
||||
"message": "NeoForge"
|
||||
},
|
||||
"instance.icon-editor.symbol.orb": {
|
||||
"message": "Orb"
|
||||
},
|
||||
@@ -2105,6 +2123,9 @@
|
||||
"instance.icon-editor.symbol.poke-ball": {
|
||||
"message": "Poke Ball"
|
||||
},
|
||||
"instance.icon-editor.symbol.quilt": {
|
||||
"message": "Quilt"
|
||||
},
|
||||
"instance.icon-editor.symbol.redstone-block": {
|
||||
"message": "Redstone Block"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -71,42 +71,15 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Katso lokeja"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Edistynyt renderointi"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Väriteema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Koti"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Kirjasto"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Kytkee pois päältä nimikyltin pelaajasi päällä skinit sivulla."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Piilota nimikyltti"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Hyppää takaisin sisään maailmoihin"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Pienennä laukaisin kun Minecraft prosessi käynnistyy."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Piilota käynnistysohjelma"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Käytä järjestelmän ikkuna kehystä (Vaatii sovelluksen uudelleen käynnistämisen)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Natiivit koristukset"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Näyttää, kuinka paljon aikaa olet käyttänyt instanssin pelaamiseen."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Näyttää peliaika"
|
||||
},
|
||||
@@ -149,9 +122,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modipaketit"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Sisällön lisääminen voi rikkoa yhteensopivuuden palvelimelle liittyessä. Kaikki lisätty sisältö katoaa myös kun päivität palvelin instanssin sisällön."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Asennetaan"
|
||||
},
|
||||
@@ -518,9 +488,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Pienten vallankumous"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Kirjaudu sisään"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Skinivalinta"
|
||||
},
|
||||
|
||||
@@ -11,9 +11,39 @@
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Itago ang mas marami pang tumatakbong instansiya"
|
||||
},
|
||||
"app.action-bar.install.copied-details": {
|
||||
"message": "Kinopya"
|
||||
},
|
||||
"app.action-bar.install.copy-details": {
|
||||
"message": "Kopyahin ang detalye"
|
||||
},
|
||||
"app.action-bar.install.retry": {
|
||||
"message": "Subukan muli"
|
||||
},
|
||||
"app.action-bar.install.summary.app-closing": {
|
||||
"message": "Kinansela dahil sa pagsara ng aplikasyon"
|
||||
},
|
||||
"app.action-bar.install.summary.bad-modpack-file": {
|
||||
"message": "Hindi mabasa ang modpack"
|
||||
},
|
||||
"app.action-bar.install.summary.canceled": {
|
||||
"message": "Kinansela"
|
||||
},
|
||||
"app.action-bar.install.summary.cleanup-incomplete": {
|
||||
"message": "Hindi matapos ang paglinis"
|
||||
},
|
||||
"app.action-bar.install.summary.content-download-failed": {
|
||||
"message": "Hindi ma-download ang mga talaksan"
|
||||
},
|
||||
"app.action-bar.install.summary.could-not-save-files": {
|
||||
"message": "Hindi ma-save ang mga talaksan"
|
||||
},
|
||||
"app.action-bar.install.summary.download-failed": {
|
||||
"message": "Hindi matapos ang pagdownload"
|
||||
},
|
||||
"app.action-bar.install.summary.instance-not-found": {
|
||||
"message": "Hindi mahanap ang instansiya"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Gumawa ng pangunahing instansiya"
|
||||
},
|
||||
@@ -47,51 +77,15 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Tignan ang logs"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Masalimuot na pagrerender"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema ng kulay"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Tahanan"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Librarya"
|
||||
},
|
||||
"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.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.show-play-time.description": {
|
||||
"message": "Magpapakita ang katagal ng iyong paglalaro sa isang instansiya."
|
||||
},
|
||||
"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"
|
||||
},
|
||||
@@ -128,9 +122,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Maaaring makasisira sa pagkakatugma sa pagsasali ng server ang pagdaragdag ng kontento. Ang anumang kontento ay mawawala sa pag-update ng kontento ng instansiyang pang-server."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Ini-install"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Téléchargements"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "Cacher les téléchargements actifs"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Masquer plus d'instances en cours d'exécution"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Recharger pour mettre à jour"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "Montrer les téléchargements actifs"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Montrer plus d'instances en cours d'exécution"
|
||||
},
|
||||
@@ -143,17 +149,11 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Votre vie privée et comment les publicités soutiennent Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Activer les effets visuels tel que le flou d'arrière-plan. Ceci peut réduire les performances sans accélération matérielle."
|
||||
"app.appearance-settings.compact-mode.description": {
|
||||
"message": "Afficher les instances de la bibliothèque dans une disposition compacte."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Rendu avancé"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Choisir la couleur de thème utilisée par Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Couleur du thème"
|
||||
"app.appearance-settings.compact-mode.title": {
|
||||
"message": "Mode compact"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Accueil"
|
||||
@@ -168,10 +168,10 @@
|
||||
"message": "Cacher le pseudo"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Affiche les mondes récemment joués dans la section \"Sautez à nouveau dans\" de la page principale."
|
||||
"message": "Affiche les mondes récemment joués dans la section \"Lancement rapide\" de la page principale."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Mondes sur partie rapide"
|
||||
"message": "Lancement rapide dans les mondes ou les instances"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimiser Modrinth App quand Minecraft se lance."
|
||||
@@ -179,12 +179,6 @@
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimiser l'application"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Utilise la barre de titre et les contrôles de fenêtre de votre système d'exploitation. Nécessite le redémarrage de l'application."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Cadre de fenêtre système"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Montre combien de temps vous avez joué à chaque instance."
|
||||
},
|
||||
@@ -224,6 +218,15 @@
|
||||
"app.behavior-settings.startup-and-navigation.title": {
|
||||
"message": "Démarrage et navigation"
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.description": {
|
||||
"message": "Utilisez ces paramètres de comportement partout où vous êtes connecté. Désactivez cette option pour conserver des paramètres séparés sur cet appareil."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.signed-out-tooltip": {
|
||||
"message": "Connectez-vous à un compte Modrinth pour synchroniser vos paramètres."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.title": {
|
||||
"message": "Synchroniser le comportement entre les appareils"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Ajouter le serveur à l'instance"
|
||||
},
|
||||
@@ -257,11 +260,26 @@
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "Cacher ce qui est déjà installé"
|
||||
},
|
||||
"app.browse.project-type.data-packs": {
|
||||
"message": "Packs de données"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.project-type.mods": {
|
||||
"message": "Mods"
|
||||
},
|
||||
"app.browse.project-type.resource-packs": {
|
||||
"message": "Packs de ressources"
|
||||
},
|
||||
"app.browse.project-type.servers": {
|
||||
"message": "Serveurs"
|
||||
},
|
||||
"app.browse.project-type.shaders": {
|
||||
"message": "Shaders"
|
||||
},
|
||||
"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."
|
||||
"message": "Ajouter du contenu peut vous empêcher de rejoindre le serveur. Tout contenu que vous ajoutez sera retiré quand le contenu géré du serveur sera mis à jour."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installation en cours"
|
||||
@@ -275,6 +293,12 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Exporter"
|
||||
},
|
||||
"app.export-modal.export-complete": {
|
||||
"message": "Exportation terminée"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name} a été exporté avec succès."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "Exporter le modpack"
|
||||
},
|
||||
@@ -290,6 +314,84 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.home.jump-back-in.instance-locked": {
|
||||
"message": "Cette instance a été verrouillée"
|
||||
},
|
||||
"app.home.jump-back-in.instance-open": {
|
||||
"message": "L'instance est déjà ouverte"
|
||||
},
|
||||
"app.home.jump-back-in.more-options": {
|
||||
"message": "Plus d'options"
|
||||
},
|
||||
"app.home.jump-back-in.never-played": {
|
||||
"message": "Jamais joué"
|
||||
},
|
||||
"app.home.jump-back-in.new-instance": {
|
||||
"message": "Nouvelle instance"
|
||||
},
|
||||
"app.home.jump-back-in.resize": {
|
||||
"message": "Glisser pour redimensionner"
|
||||
},
|
||||
"app.home.jump-back-in.title": {
|
||||
"message": "Lancement rapide"
|
||||
},
|
||||
"app.home.jump-back-in.view-instance": {
|
||||
"message": "Voir l'instance"
|
||||
},
|
||||
"app.hosting.update-required.description": {
|
||||
"message": "Vous avez besoin de mettre à jour pour utiliser Modrinth Hosting via Modrinth App"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "Télécharger la mise à jour"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "Modrinth Bot excité"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Mise à jour de Modrinth App requise"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-error": {
|
||||
"message": "Impossible d'appliquer des icônes aléatoires aux instances."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-success": {
|
||||
"message": "Appliqué une icône aléatoire pour {num, plural,one {# instance} other {# instances}}."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.badge": {
|
||||
"message": "Nouveautés"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.close": {
|
||||
"message": "Fermer"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.description": {
|
||||
"message": "Créez des icônes personnalisées pour vos instances directement dans l'application Modrinth. Mélangez et associez des arrière-plans avec des symboles de Minecraft et des mods populaires !"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.instances-without-icons": {
|
||||
"message": "{count, plural, one {Une instance n'a} other {# instances n'ont}} pas encore d'icône. Vous voulez qu'on {count, plural, one {lui} other {leur}} attribue {count, plural, one {une icône aléatoire} other {des icônes aléatoires}} pour commencer ?"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.random-icons": {
|
||||
"message": "Icônes aléatoires"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.skip": {
|
||||
"message": "Ignorer"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.title": {
|
||||
"message": "Éditeur d'icônes personnalisées"
|
||||
},
|
||||
"app.icon-editor.notification.close": {
|
||||
"message": "Fermer"
|
||||
},
|
||||
"app.icon-editor.notification.description": {
|
||||
"message": "Personnalisez vos instances !"
|
||||
},
|
||||
"app.icon-editor.notification.dismiss": {
|
||||
"message": "Ignorer"
|
||||
},
|
||||
"app.icon-editor.notification.title": {
|
||||
"message": "Nouvel éditeur d'icônes"
|
||||
},
|
||||
"app.icon-editor.notification.view-update": {
|
||||
"message": "Voir la mise à jour"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "Téléchargement du contenu"
|
||||
},
|
||||
@@ -344,6 +446,45 @@
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "Exécution des processeurs du chargeur"
|
||||
},
|
||||
"app.instance.action.add-content": {
|
||||
"message": "Ajouter du contenu"
|
||||
},
|
||||
"app.instance.action.copy-path": {
|
||||
"message": "Copier le chemin"
|
||||
},
|
||||
"app.instance.action.edit": {
|
||||
"message": "Modifier"
|
||||
},
|
||||
"app.instance.action.open-folder": {
|
||||
"message": "Ouvrir le dossier"
|
||||
},
|
||||
"app.instance.action.play": {
|
||||
"message": "Jouer"
|
||||
},
|
||||
"app.instance.action.stop": {
|
||||
"message": "Stop"
|
||||
},
|
||||
"app.instance.add-server.add": {
|
||||
"message": "Ajouter"
|
||||
},
|
||||
"app.instance.add-server.added": {
|
||||
"message": "Ajouté"
|
||||
},
|
||||
"app.instance.add-server.adding": {
|
||||
"message": "Ajout..."
|
||||
},
|
||||
"app.instance.add-server.cancel": {
|
||||
"message": "Annuler"
|
||||
},
|
||||
"app.instance.add-server.compatibility-warning": {
|
||||
"message": "Ce serveur peut ne pas être compatible avec toutes les instances."
|
||||
},
|
||||
"app.instance.add-server.search-placeholder": {
|
||||
"message": "Rechercher une instance"
|
||||
},
|
||||
"app.instance.add-server.title": {
|
||||
"message": "Ajouter le serveur à l'instance"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "Ajouté"
|
||||
},
|
||||
@@ -374,18 +515,60 @@
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Examination..."
|
||||
},
|
||||
"app.instance.card.icon-alt": {
|
||||
"message": "Icône de l'instance"
|
||||
},
|
||||
"app.instance.card.installing": {
|
||||
"message": "Installation..."
|
||||
},
|
||||
"app.instance.card.loading": {
|
||||
"message": "L'instance est en train de charger..."
|
||||
},
|
||||
"app.instance.card.never-played": {
|
||||
"message": "Jamais joué"
|
||||
},
|
||||
"app.instance.card.play": {
|
||||
"message": "Jouer"
|
||||
},
|
||||
"app.instance.card.played": {
|
||||
"message": "Joué {relativeTime}"
|
||||
},
|
||||
"app.instance.card.repair": {
|
||||
"message": "Réparer"
|
||||
},
|
||||
"app.instance.card.stop": {
|
||||
"message": "Stop"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Toutes les données pour votre instance seront supprimées à jamais, y compris vos mondes, vos configurations, et le contenu installé."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "Cette action est irréversible"
|
||||
},
|
||||
"app.instance.confirm-delete.bulk-admonition-body": {
|
||||
"message": "Toutes les données pour ces {count} instances vont être définitivement supprimées, incluant leurs mondes, configurations, et contenu installé."
|
||||
},
|
||||
"app.instance.confirm-delete.bulk-delete-button": {
|
||||
"message": "Supprimer {count} instances"
|
||||
},
|
||||
"app.instance.confirm-delete.bulk-header": {
|
||||
"message": "Supprimer {count} instances"
|
||||
},
|
||||
"app.instance.confirm-delete.delete-button": {
|
||||
"message": "Supprimer l'instance"
|
||||
},
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Supprimer l'instance"
|
||||
},
|
||||
"app.instance.confirm-delete.instances-label": {
|
||||
"message": "{count, plural, one {Instance} other {Instances ({count})}}"
|
||||
},
|
||||
"app.instance.content.managed-content.modpack-header": {
|
||||
"message": "Contenu du modpack"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "Contenu partagé"
|
||||
},
|
||||
"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 ?"
|
||||
},
|
||||
@@ -410,6 +593,9 @@
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "projet"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "Geler la version"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "Le contenu dans les instances verrouillées ne peut pas être modifié."
|
||||
},
|
||||
@@ -428,6 +614,12 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Mis en ligne avec succès"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "Dégeler la version"
|
||||
},
|
||||
"app.instance.navigation.back-to-instance": {
|
||||
"message": "Retour à l'instance"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "Vous pouvez partager cette instance avec vos amis !"
|
||||
},
|
||||
@@ -539,6 +731,27 @@
|
||||
"app.instance.shared-instance-wrong-account.warning-header": {
|
||||
"message": "Vous utilisez le mauvais compte Modrinth"
|
||||
},
|
||||
"app.instance.shortcut.created": {
|
||||
"message": "Raccourci créé"
|
||||
},
|
||||
"app.instance.shortcut.creation-error": {
|
||||
"message": "Erreur lors de la création du raccourci"
|
||||
},
|
||||
"app.instance.tab.content": {
|
||||
"message": "Contenu"
|
||||
},
|
||||
"app.instance.tab.files": {
|
||||
"message": "Fichiers"
|
||||
},
|
||||
"app.instance.tab.logs": {
|
||||
"message": "Journaux"
|
||||
},
|
||||
"app.instance.tab.share": {
|
||||
"message": "Partager"
|
||||
},
|
||||
"app.instance.tab.worlds": {
|
||||
"message": "Mondes"
|
||||
},
|
||||
"app.instance.worlds.add-server": {
|
||||
"message": "Ajouter un serveur"
|
||||
},
|
||||
@@ -638,6 +851,222 @@
|
||||
"app.java-selector.test-installation": {
|
||||
"message": "Tester l'installation Java"
|
||||
},
|
||||
"app.library.context-menu.create-instance": {
|
||||
"message": "Nouvelle instance"
|
||||
},
|
||||
"app.library.filter.add": {
|
||||
"message": "Ajouter un filtre"
|
||||
},
|
||||
"app.library.filter.clear": {
|
||||
"message": "Effacer les filtres"
|
||||
},
|
||||
"app.library.filter.game-version": {
|
||||
"message": "Version du jeu"
|
||||
},
|
||||
"app.library.filter.instance-type": {
|
||||
"message": "Type d'instance"
|
||||
},
|
||||
"app.library.filter.instance-type.custom": {
|
||||
"message": "Personnaliser"
|
||||
},
|
||||
"app.library.filter.instance-type.modpack": {
|
||||
"message": "Modpack"
|
||||
},
|
||||
"app.library.filter.instance-type.server": {
|
||||
"message": "Serveur"
|
||||
},
|
||||
"app.library.filter.label": {
|
||||
"message": "Filtrer par"
|
||||
},
|
||||
"app.library.filter.loader": {
|
||||
"message": "Loader"
|
||||
},
|
||||
"app.library.group-by.custom-group": {
|
||||
"message": "Groupe personnalisé"
|
||||
},
|
||||
"app.library.group-by.game-version": {
|
||||
"message": "Version du jeu"
|
||||
},
|
||||
"app.library.group-by.instance-type": {
|
||||
"message": "Type d'instance"
|
||||
},
|
||||
"app.library.group-by.label": {
|
||||
"message": "Grouper par"
|
||||
},
|
||||
"app.library.group-by.loader": {
|
||||
"message": "Loader"
|
||||
},
|
||||
"app.library.group-by.none": {
|
||||
"message": "Aucun regroupement"
|
||||
},
|
||||
"app.library.group.collapse": {
|
||||
"message": "Réduire le groupe"
|
||||
},
|
||||
"app.library.group.context-menu.add-to-group": {
|
||||
"message": "Ajouter au groupe"
|
||||
},
|
||||
"app.library.group.create": {
|
||||
"message": "Créer un groupe"
|
||||
},
|
||||
"app.library.group.create.cancel": {
|
||||
"message": "Annuler"
|
||||
},
|
||||
"app.library.group.delete": {
|
||||
"message": "Supprimer le groupe"
|
||||
},
|
||||
"app.library.group.delete-description": {
|
||||
"message": "Les instances de ce groupe seront dégroupées."
|
||||
},
|
||||
"app.library.group.edit-name": {
|
||||
"message": "Modifier le nom du groupe"
|
||||
},
|
||||
"app.library.group.empty": {
|
||||
"message": "Glisser-déposer pour ajouter des instances."
|
||||
},
|
||||
"app.library.group.expand": {
|
||||
"message": "Développer les groupes"
|
||||
},
|
||||
"app.library.group.favorites": {
|
||||
"message": "Favoris"
|
||||
},
|
||||
"app.library.group.instance.add": {
|
||||
"message": "Ajouter"
|
||||
},
|
||||
"app.library.group.instance.added": {
|
||||
"message": "Ajouté"
|
||||
},
|
||||
"app.library.group.instances-modal.add": {
|
||||
"message": "Ajouter"
|
||||
},
|
||||
"app.library.group.instances-modal.added": {
|
||||
"message": "Ajouté"
|
||||
},
|
||||
"app.library.group.instances-modal.no-instances-found": {
|
||||
"message": "Aucune instance trouvée"
|
||||
},
|
||||
"app.library.group.instances-modal.search-placeholder": {
|
||||
"message": "Rechercher une instance"
|
||||
},
|
||||
"app.library.group.instances-modal.title": {
|
||||
"message": "Ajouter des instances à \"{groupName}\""
|
||||
},
|
||||
"app.library.group.move-down": {
|
||||
"message": "Déplacer le groupe vers le bas"
|
||||
},
|
||||
"app.library.group.move-up": {
|
||||
"message": "Déplacer le groupe vers le haut"
|
||||
},
|
||||
"app.library.group.name": {
|
||||
"message": "Nom du groupe"
|
||||
},
|
||||
"app.library.group.name-empty": {
|
||||
"message": "Veuillez saisir un nom pour le groupe."
|
||||
},
|
||||
"app.library.group.name-placeholder": {
|
||||
"message": "Saisir le nom du groupe"
|
||||
},
|
||||
"app.library.group.new": {
|
||||
"message": "Nouveau groupe"
|
||||
},
|
||||
"app.library.group.no-instances-found": {
|
||||
"message": "Aucune instance trouvée"
|
||||
},
|
||||
"app.library.instance.action.add-content": {
|
||||
"message": "Ajouter du contenu"
|
||||
},
|
||||
"app.library.instance.action.add-to-favorites": {
|
||||
"message": "Ajouter aux favoris"
|
||||
},
|
||||
"app.library.instance.action.copy-path": {
|
||||
"message": "Copier le chemin"
|
||||
},
|
||||
"app.library.instance.action.delete": {
|
||||
"message": "Supprimer"
|
||||
},
|
||||
"app.library.instance.action.duplicate": {
|
||||
"message": "Dupliquer l'instance"
|
||||
},
|
||||
"app.library.instance.action.open-folder": {
|
||||
"message": "Ouvrir le dossier"
|
||||
},
|
||||
"app.library.instance.action.play": {
|
||||
"message": "Jouer"
|
||||
},
|
||||
"app.library.instance.action.remove-from-favorites": {
|
||||
"message": "Retirer des favoris"
|
||||
},
|
||||
"app.library.instance.action.stop": {
|
||||
"message": "Arrêter"
|
||||
},
|
||||
"app.library.instance.action.view-instance": {
|
||||
"message": "Voir l'instance"
|
||||
},
|
||||
"app.library.instance.deselect": {
|
||||
"message": "Désélectionner l'instance"
|
||||
},
|
||||
"app.library.instance.deselect-with-name": {
|
||||
"message": "Désélectionner {name}"
|
||||
},
|
||||
"app.library.instance.installing": {
|
||||
"message": "Installation..."
|
||||
},
|
||||
"app.library.instance.new": {
|
||||
"message": "Nouvelle instance"
|
||||
},
|
||||
"app.library.instance.open-with-name": {
|
||||
"message": "Ouvrir {name}"
|
||||
},
|
||||
"app.library.instance.play": {
|
||||
"message": "Jouer"
|
||||
},
|
||||
"app.library.instance.repair": {
|
||||
"message": "Réparer"
|
||||
},
|
||||
"app.library.instance.select": {
|
||||
"message": "Sélectionner l'instance"
|
||||
},
|
||||
"app.library.instance.select-with-name": {
|
||||
"message": "Sélectionner {name}"
|
||||
},
|
||||
"app.library.instance.stop": {
|
||||
"message": "Arrêter"
|
||||
},
|
||||
"app.library.search.placeholder": {
|
||||
"message": "Rechercher"
|
||||
},
|
||||
"app.library.selection.aria-label": {
|
||||
"message": "Instances sélectionnées"
|
||||
},
|
||||
"app.library.selection.new-group": {
|
||||
"message": "Nouveau groupe"
|
||||
},
|
||||
"app.library.selection.remove-from-group": {
|
||||
"message": "Retiré du groupe"
|
||||
},
|
||||
"app.library.sort.date-created": {
|
||||
"message": "Date de création"
|
||||
},
|
||||
"app.library.sort.date-modified": {
|
||||
"message": "Date de modification"
|
||||
},
|
||||
"app.library.sort.hours-played": {
|
||||
"message": "Heures de jeu"
|
||||
},
|
||||
"app.library.sort.label": {
|
||||
"message": "Trier par"
|
||||
},
|
||||
"app.library.sort.last-played": {
|
||||
"message": "Dernière partie jouée"
|
||||
},
|
||||
"app.library.sort.loader": {
|
||||
"message": "Loader"
|
||||
},
|
||||
"app.library.sort.name": {
|
||||
"message": "Nom"
|
||||
},
|
||||
"app.library.title": {
|
||||
"message": "Bibliothèque"
|
||||
},
|
||||
"app.modal.install-to-play.additional-context": {
|
||||
"message": "Contexte additionnel"
|
||||
},
|
||||
@@ -671,6 +1100,9 @@
|
||||
"app.modal.install-to-play.invite-warning-with-creator": {
|
||||
"message": "Cette invitation a été crée par <creator>{username}</creator>, pas par Modrinth. N'acceptez seulement les invitations de personnes en qui vous avez confiance."
|
||||
},
|
||||
"app.modal.install-to-play.managed-content.modpack-header": {
|
||||
"message": "Contenu du modpack"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# mod} other {# mods}}"
|
||||
},
|
||||
@@ -699,7 +1131,7 @@
|
||||
"message": "Pour des demandes de support, contactez notre <support-link>équipe de support</support-link>. Pour signaler des bugs, ouvrez <github-link>une issue Github</github-link>."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Un fichier est seulement examiné s'il est publié sur Modrinth, peu importe son extension (y compris .mrpack)."
|
||||
"message": "Les fichiers qui ne sont pas publiés sur Modrinth ne sont pas examinés."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Instance partagée"
|
||||
@@ -770,12 +1202,33 @@
|
||||
"app.nav.upgrade-to-modrinth-plus": {
|
||||
"message": "Passer à Modrinth+"
|
||||
},
|
||||
"app.navigation.go-back": {
|
||||
"message": "Retour"
|
||||
},
|
||||
"app.navigation.go-forward": {
|
||||
"message": "Suivant"
|
||||
},
|
||||
"app.navigation.home": {
|
||||
"message": "Accueil"
|
||||
},
|
||||
"app.navigation.more-options": {
|
||||
"message": "Plus d'options"
|
||||
},
|
||||
"app.navigation.next-image": {
|
||||
"message": "Prochaine image"
|
||||
},
|
||||
"app.news.title": {
|
||||
"message": "Nouveautés"
|
||||
},
|
||||
"app.news.view-all": {
|
||||
"message": "Voir toutes les nouveautés"
|
||||
},
|
||||
"app.notification.warning": {
|
||||
"message": "Avertissement"
|
||||
},
|
||||
"app.project.coming-soon": {
|
||||
"message": "Arrive bientôt"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Ce projet a déjà été installé"
|
||||
},
|
||||
@@ -788,6 +1241,18 @@
|
||||
"app.project.install-context.back-to-instance": {
|
||||
"message": "Retour à l'instance"
|
||||
},
|
||||
"app.project.more-options": {
|
||||
"message": "Plus d'options"
|
||||
},
|
||||
"app.project.tab.description": {
|
||||
"message": "Description"
|
||||
},
|
||||
"app.project.tab.gallery": {
|
||||
"message": "Galerie"
|
||||
},
|
||||
"app.project.tab.versions": {
|
||||
"message": "Versions"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Toutes les versions"
|
||||
},
|
||||
@@ -1016,6 +1481,12 @@
|
||||
"app.skins.delete-modal.title": {
|
||||
"message": "Êtes-vous sûr de vouloir supprimer ce skin ?"
|
||||
},
|
||||
"app.skins.demo.apply-tooltip": {
|
||||
"message": "Connectez-vous pour appliquer des skins."
|
||||
},
|
||||
"app.skins.demo.description": {
|
||||
"message": "Connectez-vous à votre compte Minecraft pour sauvegarder et appliquer des skins !"
|
||||
},
|
||||
"app.skins.dropped-file-error.text": {
|
||||
"message": "Impossible de lire le fichier déposé."
|
||||
},
|
||||
@@ -1049,6 +1520,9 @@
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "Cape"
|
||||
},
|
||||
"app.skins.modal.demo-save-tooltip": {
|
||||
"message": "Connectez-vous pour sauvegarder des skins."
|
||||
},
|
||||
"app.skins.modal.edit-title": {
|
||||
"message": "Modification du skin"
|
||||
},
|
||||
@@ -1137,7 +1611,7 @@
|
||||
"message": "Horde miniature"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Se connecter"
|
||||
"message": "Connectez-vous à Microsoft"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Sélecteur de skin"
|
||||
@@ -1196,6 +1670,9 @@
|
||||
"app.user.project.install-to-instance": {
|
||||
"message": "Installer dans l'instance"
|
||||
},
|
||||
"app.welcome-screen.create-instance": {
|
||||
"message": "Créer une nouvelle instance"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "exemple.modrinth.gg"
|
||||
},
|
||||
@@ -1211,6 +1688,9 @@
|
||||
"app.world.world-item.offline": {
|
||||
"message": "Hors ligne"
|
||||
},
|
||||
"app.world.world-item.players-online": {
|
||||
"message": "{count} en ligne"
|
||||
},
|
||||
"content.shared-instance.change-version-body": {
|
||||
"message": "Changer la version change seulement votre copie locale. Des futures mises à jour d'instance partagées peuvent restaurer ou changer la version."
|
||||
},
|
||||
@@ -1418,6 +1898,138 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "Sauvegarder en tant que..."
|
||||
},
|
||||
"instance.icon-editor.background": {
|
||||
"message": "Arrière plan"
|
||||
},
|
||||
"instance.icon-editor.background.blue": {
|
||||
"message": "Bleu"
|
||||
},
|
||||
"instance.icon-editor.background.dark-gray": {
|
||||
"message": "Gris foncé"
|
||||
},
|
||||
"instance.icon-editor.background.dark-green": {
|
||||
"message": "Vert foncé"
|
||||
},
|
||||
"instance.icon-editor.background.gray": {
|
||||
"message": "Gris"
|
||||
},
|
||||
"instance.icon-editor.background.green": {
|
||||
"message": "Vert"
|
||||
},
|
||||
"instance.icon-editor.background.light-gray": {
|
||||
"message": "Gris clair"
|
||||
},
|
||||
"instance.icon-editor.background.orange": {
|
||||
"message": "Orange"
|
||||
},
|
||||
"instance.icon-editor.background.purple": {
|
||||
"message": "Violet"
|
||||
},
|
||||
"instance.icon-editor.background.red": {
|
||||
"message": "Rouge"
|
||||
},
|
||||
"instance.icon-editor.background.rose": {
|
||||
"message": "Rose"
|
||||
},
|
||||
"instance.icon-editor.background.yellow": {
|
||||
"message": "Jaune"
|
||||
},
|
||||
"instance.icon-editor.save": {
|
||||
"message": "Enregistrer l'Icône"
|
||||
},
|
||||
"instance.icon-editor.surprise-me": {
|
||||
"message": "Aléatoire"
|
||||
},
|
||||
"instance.icon-editor.symbol": {
|
||||
"message": "Symbole"
|
||||
},
|
||||
"instance.icon-editor.symbol.backpack": {
|
||||
"message": "Sac à dos"
|
||||
},
|
||||
"instance.icon-editor.symbol.cake": {
|
||||
"message": "Gâteau"
|
||||
},
|
||||
"instance.icon-editor.symbol.campfire": {
|
||||
"message": "Feu de camp"
|
||||
},
|
||||
"instance.icon-editor.symbol.chest": {
|
||||
"message": "Coffre"
|
||||
},
|
||||
"instance.icon-editor.symbol.couch": {
|
||||
"message": "Canapé"
|
||||
},
|
||||
"instance.icon-editor.symbol.creeper": {
|
||||
"message": "Creeper"
|
||||
},
|
||||
"instance.icon-editor.symbol.enchanting-table": {
|
||||
"message": "Table d'enchantement"
|
||||
},
|
||||
"instance.icon-editor.symbol.ender-chest": {
|
||||
"message": "Coffre de l'Ender"
|
||||
},
|
||||
"instance.icon-editor.symbol.ender-dragon": {
|
||||
"message": "Dragon de l'Ender"
|
||||
},
|
||||
"instance.icon-editor.symbol.engine": {
|
||||
"message": "Moteur"
|
||||
},
|
||||
"instance.icon-editor.symbol.fabric": {
|
||||
"message": "Fabric"
|
||||
},
|
||||
"instance.icon-editor.symbol.forge": {
|
||||
"message": "Forge"
|
||||
},
|
||||
"instance.icon-editor.symbol.furnace": {
|
||||
"message": "Four"
|
||||
},
|
||||
"instance.icon-editor.symbol.grass-block": {
|
||||
"message": "Bloc d'herbe"
|
||||
},
|
||||
"instance.icon-editor.symbol.lantern": {
|
||||
"message": "Lanterne"
|
||||
},
|
||||
"instance.icon-editor.symbol.pancakes": {
|
||||
"message": "Pancakes"
|
||||
},
|
||||
"instance.icon-editor.symbol.pickaxe": {
|
||||
"message": "Pioche"
|
||||
},
|
||||
"instance.icon-editor.symbol.quilt": {
|
||||
"message": "Quilt"
|
||||
},
|
||||
"instance.icon-editor.symbol.redstone-block": {
|
||||
"message": "Block de Redstone"
|
||||
},
|
||||
"instance.icon-editor.symbol.skeleton": {
|
||||
"message": "Squelette"
|
||||
},
|
||||
"instance.icon-editor.symbol.slime-block": {
|
||||
"message": "Bloc de slime"
|
||||
},
|
||||
"instance.icon-editor.symbol.space-helmet": {
|
||||
"message": "Casque d'Espace"
|
||||
},
|
||||
"instance.icon-editor.symbol.sword": {
|
||||
"message": "Épée"
|
||||
},
|
||||
"instance.icon-editor.symbol.terminal": {
|
||||
"message": "Console"
|
||||
},
|
||||
"instance.icon-editor.symbol.tire": {
|
||||
"message": "Pneu"
|
||||
},
|
||||
"instance.icon-editor.symbol.tnt": {
|
||||
"message": "TNT"
|
||||
},
|
||||
"instance.icon-editor.symbol.zombie": {
|
||||
"message": "Zombie"
|
||||
},
|
||||
"instance.icon-editor.title": {
|
||||
"message": "Éditeur d'icônes"
|
||||
},
|
||||
"instance.last-played": {
|
||||
"message": "Dernière fois joué :"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "Supprimer l'instance"
|
||||
},
|
||||
@@ -1508,6 +2120,12 @@
|
||||
"instance.settings.tabs.general.edit-icon": {
|
||||
"message": "Éditer l'icône"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.create": {
|
||||
"message": "Créer une icône"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.edit-created": {
|
||||
"message": "Modifier l’icône"
|
||||
},
|
||||
"instance.settings.tabs.general.edit-icon.remove": {
|
||||
"message": "Supprimer l'icône"
|
||||
},
|
||||
@@ -1517,6 +2135,9 @@
|
||||
"instance.settings.tabs.general.edit-icon.select": {
|
||||
"message": "Sélectionner l'icône"
|
||||
},
|
||||
"instance.settings.tabs.general.icon": {
|
||||
"message": "Icône"
|
||||
},
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nom"
|
||||
},
|
||||
@@ -1769,6 +2390,9 @@
|
||||
"instance.worlds.linked_server": {
|
||||
"message": "Géré par le projet du serveur"
|
||||
},
|
||||
"instance.worlds.more-options": {
|
||||
"message": "Plus d'options"
|
||||
},
|
||||
"instance.worlds.no_contact": {
|
||||
"message": "Le serveur n'a pas pu être contacté"
|
||||
},
|
||||
@@ -1781,6 +2405,12 @@
|
||||
"instance.worlds.play_instance": {
|
||||
"message": "Jouer à l'instance"
|
||||
},
|
||||
"instance.worlds.shortcut-created": {
|
||||
"message": "Raccourci créé"
|
||||
},
|
||||
"instance.worlds.shortcut-creation-failed": {
|
||||
"message": "Échec de la création du raccourci"
|
||||
},
|
||||
"instance.worlds.view_instance": {
|
||||
"message": "Voir l'instance"
|
||||
},
|
||||
@@ -1859,6 +2489,15 @@
|
||||
"modal.modrinth-account-required.waiting-for-browser": {
|
||||
"message": "En attente de la confirmation du navigateur..."
|
||||
},
|
||||
"onboarding-checklist.login-minecraft": {
|
||||
"message": "Se connecter à Minecraft"
|
||||
},
|
||||
"onboarding-checklist.login-modrinth": {
|
||||
"message": "Se connecter à Modrinth"
|
||||
},
|
||||
"onboarding-checklist.title": {
|
||||
"message": "Premiers pas"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Procuré par l'instance"
|
||||
},
|
||||
|
||||
@@ -11,6 +11,24 @@
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "החבא יותר התקנים רצים"
|
||||
},
|
||||
"app.action-bar.install.copied-details": {
|
||||
"message": "הועתק"
|
||||
},
|
||||
"app.action-bar.install.copy-details": {
|
||||
"message": "העתקת הפרטים"
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "הצג התקנה"
|
||||
},
|
||||
"app.action-bar.install.retry": {
|
||||
"message": "נסה שוב"
|
||||
},
|
||||
"app.action-bar.install.summary.canceled": {
|
||||
"message": "ביטול"
|
||||
},
|
||||
"app.action-bar.install.summary.content-download-failed": {
|
||||
"message": "לא נתן להוריד את הקבצים"
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "הפוך למקרה ראשי"
|
||||
},
|
||||
@@ -20,8 +38,8 @@
|
||||
"app.action-bar.stop-instance": {
|
||||
"message": ""
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "חזרה לעולמות"
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "ספרייה"
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "ייתכן ששרתי האימות של Minecraft מושבתים כרגע. יש לבדוק את חיבור האינטרנט שלך ולנסות שוב מאוחר יותר."
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Letöltések"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "Aktív letöltések elrejtése"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "További futó példányok elrejtése"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Töltsd újra a frissítéshez"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "Aktív letöltések megjelenítése"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "További futó pédányok megjelenítése"
|
||||
},
|
||||
@@ -143,17 +149,11 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Adataid védelme és a hirdetések szerepe a Modrinth működésében"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Engedélyezi a vizuális effektusokat, például a háttér elmosását. Hardveres gyorsítás nélkül ez ronthatja a rendszer teljesítményét."
|
||||
"app.appearance-settings.compact-mode.description": {
|
||||
"message": "A könyvtári játékprofilok megjelenítése egy összetett, soros elrendezésben."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Haladó megjelenítési beállítások"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Válaszd ki a Modrinth App által használt színsémát."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Téma"
|
||||
"app.appearance-settings.compact-mode.title": {
|
||||
"message": "Összetett mód"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Kezdőlap"
|
||||
@@ -168,10 +168,10 @@
|
||||
"message": "Névcímke eltüntetése"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "A legutóbb játszott világok megjelenítése a kezdőlap „Folytasd itt” részében."
|
||||
"message": "A legutóbb játszott világok és játékprofilok megjelenítése a kezdőlap „Folytasd itt” részében."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Ugorj vissza a világokba"
|
||||
"message": "„Folytasd itt” a világokat és a játékprofilokat is"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "A Modrinth App bezáródik a Minecraft indításakor"
|
||||
@@ -179,12 +179,6 @@
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Alkalmazás bezárása"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Az operációs rendszer címsorának és ablakvezérlőinek használata. A módosítás érvénybe lépéséhez újra kell indítani az alkalmazást."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Rendszerablakkeret"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Megjeleníti, hogy mennyi időt töltöttél egy példányban."
|
||||
},
|
||||
@@ -257,11 +251,26 @@
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "A már telepítettek elrejtése"
|
||||
},
|
||||
"app.browse.project-type.data-packs": {
|
||||
"message": "Adatcsomagok"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modcsomagok"
|
||||
},
|
||||
"app.browse.project-type.mods": {
|
||||
"message": "Modok"
|
||||
},
|
||||
"app.browse.project-type.resource-packs": {
|
||||
"message": "Forráscsomagok"
|
||||
},
|
||||
"app.browse.project-type.servers": {
|
||||
"message": "Szerverek"
|
||||
},
|
||||
"app.browse.project-type.shaders": {
|
||||
"message": "Shaderek"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Tartalom hozzáadása kompatibilitási problémákat okozhat a szerverhez való csatlakozáskor. A hozzáadott tartalmak a szerverpéldány tartalmának frissítésekor elvesznek, ezért ezt tartsd szem előtt."
|
||||
"message": "Ha saját tartalmat adsz hozzá, előfordulhat, hogy nem tudsz csatlakozni ehhez a szerverhez. Minden általad hozzáadott tartalom törlődik, amikor a szerver által kezelt tartalom frissül."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Telepítés"
|
||||
@@ -275,6 +284,12 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Exportálás"
|
||||
},
|
||||
"app.export-modal.export-complete": {
|
||||
"message": "Exportálás befejezve"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name} exportálása sikeresen megtörtént."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "Modcsomag exportálása"
|
||||
},
|
||||
@@ -290,6 +305,75 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.home.jump-back-in.instance-locked": {
|
||||
"message": "Ez a játékprofil zárolva van"
|
||||
},
|
||||
"app.home.jump-back-in.instance-open": {
|
||||
"message": "A játékprofil már el van indítva"
|
||||
},
|
||||
"app.home.jump-back-in.more-options": {
|
||||
"message": "További beállítások"
|
||||
},
|
||||
"app.home.jump-back-in.never-played": {
|
||||
"message": "Még nem játszott"
|
||||
},
|
||||
"app.home.jump-back-in.new-instance": {
|
||||
"message": "Új példány"
|
||||
},
|
||||
"app.home.jump-back-in.title": {
|
||||
"message": "Folytasd itt"
|
||||
},
|
||||
"app.home.jump-back-in.view-instance": {
|
||||
"message": "Játékprofil megtekintése"
|
||||
},
|
||||
"app.hosting.update-required.description": {
|
||||
"message": "Frissítened kell, hogy használhasd a Modrinth Hostingot a Modrinth Appon keresztül"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "Letöltés a frissítéshez"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "Izgatott Modrinth bot"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Frissítés szükséges a Modrinth Apphoz"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-error": {
|
||||
"message": "Nem sikerült véletlenszerű ikonokat rendelni a játékprofilokhoz."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-success": {
|
||||
"message": "Véletlenszerű ikon alkalmazva {num} játékprofilnak."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.close": {
|
||||
"message": "Bezárás"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.instances-without-icons": {
|
||||
"message": "{count, plural, one {Egy játékprofilnak} other {# játékprofilnak}} még nincs ikonja. Szeretnéd, ha adnánk neki{count, plural, one { egy} other {k néhány}} véletlenszerű példát a kezdéshez?"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.random-icons": {
|
||||
"message": "Véletlenszerű ikonok"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.skip": {
|
||||
"message": "Átugrás"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.title": {
|
||||
"message": "Saját ikon szerkesztő"
|
||||
},
|
||||
"app.icon-editor.notification.close": {
|
||||
"message": "Bezárás"
|
||||
},
|
||||
"app.icon-editor.notification.description": {
|
||||
"message": "Szabd személyre a példányodat!"
|
||||
},
|
||||
"app.icon-editor.notification.dismiss": {
|
||||
"message": "Elvet"
|
||||
},
|
||||
"app.icon-editor.notification.title": {
|
||||
"message": "Új ikon szerkesztő"
|
||||
},
|
||||
"app.icon-editor.notification.view-update": {
|
||||
"message": "Frissítés megtekintése"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "A tartalom letöltése..."
|
||||
},
|
||||
@@ -344,6 +428,36 @@
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "Betöltőfolyamatok futtatása..."
|
||||
},
|
||||
"app.instance.action.add-content": {
|
||||
"message": "Tartalom hozzáadása"
|
||||
},
|
||||
"app.instance.action.copy-path": {
|
||||
"message": "Elérési út másolása"
|
||||
},
|
||||
"app.instance.action.edit": {
|
||||
"message": "Szerkesztés"
|
||||
},
|
||||
"app.instance.action.open-folder": {
|
||||
"message": "Mappa megnyitása"
|
||||
},
|
||||
"app.instance.action.play": {
|
||||
"message": "Játék"
|
||||
},
|
||||
"app.instance.action.stop": {
|
||||
"message": "Leállítás"
|
||||
},
|
||||
"app.instance.add-server.add": {
|
||||
"message": "Hozzáadás"
|
||||
},
|
||||
"app.instance.add-server.added": {
|
||||
"message": "Hozzáadva"
|
||||
},
|
||||
"app.instance.add-server.adding": {
|
||||
"message": "Hozzáadás..."
|
||||
},
|
||||
"app.instance.add-server.cancel": {
|
||||
"message": "Mégse"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "Hozzáadva"
|
||||
},
|
||||
@@ -374,6 +488,18 @@
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Áttekintés..."
|
||||
},
|
||||
"app.instance.card.play": {
|
||||
"message": "Játék"
|
||||
},
|
||||
"app.instance.card.played": {
|
||||
"message": "Játszott {relativeTime}"
|
||||
},
|
||||
"app.instance.card.repair": {
|
||||
"message": "Javítás"
|
||||
},
|
||||
"app.instance.card.stop": {
|
||||
"message": "Leállítás"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "A példányodhoz tartozó összes adat véglegesen törlődik, beleértve a világjaidat, a beállításaidat és az összes telepített tartalmat."
|
||||
},
|
||||
@@ -386,6 +512,15 @@
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Példány törlése"
|
||||
},
|
||||
"app.instance.confirm-delete.instances-label": {
|
||||
"message": "{count, plural, one {Játékprofil:} other {Játékprofilok: ({count})}}"
|
||||
},
|
||||
"app.instance.content.managed-content.modpack-header": {
|
||||
"message": "A modcsomag tartalma"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "Megosztott tartalom"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "Ez a modcsomag már telepítve van a(z) <bold>{instanceName}</bold> példányban. Biztosan meg szeretnéd kettőzni?"
|
||||
},
|
||||
@@ -410,6 +545,9 @@
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "projekt"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "Rögzített verzió"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "Zárolt példányokban a tartalom nem változtatható meg."
|
||||
},
|
||||
@@ -428,6 +566,9 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Sikeresen feltöltve"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "Verzió feloldása"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "Ezt a példányt megoszthatod a barátaiddal!"
|
||||
},
|
||||
@@ -539,6 +680,21 @@
|
||||
"app.instance.shared-instance-wrong-account.warning-header": {
|
||||
"message": "Nem a megfelelő Modrinth-fiókot használod"
|
||||
},
|
||||
"app.instance.tab.content": {
|
||||
"message": "Tartalom"
|
||||
},
|
||||
"app.instance.tab.files": {
|
||||
"message": "Fájlok"
|
||||
},
|
||||
"app.instance.tab.logs": {
|
||||
"message": "Naplók"
|
||||
},
|
||||
"app.instance.tab.share": {
|
||||
"message": "Megosztás"
|
||||
},
|
||||
"app.instance.tab.worlds": {
|
||||
"message": "Világok"
|
||||
},
|
||||
"app.instance.worlds.add-server": {
|
||||
"message": "Szerver hozzáadása"
|
||||
},
|
||||
@@ -638,6 +794,66 @@
|
||||
"app.java-selector.test-installation": {
|
||||
"message": "Java-telepítés tesztelése"
|
||||
},
|
||||
"app.library.filter.instance-type": {
|
||||
"message": "A játékprofil típusa"
|
||||
},
|
||||
"app.library.filter.instance-type.modpack": {
|
||||
"message": "Modcsomag"
|
||||
},
|
||||
"app.library.group.favorites": {
|
||||
"message": "Kedvencek"
|
||||
},
|
||||
"app.library.group.instance.add": {
|
||||
"message": "Hozzáadás"
|
||||
},
|
||||
"app.library.group.instance.added": {
|
||||
"message": "Hozzáadva"
|
||||
},
|
||||
"app.library.group.instances-modal.add": {
|
||||
"message": "Hozzáadás"
|
||||
},
|
||||
"app.library.group.instances-modal.added": {
|
||||
"message": "Hozzáadva"
|
||||
},
|
||||
"app.library.group.instances-modal.search-placeholder": {
|
||||
"message": "Játékprofil keresése..."
|
||||
},
|
||||
"app.library.group.instances-modal.title": {
|
||||
"message": "Játékprofilok hozzáadása a „{groupName}” csoporthoz"
|
||||
},
|
||||
"app.library.group.name": {
|
||||
"message": "Csoport neve"
|
||||
},
|
||||
"app.library.group.name-placeholder": {
|
||||
"message": "Add meg a csoport nevét"
|
||||
},
|
||||
"app.library.group.new": {
|
||||
"message": "Új csoport"
|
||||
},
|
||||
"app.library.instance.action.add-content": {
|
||||
"message": "Tartalom hozzáadása"
|
||||
},
|
||||
"app.library.instance.action.stop": {
|
||||
"message": "Stop"
|
||||
},
|
||||
"app.library.instance.open-with-name": {
|
||||
"message": "{name} megnyitása"
|
||||
},
|
||||
"app.library.instance.select-with-name": {
|
||||
"message": "{name} kiválasztása"
|
||||
},
|
||||
"app.library.search.placeholder": {
|
||||
"message": "Keresés"
|
||||
},
|
||||
"app.library.selection.new-group": {
|
||||
"message": "Új csoport"
|
||||
},
|
||||
"app.library.selection.selected-count": {
|
||||
"message": "{count} darab kijelölve"
|
||||
},
|
||||
"app.library.sort.name": {
|
||||
"message": "Név"
|
||||
},
|
||||
"app.modal.install-to-play.additional-context": {
|
||||
"message": "További kontextus"
|
||||
},
|
||||
@@ -671,6 +887,9 @@
|
||||
"app.modal.install-to-play.invite-warning-with-creator": {
|
||||
"message": "Ezt a meghívót <creator>{username}</creator> készítette, nem pedig a Modrinth. Csak olyan személyektől fogadd el a meghívásokat, akikben megbízol."
|
||||
},
|
||||
"app.modal.install-to-play.managed-content.modpack-header": {
|
||||
"message": "A modcsomag tartalma"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count} mod"
|
||||
},
|
||||
@@ -699,7 +918,7 @@
|
||||
"message": "Ha segítségre van szükséged, vedd fel a kapcsolatot a <support-link>támogatási csapatunkkal</support-link>. Hibajelentésekhez nyiss egy <github-link>GitHub-issue-t</github-link>."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Egy fájlt csak akkor vizsgálnak át, ha azt a Modrinthen közzétették, függetlenül a fájlformátumtól (beleértve az `.mrpack` formátumot is)."
|
||||
"message": "Azok a fájlok, amelyek nincsenek közzétéve a Modrinthon, nincsenek ellenőrizve."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Megosztott példány"
|
||||
@@ -776,6 +995,12 @@
|
||||
"app.news.view-all": {
|
||||
"message": "Összes hír"
|
||||
},
|
||||
"app.notification.warning": {
|
||||
"message": "Figyelmeztetés"
|
||||
},
|
||||
"app.project.coming-soon": {
|
||||
"message": "Nemsokára"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Ez a projekt már telepítve van"
|
||||
},
|
||||
@@ -788,6 +1013,18 @@
|
||||
"app.project.install-context.back-to-instance": {
|
||||
"message": "Vissza a példányhoz"
|
||||
},
|
||||
"app.project.more-options": {
|
||||
"message": "További lehetőségek"
|
||||
},
|
||||
"app.project.tab.description": {
|
||||
"message": "Leírás"
|
||||
},
|
||||
"app.project.tab.gallery": {
|
||||
"message": "Galléria"
|
||||
},
|
||||
"app.project.tab.versions": {
|
||||
"message": "Verziók"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Összes verzió"
|
||||
},
|
||||
@@ -1136,9 +1373,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Bejelentkezés"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Kinézetváltó"
|
||||
},
|
||||
@@ -1196,6 +1430,9 @@
|
||||
"app.user.project.install-to-instance": {
|
||||
"message": "Telepítés a példányba"
|
||||
},
|
||||
"app.welcome-screen.title": {
|
||||
"message": "Üdvözlünk a Modrinthon"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "pelda.modrinth.gg"
|
||||
},
|
||||
@@ -1418,6 +1655,45 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "Mentés másként..."
|
||||
},
|
||||
"instance.icon-editor.background": {
|
||||
"message": "Háttér"
|
||||
},
|
||||
"instance.icon-editor.background.blue": {
|
||||
"message": "Kék"
|
||||
},
|
||||
"instance.icon-editor.background.dark-gray": {
|
||||
"message": "Sötét szürke"
|
||||
},
|
||||
"instance.icon-editor.background.dark-green": {
|
||||
"message": "Sotét zöld"
|
||||
},
|
||||
"instance.icon-editor.background.gray": {
|
||||
"message": "Szürke"
|
||||
},
|
||||
"instance.icon-editor.background.green": {
|
||||
"message": "Zöld"
|
||||
},
|
||||
"instance.icon-editor.recents": {
|
||||
"message": "Előzmények"
|
||||
},
|
||||
"instance.icon-editor.save": {
|
||||
"message": "Ikon mentése"
|
||||
},
|
||||
"instance.icon-editor.symbol.chest": {
|
||||
"message": "Láda"
|
||||
},
|
||||
"instance.icon-editor.symbol.command-block": {
|
||||
"message": "Parancs block"
|
||||
},
|
||||
"instance.icon-editor.symbol.pancakes": {
|
||||
"message": "Palacsinta"
|
||||
},
|
||||
"instance.icon-editor.symbol.sword": {
|
||||
"message": "Kard"
|
||||
},
|
||||
"instance.last-played": {
|
||||
"message": "Legutóbb játszva"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "Példány törlése"
|
||||
},
|
||||
@@ -1574,6 +1850,12 @@
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Játék indítási horgok"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
|
||||
"message": "$INST_JAVA_ARGS: A megadott JVM argumentumok a játéknak"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java.description": {
|
||||
"message": "$INST_JAVA: A Java bináris abszolút útvonala"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
|
||||
"message": "$INST_MC_DIR: A(z) $INST_DIR álneve"
|
||||
},
|
||||
@@ -1844,6 +2126,9 @@
|
||||
"modal.modrinth-account-required.waiting-for-browser": {
|
||||
"message": "Várakozás a böngésző megerősítésére..."
|
||||
},
|
||||
"onboarding-checklist.login-modrinth": {
|
||||
"message": "Jelentkezz be a Modrinthba"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Pédány által megadva"
|
||||
},
|
||||
|
||||
@@ -44,42 +44,15 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Lihat catatan"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderasi tingkat lanjut"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema warna"
|
||||
},
|
||||
"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.jump-back-into-worlds.title": {
|
||||
"message": "Masuk kembali ke dunia"
|
||||
},
|
||||
"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.native-decorations.description": {
|
||||
"message": "Gunakan bingkai jendela sistem (wajib menjalankan ulang aplikasi)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Hiasan asli"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Menampilkan seberapa lama Anda memainkan sebuah instans."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Tampilkan waktu bermain"
|
||||
},
|
||||
@@ -119,9 +92,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Paket Mod"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Menambah konten dapat merusak kompatibilitas saat bergabung ke server. Konten tambahan apa pun akan hilang ketika Anda memperbarui konten instans server."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Memasang"
|
||||
},
|
||||
@@ -392,9 +362,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Masuk"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Pemilih rupa"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "ダウンロード"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "進行中のダウンロードを非表示"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "実行中のインスタンスの一部を非表示"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "再読み込みしてアップデート"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "進行中のダウンロードを表示"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "実行中のインスタンスをさらに表示"
|
||||
},
|
||||
@@ -132,7 +138,7 @@
|
||||
"message": "すべて受け入れる"
|
||||
},
|
||||
"app.ads-consent.body": {
|
||||
"message": "Modrinthは広告収益によって運営され、クリエイターへの報酬も広告収益から支払われています。当社のパートナーは、広告のパーソナライズや効果測定のために、アプリ内でCookieを保存・利用する場合があります。"
|
||||
"message": "Modrinthは広告収益によって運営され、クリエイターへの報酬もその収益から支払われています。当社のパートナーは、広告のパーソナライズや効果測定のために、アプリ内でCookieを保存・利用する場合があります。"
|
||||
},
|
||||
"app.ads-consent.manage": {
|
||||
"message": "設定の管理"
|
||||
@@ -143,18 +149,6 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "プライバシーと広告がModrinthを支える仕組み"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "背景のぼかしなどの視覚効果を有効にします。ハードウェアアクセラレーションが無効の場合、パフォーマンスが低下する可能性があります。"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "高度なレンダリング"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Modrinth Appのテーマを選びましょう。"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "テーマ"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "ホーム"
|
||||
},
|
||||
@@ -167,24 +161,12 @@
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "ネームタグを非表示にする"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "ホーム画面の「Jump back in」セクションに、最近プレイしたワールドを表示します。"
|
||||
},
|
||||
"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": "OS標準のタイトルバーとウィンドウ操作ボタンを使用します。反映にはアプリの再起動が必要です。"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "システムウィンドウフレーム"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "各インスタンスのプレイ時間を表示します。"
|
||||
},
|
||||
@@ -257,11 +239,23 @@
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "インストール済みを非表示"
|
||||
},
|
||||
"app.browse.project-type.data-packs": {
|
||||
"message": "データパック"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modパック"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "コンテンツを追加すると、サーバーに参加する際に互換性が失われる可能性があります。また、追加されたコンテンツはインスタンスのコンテンツをアップデートした際に失われます。"
|
||||
"app.browse.project-type.mods": {
|
||||
"message": "モッド"
|
||||
},
|
||||
"app.browse.project-type.resource-packs": {
|
||||
"message": "リソースパック"
|
||||
},
|
||||
"app.browse.project-type.servers": {
|
||||
"message": "サーバー"
|
||||
},
|
||||
"app.browse.project-type.shaders": {
|
||||
"message": "シェーダー"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "インストール中"
|
||||
@@ -290,6 +284,9 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.close": {
|
||||
"message": "閉じる"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "コンテンツをダウンロード中"
|
||||
},
|
||||
@@ -344,6 +341,12 @@
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "ローダーのプロセッサーを実行中"
|
||||
},
|
||||
"app.instance.add-server.add": {
|
||||
"message": "追加"
|
||||
},
|
||||
"app.instance.add-server.cancel": {
|
||||
"message": "キャンセル"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "追加済"
|
||||
},
|
||||
@@ -374,12 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "審査中…"
|
||||
},
|
||||
"app.instance.card.repair": {
|
||||
"message": "修復"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "インスタンス内のすべてのデータは、ワールド、設定、インストール済みのコンテンツを含め、完全に削除されます。"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "この操作は取り消せません"
|
||||
},
|
||||
"app.instance.confirm-delete.bulk-delete-button": {
|
||||
"message": "{count}個のインスタンスを削除"
|
||||
},
|
||||
"app.instance.confirm-delete.delete-button": {
|
||||
"message": "インスタンスを削除"
|
||||
},
|
||||
@@ -638,6 +647,9 @@
|
||||
"app.java-selector.test-installation": {
|
||||
"message": "Javaのインストールをテスト"
|
||||
},
|
||||
"app.library.group.move-up": {
|
||||
"message": "グループを上に移動"
|
||||
},
|
||||
"app.modal.install-to-play.additional-context": {
|
||||
"message": "追加のコンテンツ"
|
||||
},
|
||||
@@ -699,7 +711,7 @@
|
||||
"message": "サポートに関するお問い合わせは、<support-link>サポートチーム</support-link>までご連絡ください。バグの報告は、<github-link>GitHubのIssue</github-link>を作成してください。"
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "ファイル形式(.mrpackを含む)に関わらず、Modrinthに公開されているファイルのみが審査の対象となります。"
|
||||
"message": "追加するファイルはModrinthの認証を受けていません"
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "共有インスタンス"
|
||||
@@ -903,7 +915,7 @@
|
||||
"message": "MacOS"
|
||||
},
|
||||
"app.settings.privacy.ads-consent.intro": {
|
||||
"message": "Modrinthは広告収益によって運営され、クリエイターへの報酬も広告収益から支払われています。当社のパートナーは、広告のパーソナライズや効果測定のために、アプリ内でCookieを保存・利用する場合があります。この機能は下のボタンから個別に拒否や許可または変更することができます。"
|
||||
"message": "Modrinthは広告収益によって運営され、クリエイターへの報酬もその収益から支払われています。当社のパートナーは、広告のパーソナライズや効果測定のために、アプリ内でCookieを保存・利用する場合があります。この機能は下のボタンから個別に拒否や許可または変更することができます。"
|
||||
},
|
||||
"app.settings.privacy.discord-rich-presence.description": {
|
||||
"message": "Discordの現在のプレイ中に Modrinth App を表示します。これはModによってインスタンスに追加されるRich Presenceには影響しません。適用にはアプリの再起動が必要です。"
|
||||
@@ -1136,9 +1148,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "タイニー・テイクオーバー"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "サイン審"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "スキンセレクター"
|
||||
},
|
||||
@@ -1371,10 +1380,10 @@
|
||||
"message": "インスタンスの設定"
|
||||
},
|
||||
"instance.action.starting": {
|
||||
"message": "起動中…"
|
||||
"message": "プレイ"
|
||||
},
|
||||
"instance.action.stopping": {
|
||||
"message": "停止中…"
|
||||
"message": "停止"
|
||||
},
|
||||
"instance.add-server.add-and-play": {
|
||||
"message": "追加してプレイ"
|
||||
@@ -1418,6 +1427,138 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "名前をつけて保存…"
|
||||
},
|
||||
"instance.icon-editor.symbol.backpack": {
|
||||
"message": "リュック"
|
||||
},
|
||||
"instance.icon-editor.symbol.beacon": {
|
||||
"message": "ビーコン"
|
||||
},
|
||||
"instance.icon-editor.symbol.bookshelf": {
|
||||
"message": "本棚"
|
||||
},
|
||||
"instance.icon-editor.symbol.cake": {
|
||||
"message": "ケーキ"
|
||||
},
|
||||
"instance.icon-editor.symbol.campfire": {
|
||||
"message": "焚き火"
|
||||
},
|
||||
"instance.icon-editor.symbol.chest": {
|
||||
"message": "チェスト"
|
||||
},
|
||||
"instance.icon-editor.symbol.cogwheel": {
|
||||
"message": "歯車"
|
||||
},
|
||||
"instance.icon-editor.symbol.command-block": {
|
||||
"message": "コマンドブロック"
|
||||
},
|
||||
"instance.icon-editor.symbol.cooking-pot": {
|
||||
"message": "鍋"
|
||||
},
|
||||
"instance.icon-editor.symbol.couch": {
|
||||
"message": "ソファー"
|
||||
},
|
||||
"instance.icon-editor.symbol.crafting-table": {
|
||||
"message": "作業台"
|
||||
},
|
||||
"instance.icon-editor.symbol.create-wrench": {
|
||||
"message": "レンチ"
|
||||
},
|
||||
"instance.icon-editor.symbol.creeper": {
|
||||
"message": "クリーパー"
|
||||
},
|
||||
"instance.icon-editor.symbol.enchanting-table": {
|
||||
"message": "エンチャントテーブル"
|
||||
},
|
||||
"instance.icon-editor.symbol.ender-chest": {
|
||||
"message": "エンダーチェスト"
|
||||
},
|
||||
"instance.icon-editor.symbol.ender-dragon": {
|
||||
"message": "エンダードラゴン"
|
||||
},
|
||||
"instance.icon-editor.symbol.engine": {
|
||||
"message": "エンジン"
|
||||
},
|
||||
"instance.icon-editor.symbol.fabric": {
|
||||
"message": "Fabric"
|
||||
},
|
||||
"instance.icon-editor.symbol.forge": {
|
||||
"message": "Forge"
|
||||
},
|
||||
"instance.icon-editor.symbol.furnace": {
|
||||
"message": "かまど"
|
||||
},
|
||||
"instance.icon-editor.symbol.gizmo": {
|
||||
"message": "Gizmo"
|
||||
},
|
||||
"instance.icon-editor.symbol.globe": {
|
||||
"message": "地球儀"
|
||||
},
|
||||
"instance.icon-editor.symbol.grass-block": {
|
||||
"message": "草ブロック"
|
||||
},
|
||||
"instance.icon-editor.symbol.lantern": {
|
||||
"message": "ランタン"
|
||||
},
|
||||
"instance.icon-editor.symbol.moobloom": {
|
||||
"message": "ムーブルーム"
|
||||
},
|
||||
"instance.icon-editor.symbol.neoforge": {
|
||||
"message": "NeoForge"
|
||||
},
|
||||
"instance.icon-editor.symbol.orb": {
|
||||
"message": "宝玉"
|
||||
},
|
||||
"instance.icon-editor.symbol.oxygen-distributor": {
|
||||
"message": "酸素分配機"
|
||||
},
|
||||
"instance.icon-editor.symbol.pancakes": {
|
||||
"message": "パンケーキ"
|
||||
},
|
||||
"instance.icon-editor.symbol.pickaxe": {
|
||||
"message": "ツルハシ"
|
||||
},
|
||||
"instance.icon-editor.symbol.poke-ball": {
|
||||
"message": "モンスターボール"
|
||||
},
|
||||
"instance.icon-editor.symbol.quilt": {
|
||||
"message": "Quilt"
|
||||
},
|
||||
"instance.icon-editor.symbol.redstone-block": {
|
||||
"message": "レッドストーンブロック"
|
||||
},
|
||||
"instance.icon-editor.symbol.sculk-sensor": {
|
||||
"message": "スカルクセンサー"
|
||||
},
|
||||
"instance.icon-editor.symbol.skeleton": {
|
||||
"message": "スケルトン"
|
||||
},
|
||||
"instance.icon-editor.symbol.skillet": {
|
||||
"message": "フライパン"
|
||||
},
|
||||
"instance.icon-editor.symbol.slime-block": {
|
||||
"message": "スライムブロック"
|
||||
},
|
||||
"instance.icon-editor.symbol.space-helmet": {
|
||||
"message": "宇宙ヘルメット"
|
||||
},
|
||||
"instance.icon-editor.symbol.sticky-piston": {
|
||||
"message": "粘着ピストン"
|
||||
},
|
||||
"instance.icon-editor.symbol.sword": {
|
||||
"message": "剣"
|
||||
},
|
||||
"instance.icon-editor.symbol.tiny-potato": {
|
||||
"message": "小さなポテト"
|
||||
},
|
||||
"instance.icon-editor.symbol.tire": {
|
||||
"message": "タイヤ"
|
||||
},
|
||||
"instance.icon-editor.symbol.tnt": {
|
||||
"message": "TNT"
|
||||
},
|
||||
"instance.icon-editor.symbol.zombie": {
|
||||
"message": "ゾンビ"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "インスタンスを削除"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "다운로드"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "진행 중인 다운로드 닫기"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "실행 중인 인스턴스 숨기기"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "업데이트하려면 다시 로드"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "다운로드 목록 보기"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "실행 중인 인스턴스 보이기"
|
||||
},
|
||||
@@ -143,17 +149,11 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "개인정보와 광고가 Modrinth를 지원하는 방식"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "배경 흐림 효과와 같은 시각 효과를 활성화합니다. 하드웨어 가속이 지원되지 않는 경우 성능이 저하될 수 있습니다."
|
||||
"app.appearance-settings.compact-mode.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.compact-mode.title": {
|
||||
"message": "컴팩트 모드"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "홈"
|
||||
@@ -168,10 +168,10 @@
|
||||
"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": "마인크래프트가 시작되면 Modrinth 앱을 최소화 합니다."
|
||||
@@ -179,12 +179,6 @@
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "앱 최소화"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "운영 체제의 제목 표시줄 및 창 제어 기능을 사용합니다. 앱 재시작이 필요합니다."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "시스템 창 프레임"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "각 인스턴스를 얼마나 플레이했는지 표시합니다."
|
||||
},
|
||||
@@ -224,6 +218,15 @@
|
||||
"app.behavior-settings.startup-and-navigation.title": {
|
||||
"message": "시작 및 탐색"
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.description": {
|
||||
"message": "로그인한 모든 기기에서 이 설정을 사용합니다. 이 기기에서 별도의 설정을 사용하려면 이 옵션을 끄세요."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.signed-out-tooltip": {
|
||||
"message": "Modrinth 계정에 로그인하여 설정을 동기화하세요."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.title": {
|
||||
"message": "기기 간 동기화"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "인스턴스에 서버 추가"
|
||||
},
|
||||
@@ -257,11 +260,23 @@
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "이미 설치된 항목 숨기기"
|
||||
},
|
||||
"app.browse.project-type.data-packs": {
|
||||
"message": "데이터팩"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "모드팩"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "콘텐츠를 추가하면 서버 접속 시 호환이 안 될 수 있습니다. 또한 서버 인스턴스 콘텐츠를 업데이트하면 임의로 추가된 콘텐츠는 모두 손실됩니다."
|
||||
"app.browse.project-type.mods": {
|
||||
"message": "모드"
|
||||
},
|
||||
"app.browse.project-type.resource-packs": {
|
||||
"message": "리소스팩"
|
||||
},
|
||||
"app.browse.project-type.servers": {
|
||||
"message": "서버"
|
||||
},
|
||||
"app.browse.project-type.shaders": {
|
||||
"message": "쉐이더"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "설치 중"
|
||||
@@ -275,6 +290,12 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "내보내기"
|
||||
},
|
||||
"app.export-modal.export-complete": {
|
||||
"message": "내보내기 완료"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name}을(를) 성공적으로 내보냈습니다."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "모드팩 내보내기"
|
||||
},
|
||||
@@ -290,6 +311,69 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.home.jump-back-in.instance-locked": {
|
||||
"message": "이 인스턴스는 잠겨있습니다"
|
||||
},
|
||||
"app.home.jump-back-in.instance-open": {
|
||||
"message": "인스턴스가 이미 열려있습니다"
|
||||
},
|
||||
"app.home.jump-back-in.more-options": {
|
||||
"message": "옵션 더보기"
|
||||
},
|
||||
"app.home.jump-back-in.never-played": {
|
||||
"message": "플레이한 적 없음"
|
||||
},
|
||||
"app.home.jump-back-in.new-instance": {
|
||||
"message": "새 인스턴스"
|
||||
},
|
||||
"app.home.jump-back-in.resize": {
|
||||
"message": "드래그하여 크기 조절"
|
||||
},
|
||||
"app.home.jump-back-in.title": {
|
||||
"message": "바로 입장"
|
||||
},
|
||||
"app.home.jump-back-in.view-instance": {
|
||||
"message": "인스턴스 보기"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "다운로드하여 업데이트"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "신난 Modrinth Bot"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Modrinth App 업데이트 필요"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-error": {
|
||||
"message": "인스턴스에 무작위 아이콘을 표시하는 데 실패했습니다."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-success": {
|
||||
"message": "인스턴스 {num, plural, other {#}}개의 아이콘을 무작위로 섞었습니다."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.badge": {
|
||||
"message": "이번 업데이트"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.close": {
|
||||
"message": "닫기"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.description": {
|
||||
"message": "Modrinth App에서 자신의 인스턴스에 사용자 지정 아이콘을 만들어 보세요. 배경을 Minecraft 및 인기 모드의 문양과 자유롭게 조합할 수 있습니다!"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.random-icons": {
|
||||
"message": "무작위 아이콘"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.skip": {
|
||||
"message": "건너뛰기"
|
||||
},
|
||||
"app.icon-editor.notification.close": {
|
||||
"message": "닫기"
|
||||
},
|
||||
"app.icon-editor.notification.dismiss": {
|
||||
"message": "닫기"
|
||||
},
|
||||
"app.icon-editor.notification.view-update": {
|
||||
"message": "업데이트 보기"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "콘텐츠 다운로드 중"
|
||||
},
|
||||
@@ -344,6 +428,42 @@
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "로더 진행기 실행 중"
|
||||
},
|
||||
"app.instance.action.add-content": {
|
||||
"message": "콘텐츠 추가"
|
||||
},
|
||||
"app.instance.action.copy-path": {
|
||||
"message": "경로 복사"
|
||||
},
|
||||
"app.instance.action.edit": {
|
||||
"message": "수정"
|
||||
},
|
||||
"app.instance.action.open-folder": {
|
||||
"message": "폴더 열기"
|
||||
},
|
||||
"app.instance.action.play": {
|
||||
"message": "플레이"
|
||||
},
|
||||
"app.instance.action.stop": {
|
||||
"message": "중지"
|
||||
},
|
||||
"app.instance.add-server.add": {
|
||||
"message": "추가"
|
||||
},
|
||||
"app.instance.add-server.added": {
|
||||
"message": "추가됨"
|
||||
},
|
||||
"app.instance.add-server.adding": {
|
||||
"message": "추가 중..."
|
||||
},
|
||||
"app.instance.add-server.cancel": {
|
||||
"message": "취소"
|
||||
},
|
||||
"app.instance.add-server.search-placeholder": {
|
||||
"message": "인스턴스 검색"
|
||||
},
|
||||
"app.instance.add-server.title": {
|
||||
"message": "인스턴스에 서버 추가"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "추가됨"
|
||||
},
|
||||
@@ -374,6 +494,27 @@
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "검토중..."
|
||||
},
|
||||
"app.instance.card.icon-alt": {
|
||||
"message": "인스턴스 아이콘"
|
||||
},
|
||||
"app.instance.card.installing": {
|
||||
"message": "설치 중..."
|
||||
},
|
||||
"app.instance.card.loading": {
|
||||
"message": "인스턴스 로딩 중..."
|
||||
},
|
||||
"app.instance.card.never-played": {
|
||||
"message": "플레이한 적 없음"
|
||||
},
|
||||
"app.instance.card.play": {
|
||||
"message": "플레이"
|
||||
},
|
||||
"app.instance.card.repair": {
|
||||
"message": "복구"
|
||||
},
|
||||
"app.instance.card.stop": {
|
||||
"message": "중지"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "인스턴스의 모든 데이터가 삭제됩니다. 세계 폴더, 설정 폴더 등 설치된 모든 컨텐츠가 포함됩니다."
|
||||
},
|
||||
@@ -386,8 +527,14 @@
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "인스턴스 삭제"
|
||||
},
|
||||
"app.instance.content.managed-content.modpack-header": {
|
||||
"message": "모드팩 콘텐츠"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "공유된 콘텐츠"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "이 모드팩은 이미 <bold>{instanceName}</bold> 인스턴스에 설치되어 있습니다. 정말로 복제하시겠습니까?"
|
||||
"message": "이 모드팩은 이미 <bold>{instanceName}</bold> 인스턴스에 설치되어 있습니다. 정말로 한 번 더 생성하시겠습니까?"
|
||||
},
|
||||
"app.instance.modpack-already-installed.create": {
|
||||
"message": "만들기"
|
||||
@@ -410,6 +557,9 @@
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "프로젝트"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "버전 고정"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "잠긴 인스턴스의 콘텐츠는 변경할 수 없습니다."
|
||||
},
|
||||
@@ -428,6 +578,12 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "업로드 성공"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "버전 고정 해제"
|
||||
},
|
||||
"app.instance.navigation.back-to-instance": {
|
||||
"message": "인스턴스로 돌아가기"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "친구들과 이 인스턴스를 공유할 수 있습니다!"
|
||||
},
|
||||
@@ -539,6 +695,27 @@
|
||||
"app.instance.shared-instance-wrong-account.warning-header": {
|
||||
"message": "잘못된 Modrinth 계정을 사용하고 있습니다"
|
||||
},
|
||||
"app.instance.shortcut.created": {
|
||||
"message": "바로가기 생성됨"
|
||||
},
|
||||
"app.instance.shortcut.creation-error": {
|
||||
"message": "바로가기 생성 중 오류 발생"
|
||||
},
|
||||
"app.instance.tab.content": {
|
||||
"message": "콘텐츠"
|
||||
},
|
||||
"app.instance.tab.files": {
|
||||
"message": "파일"
|
||||
},
|
||||
"app.instance.tab.logs": {
|
||||
"message": "로그"
|
||||
},
|
||||
"app.instance.tab.share": {
|
||||
"message": "공유"
|
||||
},
|
||||
"app.instance.tab.worlds": {
|
||||
"message": "세계"
|
||||
},
|
||||
"app.instance.worlds.add-server": {
|
||||
"message": "서버 추가"
|
||||
},
|
||||
@@ -575,6 +752,9 @@
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "추가된 서버 또는 세계 없음"
|
||||
},
|
||||
"app.instance.worlds.refreshing": {
|
||||
"message": "새로고침 중..."
|
||||
},
|
||||
"app.instance.worlds.remove-server-modal.remove-button": {
|
||||
"message": "서버 제거"
|
||||
},
|
||||
@@ -635,6 +815,240 @@
|
||||
"app.java-selector.test-installation": {
|
||||
"message": "Java 설치 테스트"
|
||||
},
|
||||
"app.library.context-menu.create-instance": {
|
||||
"message": "새 인스턴스"
|
||||
},
|
||||
"app.library.filter.add": {
|
||||
"message": "필터 추가"
|
||||
},
|
||||
"app.library.filter.clear": {
|
||||
"message": "필터 초기화"
|
||||
},
|
||||
"app.library.filter.game-version": {
|
||||
"message": "게임 버전"
|
||||
},
|
||||
"app.library.filter.instance-type": {
|
||||
"message": "인스턴스 유형"
|
||||
},
|
||||
"app.library.filter.instance-type.custom": {
|
||||
"message": "사용자 지정"
|
||||
},
|
||||
"app.library.filter.instance-type.modpack": {
|
||||
"message": "모드팩"
|
||||
},
|
||||
"app.library.filter.instance-type.server": {
|
||||
"message": "서버"
|
||||
},
|
||||
"app.library.filter.loader": {
|
||||
"message": "로더"
|
||||
},
|
||||
"app.library.group-by.custom-group": {
|
||||
"message": "사용자 지정 그룹"
|
||||
},
|
||||
"app.library.group-by.game-version": {
|
||||
"message": "게임 버전"
|
||||
},
|
||||
"app.library.group-by.instance-type": {
|
||||
"message": "인스턴스 유형"
|
||||
},
|
||||
"app.library.group-by.label": {
|
||||
"message": "그룹화 기준"
|
||||
},
|
||||
"app.library.group-by.loader": {
|
||||
"message": "로더"
|
||||
},
|
||||
"app.library.group-by.none": {
|
||||
"message": "그룹 없음"
|
||||
},
|
||||
"app.library.group.collapse": {
|
||||
"message": "그룹 접기"
|
||||
},
|
||||
"app.library.group.context-menu.add-to-group": {
|
||||
"message": "그룹에 추가"
|
||||
},
|
||||
"app.library.group.create": {
|
||||
"message": "그룹 생성"
|
||||
},
|
||||
"app.library.group.create.cancel": {
|
||||
"message": "취소"
|
||||
},
|
||||
"app.library.group.delete": {
|
||||
"message": "그룹 삭제"
|
||||
},
|
||||
"app.library.group.delete-description": {
|
||||
"message": "이 그룹에 속한 인스턴스는 그룹에서 제외됩니다."
|
||||
},
|
||||
"app.library.group.edit-name": {
|
||||
"message": "그룹 이름 수정"
|
||||
},
|
||||
"app.library.group.empty": {
|
||||
"message": "드래그 앤 드롭하여 인스턴스를 추가하세요."
|
||||
},
|
||||
"app.library.group.expand": {
|
||||
"message": "그룹 펼치기"
|
||||
},
|
||||
"app.library.group.favorites": {
|
||||
"message": "즐겨찾기"
|
||||
},
|
||||
"app.library.group.instance.add": {
|
||||
"message": "추가"
|
||||
},
|
||||
"app.library.group.instance.added": {
|
||||
"message": "추가됨"
|
||||
},
|
||||
"app.library.group.instances-modal.add": {
|
||||
"message": "추가"
|
||||
},
|
||||
"app.library.group.instances-modal.added": {
|
||||
"message": "추가됨"
|
||||
},
|
||||
"app.library.group.instances-modal.no-instances-found": {
|
||||
"message": "인스턴스를 찾을 수 없음"
|
||||
},
|
||||
"app.library.group.instances-modal.search-placeholder": {
|
||||
"message": "인스턴스 검색"
|
||||
},
|
||||
"app.library.group.instances-modal.title": {
|
||||
"message": "“{groupName}”에 인스턴스 추가"
|
||||
},
|
||||
"app.library.group.move-down": {
|
||||
"message": "그룹 아래로 이동"
|
||||
},
|
||||
"app.library.group.move-up": {
|
||||
"message": "그룹 위로 이동"
|
||||
},
|
||||
"app.library.group.name": {
|
||||
"message": "그룹 이름"
|
||||
},
|
||||
"app.library.group.name-empty": {
|
||||
"message": "그룹 이름은 비워 둘 수 없습니다."
|
||||
},
|
||||
"app.library.group.name-placeholder": {
|
||||
"message": "그룹 이름 입력"
|
||||
},
|
||||
"app.library.group.name-reserved": {
|
||||
"message": "“None”은 그룹 이름으로 사용할 수 없습니다."
|
||||
},
|
||||
"app.library.group.name-too-long": {
|
||||
"message": "그룹 이름은 {maxLength}자 이내여야 합니다."
|
||||
},
|
||||
"app.library.group.new": {
|
||||
"message": "새 그룹"
|
||||
},
|
||||
"app.library.group.no-instances-found": {
|
||||
"message": "인스턴스를 찾을 수 없음"
|
||||
},
|
||||
"app.library.group.rename-failed": {
|
||||
"message": "그룹 이름을 변경할 수 없음"
|
||||
},
|
||||
"app.library.group.search-instance": {
|
||||
"message": "인스턴스 검색"
|
||||
},
|
||||
"app.library.group.ungrouped": {
|
||||
"message": "그룹 해제"
|
||||
},
|
||||
"app.library.group.unknown": {
|
||||
"message": "알 수 없는 그룹"
|
||||
},
|
||||
"app.library.instance.action.add-content": {
|
||||
"message": "콘텐츠 추가"
|
||||
},
|
||||
"app.library.instance.action.add-to-favorites": {
|
||||
"message": "즐겨찾기에 추가"
|
||||
},
|
||||
"app.library.instance.action.copy-path": {
|
||||
"message": "경로 복사"
|
||||
},
|
||||
"app.library.instance.action.delete": {
|
||||
"message": "삭제"
|
||||
},
|
||||
"app.library.instance.action.duplicate": {
|
||||
"message": "인스턴스 복제"
|
||||
},
|
||||
"app.library.instance.action.open-folder": {
|
||||
"message": "폴더 열기"
|
||||
},
|
||||
"app.library.instance.action.play": {
|
||||
"message": "플레이"
|
||||
},
|
||||
"app.library.instance.action.remove-from-favorites": {
|
||||
"message": "즐겨찾기에서 제거"
|
||||
},
|
||||
"app.library.instance.action.remove-from-group": {
|
||||
"message": "그룹에서 삭제"
|
||||
},
|
||||
"app.library.instance.action.stop": {
|
||||
"message": "중지"
|
||||
},
|
||||
"app.library.instance.action.view-instance": {
|
||||
"message": "인스턴스 보기"
|
||||
},
|
||||
"app.library.instance.deselect": {
|
||||
"message": "인스턴스 선택 해제"
|
||||
},
|
||||
"app.library.instance.deselect-with-name": {
|
||||
"message": "{name} 선택 해제"
|
||||
},
|
||||
"app.library.instance.installing": {
|
||||
"message": "설치 중..."
|
||||
},
|
||||
"app.library.instance.loading": {
|
||||
"message": "인스턴스 로딩 중..."
|
||||
},
|
||||
"app.library.instance.new": {
|
||||
"message": "새 인스턴스"
|
||||
},
|
||||
"app.library.instance.open-with-name": {
|
||||
"message": "{name} 열기"
|
||||
},
|
||||
"app.library.instance.play": {
|
||||
"message": "플레이"
|
||||
},
|
||||
"app.library.instance.repair": {
|
||||
"message": "복구"
|
||||
},
|
||||
"app.library.instance.select": {
|
||||
"message": "인스턴스 선택"
|
||||
},
|
||||
"app.library.instance.select-with-name": {
|
||||
"message": "{name} 선택"
|
||||
},
|
||||
"app.library.instance.stop": {
|
||||
"message": "중지"
|
||||
},
|
||||
"app.library.search.no-results.title": {
|
||||
"message": "검색 조건과 일치하는 인스턴스가 없습니다."
|
||||
},
|
||||
"app.library.search.placeholder": {
|
||||
"message": "검색"
|
||||
},
|
||||
"app.library.selection.aria-label": {
|
||||
"message": "선택한 인스턴스"
|
||||
},
|
||||
"app.library.selection.deleting": {
|
||||
"message": "선택한 인스턴스 삭제 중"
|
||||
},
|
||||
"app.library.selection.new-group": {
|
||||
"message": "새 그룹"
|
||||
},
|
||||
"app.library.selection.remove-from-group": {
|
||||
"message": "그룹에서 삭제"
|
||||
},
|
||||
"app.library.selection.selected-count": {
|
||||
"message": "{count} 선택됨"
|
||||
},
|
||||
"app.library.sort.game-version": {
|
||||
"message": "게임 버전"
|
||||
},
|
||||
"app.library.sort.loader": {
|
||||
"message": "로더"
|
||||
},
|
||||
"app.library.sort.name": {
|
||||
"message": "이름"
|
||||
},
|
||||
"app.library.title": {
|
||||
"message": "라이브러리"
|
||||
},
|
||||
"app.modal.install-to-play.additional-context": {
|
||||
"message": "추가 정보"
|
||||
},
|
||||
@@ -668,6 +1082,9 @@
|
||||
"app.modal.install-to-play.invite-warning-with-creator": {
|
||||
"message": "이 초대장은 Modrinth가 아닌 <creator>{username}</creator> 님이 생성했습니다. 신뢰할 수 있는 사람의 초대만 수락하세요."
|
||||
},
|
||||
"app.modal.install-to-play.managed-content.modpack-header": {
|
||||
"message": "모드팩 콘텐츠"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {모드 #개} other {모드 #개}}"
|
||||
},
|
||||
@@ -695,9 +1112,6 @@
|
||||
"app.modal.install-to-play.report-support-and-bugs": {
|
||||
"message": "지원 요청은 <support-link>지원 팀</support-link> 문의를 이용해 주세요. 버그 제보는 <github-link>GitHub 이슈</github-link>를 등록하세요."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "모든 파일은 형식(.mrpack 포함)에 무관하게 Modrinth에 게시되어야만 검수를 거칩니다."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "인스턴스 공유됨"
|
||||
},
|
||||
@@ -767,6 +1181,15 @@
|
||||
"app.nav.upgrade-to-modrinth-plus": {
|
||||
"message": "Modrinth+ 로 업그레이드"
|
||||
},
|
||||
"app.navigation.go-back": {
|
||||
"message": "뒤로 가기"
|
||||
},
|
||||
"app.navigation.go-forward": {
|
||||
"message": "앞으로 가기"
|
||||
},
|
||||
"app.navigation.home": {
|
||||
"message": "홈"
|
||||
},
|
||||
"app.news.title": {
|
||||
"message": "뉴스"
|
||||
},
|
||||
@@ -1133,9 +1556,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "로그인"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "스킨 선택"
|
||||
},
|
||||
@@ -1368,7 +1788,7 @@
|
||||
"message": "시작중..."
|
||||
},
|
||||
"instance.action.stopping": {
|
||||
"message": "정지중..."
|
||||
"message": "중지중..."
|
||||
},
|
||||
"instance.add-server.add-and-play": {
|
||||
"message": "추가 후 플레이"
|
||||
@@ -1412,6 +1832,9 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "다른 이름으로 저장..."
|
||||
},
|
||||
"instance.icon-editor.symbol": {
|
||||
"message": "문양"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "인스턴스 삭제"
|
||||
},
|
||||
@@ -1419,7 +1842,7 @@
|
||||
"message": "이 인스턴스는 잠겼습니다"
|
||||
},
|
||||
"instance.playtime.never-played": {
|
||||
"message": "아직 플레이 하지 않음"
|
||||
"message": "플레이한 적 없음"
|
||||
},
|
||||
"instance.server-modal.address": {
|
||||
"message": "주소"
|
||||
@@ -1746,10 +2169,10 @@
|
||||
"message": "서버가 응답하지 않음"
|
||||
},
|
||||
"instance.worlds.no_server_quick_play": {
|
||||
"message": "Minecraft 알파 1.0.5 이후 버전에서만 서버에 바로 진입할 수 있습니다"
|
||||
"message": "Minecraft 알파 1.0.5 이후 버전에서만 서버에 바로 입장할 수 있습니다"
|
||||
},
|
||||
"instance.worlds.no_singleplayer_quick_play": {
|
||||
"message": "Minecraft 1.20 이후 버전에서만 싱글플레이 세계로 바로 진입할 수 있습니다"
|
||||
"message": "Minecraft 1.20 이후 버전에서만 싱글플레이 세계에 바로 입장할 수 있습니다"
|
||||
},
|
||||
"instance.worlds.play_instance": {
|
||||
"message": "인스턴스 플레이"
|
||||
|
||||
@@ -53,51 +53,15 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Lihat log"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Pemaparan lanjutan"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema warna"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Laman utama"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Pustaka"
|
||||
},
|
||||
"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.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.skip-non-essential-warnings.description": {
|
||||
"message": "Melangkau pengesahan berisiko rendah secara automatik seperti pemasangan pek mod pendua, pemadaman kandungan biasa, kemas kini pukal, menyahpautkan pek mod dan gesaan pembaikan. Amaran berbahaya masih akan ditunjukkan."
|
||||
},
|
||||
"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"
|
||||
},
|
||||
@@ -134,8 +98,8 @@
|
||||
"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.project-type.mods": {
|
||||
"message": "Mod"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Sedang memasang"
|
||||
@@ -161,6 +125,9 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.skip": {
|
||||
"message": "Langkau"
|
||||
},
|
||||
"app.install.phase.preparing_java.downloading": {
|
||||
"message": "Sedang memuat turun Java {version}"
|
||||
},
|
||||
@@ -179,9 +146,27 @@
|
||||
"app.install.phase.reading_pack_manifest": {
|
||||
"message": "Sedang membaca manifest pek"
|
||||
},
|
||||
"app.instance.action.edit": {
|
||||
"message": "Sunting"
|
||||
},
|
||||
"app.instance.action.play": {
|
||||
"message": "Main"
|
||||
},
|
||||
"app.instance.action.stop": {
|
||||
"message": "Hentikan"
|
||||
},
|
||||
"app.instance.add-server.add": {
|
||||
"message": "Tambah"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "Ditambah"
|
||||
},
|
||||
"app.instance.card.play": {
|
||||
"message": "Main"
|
||||
},
|
||||
"app.instance.card.stop": {
|
||||
"message": "Hentikan"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Semua data untuk pemasangan anda akan dipadamkan secara kekal, termasuk dunia, konfigurasi dan semua kandungan yang dipasang."
|
||||
},
|
||||
@@ -224,6 +209,9 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Berjaya dimuat naik"
|
||||
},
|
||||
"app.instance.tab.logs": {
|
||||
"message": "Log"
|
||||
},
|
||||
"app.instance.worlds.add-server": {
|
||||
"message": "Tambah pelayan"
|
||||
},
|
||||
@@ -260,6 +248,27 @@
|
||||
"app.java-detection.select": {
|
||||
"message": "Pilih"
|
||||
},
|
||||
"app.library.group.instance.add": {
|
||||
"message": "Tambah"
|
||||
},
|
||||
"app.library.group.instances-modal.add": {
|
||||
"message": "Tambah"
|
||||
},
|
||||
"app.library.instance.action.play": {
|
||||
"message": "Main"
|
||||
},
|
||||
"app.library.instance.action.stop": {
|
||||
"message": "Hentikan"
|
||||
},
|
||||
"app.library.instance.play": {
|
||||
"message": "Main"
|
||||
},
|
||||
"app.library.instance.stop": {
|
||||
"message": "Hentikan"
|
||||
},
|
||||
"app.library.sort.name": {
|
||||
"message": "Nama"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "Pasang untuk mainkan"
|
||||
},
|
||||
@@ -284,6 +293,9 @@
|
||||
"app.nav.home": {
|
||||
"message": "Laman utama"
|
||||
},
|
||||
"app.navigation.home": {
|
||||
"message": "Laman utama"
|
||||
},
|
||||
"app.news.title": {
|
||||
"message": "Berita"
|
||||
},
|
||||
@@ -410,9 +422,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Daftar Masuk"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Pemilih kekulit"
|
||||
},
|
||||
@@ -563,6 +572,36 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "Simpan sebagai..."
|
||||
},
|
||||
"instance.icon-editor.background.blue": {
|
||||
"message": "Biru"
|
||||
},
|
||||
"instance.icon-editor.background.gray": {
|
||||
"message": "Kelabu"
|
||||
},
|
||||
"instance.icon-editor.background.lime": {
|
||||
"message": "Hijau Muda"
|
||||
},
|
||||
"instance.icon-editor.background.pink": {
|
||||
"message": "Merah Jambu"
|
||||
},
|
||||
"instance.icon-editor.background.red": {
|
||||
"message": "Merah"
|
||||
},
|
||||
"instance.icon-editor.background.rose": {
|
||||
"message": "Mawar"
|
||||
},
|
||||
"instance.icon-editor.symbol.cake": {
|
||||
"message": "Kek"
|
||||
},
|
||||
"instance.icon-editor.symbol.orb": {
|
||||
"message": "Orb"
|
||||
},
|
||||
"instance.icon-editor.symbol.tire": {
|
||||
"message": "Tayar"
|
||||
},
|
||||
"instance.icon-editor.symbol.tnt": {
|
||||
"message": "TNT"
|
||||
},
|
||||
"instance.server-modal.address": {
|
||||
"message": "Alamat"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "Actieve downloads verbergen"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Verberg meer actieve instanties"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Herlaadt om te updaten"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "Actieve downloads weergeven"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Toon meer actieve instanties"
|
||||
},
|
||||
@@ -143,18 +149,6 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Je privacy en hoe advertenties Modrinth ondersteunen"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Schakel visuele effecten zoals achtergrondvervaging in. Zonder hardwareversnelling kan dit ten koste gaan van de prestaties."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Geavanceerde weergave"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Kies het kleurenthema dat door de Modrinth-app wordt gebruikt."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Kleurenthema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Start"
|
||||
},
|
||||
@@ -167,24 +161,12 @@
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Verberg nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Toon recent gespeelde werelden in het Ga terug-gedeelte 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 Modrinth-app zodra Minecraft opstart."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "App minimaliseren"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Gebruik de titelbalk en vensterbedieningselementen van je besturingssysteem. Hiervoor moet de app opnieuw worden opgestart."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Systeemvensterframe"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Laat zien hoe lang je elke instantie hebt gespeeld."
|
||||
},
|
||||
@@ -261,7 +243,7 @@
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Het toevoegen van inhoud kan de compatibiliteit verstoren bij het verbinden met de server. Eventuele toegevoegde inhoud gaat bovendien verloren wanneer je de inhoud van de serverinstantie bijwerkt."
|
||||
"message": "Als je inhoud toevoegt, kan dit ertoe leiden dat je geen toegang hebt tot deze server. Alle inhoud die je toevoegt, wordt verwijderd wanneer de beheerde inhoud van de server wordt bijgewerkt."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installeren"
|
||||
@@ -275,6 +257,12 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Exporteer"
|
||||
},
|
||||
"app.export-modal.export-complete": {
|
||||
"message": "Exporteren voltooid"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name} is succesvol geëxporteerd."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "Exporteer modpack"
|
||||
},
|
||||
@@ -290,6 +278,12 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "Opgewonden Modrinth-bot"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Update van Modrinth-app vereist"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "Inhoud aan het downloaden"
|
||||
},
|
||||
@@ -699,7 +693,7 @@
|
||||
"message": "Neem voor ondersteuningsverzoeken contact op met ons <support-link>ondersteuningsteam</support-link>. Voor bugmeldingen kun je een <github-link>GitHub-issue</github-link> aanmaken."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Een bestand wordt alleen beoordeeld als het op Modrinth wordt gepubliceerd, ongeacht het bestandsformaat (inclusief .mrpack)."
|
||||
"message": "Bestanden die niet op Modrinth worden gepubliceerd, worden niet beoordeeld."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Gedeelde instantie"
|
||||
@@ -1136,9 +1130,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Inloggen"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Skinkiezer"
|
||||
},
|
||||
|
||||
@@ -56,30 +56,15 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Vis logger"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Avansert rendering"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Fargetema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Hjem"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Bibliotek"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Skjuler navnelappen over din spiller på utseende siden."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Gjøm navnelapp"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimer launcheren"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Viser hvor lenge du har spilt et tilfelle."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Vis spilletid"
|
||||
},
|
||||
@@ -125,9 +110,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpakker"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Å legge til innhold kan ødelegge komatibilitet når du blir med i serveren. Alt tilleggsinnhold vil også forsvinne når du oppdaterer servertilfelleinnholdet."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installerer"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "Ocultar downloads ativos"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Ocultar mais instâncias em execução"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Recarregar para atualizar"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "Exibir downloads ativos"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Exibir mais instâncias em execução"
|
||||
},
|
||||
@@ -143,18 +149,6 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Sua privacidade e como anúncios apoiam o Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Ativa os efeitos visuais como o desfoque de fundo. Isso talvez reduza o desempenho sem o aceleramento de hardware."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderização avançada"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Escolha o tema usado pelo Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Início"
|
||||
},
|
||||
@@ -167,24 +161,12 @@
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Ocultar nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Exibe os mundos jogados recentemente na seção \"Volte aos mundos\" na página inicial."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Volte aos mundos"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimiza o Modrinth App ao iniciar o Minecraft."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimizar aplicativo"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Usa a barra de título e os controles de janela do seu sistema operacional. Exige a reinicialização do aplicativo."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Moldura de janela do sistema"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Exibe o tempo de jogo de cada instância."
|
||||
},
|
||||
@@ -204,7 +186,7 @@
|
||||
"message": "Ocultar barra lateral direita"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Exibe um aviso de segurança ao instalar um Modrinth Pack (.mrpack) que não seja hospedado no Modrinth."
|
||||
"message": "Exibe um aviso de segurança ao instalar um Pacote Modrinth (.mrpack) que não seja hospedado no Modrinth."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Avise-me antes de instalar pacotes de mods desconhecidos"
|
||||
@@ -257,11 +239,26 @@
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "Ocultar já instalado"
|
||||
},
|
||||
"app.browse.project-type.data-packs": {
|
||||
"message": "Pacote de dados"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Pacotes de mods"
|
||||
},
|
||||
"app.browse.project-type.mods": {
|
||||
"message": "Mods"
|
||||
},
|
||||
"app.browse.project-type.resource-packs": {
|
||||
"message": "Pacote de recursos"
|
||||
},
|
||||
"app.browse.project-type.servers": {
|
||||
"message": "Servidores"
|
||||
},
|
||||
"app.browse.project-type.shaders": {
|
||||
"message": "Sombreadores"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Adicionar conteúdo talvez quebre a compatibilidade ao entrar no servidor. Qualquer conteúdo adicionado será perdido ao atualizar o conteúdo da instância do servidor."
|
||||
"message": "Adicionar conteúdo pode impedir que você entre neste servidor. Qualquer conteúdo adicionado será removido quando o conteúdo do servidor gerenciado for atualizado."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instalando"
|
||||
@@ -275,6 +272,12 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Exportar"
|
||||
},
|
||||
"app.export-modal.export-complete": {
|
||||
"message": "Exportação concluída"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name} foi exportado com sucesso."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "Exportar pacote de mods"
|
||||
},
|
||||
@@ -290,6 +293,69 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.home.jump-back-in.instance-locked": {
|
||||
"message": "Esta instância foi bloqueada"
|
||||
},
|
||||
"app.home.jump-back-in.instance-open": {
|
||||
"message": "A instância já está aberta"
|
||||
},
|
||||
"app.home.jump-back-in.more-options": {
|
||||
"message": "Mais opções"
|
||||
},
|
||||
"app.home.jump-back-in.never-played": {
|
||||
"message": "Nunca jogou"
|
||||
},
|
||||
"app.home.jump-back-in.new-instance": {
|
||||
"message": "Nova instância"
|
||||
},
|
||||
"app.home.jump-back-in.title": {
|
||||
"message": "Entrar"
|
||||
},
|
||||
"app.home.jump-back-in.view-instance": {
|
||||
"message": "Ver instância"
|
||||
},
|
||||
"app.hosting.update-required.description": {
|
||||
"message": "Você precisa atualizar para usar o Modrinth Hosting através do Modrinth App"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "Baixar para atualizar"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "Modrinth Bot animado"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Atualização do Modrinth App necessária"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-error": {
|
||||
"message": "Falha ao aplicar ícones aleatórios nas instâncias."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-success": {
|
||||
"message": "Ícone aleatório para {num, plural, one {# instância} other {# instâncias}}."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.badge": {
|
||||
"message": "Novidade desta atualização"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.close": {
|
||||
"message": "Fechar"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.description": {
|
||||
"message": "Crie ícones personalizados para as suas instâncias diretamente no Modrinth App. Misture e combine os fundos com símbolos do Minecraft e mods populares!"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.instances-without-icons": {
|
||||
"message": "{count, plural, one {# instância} other {# instâncias}} ainda não têm ícones. Quer dar {count, plural, one {ícone aleatório} other {ícones aleatórios}} para começar?"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.random-icons": {
|
||||
"message": "Ícones aleatórios"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.skip": {
|
||||
"message": "Pular"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.title": {
|
||||
"message": "Editor de Ícone personalizado"
|
||||
},
|
||||
"app.icon-editor.notification.close": {
|
||||
"message": "Fechar"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "Instalando conteúdo"
|
||||
},
|
||||
@@ -342,7 +408,7 @@
|
||||
"message": "Indo de volta"
|
||||
},
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "Executando processadores de loader"
|
||||
"message": "Executando processadores de carregamento"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "Adicionado"
|
||||
@@ -374,6 +440,12 @@
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Revisando..."
|
||||
},
|
||||
"app.instance.card.installing": {
|
||||
"message": "Instalando..."
|
||||
},
|
||||
"app.instance.card.never-played": {
|
||||
"message": "Nunca jogado"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Todos os dados para sua instância serão excluídos permanentemente, incluindo seus mundos, configs e todo o conteúdo instalado."
|
||||
},
|
||||
@@ -386,6 +458,12 @@
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Excluir instância"
|
||||
},
|
||||
"app.instance.content.managed-content.modpack-header": {
|
||||
"message": "Conteúdo do pacote de mods"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "Conteúdo compartilhado"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "Este pacote de mods já foi instalado na instância <bold>{instanceName}</bold>. Deseja mesmo duplicá-lo?"
|
||||
},
|
||||
@@ -410,6 +488,9 @@
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "projeto"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "Congelar versão"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "O conteúdo em instâncias privadas não podem ser alterados."
|
||||
},
|
||||
@@ -428,6 +509,9 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Adicionado com sucesso"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "Descongelar versão"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "Você pode compartilhar esta instância com seus amigos!"
|
||||
},
|
||||
@@ -671,6 +755,9 @@
|
||||
"app.modal.install-to-play.invite-warning-with-creator": {
|
||||
"message": "Este convite foi criado por <creator>{username}</creator>, não pelo Modrinth. Aceite apenas convites de pessoas que você confia."
|
||||
},
|
||||
"app.modal.install-to-play.managed-content.modpack-header": {
|
||||
"message": "Conteúdo do pacote de mods"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, =0 {Nenhum mod} one {# mod} other {# mods}}"
|
||||
},
|
||||
@@ -699,7 +786,7 @@
|
||||
"message": "Para solicitações de suporte, entre em contato com nossa <support-link>equipe de suporte</support-link>. Para informar erros, abra uma <github-link>discussão no GitHub</github-link>."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Um arquivo só é revisado se for publicado no Modrinth, independente do formato de arquivo (incluindo .mrpack)."
|
||||
"message": "Arquivos não publicados no Modrinth não são revisados."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Instância compartilhada"
|
||||
@@ -1136,9 +1223,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Iniciar sessão"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Seletor de skins"
|
||||
},
|
||||
@@ -1418,6 +1502,9 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "Salvar como..."
|
||||
},
|
||||
"instance.last-played": {
|
||||
"message": "Jogado pela última vez"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "Excluir instância"
|
||||
},
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
"app.action-bar.install.unknown-instance": {
|
||||
"message": "Instância desconhecida"
|
||||
},
|
||||
"app.action-bar.install.updating-shared-content": {
|
||||
"message": "A atualizar conteúdo compartilhado"
|
||||
},
|
||||
"app.action-bar.installs": {
|
||||
"message": "Instalações"
|
||||
},
|
||||
@@ -140,48 +143,45 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "A sua privacidade e como os anúncios são financiados pela Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Renderização avançada"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Cor de tema"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Casa"
|
||||
"message": "Início"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "Livraria"
|
||||
"message": "Biblioteca"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Desativa o nametag acima do seu player na página de skins."
|
||||
"message": "Esconde o teu nome de utilizador acima da pré-visualização na página de seleção de Skin."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Ocultar nametag"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Volte aos mundos"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Minimiza o inicializador quando um processo Minecraft é iniciado."
|
||||
"message": "Minimizar a Modrinth App quando o Minecraft iniciar."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Minimizar launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Utilizar a moldura da janela do sistema (é necessário reiniciar a aplicação)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Decorações nativas"
|
||||
"message": "Minimizar app"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Apresenta quanto tempo passou a jogar uma instância."
|
||||
"message": "Mostrar quanto tempo jogaste cada instância."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Mostrar tempo de jogo"
|
||||
},
|
||||
"app.appearance-settings.skip-non-essential-warnings.description": {
|
||||
"message": "Pular confirmações para ações de baixo risco como duplicar instalações, exclusão de conteúdo normal, atualizações em massa, desvinculação e reparos. Avisos para ações perigosas são sempre mostrados."
|
||||
},
|
||||
"app.appearance-settings.skip-non-essential-warnings.title": {
|
||||
"message": "Ignorar avisos não essenciais"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Esconde a barra lateral direita por defeito e adiciona uma barra para mostrar ou escondê-la."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Esconder barra lateral direita"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Mostrar um aviso de segurança antes de instalar um Modrinth Pack (.mrpack) que não está hospedado no Modrinth."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Avise-me antes de instalar pacotes de mods desconhecidos."
|
||||
},
|
||||
@@ -191,6 +191,15 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Não foi possível alcançar os servidores de autenticação"
|
||||
},
|
||||
"app.behavior-settings.confirmations.title": {
|
||||
"message": "Confirmações"
|
||||
},
|
||||
"app.behavior-settings.content.title": {
|
||||
"message": "Início e conteúdo"
|
||||
},
|
||||
"app.behavior-settings.startup-and-navigation.title": {
|
||||
"message": "Inicialização e navegação"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Adicionar servidor à instância"
|
||||
},
|
||||
@@ -212,18 +221,21 @@
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Voltar à instância"
|
||||
},
|
||||
"app.browse.discover-project-type": {
|
||||
"message": "Descobrir {projectType}"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Descobrir servidores"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Ocultar servidores já adicionados"
|
||||
},
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "Esconder já instalados"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"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.installing": {
|
||||
"message": "Instalando.."
|
||||
},
|
||||
@@ -305,6 +317,27 @@
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "Executar processadores de carregamento"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "Adicionado"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.changes-body": {
|
||||
"message": "A tua instância local está mais atualizada do que a dos utilizadores com quem tu a partilhaste."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.changes-header": {
|
||||
"message": "As tuas alterações ainda não foram partilhadas"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.publish-button": {
|
||||
"message": "Enviar atualização"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.publishing-button": {
|
||||
"message": "Enviando..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.removed-label": {
|
||||
"message": "Removido"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-admonition-header": {
|
||||
"message": "Enviar atualização"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Todos os dados da tua instância vão ser permanentemente apagados, incluídos os teus mundos, definições, e todos os conteúdos instalados."
|
||||
},
|
||||
@@ -407,6 +440,15 @@
|
||||
"app.instance.worlds.search-worlds-placeholder": {
|
||||
"message": "Pesquisar {count} mundos..."
|
||||
},
|
||||
"app.java-detection.columns.actions": {
|
||||
"message": "Ações"
|
||||
},
|
||||
"app.java-detection.columns.path": {
|
||||
"message": "Caminho"
|
||||
},
|
||||
"app.java-selector.path.placeholder": {
|
||||
"message": "/caminho/para/java"
|
||||
},
|
||||
"app.modal.install-to-play.header": {
|
||||
"message": "Instala para jogar"
|
||||
},
|
||||
@@ -422,9 +464,6 @@
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {mod} other {mods}}"
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Um ficheiro só é analisado se for publicado no Modrinth, independentemente do seu formato (incluindo .mrpack)."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Instância partilhada"
|
||||
},
|
||||
@@ -446,6 +485,15 @@
|
||||
"app.modal.update-to-play.removed": {
|
||||
"message": "Removido"
|
||||
},
|
||||
"app.modal.update-to-play.shared-instance-added-label": {
|
||||
"message": "Adicionado"
|
||||
},
|
||||
"app.nav.home": {
|
||||
"message": "Início"
|
||||
},
|
||||
"app.news.title": {
|
||||
"message": "Novidades"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Este projeto já está instalado."
|
||||
},
|
||||
@@ -464,12 +512,18 @@
|
||||
"app.project.version.installing": {
|
||||
"message": "Instalando.."
|
||||
},
|
||||
"app.settings.default-instance-options.width.title": {
|
||||
"message": "Largura"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modo de desenvolvedor ativado."
|
||||
},
|
||||
"app.settings.downloading": {
|
||||
"message": "A transferir v{version}"
|
||||
},
|
||||
"app.settings.operating-system.macos": {
|
||||
"message": "macOS"
|
||||
},
|
||||
"app.settings.privacy.ads-consent.intro": {
|
||||
"message": "Os anúncios tornam o Modrinth possível e financiam os pagamentos aos criadores. Os nossos parceiros podem armazenar ou aceder a cookies na aplicação para personalizar anúncios e medir o desempenho."
|
||||
},
|
||||
@@ -488,6 +542,9 @@
|
||||
"app.settings.tabs.resource-management": {
|
||||
"message": "Gestão de recursos"
|
||||
},
|
||||
"app.skins.apply-button": {
|
||||
"message": "Aplicar"
|
||||
},
|
||||
"app.skins.delete-button": {
|
||||
"message": "Apagar skin"
|
||||
},
|
||||
@@ -506,8 +563,20 @@
|
||||
"app.skins.modal.add-title": {
|
||||
"message": "Adicionar uma skin"
|
||||
},
|
||||
"app.skins.modal.arm-style-slim": {
|
||||
"message": "Fino"
|
||||
},
|
||||
"app.skins.modal.arm-style-wide": {
|
||||
"message": "Largo"
|
||||
},
|
||||
"app.skins.modal.cape-fallback-name": {
|
||||
"message": "Capa"
|
||||
},
|
||||
"app.skins.modal.cape-section": {
|
||||
"message": "Capa"
|
||||
},
|
||||
"app.skins.modal.none-cape-option": {
|
||||
"message": "Nada"
|
||||
"message": "Nenhuma"
|
||||
},
|
||||
"app.skins.modal.saving-tooltip": {
|
||||
"message": "A guardar..."
|
||||
@@ -683,6 +752,12 @@
|
||||
"instance.server-modal.resource-pack": {
|
||||
"message": "Pacote de recursos"
|
||||
},
|
||||
"instance.settings.sharing.active-invites.actions": {
|
||||
"message": "Ações"
|
||||
},
|
||||
"instance.settings.sharing.active-invites.uses": {
|
||||
"message": "Usos"
|
||||
},
|
||||
"instance.settings.tabs.general": {
|
||||
"message": "Geral"
|
||||
},
|
||||
@@ -725,6 +800,12 @@
|
||||
"instance.settings.tabs.general.name": {
|
||||
"message": "Nome"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.alpha": {
|
||||
"message": "Alpha"
|
||||
},
|
||||
"instance.settings.tabs.general.update-channel.beta": {
|
||||
"message": "Beta"
|
||||
},
|
||||
"instance.settings.tabs.hooks": {
|
||||
"message": "Ganchos de inicio"
|
||||
},
|
||||
@@ -755,6 +836,24 @@
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Ganchos de inicio do jogo"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-dir.description": {
|
||||
"message": "$INST_DIR: O caminho absoluto para a pasta da instância"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-id.description": {
|
||||
"message": "$INST_ID: O nome da pasta da instância"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
|
||||
"message": "$INST_JAVA_ARGS: Os argumentos JVM fornecidos ao jogo"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java.description": {
|
||||
"message": "$INST_JAVA: O caminho absoluto para o executável java"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
|
||||
"message": "$INST_MC_DIR: Um alias para "
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-name.description": {
|
||||
"message": "$INST_NAME: O nome da instância"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Wrapper"
|
||||
},
|
||||
@@ -788,6 +887,9 @@
|
||||
"instance.settings.tabs.java.java-memory": {
|
||||
"message": "Memória alocada"
|
||||
},
|
||||
"instance.settings.tabs.java.java-path-placeholder": {
|
||||
"message": "/caminho/para/java"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "Janela"
|
||||
},
|
||||
@@ -818,6 +920,9 @@
|
||||
"instance.settings.tabs.window.width.enter": {
|
||||
"message": "Inserir largura..."
|
||||
},
|
||||
"instance.shared-instance.publish-review.added-label": {
|
||||
"message": "Adicionado"
|
||||
},
|
||||
"instance.worlds.a_minecraft_server": {
|
||||
"message": "Um Servidor de Minecraft"
|
||||
},
|
||||
@@ -860,6 +965,12 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "Mundo está em uso"
|
||||
},
|
||||
"modal.modrinth-account-required.browser-description": {
|
||||
"message": "Uma nova aba abriu para iniciar sessão. Completa o início de sessão lá, depois volta para a app."
|
||||
},
|
||||
"modal.modrinth-account-required.header": {
|
||||
"message": "Conta necessária"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Fornecido pela instância"
|
||||
},
|
||||
@@ -874,5 +985,8 @@
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Apenas mods no cliente podem ser adicionados à instância de servidor"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Conta"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -143,18 +143,6 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Tvoja bezbednost i kako reklame pomažu Modrinth-u"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.description": {
|
||||
"message": "Uključi vizuelne efekte kao što je blur pozadine. Ovo može da pogorša performanse bez hardverskog ubrzanja."
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Napredno renderovanje"
|
||||
},
|
||||
"app.appearance-settings.color-theme.description": {
|
||||
"message": "Izaberi boju teme koju koristi Modrinth App."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Tema boje"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Početna stranica"
|
||||
},
|
||||
@@ -167,24 +155,12 @@
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Sakrij ime"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Prikaži skorije igrane svetove na odeljku \"Vrati se u\" na početnoj stranici."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Vrati se u svetove"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Smanji Modrinth App kada se Minecraft pokrene."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Smanji aplikaciju"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Koristi naslovnu traku i kontrole prozora tvog operativnog sistema. Restart aplikacije obavezan."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Sistemski okvir prozora"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Prikaži koliko dugo ste igrali svaku instancu."
|
||||
},
|
||||
@@ -245,9 +221,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpackovi"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Dodavanje sadržaja može da prekine kompatibilnost prilikom pridruživanja serveru. Bilo koji dodat sadržaj će takođe biti izgubljen kada ažuriraš sadržaj instance servera."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Instaliranje"
|
||||
},
|
||||
@@ -446,9 +419,6 @@
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# mod} other {# modova}}"
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Datoteka se pregleda samo ako je postavljena na Modrinth, bez obzira na njen format (uključujući .mrpack)."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Deljena instanca"
|
||||
},
|
||||
@@ -647,9 +617,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Prijavi se"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Selektor skina"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -59,42 +59,15 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "ดู Log"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "การเรนเดอร์ขั้นสูง"
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "ธีมสี"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "หน้าหลัก"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.library": {
|
||||
"message": "ไลบรารี"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "ซ่อนป้ายชื่อบนศีรษะของผู้เล่นในหน้าแสดงสกิน"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"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.show-play-time.description": {
|
||||
"message": "แสดงเวลาที่คุณใช้เล่นในแต่ละอินสแตนซ์"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "แสดงระยะเวลาที่เล่น"
|
||||
},
|
||||
@@ -134,9 +107,6 @@
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "แพ็กม็อด"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "การเพิ่มเนื้อหาอาจทำให้ใช้งานร่วมกันไม่ได้เมื่อเข้าร่วมเซิร์ฟเวอร์ เนื้อหาที่เพิ่มเข้ามาจะหายไปเมื่อคุณอัปเดตอินสแตนซ์ของเซิร์ฟเวอร์"
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "กำลังติดตั้ง"
|
||||
},
|
||||
@@ -407,9 +377,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "ลงชื่อเข้าใช้"
|
||||
},
|
||||
"app.update-popup.body.download-complete": {
|
||||
"message": "ดาวน์โหลด Modrinth v{version} สำเร็จแล้ว เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Tải xuống"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "Ẩn các lượt tải xuống đang hoạt động"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Đóng các phiên bản đang chạy"
|
||||
},
|
||||
@@ -20,6 +23,9 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Bỏ qua"
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Xem phiên bản"
|
||||
},
|
||||
"app.action-bar.install.retry": {
|
||||
"message": "Thử lại"
|
||||
},
|
||||
@@ -47,6 +53,9 @@
|
||||
"app.action-bar.install.summary.download-failed": {
|
||||
"message": "Không hoàn tất tải xuống"
|
||||
},
|
||||
"app.action-bar.install.summary.instance-not-found": {
|
||||
"message": "Không tìm thấy phiên bản"
|
||||
},
|
||||
"app.action-bar.install.summary.invalid-file-path": {
|
||||
"message": "Đường dẫn tệp tin không hợp lệ"
|
||||
},
|
||||
@@ -83,6 +92,9 @@
|
||||
"app.action-bar.install.unknown-instance": {
|
||||
"message": "Instance không rõ"
|
||||
},
|
||||
"app.action-bar.install.updating-shared-content": {
|
||||
"message": "Đang cập nhật nội dung được chia sẻ"
|
||||
},
|
||||
"app.action-bar.installs": {
|
||||
"message": "Cài đặt"
|
||||
},
|
||||
@@ -101,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Tải lại để cập nhật"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "Hiển thị các lượt tải xuống đang hoạt động"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Hiện thêm các phiên bản đang chạy"
|
||||
},
|
||||
@@ -134,11 +149,11 @@
|
||||
"app.ads-consent.title": {
|
||||
"message": "Quyền riêng tư của bạn và cách quảng cáo hỗ trợ Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Dựng hình nâng cao"
|
||||
"app.appearance-settings.compact-mode.description": {
|
||||
"message": "Hiển thị thư viện phiên bản theo bố cục hàng nhỏ gọn."
|
||||
},
|
||||
"app.appearance-settings.color-theme.title": {
|
||||
"message": "Màu chủ đề"
|
||||
"app.appearance-settings.compact-mode.title": {
|
||||
"message": "Chế độ nhỏ gọn"
|
||||
},
|
||||
"app.appearance-settings.default-landing-page.home": {
|
||||
"message": "Trang chủ"
|
||||
@@ -147,35 +162,44 @@
|
||||
"message": "Thư viện"
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.description": {
|
||||
"message": "Tắt hiển thị thẻ tên phía trên người chơi ở trang skin."
|
||||
"message": "Ẩn tên người dùng của bạn phía trên phần xem trước nhân vật tại trang chọn giao diện skin."
|
||||
},
|
||||
"app.appearance-settings.hide-nametag.title": {
|
||||
"message": "Ẩn thẻ tên"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Hiển thị các thế giới hoặc phiên chơi gần đây trong mục \"Jump in\" trên trang chủ."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Tiếp tục chơi"
|
||||
"message": "Bước vào các thế giới hoặc các khu vực riêng biệt"
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.description": {
|
||||
"message": "Thu nhỏ launcher khi game Minecraft bắt đầu chạy."
|
||||
"message": "Thu nhỏ ứng dụng Modrinth khi Minecraft khởi động."
|
||||
},
|
||||
"app.appearance-settings.minimize-launcher.title": {
|
||||
"message": "Thu nhỏ launcher"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Sử dụng khung cửa sổ hệ thống (yêu cầu khởi động lại ứng dụng)."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Giao diện cửa sổ hệ thống"
|
||||
"message": "Thu nhỏ ứng dụng"
|
||||
},
|
||||
"app.appearance-settings.show-play-time.description": {
|
||||
"message": "Hiển thị thời gian bạn đã dành để chơi bằng một cấu hình."
|
||||
"message": "Hiển thị thời gian bạn đã chơi mỗi phiên."
|
||||
},
|
||||
"app.appearance-settings.show-play-time.title": {
|
||||
"message": "Hiện thời gian chơi"
|
||||
},
|
||||
"app.appearance-settings.skip-non-essential-warnings.description": {
|
||||
"message": "Bỏ qua bước xác nhận đối với các thao tác ít rủi ro như cài đặt lại, xóa nội dung thông thường, cập nhật hàng loạt, hủy liên kết và sửa chữa. Các cảnh báo cho những thao tác nguy hiểm sẽ luôn được hiển thị."
|
||||
},
|
||||
"app.appearance-settings.skip-non-essential-warnings.title": {
|
||||
"message": "Bỏ qua các cảnh báo không quan trọng"
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.description": {
|
||||
"message": "Ẩn thanh bên phải theo mặc định và thêm nút để hiển thị hoặc ẩn nó."
|
||||
},
|
||||
"app.appearance-settings.toggle-sidebar.title": {
|
||||
"message": "Ẩn thanh bên"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Hiển thị cảnh báo an toàn trước khi cài đặt một gói Modrinth (.mrpack) không được lưu trữ trên Modrinth."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Cảnh báo tôi trước khi cài đặt các modpacks không rõ nguồn gốc"
|
||||
},
|
||||
@@ -185,6 +209,21 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Không thể kết nối đến máy chủ xác thực"
|
||||
},
|
||||
"app.behavior-settings.confirmations.title": {
|
||||
"message": "Các xác nhận"
|
||||
},
|
||||
"app.behavior-settings.startup-and-navigation.title": {
|
||||
"message": "Khởi động và điều hướng"
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.description": {
|
||||
"message": "Sử dụng các cài đặt hành vi này ở mọi nơi bạn đăng nhập. Hãy tắt tùy chọn này nếu muốn duy trì các cài đặt riêng cho thiết bị này."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.signed-out-tooltip": {
|
||||
"message": "Đăng nhập vào tài khoản Modrinth để đồng bộ hóa cài đặt."
|
||||
},
|
||||
"app.behavior-settings.sync-across-devices.title": {
|
||||
"message": "Đồng bộ hóa hành vi trên các thiết bị"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Thêm máy chủ vào phiên bản"
|
||||
},
|
||||
@@ -206,6 +245,9 @@
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Quay lại phiên bản"
|
||||
},
|
||||
"app.browse.discover-project-type": {
|
||||
"message": "Khám phá {projectType}"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Khám phá máy chủ"
|
||||
},
|
||||
@@ -216,7 +258,7 @@
|
||||
"message": "Modpack"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Thêm nội dung có thể gây lỗi tương thích khi tham gia máy chủ. Bất kỳ nội dung nào được thêm vào cũng sẽ bị mất khi bạn cập nhật nội dung của phiên bản máy chủ."
|
||||
"message": "Việc thêm nội dung có thể khiến bạn không thể tham gia máy chủ này. Mọi nội dung bạn thêm vào sẽ bị xóa khi nội dung do máy chủ quản lý được cập nhật."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Đang cài đặt"
|
||||
@@ -230,6 +272,9 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Xuất"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name} đã được xuất thành công."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "Xuất modpack"
|
||||
},
|
||||
@@ -245,6 +290,42 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.home.jump-back-in.instance-locked": {
|
||||
"message": "Phiên bản này đã bị khóa"
|
||||
},
|
||||
"app.home.jump-back-in.never-played": {
|
||||
"message": "Chưa từng chơi"
|
||||
},
|
||||
"app.home.jump-back-in.title": {
|
||||
"message": "Tham gia ngay"
|
||||
},
|
||||
"app.hosting.update-required.description": {
|
||||
"message": "Bạn cần cập nhật để sử dụng Modrinth Hosting thông qua ứng dụng Modrinth"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "Tải xuống để cập nhật"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Yêu cầu cập nhật ứng dụng Modrinth"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.apply-error": {
|
||||
"message": "Không thể áp dụng các biểu tượng ngẫu nhiên cho các thực thể."
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.badge": {
|
||||
"message": "Điểm mới trong bản cập nhật này"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.description": {
|
||||
"message": "Tạo biểu tượng tùy chỉnh cho các instance của bạn ngay trong ứng dụng Modrinth. Hãy kết hợp các hình nền với những biểu tượng từ Minecraft và các bản mod phổ biến!"
|
||||
},
|
||||
"app.icon-editor.apply-icons-modal.title": {
|
||||
"message": "Trình chỉnh sửa biểu tượng tùy chỉnh"
|
||||
},
|
||||
"app.icon-editor.notification.description": {
|
||||
"message": "Tùy chỉnh các instance của bạn!"
|
||||
},
|
||||
"app.icon-editor.notification.title": {
|
||||
"message": "Trình chỉnh sửa biểu tượng mới"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "Đang tải nội dung"
|
||||
},
|
||||
@@ -278,18 +359,36 @@
|
||||
"app.install.phase.preparing_java.resolving": {
|
||||
"message": "Đang chuẩn bị Java {version}"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Xem lại các thay đổi"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Đang xem xét..."
|
||||
},
|
||||
"app.instance.card.loading": {
|
||||
"message": "Đang tải phiên bản..."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Tất cả dữ liệu trong gói hồ sơ này sẽ bị xoá, bao gồm thế giới của bạn, cấu hình, và tất cả nội dung đã được tải xuống."
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-header": {
|
||||
"message": "Hành động này sẽ không thể huỷ bỏ"
|
||||
},
|
||||
"app.instance.confirm-delete.bulk-admonition-body": {
|
||||
"message": "Tất cả dữ liệu của {count} phiên bản này sẽ bị xóa vĩnh viễn, bao gồm thế giới, cấu hình và toàn bộ nội dung đã cài đặt."
|
||||
},
|
||||
"app.instance.confirm-delete.bulk-delete-button": {
|
||||
"message": "Xóa {count} mục"
|
||||
},
|
||||
"app.instance.confirm-delete.delete-button": {
|
||||
"message": "Xoá hồ sơ"
|
||||
},
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Xoá hồ sơ"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "Nội dung được chia sẻ"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "Gói modpack này đã được cài đặt trong hồ sơ <bold>{instanceName}</bold> rồi. Bạn có chắc chắn muốn sao chép nó không?"
|
||||
},
|
||||
@@ -302,9 +401,24 @@
|
||||
"app.instance.modpack-already-installed.instance": {
|
||||
"message": "Hồ sơ"
|
||||
},
|
||||
"app.instance.mods.bulk-update.downloading-projects": {
|
||||
"message": "Đang tải xuống {current, number}/{total, number} dự án..."
|
||||
},
|
||||
"app.instance.mods.bulk-update.finishing": {
|
||||
"message": "Đang hoàn tất cập nhật..."
|
||||
},
|
||||
"app.instance.mods.bulk-update.resolving-versions": {
|
||||
"message": "Đang xác định các phiên bản..."
|
||||
},
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "Dự án"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "Phiên bản cố định"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "Không thể thay đổi nội dung trong các instance bị khóa."
|
||||
},
|
||||
"app.instance.mods.project-was-added": {
|
||||
"message": "\"{name}\" đã được thêm"
|
||||
},
|
||||
@@ -320,6 +434,33 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Tải lên thành công"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "Hủy đóng băng phiên bản"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "Bạn có thể chia sẻ phiên bản này với bạn bè của mình!"
|
||||
},
|
||||
"app.instance.share.empty.heading": {
|
||||
"message": "Không có bạn để mời"
|
||||
},
|
||||
"app.instance.share.invite-modal.user-limit-reached": {
|
||||
"message": "Phiên bản này đã đạt đến giới hạn {limit} người dùng."
|
||||
},
|
||||
"app.instance.share.locked.empty-description-prefix": {
|
||||
"message": "Bạn cần đăng nhập với tư cách là"
|
||||
},
|
||||
"app.instance.share.locked.empty-description-suffix": {
|
||||
"message": "để truy cập trang này."
|
||||
},
|
||||
"app.instance.share.locked.linked-account-fallback": {
|
||||
"message": "tài khoản được liên kết"
|
||||
},
|
||||
"app.instance.share.locked.wrong-account-heading": {
|
||||
"message": "Sai tài khoản"
|
||||
},
|
||||
"app.instance.share.members.empty": {
|
||||
"message": "Chưa có người dùng nào tham gia"
|
||||
},
|
||||
"app.instance.worlds.add-server": {
|
||||
"message": "Thêm máy chủ"
|
||||
},
|
||||
@@ -566,9 +707,6 @@
|
||||
"app.skins.section.tiny-takeover": {
|
||||
"message": "Tiny Takeover"
|
||||
},
|
||||
"app.skins.sign-in.button": {
|
||||
"message": "Đăng nhập"
|
||||
},
|
||||
"app.skins.title": {
|
||||
"message": "Đổi skin"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ import * as Sentry from '@sentry/vue'
|
||||
import { VueScanPlugin } from '@taijased/vue-render-tracker'
|
||||
import { VueQueryPlugin } from '@tanstack/vue-query'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from '@/App.vue'
|
||||
@@ -21,8 +20,6 @@ const vueScan = new VueScanPlugin({
|
||||
playSound: false, // Play sound on each render
|
||||
})
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
let app = createApp(App)
|
||||
|
||||
Sentry.init({
|
||||
@@ -35,7 +32,6 @@ Sentry.init({
|
||||
app.use(VueQueryPlugin)
|
||||
app.use(vueScan)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
app.use(FloatingVue, {
|
||||
themes: {
|
||||
'ribbit-popout': {
|
||||
|
||||
@@ -34,13 +34,14 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, ref, shallowRef, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'
|
||||
import type { LocationQuery } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import { useAppServerBrowse } from '@/composables/browse/use-app-server-browse'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get_project, get_search_results_v3, get_version_many } from '@/helpers/cache.js'
|
||||
import {
|
||||
get_installed_project_ids as getInstalledProjectIds,
|
||||
@@ -63,7 +64,6 @@ import {
|
||||
createServerInstallContent,
|
||||
provideServerInstallContent,
|
||||
} from '@/providers/setup/server-install-content'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -109,7 +109,7 @@ const breadcrumbLabel = computed(() => {
|
||||
),
|
||||
})
|
||||
})
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const browseRouteActive = computed(() => route.path.startsWith('/browse/'))
|
||||
const serverSetupModalRef = ref<InstanceType<typeof CreationFlowModal> | null>(null)
|
||||
const serverInstallContent = createServerInstallContent({ serverSetupModalRef })
|
||||
@@ -384,7 +384,7 @@ async function initInstanceContext() {
|
||||
}
|
||||
|
||||
function setBrowseHideInstalledFlag(flag: 'hide_installed_modpacks', value: boolean) {
|
||||
themeStore.featureFlags[flag] = value
|
||||
appSettings.featureFlags[flag] = value
|
||||
getSettings()
|
||||
.then((settings) => {
|
||||
settings.feature_flags[flag] = value
|
||||
@@ -394,7 +394,7 @@ function setBrowseHideInstalledFlag(flag: 'hide_installed_modpacks', value: bool
|
||||
}
|
||||
|
||||
const hideInstalledModpacks = computed({
|
||||
get: () => themeStore.getFeatureFlag('hide_installed_modpacks'),
|
||||
get: () => appSettings.getFeatureFlag('hide_installed_modpacks'),
|
||||
set: (value: boolean) => setBrowseHideInstalledFlag('hide_installed_modpacks', value),
|
||||
})
|
||||
|
||||
@@ -530,13 +530,20 @@ const {
|
||||
})
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
const handleOffline = () => {
|
||||
debugLog('went offline')
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
}
|
||||
const handleOnline = () => {
|
||||
debugLog('went online')
|
||||
offline.value = false
|
||||
}
|
||||
window.addEventListener('offline', handleOffline)
|
||||
window.addEventListener('online', handleOnline)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('offline', handleOffline)
|
||||
window.removeEventListener('online', handleOnline)
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -742,7 +749,7 @@ const installContext = computed(() => {
|
||||
queuedCount: queuedServerInstallCount.value,
|
||||
selectedProjects: selectedServerInstallProjects.value,
|
||||
isInstallingSelected: isInstallingQueuedServerInstalls.value,
|
||||
skipNonEssentialWarnings: themeStore.getFeatureFlag('skip_non_essential_warnings'),
|
||||
skipNonEssentialWarnings: appSettings.getFeatureFlag('skip_non_essential_warnings'),
|
||||
installProgress: queuedInstallProgress.value,
|
||||
clearQueued: clearQueuedServerInstalls,
|
||||
clearSelected: clearQueuedServerInstalls,
|
||||
@@ -900,17 +907,18 @@ function getCardActions(
|
||||
const isQueued = queuedServerInstallProjectIds.value.has(projectResult.project_id)
|
||||
const isQueuedRoot = queuedServerInstallRootProjectIds.value.has(projectResult.project_id)
|
||||
const isInstallingSelection = isInstallingQueuedServerInstalls.value
|
||||
const showAsInstalling = isInstalling || (isInstallingSelection && isQueuedRoot)
|
||||
const validatingInstall =
|
||||
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
|
||||
const installLabel = showAsInstalled
|
||||
? commonMessages.installedLabel
|
||||
: isQueued
|
||||
? isInstalling || isInstallingSelection
|
||||
? showAsInstalling
|
||||
? validatingInstall
|
||||
? commonMessages.validatingLabel
|
||||
: messages.installingToServer
|
||||
: commonMessages.selectedLabel
|
||||
: isInstalling || isInstallingSelection
|
||||
: showAsInstalling
|
||||
? validatingInstall
|
||||
? commonMessages.validatingLabel
|
||||
: messages.installingToServer
|
||||
@@ -919,16 +927,11 @@ function getCardActions(
|
||||
{
|
||||
key: 'install',
|
||||
label: formatMessage(installLabel),
|
||||
icon:
|
||||
isInstalling || isInstallingSelection
|
||||
? SpinnerIcon
|
||||
: isQueued || showAsInstalled
|
||||
? CheckIcon
|
||||
: PlusIcon,
|
||||
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
|
||||
icon: showAsInstalling ? SpinnerIcon : isQueued || showAsInstalled ? CheckIcon : PlusIcon,
|
||||
iconClass: showAsInstalling ? 'animate-spin' : undefined,
|
||||
disabled:
|
||||
showAsInstalled || isInstalling || isInstallingSelection || (isQueued && !isQueuedRoot),
|
||||
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
|
||||
color: isQueued && !showAsInstalling ? 'green' : 'brand',
|
||||
type: 'outlined',
|
||||
onClick: async () => {
|
||||
if (isQueuedRoot) {
|
||||
@@ -1222,9 +1225,9 @@ function getProjectBrowseQuery() {
|
||||
}
|
||||
|
||||
const advancedFiltersCollapsed = computed({
|
||||
get: () => themeStore.getFeatureFlag('advanced_filters_collapsed'),
|
||||
get: () => appSettings.getFeatureFlag('advanced_filters_collapsed'),
|
||||
set: (value) => {
|
||||
themeStore.featureFlags['advanced_filters_collapsed'] = value
|
||||
appSettings.featureFlags['advanced_filters_collapsed'] = value
|
||||
getSettings()
|
||||
.then((settings) => {
|
||||
settings.feature_flags['advanced_filters_collapsed'] = value
|
||||
@@ -1235,9 +1238,9 @@ const advancedFiltersCollapsed = computed({
|
||||
})
|
||||
|
||||
const dismissedPhotosensitivityFilterWarning = computed({
|
||||
get: () => themeStore.getFeatureFlag('dismissed_photosensitivity_filter_warning'),
|
||||
get: () => appSettings.getFeatureFlag('dismissed_photosensitivity_filter_warning'),
|
||||
set: (value) => {
|
||||
themeStore.featureFlags['dismissed_photosensitivity_filter_warning'] = value
|
||||
appSettings.featureFlags['dismissed_photosensitivity_filter_warning'] = value
|
||||
getSettings()
|
||||
.then((settings) => {
|
||||
settings.feature_flags['dismissed_photosensitivity_filter_warning'] = value
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { HomeIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { PlayIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, inject, onActivated, ref } from 'vue'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ContextMenu from '@/components/ui/context-menu/index.vue'
|
||||
import LibrarySection from '@/components/ui/library/index.vue'
|
||||
import WelcomeScreen from '@/components/ui/WelcomeScreen.vue'
|
||||
import RecentWorldsList from '@/components/ui/world/RecentWorldsList.vue'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import { list } from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { useRootBreadcrumb } from '@/providers/breadcrumbs'
|
||||
import { injectOnboardingChecklist } from '@/providers/onboarding-checklist'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
defineOptions({
|
||||
name: 'LibraryPage',
|
||||
@@ -25,7 +25,7 @@ const { formatMessage } = useVIntl()
|
||||
const { hasCreatedInstance, isReady } = injectOnboardingChecklist()
|
||||
const showCreationModal = inject<() => void>('showCreationModal')
|
||||
const pageOptions = ref<InstanceType<typeof ContextMenu>>()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const messages = defineMessages({
|
||||
home: {
|
||||
@@ -43,7 +43,7 @@ const homeBreadcrumb = useRootBreadcrumb({
|
||||
id: 'home',
|
||||
label: formatMessage(messages.home),
|
||||
to: '/',
|
||||
visual: { type: 'icon', component: HomeIcon },
|
||||
visual: { type: 'icon', component: PlayIcon },
|
||||
})
|
||||
onActivated(homeBreadcrumb.reset)
|
||||
|
||||
@@ -102,11 +102,11 @@ function handlePageOption({ option }: { option: string }) {
|
||||
<div
|
||||
v-else-if="isReady"
|
||||
data-library-page-background
|
||||
class="flex flex-col gap-6 p-6"
|
||||
class="flex flex-col gap-3 p-6"
|
||||
@contextmenu="openPageContextMenu"
|
||||
>
|
||||
<RecentWorldsList
|
||||
v-if="recentInstances?.length > 0 && themeStore.getFeatureFlag('worlds_in_home')"
|
||||
v-if="recentInstances?.length > 0 && appSettings.getFeatureFlag('worlds_in_home')"
|
||||
:recent-instances="recentInstances"
|
||||
/>
|
||||
<LibrarySection :instances="instances" />
|
||||
|
||||
@@ -16,7 +16,7 @@ const client = injectModrinthClient()
|
||||
useRootBreadcrumb({
|
||||
slot: 'root',
|
||||
id: 'servers',
|
||||
label: 'Servers',
|
||||
label: 'Hosting',
|
||||
to: '/hosting/manage/',
|
||||
visual: { type: 'icon', component: ServerStackIcon },
|
||||
})
|
||||
|
||||
@@ -32,6 +32,8 @@ import EarsModIcon from '@/assets/skins/ears-mod.png'
|
||||
import type AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import EditSkinModal from '@/components/ui/skin/EditSkinModal.vue'
|
||||
import VirtualSkinSectionList from '@/components/ui/skin/VirtualSkinSectionList.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { check_reachable, get_default_user, login as login_flow, users } from '@/helpers/auth'
|
||||
import type { RenderResult } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
@@ -58,8 +60,6 @@ import {
|
||||
} from '@/helpers/skins.ts'
|
||||
import { hasPride26Badge } from '@/helpers/user-campaigns.ts'
|
||||
import { useRootBreadcrumb } from '@/providers/breadcrumbs'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { appMessages } from '@/utils/app-messages'
|
||||
|
||||
useRootBreadcrumb({
|
||||
@@ -217,7 +217,7 @@ const { addNotification, handleError } = notifications
|
||||
const auth = injectAuth()
|
||||
const client = injectModrinthClient()
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const skins = ref<Skin[]>([])
|
||||
const capes = ref<Cape[]>([])
|
||||
const offline = ref(!navigator.onLine)
|
||||
@@ -339,7 +339,7 @@ const skinTexture = computedAsync(async () => {
|
||||
})
|
||||
const capeTexture = computed(() => currentCape.value?.texture)
|
||||
const skinVariant = computed(() => selectedSkin.value?.variant)
|
||||
const skinNametag = computed(() => (themeStore.hideNametagSkinsPage ? undefined : username.value))
|
||||
const skinNametag = computed(() => (appSettings.hideNametagSkinsPage ? undefined : username.value))
|
||||
const isSkinManagementReadOnly = computed(
|
||||
() =>
|
||||
!!currentUser.value &&
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
>
|
||||
<template #project-actions="{ project }">
|
||||
<Button
|
||||
v-if="project.minecraft_server == null"
|
||||
type="outlined"
|
||||
class="!text-brand [&>svg]:!text-brand !shadow-[inset_0_0_0_1px_var(--color-brand)]"
|
||||
:disabled="isProjectInstalling(project.id)"
|
||||
@click.stop="installProject(project)"
|
||||
>
|
||||
<SpinnerIcon v-if="isProjectInstalling(project.id)" class="animate-spin" />
|
||||
<DownloadIcon v-else-if="project.project_type === 'modpack'" />
|
||||
<DownloadIcon v-else-if="project.project_types.includes('modpack')" />
|
||||
<PlusIcon v-else />
|
||||
{{
|
||||
formatMessage(
|
||||
isProjectInstalling(project.id)
|
||||
? commonMessages.installingLabel
|
||||
: project.project_type === 'modpack'
|
||||
: project.project_types.includes('modpack')
|
||||
? commonMessages.installButton
|
||||
: messages.installToInstance,
|
||||
)
|
||||
@@ -96,7 +97,7 @@ function setProjectInstalling(projectId: string, installing: boolean): void {
|
||||
installingProjectIds.value = next
|
||||
}
|
||||
|
||||
async function installProject(project: Labrinth.Projects.v2.Project): Promise<void> {
|
||||
async function installProject(project: Labrinth.Projects.v3.Project): Promise<void> {
|
||||
if (isProjectInstalling(project.id)) return
|
||||
|
||||
setProjectInstalling(project.id, true)
|
||||
@@ -130,14 +131,19 @@ const userProfile = provideUserProfile({
|
||||
unblockUser: unblock_user,
|
||||
})
|
||||
|
||||
const userId = computed(() => {
|
||||
const value = route.params.user
|
||||
return Array.isArray(value) ? (value[0] ?? '') : (value ?? '')
|
||||
})
|
||||
const projectType = computed(() => {
|
||||
const value = route.params.projectType
|
||||
return Array.isArray(value) ? value[0] : value
|
||||
})
|
||||
function readRouteParam(param: unknown): string | undefined {
|
||||
const value = Array.isArray(param) ? param[0] : param
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined
|
||||
}
|
||||
|
||||
const userId = ref(readRouteParam(route.params.user) ?? '')
|
||||
watch(
|
||||
() => readRouteParam(route.params.user),
|
||||
(next) => {
|
||||
if (next) userId.value = next
|
||||
},
|
||||
)
|
||||
const projectType = computed(() => readRouteParam(route.params.projectType))
|
||||
|
||||
function getCachedUserSummary(id: string) {
|
||||
return queryClient.getQueryData<Labrinth.Users.v3.User>(['users', 'summary', id])
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:layout-mode="isContainedServerRoute ? 'contained' : 'page'"
|
||||
:reload-page="() => router.go(0)"
|
||||
:resolve-viewer="resolveViewer"
|
||||
:show-copy-id-action="themeStore.devMode"
|
||||
:show-copy-id-action="appSettings.devMode"
|
||||
:auth-user="authUser"
|
||||
:navigate-to-billing="() => openUrl('https://modrinth.com/settings/billing')"
|
||||
:navigate-to-servers="() => router.push('/hosting/manage')"
|
||||
@@ -61,17 +61,17 @@ import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get_user } from '@/helpers/cache'
|
||||
import { get as getCreds } from '@/helpers/mr_auth'
|
||||
import { provideBreadcrumbParent, useBreadcrumb } from '@/providers/breadcrumbs'
|
||||
import { useTheming } from '@/store/theme'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = injectAuth()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const isContainedServerRoute = computed(() => route.name === 'ServerManageOverview')
|
||||
|
||||
+3
-3
@@ -16,6 +16,7 @@ import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useManagedContentPolicy } from '@/composables/instances/use-managed-content-policy'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_versions, get_version } from '@/helpers/cache'
|
||||
import {
|
||||
@@ -34,7 +35,6 @@ import { get_loader_versions } from '@/helpers/metadata'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import { injectAppEvents } from '@/providers/app-events'
|
||||
import { provideInstanceBackup } from '@/providers/instance-backup'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
import type { Manifest } from '../../../../helpers/types'
|
||||
import { instanceKeys } from '../../query-options.ts'
|
||||
@@ -47,12 +47,12 @@ const filePicker = injectFilePicker()
|
||||
const { formatMessage } = useVIntl()
|
||||
const queryClient = useQueryClient()
|
||||
const debug = useDebugLogger('AppInstallationSettings')
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const { instance, offline, isMinecraftServer, onUnlinked, closeModal } = injectInstanceSettings()
|
||||
const managedContentPolicy = useManagedContentPolicy(instance)
|
||||
const skipNonEssentialWarnings = computed(() =>
|
||||
themeStore.getFeatureFlag('skip_non_essential_warnings'),
|
||||
appSettings.getFeatureFlag('skip_non_essential_warnings'),
|
||||
)
|
||||
|
||||
debug('metadata load: start', {
|
||||
|
||||
@@ -127,6 +127,7 @@ import ExportModal from '@/components/ui/ExportModal.vue'
|
||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
||||
import { useManagedContentPolicy } from '@/composables/instances/use-managed-content-policy'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { type FeatureFlag, useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_versions, get_version, get_version_many } from '@/helpers/cache.js'
|
||||
import {
|
||||
@@ -149,8 +150,6 @@ import type { CacheBehaviour } from '@/helpers/types'
|
||||
import { highlightModInInstance } from '@/helpers/utils.js'
|
||||
import { type AppEventPayload, injectAppEvents } from '@/providers/app-events'
|
||||
import { injectContentInstall } from '@/providers/content-install'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { FeatureFlag } from '@/store/theme'
|
||||
|
||||
import { injectInstancePage } from '../instance-context'
|
||||
import { instanceContentQueryOptions, instanceKeys } from '../query-options'
|
||||
@@ -234,10 +233,10 @@ const { installingItems, installRevisionByInstance, installFailureRevisionByInst
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const debug = useDebugLogger('Mods:ContentUpdate')
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const skipUnknownFileWarningFeatureFlag = 'skip_unknown_pack_warning' as FeatureFlag
|
||||
const skipNonEssentialWarnings = computed(() =>
|
||||
themeStore.getFeatureFlag('skip_non_essential_warnings'),
|
||||
appSettings.getFeatureFlag('skip_non_essential_warnings'),
|
||||
)
|
||||
|
||||
const instancePage = injectInstancePage()
|
||||
@@ -759,7 +758,7 @@ async function handleUploadFiles() {
|
||||
}
|
||||
|
||||
function confirmUnknownFileInstallation(fileName: string) {
|
||||
if (themeStore.getFeatureFlag(skipUnknownFileWarningFeatureFlag)) {
|
||||
if (appSettings.getFeatureFlag(skipUnknownFileWarningFeatureFlag)) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
@@ -779,7 +778,7 @@ function resolveUnknownFileWarning(confirmed: boolean) {
|
||||
|
||||
async function handleUnknownFileContinue(dontShowAgain: boolean) {
|
||||
if (dontShowAgain) {
|
||||
themeStore.featureFlags[skipUnknownFileWarningFeatureFlag] = true
|
||||
appSettings.featureFlags[skipUnknownFileWarningFeatureFlag] = true
|
||||
try {
|
||||
const settings = await getSettings()
|
||||
settings.feature_flags[skipUnknownFileWarningFeatureFlag] = true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user