mirror of
https://github.com/modrinth/code.git
synced 2026-08-29 02:54:51 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
157481cc9c |
Generated
-11
@@ -17,17 +17,6 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-util/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/muralpay/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/labrinth-derive/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/neverbounce/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/serde-binhum/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/xredis/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
Generated
-18
@@ -5347,16 +5347,6 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonptr"
|
||||
version = "0.6.3"
|
||||
@@ -10969,7 +10959,6 @@ dependencies = [
|
||||
"image",
|
||||
"indicatif",
|
||||
"itertools 0.14.0",
|
||||
"json5",
|
||||
"modrinth-content-management",
|
||||
"notify",
|
||||
"notify-debouncer-mini",
|
||||
@@ -10999,7 +10988,6 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml 0.9.8",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
@@ -11722,12 +11710,6 @@ version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
|
||||
@@ -113,7 +113,6 @@ indicatif = "0.18.0"
|
||||
itertools = "0.14.0"
|
||||
jemalloc_pprof = "0.8.1"
|
||||
json-patch = { version = "4.1.0", default-features = false }
|
||||
json5 = "1.3.1"
|
||||
lettre = { version = "0.11.19", default-features = false, features = [
|
||||
"aws-lc-rs",
|
||||
"builder",
|
||||
@@ -225,7 +224,6 @@ tikv-jemallocator = "0.6.0"
|
||||
tokio = "1.47.1"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.16"
|
||||
toml = "0.9.8"
|
||||
totp-rs = "5.7.0"
|
||||
tracing = "0.1.41"
|
||||
tracing-actix-web = { version = "0.7.19", default-features = false }
|
||||
|
||||
@@ -71,7 +71,6 @@ import AppActionBar from '@/components/ui/AppActionBar.vue'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
||||
import HostingUpdateRequired from '@/components/ui/HostingUpdateRequired.vue'
|
||||
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
|
||||
import UnknownPackWarningModal from '@/components/ui/install_flow/UnknownPackWarningModal.vue'
|
||||
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
|
||||
@@ -203,19 +202,10 @@ watch(
|
||||
},
|
||||
)
|
||||
const forceSidebar = computed(
|
||||
() =>
|
||||
route.path.startsWith('/browse') ||
|
||||
route.path.startsWith('/project') ||
|
||||
route.path.startsWith('/user'),
|
||||
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
|
||||
)
|
||||
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
|
||||
const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
|
||||
const hostingUpdateRequired = computed(
|
||||
() =>
|
||||
hostingRouteActive.value &&
|
||||
!!appUpdateState.availableUpdate.value &&
|
||||
appUpdateState.updatesEnabled.value,
|
||||
)
|
||||
const prideFundraiserEnabled = computed(
|
||||
() => themeStore.getFeatureFlag('pride_fundraiser') && Date.now() < PRIDE_FUNDRAISER_END_DATE,
|
||||
)
|
||||
@@ -226,9 +216,7 @@ const hostingIntercomIdentityKey = computed(() => {
|
||||
return `${userId}:${serverId ?? 'hosting'}`
|
||||
})
|
||||
const hostingIntercom = useHostingIntercom({
|
||||
enabled: computed(
|
||||
() => hostingRouteActive.value && !hostingUpdateRequired.value && !!credentials.value?.session,
|
||||
),
|
||||
enabled: computed(() => hostingRouteActive.value && !!credentials.value?.session),
|
||||
appId: 'ykeritl9',
|
||||
fetchToken: fetchIntercomToken,
|
||||
identityKey: hostingIntercomIdentityKey,
|
||||
@@ -1796,8 +1784,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
>
|
||||
{{ formatMessage(messages.authUnreachableBody) }}
|
||||
</Admonition>
|
||||
<HostingUpdateRequired v-if="hostingUpdateRequired" />
|
||||
<RouterView v-else v-slot="{ Component }">
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
|
||||
<component :is="Component"></component>
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, ExcitedRinthbot, RefreshCwIcon, ServerStackIcon } from '@modrinth/assets'
|
||||
import { Button, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import {
|
||||
appUpdateState,
|
||||
downloadAvailableAppUpdate,
|
||||
installAvailableAppUpdate,
|
||||
} from '@/providers/app-update'
|
||||
import { useRootBreadcrumb } from '@/providers/breadcrumbs'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'app.hosting.update-required.title',
|
||||
defaultMessage: 'Modrinth App update required',
|
||||
},
|
||||
description: {
|
||||
id: 'app.hosting.update-required.description',
|
||||
defaultMessage: 'You need to update to use Modrinth Hosting through the Modrinth App',
|
||||
},
|
||||
downloadToUpdate: {
|
||||
id: 'app.hosting.update-required.download',
|
||||
defaultMessage: 'Download to update',
|
||||
},
|
||||
downloadingUpdate: {
|
||||
id: 'app.action-bar.downloading-update',
|
||||
defaultMessage: 'Downloading update',
|
||||
},
|
||||
reloadToUpdate: {
|
||||
id: 'app.action-bar.reload-to-update',
|
||||
defaultMessage: 'Reload to update',
|
||||
},
|
||||
excitedRinthbotAlt: {
|
||||
id: 'app.hosting.update-required.rinthbot-alt',
|
||||
defaultMessage: 'Excited Modrinth Bot',
|
||||
},
|
||||
})
|
||||
|
||||
useRootBreadcrumb({
|
||||
slot: 'root',
|
||||
id: 'servers',
|
||||
label: () => formatMessage(commonMessages.serversLabel),
|
||||
to: '/hosting/manage/',
|
||||
visual: { type: 'icon', component: ServerStackIcon },
|
||||
})
|
||||
|
||||
const { downloading, downloadPercent, downloadProgress, finishedDownloading } = appUpdateState
|
||||
|
||||
const isUpdateDownloading = computed(
|
||||
() =>
|
||||
downloading.value ||
|
||||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
|
||||
)
|
||||
|
||||
async function handleUpdateClick() {
|
||||
if (isUpdateDownloading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (finishedDownloading.value) {
|
||||
await installAvailableAppUpdate()
|
||||
} else {
|
||||
await downloadAvailableAppUpdate()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="box-border flex min-h-full items-center justify-center p-4">
|
||||
<div class="relative mx-auto w-full max-w-xl pt-28">
|
||||
<img
|
||||
:src="ExcitedRinthbot"
|
||||
:alt="formatMessage(messages.excitedRinthbotAlt)"
|
||||
class="absolute right-8 top-0 h-28 w-auto md:right-20"
|
||||
/>
|
||||
<div class="relative flex flex-col gap-5 rounded-lg bg-bg-raised p-7 shadow-lg">
|
||||
<div
|
||||
class="absolute left-0 top-0 h-px w-full bg-gradient-to-r from-transparent via-green-500 to-transparent opacity-40"
|
||||
style="
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 2rem,
|
||||
var(--color-green) calc(100% - 13rem),
|
||||
var(--color-green) calc(100% - 5rem),
|
||||
transparent calc(100% - 2rem)
|
||||
);
|
||||
"
|
||||
></div>
|
||||
|
||||
<div class="flex flex-col gap-5">
|
||||
<h1 class="m-0 text-3xl font-extrabold">
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h1>
|
||||
<p class="m-0 text-lg">
|
||||
{{ formatMessage(messages.description) }}
|
||||
</p>
|
||||
<Button
|
||||
type="colored"
|
||||
color="brand"
|
||||
:disabled="isUpdateDownloading"
|
||||
:aria-busy="isUpdateDownloading"
|
||||
@click="handleUpdateClick"
|
||||
>
|
||||
<RefreshCwIcon v-if="finishedDownloading" />
|
||||
<DownloadIcon v-else />
|
||||
<span v-if="isUpdateDownloading">
|
||||
{{ formatMessage(messages.downloadingUpdate) }}
|
||||
<span class="inline-block w-[3ch] text-right tabular-nums">
|
||||
{{ downloadPercent }}%
|
||||
</span>
|
||||
</span>
|
||||
<span v-else-if="finishedDownloading">
|
||||
{{ formatMessage(messages.reloadToUpdate) }}
|
||||
</span>
|
||||
<span v-else>{{ formatMessage(messages.downloadToUpdate) }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -140,11 +140,10 @@
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
<ManagedContentModal
|
||||
ref="managedContentModal"
|
||||
:header="formatMessage(messages.modpackContent)"
|
||||
:source-name="project?.name ?? ''"
|
||||
:source-icon-url="project?.icon_url ?? undefined"
|
||||
<ModpackContentModal
|
||||
ref="modpackContentModal"
|
||||
:modpack-name="project?.name ?? ''"
|
||||
:modpack-icon-url="project?.icon_url ?? undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -159,7 +158,7 @@ import {
|
||||
type ContentItem,
|
||||
defineMessages,
|
||||
formatLoader,
|
||||
ManagedContentModal,
|
||||
ModpackContentModal,
|
||||
NewModal,
|
||||
Table,
|
||||
type TableColumn,
|
||||
@@ -268,10 +267,10 @@ function handleReport() {
|
||||
}
|
||||
}
|
||||
|
||||
const managedContentModal = ref<InstanceType<typeof ManagedContentModal>>()
|
||||
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal>>()
|
||||
|
||||
async function openViewContents() {
|
||||
managedContentModal.value?.showLoading()
|
||||
modpackContentModal.value?.showLoading()
|
||||
try {
|
||||
// Ensure version data is available — the useQuery may not have resolved yet
|
||||
const versionId = modpackVersionId.value
|
||||
@@ -329,10 +328,10 @@ async function openViewContents() {
|
||||
}
|
||||
},
|
||||
)
|
||||
managedContentModal.value?.show(contentItems)
|
||||
modpackContentModal.value?.show(contentItems)
|
||||
} catch (err) {
|
||||
console.error('Failed to load modpack contents:', err)
|
||||
managedContentModal.value?.show([])
|
||||
modpackContentModal.value?.show([])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,10 +360,6 @@ function hide() {
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
modpackContent: {
|
||||
id: 'app.modal.install-to-play.managed-content.modpack-header',
|
||||
defaultMessage: 'Modpack content',
|
||||
},
|
||||
installToPlay: {
|
||||
id: 'app.modal.install-to-play.header',
|
||||
defaultMessage: 'Install to play',
|
||||
|
||||
+20
-9
@@ -229,11 +229,11 @@
|
||||
</div>
|
||||
</template>
|
||||
</NewModal>
|
||||
<ManagedContentModal
|
||||
<ModpackContentModal
|
||||
ref="contentModal"
|
||||
:header="formatMessage(messages.sharedInstanceContent)"
|
||||
:source-name="preview?.name ?? ''"
|
||||
:source-icon-url="preview?.iconUrl ?? undefined"
|
||||
:modpack-name="preview?.name ?? ''"
|
||||
:modpack-icon-url="preview?.iconUrl ?? undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -251,13 +251,12 @@ import {
|
||||
type ComboboxOption,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
formatReportType,
|
||||
injectAuth,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
ManagedContentModal,
|
||||
MarkdownEditor,
|
||||
ModpackContentModal,
|
||||
NewModal,
|
||||
Table,
|
||||
type TableColumn,
|
||||
@@ -289,7 +288,7 @@ type SharedInstanceCreator = {
|
||||
}
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const contentModal = ref<InstanceType<typeof ManagedContentModal>>()
|
||||
const contentModal = ref<InstanceType<typeof ModpackContentModal>>()
|
||||
const externalFileTable = ref<HTMLElement | null>(null)
|
||||
const preview = ref<SharedInstanceInstallPreview | null>(null)
|
||||
const creator = ref<SharedInstanceCreator | null>(null)
|
||||
@@ -329,9 +328,9 @@ const externalFileRows = computed<ExternalFileRow[]>(() =>
|
||||
.sort((left, right) => left.name.localeCompare(right.name)),
|
||||
)
|
||||
const reportReasonOptions = computed<ComboboxOption<ReportReason>[]>(() => [
|
||||
{ value: 'malicious', label: formatReportType(formatMessage, 'malicious') },
|
||||
{ value: 'inappropriate', label: formatReportType(formatMessage, 'inappropriate') },
|
||||
{ value: 'spam', label: formatReportType(formatMessage, 'spam') },
|
||||
{ value: 'malicious', label: formatMessage(messages.maliciousReason) },
|
||||
{ value: 'inappropriate', label: formatMessage(messages.inappropriateReason) },
|
||||
{ value: 'spam', label: formatMessage(messages.spamReason) },
|
||||
])
|
||||
const canSubmitReport = computed(
|
||||
() => Boolean(preview.value && additionalContext.value.trim()) && !submitLoading.value,
|
||||
@@ -541,6 +540,18 @@ const messages = defineMessages({
|
||||
id: 'app.modal.install-to-play.report-reason',
|
||||
defaultMessage: 'Which rule does this instance violate?',
|
||||
},
|
||||
maliciousReason: {
|
||||
id: 'app.modal.install-to-play.report-reason.malicious',
|
||||
defaultMessage: 'Malicious',
|
||||
},
|
||||
inappropriateReason: {
|
||||
id: 'app.modal.install-to-play.report-reason.inappropriate',
|
||||
defaultMessage: 'Inappropriate',
|
||||
},
|
||||
spamReason: {
|
||||
id: 'app.modal.install-to-play.report-reason.spam',
|
||||
defaultMessage: 'Spam',
|
||||
},
|
||||
additionalContext: {
|
||||
id: 'app.modal.install-to-play.additional-context',
|
||||
defaultMessage: 'Additional context',
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import type { ContentItem, ManagedContentProject, ManagedContentVersion } from '@modrinth/ui'
|
||||
import type {
|
||||
ContentItem,
|
||||
ContentModpackCardCategory,
|
||||
ContentModpackCardProject,
|
||||
ContentModpackCardVersion,
|
||||
ContentOwner,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import {
|
||||
get_content_items,
|
||||
get_linked_modpack_info,
|
||||
type LinkedModpackInfo,
|
||||
} from '@/helpers/instance'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import type { CacheBehaviour } from '@/helpers/types'
|
||||
|
||||
export type InstanceContentData = {
|
||||
@@ -14,8 +21,11 @@ export type InstanceContentData = {
|
||||
}
|
||||
|
||||
export type InstanceContentModpackData = {
|
||||
project: ManagedContentProject
|
||||
version: ManagedContentVersion
|
||||
project: ContentModpackCardProject
|
||||
version: ContentModpackCardVersion
|
||||
owner: ContentOwner | null
|
||||
categories: ContentModpackCardCategory[]
|
||||
hasUpdate: boolean
|
||||
updateVersionId: string | null
|
||||
}
|
||||
|
||||
@@ -24,15 +34,19 @@ export async function loadInstanceContentData(
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
onError?: (error: Error) => unknown,
|
||||
): Promise<InstanceContentData> {
|
||||
const [contentItems, modpackInfo] = await Promise.all([
|
||||
const [contentItems, modpackInfo, allCategories] = await Promise.all([
|
||||
get_content_items(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
|
||||
get_linked_modpack_info(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
|
||||
get_categories().catch((error) => handleLoadError(error, onError)),
|
||||
])
|
||||
|
||||
return {
|
||||
path,
|
||||
contentItems: (contentItems as ContentItem[] | null | undefined) ?? null,
|
||||
modpack: normalizeLinkedModpackInfo(modpackInfo as LinkedModpackInfo | null | undefined),
|
||||
modpack: normalizeLinkedModpackInfo(
|
||||
modpackInfo as LinkedModpackInfo | null | undefined,
|
||||
allCategories as ContentModpackCardCategory[] | null | undefined,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +58,7 @@ function handleLoadError(error: unknown, onError?: (error: Error) => unknown) {
|
||||
|
||||
function normalizeLinkedModpackInfo(
|
||||
modpackInfo: LinkedModpackInfo | null | undefined,
|
||||
allCategories: ContentModpackCardCategory[] | null | undefined,
|
||||
): InstanceContentModpackData | null {
|
||||
if (!modpackInfo) return null
|
||||
|
||||
@@ -57,6 +72,30 @@ function normalizeLinkedModpackInfo(
|
||||
...modpackInfo.version,
|
||||
date_published: modpackInfo.version.date_published.toString(),
|
||||
},
|
||||
owner: modpackInfo.owner
|
||||
? {
|
||||
...modpackInfo.owner,
|
||||
avatar_url: modpackInfo.owner.avatar_url ?? undefined,
|
||||
}
|
||||
: null,
|
||||
categories: resolveLinkedModpackCategories(modpackInfo, allCategories),
|
||||
hasUpdate: modpackInfo.has_update,
|
||||
updateVersionId: modpackInfo.update_version_id,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLinkedModpackCategories(
|
||||
modpackInfo: LinkedModpackInfo,
|
||||
allCategories: ContentModpackCardCategory[] | null | undefined,
|
||||
) {
|
||||
if (!allCategories || !modpackInfo.project.categories) return []
|
||||
|
||||
const seen = new Set<string>()
|
||||
return allCategories.filter((category) => {
|
||||
if (modpackInfo.project.categories.includes(category.name) && !seen.has(category.name)) {
|
||||
seen.add(category.name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import type { ContentItem, ContentOwner } from '@modrinth/ui'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import type { InstallJobSnapshot, SharedInstanceUpdateDiff } from './install'
|
||||
import type {
|
||||
@@ -74,11 +74,7 @@ export async function get_content_items(
|
||||
instanceId: string,
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
): Promise<ContentItem[]> {
|
||||
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_content_items', {
|
||||
instanceId,
|
||||
cacheBehaviour,
|
||||
})
|
||||
return adaptContentItems(items)
|
||||
return await invoke('plugin:instance|instance_get_content_items', { instanceId, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function refresh_content_updates(instanceId: string): Promise<void> {
|
||||
@@ -115,11 +111,10 @@ export async function get_linked_modpack_content(
|
||||
instanceId: string,
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
): Promise<ContentItem[]> {
|
||||
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_linked_modpack_content', {
|
||||
return await invoke('plugin:instance|instance_get_linked_modpack_content', {
|
||||
instanceId,
|
||||
cacheBehaviour,
|
||||
})
|
||||
return adaptContentItems(items)
|
||||
}
|
||||
|
||||
// Convert a list of dependencies into ContentItems with rich metadata
|
||||
@@ -127,28 +122,9 @@ export async function get_dependencies_as_content_items(
|
||||
dependencies: Labrinth.Versions.v3.Dependency[],
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
): Promise<ContentItem[]> {
|
||||
const items = await invoke<ContentItem[]>(
|
||||
'plugin:instance|instance_get_dependencies_as_content_items',
|
||||
{
|
||||
dependencies,
|
||||
cacheBehaviour,
|
||||
},
|
||||
)
|
||||
return adaptContentItems(items)
|
||||
}
|
||||
|
||||
function adaptContentItems(items: ContentItem[]): ContentItem[] {
|
||||
return items.map((item) => {
|
||||
const embeddedMetadata = item.embedded_metadata
|
||||
if (!embeddedMetadata?.icon_path) return item
|
||||
|
||||
return {
|
||||
...item,
|
||||
embedded_metadata: {
|
||||
...embeddedMetadata,
|
||||
icon_url: convertFileSrc(embeddedMetadata.icon_path),
|
||||
},
|
||||
}
|
||||
return await invoke('plugin:instance|instance_get_dependencies_as_content_items', {
|
||||
dependencies,
|
||||
cacheBehaviour,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,18 +264,6 @@ export async function toggle_disable_project(
|
||||
})
|
||||
}
|
||||
|
||||
export async function set_project_locked(
|
||||
instanceId: string,
|
||||
projectPath: string,
|
||||
locked: boolean,
|
||||
): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_set_project_locked', {
|
||||
instanceId,
|
||||
projectPath,
|
||||
locked,
|
||||
})
|
||||
}
|
||||
|
||||
// Remove a project
|
||||
export async function remove_project(instanceId: string, projectPath: string): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_remove_project', { instanceId, projectPath })
|
||||
|
||||
-1
@@ -128,7 +128,6 @@ export type ContentSourceKind =
|
||||
|
||||
type ContentFile = {
|
||||
enabled: boolean
|
||||
locked: boolean
|
||||
source_kind?: ContentSourceKind | null
|
||||
metadata?: {
|
||||
project_id: string
|
||||
|
||||
@@ -1073,7 +1073,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "مزامنة مع النسخة"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "يقدمها الخادم"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "يمكن إضافة التعديلات **المحليه** فقط إلى نموذج الخادم"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "يتم توفير نسخة اللعبة من قبل الخادم"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "يتم توفير المحمّل من قبل الخادم"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,7 +965,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchronizováno s instancí"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Poskytováno serverem"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Pouze módy ze strany klientu mohou být přidány na server"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Verze hry je poskytována serverem"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader zprostředkovává server"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,9 +296,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Gennemså ændringer"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Gennemgå opdatering"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Gennemgår..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "En opdatering er krævet for at spille {name}. Venligst opdater til den seneste version for at køre spillet."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "En opdatering er tilgængelig"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Al' data for din instance vil blive permanent slettet, dette inkludere dine verdener, konfigurationer, og alt installeret indhold."
|
||||
},
|
||||
@@ -461,6 +470,9 @@
|
||||
"app.modal.install-to-play.report-image-too-large": {
|
||||
"message": "Filen overskrider 1MiB størrelse begrænsning"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Delt instance"
|
||||
},
|
||||
@@ -1169,7 +1181,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synkroniser med instance"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Givet af serveren"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Kun klient-sided mods kan blive tilføjet til denne server instance"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spille version er givet af serveren"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader er givet af serveren"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Änderungen überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Update überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Wird überprüft..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Ein Update ist erforderlich, um {name} zu spielen. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Ein Update ist verfügbar"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Alle Daten deiner Instanz werden permanent gelöscht, inlusive deiner Welten, Konfigurationen und allen installierten Inhalten."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Gegen welche Regel verstößt diese Instanz?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Unangemessen"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Bösartig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Geteilte Instanz melden"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Mit Instanz synchronisieren"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Vom Server bereitgestellt"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Nur Clientseitige Mods können der Serverinstanz hinzugefügt werden"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spielversion wird vom Server bereitgestellt"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader wird vom Server bereitgestellt"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Konto"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Änderungen überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Update überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Wird überprüft..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Ein Update ist erforderlich, um {name} zu spielen. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Ein Update ist verfügbar"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Alle Daten deiner Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Gegen welche Regel verstößt diese Instanz?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Unangemessen"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Bösartig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Geteilte Instanz melden"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Mit Instanz synchronisieren"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Vom Server vorgegeben"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Nur clientseitige Mods können der Serverinstanz hinzugefügt werden"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spielversion vom Server vorgegeben"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader vom Server vorgegeben"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Konto"
|
||||
},
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Adding content may prevent you from joining this server. Any content you add will be removed when the managed server content is updated."
|
||||
"message": "Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installing"
|
||||
@@ -302,18 +302,6 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.hosting.update-required.description": {
|
||||
"message": "You need to update to use Modrinth Hosting through the Modrinth App"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "Download to update"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "Excited Modrinth Bot"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Modrinth App update required"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "Downloading content"
|
||||
},
|
||||
@@ -395,9 +383,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Review changes"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Review update"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Reviewing..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "An update is required to play {name}. Please update to latest version to launch the game."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "An update is available"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "All data for your instance will be permanently deleted, including your worlds, configs, and all installed content."
|
||||
},
|
||||
@@ -410,12 +407,6 @@
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Delete instance"
|
||||
},
|
||||
"app.instance.content.managed-content.modpack-header": {
|
||||
"message": "Modpack content"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "Shared content"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "This modpack is already installed in the <bold>{instanceName}</bold> instance. Are you sure you want to duplicate it?"
|
||||
},
|
||||
@@ -440,9 +431,6 @@
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "project"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "Freeze version"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "Content in locked instances cannot be changed."
|
||||
},
|
||||
@@ -461,9 +449,6 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Successfully uploaded"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "Unfreeze version"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "You can share this instance with your friends!"
|
||||
},
|
||||
@@ -707,9 +692,6 @@
|
||||
"app.modal.install-to-play.invite-warning-with-creator": {
|
||||
"message": "This invite was created by <creator>{username}</creator>, not Modrinth. Only accept invites from people you trust."
|
||||
},
|
||||
"app.modal.install-to-play.managed-content.modpack-header": {
|
||||
"message": "Modpack content"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# mod} other {# mods}}"
|
||||
},
|
||||
@@ -728,6 +710,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Which rule does this instance violate?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inappropriate"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Malicious"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Report shared instance"
|
||||
},
|
||||
@@ -1472,9 +1463,6 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "Save as..."
|
||||
},
|
||||
"instance.last-played": {
|
||||
"message": "Last played"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "Delete instance"
|
||||
},
|
||||
@@ -1940,9 +1928,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sync with instance"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Provided by the server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Only client-side mods can be added to the server instance"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Game version is provided by the server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader is provided by the server"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Account"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Revisar cambios"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Actualizar"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Actualizando..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Se necesita una actualización para jugar a {name}. Por favor actualiza a la versión más reciente para iniciar el juego."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Hay una actualización disponible"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "¿Qué regla rompe esta instancia?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inapropiado"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Malicioso"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Reportar instancia compartida"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar con la instancia"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Proporcionado por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Solo se pueden añadir mods que sean del lado del cliente a la instancia del servidor"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "La versión del juego es proporcionada por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "El loader es proporcionado por el servidor"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Cuenta"
|
||||
},
|
||||
|
||||
@@ -878,7 +878,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar con la instancia"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Proporcionado por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Solo se pueden añadir a la instancia del servidor modificaciones del lado del cliente"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "La versión del juego es proporcionada por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader proporcionado por el servidor"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,7 +959,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synkronoi instanssin kanssa"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Palvelimen tarjoama"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Voit lisätä vain paikallisia modeja palvelin instanssiin"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Peliversio on palvelimen tarjoama"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Modialusta on palvelimen tarjoama"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +659,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Maki-sync sa instansiya"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Handog ng server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Mga mod sa panig ng client lamang ang maidadaragdag sa instansiyang server"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Ang bersiyon ng laro ay handog ng server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Ang loader ay handog na ng server"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Examiner les modifications"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Télécharger la mise à jour"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Examination..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Une mise à jour est nécessaire pour jouer à {name}. Veuillez mettre à jour vers la dernière version pour lancer le jeu."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Une mise à jour est disponible"
|
||||
},
|
||||
"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é."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Quelle règle cette instance enfreint-elle ?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inapproprié"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Malicieux"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Signaler une instance partagée"
|
||||
},
|
||||
@@ -1901,9 +1919,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchroniser avec l'instance"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Fournis par le serveur"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Seuls les mods client peuvent être ajoutés à l'instance du serveur"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Version du jeu est procurée par le serveur"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Le loader est procuré par le serveur"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Compte"
|
||||
},
|
||||
|
||||
@@ -533,7 +533,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "סנכרן עם התקנה"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "מסופק על ידי השרת"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "ניתן להוסיף רק מודים בצד הלקוח לשרת"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "גרסת המשחק מסופקת על ידי השרת"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "הטוען מסופק על ידי השרת"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Változtatások áttekintése"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Frissítés áttekintése"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Áttekintés..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Egy frssítés szükséges a(z) {name} példányhoz. Kérjük, frissítsd a játékot a legújabb verzióra az indításhoz."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Frissítés érhető el"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Melyik szabályt sérti ez az adott példány?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Nem megfelelő"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Káros"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Megosztott példány jelentése"
|
||||
},
|
||||
@@ -1889,9 +1907,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Szinkronizálás a példánnyal"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "A szerver által van megadva"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Szerverpéldányokhoz csak szerveroldali modok adhatók hozzá"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "A játékverzió a szerver által van megadva"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "A betöltőt az adott szerver biztosítja"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Fiók"
|
||||
},
|
||||
|
||||
@@ -821,7 +821,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sinkronkan dengan instans"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Disediakan oleh server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Anda hanya dapat menambahkan mod sisi klien pada instans server"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Versi permainan disediakan oleh server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Pemuat disediakan oleh server"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Revisiona modifiche"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Revisiona aggiornamento"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Revisione..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "{name} richiede degli aggiornamenti. Installa l'ultima versione per poter giocare."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Aggiornamento disponibile"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Tutti i dati della tua istanza verranno eliminati permanentemente, inclusi i tuoi mondi, configurazioni e tutti i contenuti installati."
|
||||
},
|
||||
@@ -692,6 +701,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Quale regola è violata da quest'istanza?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inappropriato"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Dannoso"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Segnala istanza condivisa"
|
||||
},
|
||||
@@ -1901,9 +1919,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizza con l'istanza"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Determinato dal server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Solo mod lato client possono essere aggiunte all'istanza del server"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "La versione del gioco è determinata dal server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Il loader è determinato dal server"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Account"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "変更点を確認"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "アップデート内容を確認"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "審査中…"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "{name}をプレイするにはアップデートが必要です。ゲームを起動するには最新バージョンにアップデートしてください。"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "アップデートが利用可能です"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "インスタンス内のすべてのデータは、ワールド、設定、インストール済みのコンテンツを含め、完全に削除されます。"
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "この起動構成はどの規則に違反していますか?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "不適切なコンテンツ"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "悪意がある"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "スパム"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "共有された起動構成を通報する"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "インスタンスと同期"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "サーバーによる条件"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "サーバーインスタンスにはクライアント側Modのみ追加可能"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "ゲームバージョンはサーバーによる条件です"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "ローダーはサーバーによる条件です"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "アカウント"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Review changes"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "업데이트 검토"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "검토중..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "{name} 플레이를 위해 업데이트가 필요합니다. 게임을 실행하려면 최신 버전 업데이트를 진행하세요."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "업데이트가 있습니다"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "인스턴스의 모든 데이터가 삭제됩니다. 세계 폴더, 설정 폴더 등 설치된 모든 컨텐츠가 포함됩니다."
|
||||
},
|
||||
@@ -692,6 +701,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "이 인스턴스가 위반한 규칙은 무엇인가요?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "부적절함"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "악성/유해함"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "스팸"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "공유 인스턴스 신고"
|
||||
},
|
||||
@@ -1877,9 +1895,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "인스턴스와 동기화"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "서버에 의해 관리됨"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "서버 인스턴스에는 클라이언트 전용 모드만 추가할 수 있습니다"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "게임 버전이 서버에 의해 제공됩니다"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "로더가 서버에 의해 제공됩니다"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "계정"
|
||||
},
|
||||
|
||||
@@ -245,6 +245,9 @@
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, other {# mod}}"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Pemasangan yang dikongsi"
|
||||
},
|
||||
@@ -815,7 +818,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Selaraskan dengan pemasangan"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Disediakan oleh pelayan"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Hanya mod bahagian pelanggan sahaja yang boleh ditambah pada pemasangan pelayan"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Versi permainan adalah disediakan oleh pelayan"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Pemuat adalah disediakan oleh pelayan"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Wijzigingen bekijken"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Update bekijken"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Controleren..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Er is een update nodig om {name} te kunnen spelen. Werk het spel bij naar de nieuwste versie om het te kunnen starten."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Update is beschikbaar"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Alle gegevens van je instantie worden definitief verwijderd, inclusief je werelden, configuraties en alle geïnstalleerde inhoud."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Welke regel wordt in deze instantie overtreden?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Ongepast"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Kwaadaardig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Gedeelde instantie melden"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchroniseer installatie"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Aangeleverd door de server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Alleen client-side mods kunnen toegevoegd worden aan de server instantie"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spel versie is gegeven door de server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader is gegeven door de server"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Account"
|
||||
},
|
||||
|
||||
@@ -371,9 +371,15 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Sprawdź zmiany"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Sprawdź aktualizację"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Trwa przegląd..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Dostępna aktualizacja"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Wszystkie dane z Twojej instancji zostaną trwale usunięte, w tym Twoje światy, pliki konfiguracji i jakakolwiek dodana zawartość."
|
||||
},
|
||||
@@ -662,6 +668,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Którą zasadę narusza ta instancja?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Treści nieodpowiednie"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Zawiera Złośliwy kod"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Zgłoś przesłaną Instalacje"
|
||||
},
|
||||
@@ -1688,9 +1703,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchronizuj z instancją"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Dostarczone przez serwer"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Tylko mody po stronie klienta mogą być dodane do instancji serwera"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Wersja gry jest dostarczona przez serwer"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader jest dostarczony przez serwer"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Konto"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Revisar alterações"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Revisar atualização"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Revisando..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Uma atualização é necessária para jogar {name}. Atualize para a versão mais recente para iniciar o jogo."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Uma atualização está disponível"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Qual regra esta instância viola?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inapropriado"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Malicioso"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Denunciar instância compartilhada"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar com a instância"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Fornecido pelo servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Somente mods do cliente podem ser adicionados à instância do servidor"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "A versão do jogo é fornecida pelo servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "O loader é fornecido pelo servidor"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Conta"
|
||||
},
|
||||
|
||||
@@ -872,7 +872,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar com a instância"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Fornecido pelo servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Apenas mods no cliente podem ser adicionados à instância de servidor"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Versão do jogo é fornecida pelo servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Carregador é fornecido pelo servidor"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +506,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizează cu instanța"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Furnizat de server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Numai modurile de pe partea clientului pot fi adăugate la instanța serverului"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Versiunea jocului este furnizată de server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Încărcătorul este furnizat de server"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Проверьте изменения"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Обзор обновления"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Проверка..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Необходимо обновление для игры в {name}. Обновитесь до последней версии и запустите игру."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Доступно обновление"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Все данные сборки будут удалены навсегда, в том числе миры, настройки и весь установленный контент."
|
||||
},
|
||||
@@ -686,6 +695,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Какое правило нарушает данная сборка?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Неприемлемое содержимое"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Вредоносное содержимое"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Спам"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Сообщить об общем экземпляре"
|
||||
},
|
||||
@@ -1892,9 +1910,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Использовать из сборки"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Управляется сервером"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "В серверной сборке доступны только клиентские моды"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Версия игры управляется сервером"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Загрузчик управляется сервером"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Аккаунт"
|
||||
},
|
||||
|
||||
@@ -1100,7 +1100,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sinhroniziraj sa instancom"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Obezbeđeno od servera"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Samo modovi za kliente mogu biti dodani na instancu servera"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Verzije igre je obezbeđena od servera"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Učitavač je obezbeđen od servera"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,9 +359,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Granskar ändringar"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Granska uppdatering"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Granskar..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "En uppdatering krävs för att spela {name}. Vänligen uppdatera till den senaste verisoner för att köra spelet."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "En uppdatering finns tillgänglig"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "All data från din instans kommer permanent raderas, däribland dina världar, konfigurationer samt allt installerat innehåll."
|
||||
},
|
||||
@@ -674,6 +683,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Vilken regel bryter instansen mot?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Olämplig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Skadlig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Rapportera delad instans"
|
||||
},
|
||||
@@ -1700,9 +1718,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synkronisera med instansen"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Tillhandahållet av servern"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Endast klient-sido moddar kan läggas till i serverinstansen"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spelversion tillhandahålls av servern"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader tillhandahålls av servern"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Konto"
|
||||
},
|
||||
|
||||
@@ -785,7 +785,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "เชื่อมต่อกับโปรแกรม"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "เซิร์ฟเวอร์เป็นผู้กำหนด"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "มีเพียงม็อดสำหรับฝั่งเครื่องของผู้เล่นเท่านั้นที่สามารถเพิ่มลงในโปรแกรมเซิร์ฟเวอร์ดังกล่าวได้"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "เวอร์ชันเกมถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "ตัวรันถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Değişimleri inceleniyor"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Güncellemeyi gözden geçir"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "İnceleniyor..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "{name}'i oynamak için bir güncelleme gerekli. Oyunu açabilmek için lütfen son sürüme güncelleyin."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Bir güncelleme mevcut"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Dünyalarınız, yapılandırmalarınız ve yüklü tüm içerikler ve tüm veriler kalıcı olarak silinecek."
|
||||
},
|
||||
@@ -656,6 +665,15 @@
|
||||
"app.modal.install-to-play.report-image-too-large": {
|
||||
"message": "Dosya 1 MiB boyut sınırını aşıyor"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Uygunsuz"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Kötü niyetli"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-submitted": {
|
||||
"message": "Rapor sunuldu"
|
||||
},
|
||||
@@ -1490,9 +1508,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Kurulum ile eşitle"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Sunucu tarafından sağlanmıştır"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Sunucu örneğine yalnızca istemci tarafında çalışan modlar eklenebilir"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Oyun sürümü sunucu tarafından sağlanıyor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Yükleyici sunucu tarafından sağlanıyor"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Hesap"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Огляд змін"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Переглянути оновлення"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Оглядаємо…"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Для гри в {name} потрібне оновлення. Будь ласка, оновіть до найновішої версії, щоб запустити гру."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Доступне оновлення"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Усі дані вашого профілю будуть видалені назавжди, включно з вашими світами, налаштуваннями та всім установленим умістом."
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Яке правило порушує цей профіль?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Неприпустимий уміст"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Шкідливий уміст"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Спам"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Скарга на спільний профіль"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Синхронізувати з профілем"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Надано сервером"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "До профілю сервера можна додавати лише клієнтські моди"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Версія гри надана сервером"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Завантажувач наданий сервером"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Обліковий запис"
|
||||
},
|
||||
|
||||
@@ -998,7 +998,16 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Đồng bộ với phiên bản"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Do máy chủ cung cấp"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Chỉ các mod chạy phía client mới có thể thêm hồ sơ máy chủ"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Phiên bản trò chơi được cung cấp bởi máy chủ"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader được cung cấp bởi máy chủ"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "查看变更"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "查看更新"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "正在审查……"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "{name}需要更新。请更新到最新版本以启动游戏。"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "有更新可用"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "你实例的所有数据将被永久删除,包括你的世界、配置和所有已安装的内容。"
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "该实例违反了哪项规则?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "不当内容"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "恶意内容"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "垃圾内容"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "举报共享实例"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "与实例同步"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "由该服务器提供"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "只能将客户端模组添加到服务器实例中"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "游戏版本由服务器提供"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "加载器由服务器提供"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "账户"
|
||||
},
|
||||
|
||||
@@ -377,9 +377,18 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "檢視變更"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "檢視更新"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "正在檢視..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "需要更新才能遊玩「{name}」。請更新至最新版本以啟動遊戲。"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "有可用的更新"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "你實例中的所有資料將被永久刪除,包含你的世界、設定檔以及所有已安裝的內容。"
|
||||
},
|
||||
@@ -695,6 +704,15 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "這個實例違反了哪一條規則?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "不當內容"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "惡意內容"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "垃圾內容"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "檢舉共用實例"
|
||||
},
|
||||
@@ -1904,9 +1922,18 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "與實例同步"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "由伺服器提供"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "只有用戶端模組可以被加到伺服器實例"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "遊戲版本由伺服器提供"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "載入器由伺服器提供"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "帳號"
|
||||
},
|
||||
|
||||
@@ -399,10 +399,9 @@ const hideInstalledModpacks = computed({
|
||||
const instanceFilters = computed(() => {
|
||||
const filters = []
|
||||
|
||||
if (instance.value && projectType.value !== 'resourcepack') {
|
||||
const isVanillaShader = projectType.value === 'shader' && instance.value.loader === 'vanilla'
|
||||
if (instance.value) {
|
||||
const gameVersion = instance.value.game_version
|
||||
if (gameVersion && !isVanillaShader) {
|
||||
if (gameVersion) {
|
||||
filters.push({ type: 'game_version', option: gameVersion })
|
||||
}
|
||||
|
||||
@@ -412,9 +411,6 @@ const instanceFilters = computed(() => {
|
||||
if (platform && projectType.value === 'mod' && supportedModLoaders.includes(platform)) {
|
||||
filters.push({ type: 'mod_loader', option: platform })
|
||||
}
|
||||
if (isVanillaShader) {
|
||||
filters.push({ type: 'shader_loader', option: 'vanilla' })
|
||||
}
|
||||
|
||||
if (isServerInstance.value) {
|
||||
filters.push({ type: 'environment', option: 'client' })
|
||||
@@ -554,6 +550,10 @@ const messages = defineMessages({
|
||||
id: 'search.filter.locked.instance-game-version.title',
|
||||
defaultMessage: 'Game version is provided by the instance',
|
||||
},
|
||||
gameVersionProvidedByServer: {
|
||||
id: 'search.filter.locked.server-game-version.title',
|
||||
defaultMessage: 'Game version is provided by the server',
|
||||
},
|
||||
hideAddedServers: {
|
||||
id: 'app.browse.hide-added-servers',
|
||||
defaultMessage: 'Hide servers already added',
|
||||
@@ -573,7 +573,7 @@ const messages = defineMessages({
|
||||
serverInstanceContentWarning: {
|
||||
id: 'app.browse.server-instance-content-warning',
|
||||
defaultMessage:
|
||||
'Adding content may prevent you from joining this server. Any content you add will be removed when the managed server content is updated.',
|
||||
'Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content.',
|
||||
},
|
||||
modLoaderProvidedByInstance: {
|
||||
id: 'search.filter.locked.instance-loader.title',
|
||||
@@ -583,10 +583,18 @@ const messages = defineMessages({
|
||||
id: 'app.browse.project-type.modpacks',
|
||||
defaultMessage: 'Modpacks',
|
||||
},
|
||||
modLoaderProvidedByServer: {
|
||||
id: 'search.filter.locked.server-loader.title',
|
||||
defaultMessage: 'Loader is provided by the server',
|
||||
},
|
||||
providedByInstance: {
|
||||
id: 'search.filter.locked.instance',
|
||||
defaultMessage: 'Provided by the instance',
|
||||
},
|
||||
providedByServer: {
|
||||
id: 'search.filter.locked.server',
|
||||
defaultMessage: 'Provided by the server',
|
||||
},
|
||||
syncFilterButton: {
|
||||
id: 'search.filter.locked.instance.sync',
|
||||
defaultMessage: 'Sync with instance',
|
||||
@@ -735,7 +743,7 @@ const installContext = computed(() => {
|
||||
isFromWorlds.value ? messages.addServersToInstance : commonMessages.installingContentLabel,
|
||||
),
|
||||
warning:
|
||||
isServerInstance.value && instance.value.loader !== 'vanilla' && !isFromWorlds.value
|
||||
isServerInstance.value && !isFromWorlds.value
|
||||
? formatMessage(messages.serverInstanceContentWarning)
|
||||
: undefined,
|
||||
}
|
||||
@@ -1098,12 +1106,24 @@ async function search(requestParams: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const isServerFilterContext = computed(() => isServerContext.value || isServerInstance.value)
|
||||
|
||||
const lockedFilterMessages = computed(() => ({
|
||||
gameVersion: formatMessage(messages.gameVersionProvidedByInstance),
|
||||
modLoader: formatMessage(messages.modLoaderProvidedByInstance),
|
||||
gameVersion: formatMessage(
|
||||
isServerFilterContext.value
|
||||
? messages.gameVersionProvidedByServer
|
||||
: messages.gameVersionProvidedByInstance,
|
||||
),
|
||||
modLoader: formatMessage(
|
||||
isServerFilterContext.value
|
||||
? messages.modLoaderProvidedByServer
|
||||
: messages.modLoaderProvidedByInstance,
|
||||
),
|
||||
environment: formatMessage(messages.environmentProvidedByServer),
|
||||
syncButton: formatMessage(messages.syncFilterButton),
|
||||
providedBy: formatMessage(messages.providedByInstance),
|
||||
providedBy: formatMessage(
|
||||
isServerFilterContext.value ? messages.providedByServer : messages.providedByInstance,
|
||||
),
|
||||
}))
|
||||
|
||||
const searchState = useBrowseSearch({
|
||||
@@ -1222,25 +1242,11 @@ const advancedFiltersCollapsed = computed({
|
||||
},
|
||||
})
|
||||
|
||||
const dismissedPhotosensitivityFilterWarning = computed({
|
||||
get: () => themeStore.getFeatureFlag('dismissed_photosensitivity_filter_warning'),
|
||||
set: (value) => {
|
||||
themeStore.featureFlags['dismissed_photosensitivity_filter_warning'] = value
|
||||
getSettings()
|
||||
.then((settings) => {
|
||||
settings.feature_flags['dismissed_photosensitivity_filter_warning'] = value
|
||||
return setSettings(settings)
|
||||
})
|
||||
.catch(handleError)
|
||||
},
|
||||
})
|
||||
|
||||
provideBrowseManager({
|
||||
tags,
|
||||
projectType,
|
||||
...searchState,
|
||||
advancedFiltersCollapsed,
|
||||
dismissedPhotosensitivityFilterWarning,
|
||||
getProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) => ({
|
||||
path: `/project/${result.project_id ?? result.slug}`,
|
||||
query: getProjectBrowseQuery(),
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
:instance="instance"
|
||||
@published="emit('published')"
|
||||
/>
|
||||
<InstanceAdmonitionsSharedInstanceUpdateAvailable
|
||||
v-else-if="item.kind === 'shared-instance-update-available'"
|
||||
:instance-name="instance.name"
|
||||
@review="emit('review-update', $event)"
|
||||
/>
|
||||
<InstanceAdmonitionsSharedInstanceWrongAccount
|
||||
v-else-if="item.kind === 'shared-instance-wrong-account'"
|
||||
:expected-user-id="sharedInstanceExpectedUserId"
|
||||
@@ -33,6 +38,7 @@ import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
import InstanceAdmonitionsSharedInstanceStale from './shared-instance-stale.vue'
|
||||
import InstanceAdmonitionsSharedInstanceUnavailable from './shared-instance-unavailable.vue'
|
||||
import InstanceAdmonitionsSharedInstanceUpdateAvailable from './shared-instance-update-available.vue'
|
||||
import InstanceAdmonitionsSharedInstanceWrongAccount from './shared-instance-wrong-account.vue'
|
||||
import type { InstanceAdmonitionItem, SharedInstanceRole } from './types.ts'
|
||||
|
||||
@@ -48,11 +54,13 @@ const props = defineProps<{
|
||||
sharedInstanceExpectedUserId?: string | null
|
||||
sharedInstanceRole?: SharedInstanceRole | null
|
||||
sharedInstanceSignedOut?: boolean
|
||||
sharedInstanceUpdateAvailable?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
published: []
|
||||
delete: []
|
||||
'review-update': [event: MouseEvent]
|
||||
}>()
|
||||
|
||||
const sharedInstanceWrongAccount = computed(() => props.sharedInstanceWrongAccount ?? false)
|
||||
@@ -68,6 +76,15 @@ const showSharedInstancePublishAdmonition = computed(
|
||||
props.instance.shared_instance?.role === 'owner' &&
|
||||
props.instance.shared_instance.status === 'stale',
|
||||
)
|
||||
const showSharedInstanceUpdateAdmonition = computed(
|
||||
() =>
|
||||
!sharedInstanceWrongAccount.value &&
|
||||
!displayedSharedInstanceUnavailableReason.value &&
|
||||
props.instance.install_stage === 'installed' &&
|
||||
props.sharedInstanceRole === 'member' &&
|
||||
props.sharedInstanceUpdateAvailable === true,
|
||||
)
|
||||
|
||||
const stackItems = computed<InstanceAdmonitionItem[]>(() => {
|
||||
const items: InstanceAdmonitionItem[] = []
|
||||
|
||||
@@ -103,6 +120,15 @@ const stackItems = computed<InstanceAdmonitionItem[]>(() => {
|
||||
})
|
||||
}
|
||||
|
||||
if (showSharedInstanceUpdateAdmonition.value) {
|
||||
items.push({
|
||||
id: 'shared-instance-update-available',
|
||||
type: 'info',
|
||||
dismissible: false,
|
||||
kind: 'shared-instance-update-available',
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,19 @@ export const instanceAdmonitionsMessages = defineMessages({
|
||||
id: 'app.instance.admonitions.shared-instance.reviewing-button',
|
||||
defaultMessage: 'Reviewing...',
|
||||
},
|
||||
sharedInstanceUpdateAvailableHeader: {
|
||||
id: 'app.instance.admonitions.shared-instance.update-available-header',
|
||||
defaultMessage: 'An update is available',
|
||||
},
|
||||
sharedInstanceUpdateAvailableBody: {
|
||||
id: 'app.instance.admonitions.shared-instance.update-available-body',
|
||||
defaultMessage:
|
||||
'An update is required to play {name}. Please update to latest version to launch the game.',
|
||||
},
|
||||
sharedInstanceReviewUpdateButton: {
|
||||
id: 'app.instance.admonitions.shared-instance.review-update-button',
|
||||
defaultMessage: 'Review update',
|
||||
},
|
||||
sharedInstanceReviewHeader: {
|
||||
id: 'app.instance.admonitions.shared-instance.review-header',
|
||||
defaultMessage: 'Review changes',
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<Admonition
|
||||
type="info"
|
||||
inline-actions
|
||||
:header="formatMessage(messages.sharedInstanceUpdateAvailableHeader)"
|
||||
>
|
||||
{{ formatMessage(messages.sharedInstanceUpdateAvailableBody, { name: instanceName }) }}
|
||||
<template #actions>
|
||||
<Button type="colored" color="blue" size="lg" @click="emit('review', $event)">
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.sharedInstanceReviewUpdateButton) }}
|
||||
</Button>
|
||||
</template>
|
||||
</Admonition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon } from '@modrinth/assets'
|
||||
import { Admonition, Button, useVIntl } from '@modrinth/ui'
|
||||
|
||||
import { instanceAdmonitionsMessages as messages } from './messages'
|
||||
|
||||
defineProps<{
|
||||
instanceName: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
review: [event: MouseEvent]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
</script>
|
||||
@@ -2,6 +2,7 @@ import type { StackedAdmonitionItem } from '@modrinth/ui'
|
||||
|
||||
export type InstanceAdmonitionKind =
|
||||
| 'shared-instance-stale'
|
||||
| 'shared-instance-update-available'
|
||||
| 'shared-instance-unavailable'
|
||||
| 'shared-instance-wrong-account'
|
||||
|
||||
|
||||
@@ -25,39 +25,60 @@
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<InstanceHeaderServerMetadata
|
||||
v-if="isServerInstance"
|
||||
:loading-server-ping="loadingServerPing"
|
||||
:players-online="playersOnline"
|
||||
:status-online="statusOnline"
|
||||
:ping="ping"
|
||||
:minecraft-server="minecraftServer"
|
||||
:show-instance-play-time="showInstancePlayTime"
|
||||
:playtime-label="playtimeLabel"
|
||||
/>
|
||||
<div v-if="isServerInstance" class="flex flex-wrap items-center gap-2">
|
||||
<InstanceHeaderServerMetadata
|
||||
:loading-server-ping="loadingServerPing"
|
||||
:players-online="playersOnline"
|
||||
:status-online="statusOnline"
|
||||
:recent-plays="recentPlays"
|
||||
:ping="ping"
|
||||
:minecraft-server="minecraftServer"
|
||||
:linked-project-v3="linkedProjectV3"
|
||||
:instance-id="instance.id"
|
||||
/>
|
||||
<PageHeaderMetadataItem v-if="sharedInstanceManager" :action="sharedInstanceManagerAction">
|
||||
{{ sharedInstanceManagerLabel }}
|
||||
<Avatar
|
||||
:src="sharedInstanceManager.avatarUrl"
|
||||
:alt="sharedInstanceManager.name"
|
||||
:tint-by="sharedInstanceManager.tintBy"
|
||||
size="24px"
|
||||
:circle="sharedInstanceManager.type === 'user'"
|
||||
no-shadow
|
||||
/>
|
||||
<span class="min-w-0 truncate">{{ sharedInstanceManager.name }}</span>
|
||||
</PageHeaderMetadataItem>
|
||||
</div>
|
||||
<PageHeaderMetadata v-else>
|
||||
<PageHeaderMetadataItem :icon="Gamepad2Icon" tooltip="Minecraft version">
|
||||
Minecraft {{ instance.game_version }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="sharedInstanceManager?.type !== 'user'"
|
||||
:icon="TagIcon"
|
||||
:icon-props="{ tag: loaderDisplayName, enforceType: 'loader' }"
|
||||
tooltip="Mod loader and Minecraft version"
|
||||
tooltip="Mod loader"
|
||||
>
|
||||
{{ loaderLabel }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="showInstancePlayTime && playtimeLabel"
|
||||
v-if="showInstancePlayTime"
|
||||
:icon="TimerIcon"
|
||||
tooltip="Total playtime"
|
||||
>
|
||||
{{ playtimeLabel }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataTimeItem
|
||||
v-if="instance.last_played"
|
||||
:icon="ClockIcon"
|
||||
:date="instance.last_played"
|
||||
:label="formatMessage(messages.lastPlayed)"
|
||||
/>
|
||||
<PageHeaderMetadataItem v-else :icon="ClockIcon" tooltip="Last played">
|
||||
{{ formatMessage(messages.neverPlayed) }}
|
||||
<PageHeaderMetadataItem v-if="sharedInstanceManager" :action="sharedInstanceManagerAction">
|
||||
{{ sharedInstanceManagerLabel }}
|
||||
<Avatar
|
||||
:src="sharedInstanceManager.avatarUrl"
|
||||
:alt="sharedInstanceManager.name"
|
||||
:tint-by="sharedInstanceManager.tintBy"
|
||||
size="24px"
|
||||
:circle="sharedInstanceManager.type === 'user'"
|
||||
no-shadow
|
||||
/>
|
||||
<span class="min-w-0 truncate">{{ sharedInstanceManager.name }}</span>
|
||||
</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
@@ -164,7 +185,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
ClockIcon,
|
||||
DownloadIcon,
|
||||
ExternalIcon,
|
||||
FolderOpenIcon,
|
||||
@@ -175,6 +195,7 @@ import {
|
||||
ReportIcon,
|
||||
SettingsIcon,
|
||||
StopCircleIcon,
|
||||
TagCategoryGamepad2Icon as Gamepad2Icon,
|
||||
TimerIcon,
|
||||
UnknownIcon,
|
||||
} from '@modrinth/assets'
|
||||
@@ -190,12 +211,12 @@ import {
|
||||
PageHeaderBadgeItem,
|
||||
PageHeaderMetadata,
|
||||
PageHeaderMetadataItem,
|
||||
PageHeaderMetadataTimeItem,
|
||||
type ServerLoader,
|
||||
TagIcon,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
@@ -226,10 +247,6 @@ const messages = defineMessages({
|
||||
id: 'instance.playtime.never-played',
|
||||
defaultMessage: 'Never played',
|
||||
},
|
||||
lastPlayed: {
|
||||
id: 'instance.last-played',
|
||||
defaultMessage: 'Last played',
|
||||
},
|
||||
openFolder: {
|
||||
id: 'instance.action.open-folder',
|
||||
defaultMessage: 'Open folder',
|
||||
@@ -259,6 +276,7 @@ const messages = defineMessages({
|
||||
defaultMessage: "This instance's content is being shared to other users.",
|
||||
},
|
||||
})
|
||||
const router = useRouter()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -273,8 +291,16 @@ const props = withDefaults(
|
||||
loadingServerPing?: boolean
|
||||
playersOnline?: number
|
||||
statusOnline?: boolean
|
||||
recentPlays?: number
|
||||
ping?: number
|
||||
minecraftServer?: Labrinth.Projects.v3.Project['minecraft_server']
|
||||
linkedProjectV3?: Labrinth.Projects.v3.Project
|
||||
sharedInstanceManager?: {
|
||||
type: 'user' | 'server'
|
||||
name: string
|
||||
avatarUrl?: string
|
||||
tintBy: string
|
||||
} | null
|
||||
}>(),
|
||||
{
|
||||
iconSrc: null,
|
||||
@@ -287,8 +313,11 @@ const props = withDefaults(
|
||||
loadingServerPing: false,
|
||||
playersOnline: undefined,
|
||||
statusOnline: false,
|
||||
recentPlays: undefined,
|
||||
ping: undefined,
|
||||
minecraftServer: undefined,
|
||||
linkedProjectV3: undefined,
|
||||
sharedInstanceManager: null,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -317,7 +346,7 @@ const { formatMessage } = useVIntl()
|
||||
const isInstalling = computed(() => installingStages.includes(props.instance.install_stage))
|
||||
const loaderDisplayName = computed(() => formatLoaderLabel(props.instance.loader) as ServerLoader)
|
||||
const loaderLabel = computed(() =>
|
||||
[loaderDisplayName.value, props.instance.game_version].filter(Boolean).join(' '),
|
||||
[loaderDisplayName.value, props.instance.loader_version].filter(Boolean).join(' '),
|
||||
)
|
||||
const sharedInstanceTooltip = computed(() =>
|
||||
formatMessage(
|
||||
@@ -326,23 +355,29 @@ const sharedInstanceTooltip = computed(() =>
|
||||
: messages.sharedInstanceTooltip,
|
||||
),
|
||||
)
|
||||
const sharedInstanceManagerLabel = computed(() =>
|
||||
props.sharedInstanceManager?.type === 'server' ? 'Linked to' : 'Managed by',
|
||||
)
|
||||
const sharedInstanceManagerAction = computed(() => {
|
||||
const manager = props.sharedInstanceManager
|
||||
if (manager?.type !== 'user') return undefined
|
||||
return () => router.push(`/user/${encodeURIComponent(manager.name)}`)
|
||||
})
|
||||
const playtimeLabel = computed(() => {
|
||||
const seconds = Math.floor(props.timePlayed)
|
||||
if (seconds <= 0) {
|
||||
return undefined
|
||||
}
|
||||
if (props.timePlayed <= 0) return formatMessage(messages.neverPlayed)
|
||||
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const hours = Math.floor(props.timePlayed / 3600)
|
||||
if (hours >= 1) {
|
||||
return `${hours} hour${hours > 1 ? 's' : ''}`
|
||||
}
|
||||
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const minutes = Math.floor(props.timePlayed / 60)
|
||||
if (minutes >= 1) {
|
||||
return `${minutes} minute${minutes > 1 ? 's' : ''}`
|
||||
}
|
||||
|
||||
return `${seconds} second${seconds === 1 ? '' : 's'}`
|
||||
const seconds = Math.floor(props.timePlayed)
|
||||
return `${seconds} second${seconds > 1 ? 's' : ''}`
|
||||
})
|
||||
const serverPlayOptions = computed<OverflowMenuOption[]>(() => [
|
||||
{
|
||||
|
||||
+42
-25
@@ -1,36 +1,52 @@
|
||||
<template>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem v-if="loadingServerPing && playersOnline !== undefined">
|
||||
<ServerOnlinePlayers :online="playersOnline" :status-online="statusOnline" hide-label />
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="minecraftServer?.region || (loadingServerPing && ping !== undefined)"
|
||||
>
|
||||
<ServerRegion v-if="minecraftServer?.region" :region="minecraftServer.region" />
|
||||
<ServerPing
|
||||
v-if="loadingServerPing && ping !== undefined"
|
||||
:ping="ping"
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<template v-if="loadingServerPing">
|
||||
<ServerOnlinePlayers
|
||||
v-if="playersOnline !== undefined"
|
||||
:online="playersOnline"
|
||||
:status-online="statusOnline"
|
||||
hide-label
|
||||
/>
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="showInstancePlayTime && playtimeLabel"
|
||||
:icon="TimerIcon"
|
||||
tooltip="Total playtime"
|
||||
>
|
||||
{{ playtimeLabel }}
|
||||
</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
<ServerRecentPlays :recent-plays="recentPlays ?? 0" hide-label />
|
||||
<div
|
||||
v-if="
|
||||
(playersOnline !== undefined || recentPlays !== undefined) &&
|
||||
(minecraftServer?.region || ping)
|
||||
"
|
||||
class="w-1.5 h-1.5 rounded-full bg-surface-5"
|
||||
></div>
|
||||
<ServerPing v-if="ping" :ping="ping" />
|
||||
</template>
|
||||
|
||||
<ServerRegion v-if="minecraftServer?.region" :region="minecraftServer?.region" />
|
||||
|
||||
<div v-if="minecraftServer?.region || ping" class="w-1.5 h-1.5 rounded-full bg-surface-5"></div>
|
||||
|
||||
<div v-if="linkedProjectV3" class="flex gap-1.5 items-center font-medium text-primary">
|
||||
Linked to
|
||||
<Avatar
|
||||
:src="linkedProjectV3.icon_url"
|
||||
:alt="linkedProjectV3.name"
|
||||
:tint-by="instanceId"
|
||||
size="24px"
|
||||
/>
|
||||
<router-link
|
||||
:to="`/project/${linkedProjectV3.slug ?? linkedProjectV3.id}`"
|
||||
class="hover:underline text-primary truncate"
|
||||
>
|
||||
{{ linkedProjectV3.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { TimerIcon } from '@modrinth/assets'
|
||||
import {
|
||||
PageHeaderMetadata,
|
||||
PageHeaderMetadataItem,
|
||||
Avatar,
|
||||
ServerOnlinePlayers,
|
||||
ServerPing,
|
||||
ServerRecentPlays,
|
||||
ServerRegion,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
@@ -38,9 +54,10 @@ defineProps<{
|
||||
loadingServerPing?: boolean
|
||||
playersOnline?: number
|
||||
statusOnline?: boolean
|
||||
recentPlays?: number
|
||||
ping?: number
|
||||
minecraftServer?: Labrinth.Projects.v3.Project['minecraft_server']
|
||||
showInstancePlayTime?: boolean
|
||||
playtimeLabel?: string
|
||||
linkedProjectV3?: Labrinth.Projects.v3.Project
|
||||
instanceId?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
-1
@@ -232,7 +232,6 @@ provideInstallationSettings({
|
||||
() =>
|
||||
isModrinthLinkedModpack.value ||
|
||||
isImportedModpack.value ||
|
||||
instance.value.link?.type === 'server_project' ||
|
||||
isSharedInstanceManagedModpack.value,
|
||||
),
|
||||
isBusy: installationSettingsBusy,
|
||||
|
||||
@@ -15,28 +15,27 @@
|
||||
:share-text="formatMessage(messages.shareText)"
|
||||
:open-in-new-tab="false"
|
||||
/>
|
||||
<ManagedContentModal
|
||||
ref="managedContentModal"
|
||||
:header="managedContentModalHeader"
|
||||
:source-name="managedContent?.card.manager.name"
|
||||
:source-icon-url="managedContent?.card.manager.iconUrl"
|
||||
<ModpackContentModal
|
||||
ref="modpackContentModal"
|
||||
:modpack-name="displayedModpackProject?.title"
|
||||
:modpack-icon-url="displayedModpackProject?.icon_url ?? undefined"
|
||||
:enable-toggle="!isServerInstance && !isSharedMember && !isQuarantined"
|
||||
:action-disabled="isBulkOperating || isInstanceBusy"
|
||||
:busy="isBulkOperating"
|
||||
:get-overflow-options="getOverflowOptions"
|
||||
:switch-version="
|
||||
isServerInstance || isSharedMember || isQuarantined ? undefined : handleSwitchVersion
|
||||
"
|
||||
@update:enabled="handleManagedContentToggle"
|
||||
@bulk:enable="(items) => handleManagedContentBulkToggle(items, true)"
|
||||
@bulk:disable="(items) => handleManagedContentBulkToggle(items, false)"
|
||||
@update:enabled="handleModpackContentToggle"
|
||||
@bulk:enable="(items) => handleModpackContentBulkToggle(items, true)"
|
||||
@bulk:disable="(items) => handleModpackContentBulkToggle(items, false)"
|
||||
/>
|
||||
<ConfirmDisableModal
|
||||
ref="sharedDisableConfirmModal"
|
||||
:count="pendingManagedContentDisableItems.length"
|
||||
:count="pendingModpackDisableItems.length"
|
||||
:item-type="formatMessage(messages.contentTypeProject)"
|
||||
:warning="managedContentPolicy.disableWarning(pendingManagedContentDisableItems)"
|
||||
:warning="managedContentPolicy.disableWarning(pendingModpackDisableItems)"
|
||||
:action-disabled="isInstanceBusy"
|
||||
@disable="confirmPendingManagedContentDisable"
|
||||
@disable="confirmPendingModpackContentDisable"
|
||||
/>
|
||||
<ConfirmModpackUpdateModal
|
||||
ref="modpackUpdateConfirmModal"
|
||||
@@ -89,7 +88,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { ClipboardCopyIcon, FolderOpenIcon, LockIcon, LockOpenIcon } from '@modrinth/assets'
|
||||
import { ClipboardCopyIcon, FolderOpenIcon } from '@modrinth/assets'
|
||||
import {
|
||||
type BulkOperationStatus,
|
||||
commonMessages,
|
||||
@@ -97,20 +96,18 @@ import {
|
||||
ConfirmModpackUpdateModal,
|
||||
ContentCardLayout as ContentPageLayout,
|
||||
type ContentItem,
|
||||
type ContentModpackCardCategory,
|
||||
type ContentModpackCardProject,
|
||||
type ContentModpackCardVersion,
|
||||
type ContentOwner,
|
||||
ContentUpdaterModal,
|
||||
dedupeManagedContentItems,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
type ManagedContentData,
|
||||
ManagedContentModal,
|
||||
type ManagedContentModalState,
|
||||
type ManagedContentProject,
|
||||
type ManagedContentVersion,
|
||||
ModpackContentModal,
|
||||
type ModpackContentModalState,
|
||||
type OverflowMenuOption,
|
||||
provideContentManager,
|
||||
ReadyTransition,
|
||||
summarizeManagedContent,
|
||||
UnknownFileWarningModal,
|
||||
useDebugLogger,
|
||||
useVIntl,
|
||||
@@ -138,10 +135,8 @@ import {
|
||||
add_project_from_path,
|
||||
edit,
|
||||
get_linked_modpack_content,
|
||||
get_shared_instance_publish_preview,
|
||||
is_file_on_modrinth,
|
||||
remove_project,
|
||||
set_project_locked,
|
||||
switch_project_version_with_dependencies,
|
||||
toggle_disable_project,
|
||||
update_all,
|
||||
@@ -157,17 +152,8 @@ import type { FeatureFlag } from '@/store/theme'
|
||||
|
||||
import { injectInstancePage } from '../instance-context'
|
||||
import { instanceContentQueryOptions, instanceKeys } from '../query-options'
|
||||
import { injectSharedInstance } from '../shared-instance-context'
|
||||
|
||||
const messages = defineMessages({
|
||||
modpackContentHeader: {
|
||||
id: 'app.instance.content.managed-content.modpack-header',
|
||||
defaultMessage: 'Modpack content',
|
||||
},
|
||||
sharedContentHeader: {
|
||||
id: 'app.instance.content.managed-content.shared-header',
|
||||
defaultMessage: 'Shared content',
|
||||
},
|
||||
shareTitle: {
|
||||
id: 'app.instance.mods.share-title',
|
||||
defaultMessage: 'Sharing modpack content',
|
||||
@@ -192,14 +178,6 @@ const messages = defineMessages({
|
||||
id: 'app.instance.mods.locked-content',
|
||||
defaultMessage: 'Content in locked instances cannot be changed.',
|
||||
},
|
||||
freezeContent: {
|
||||
id: 'app.instance.mods.freeze-content',
|
||||
defaultMessage: 'Freeze version',
|
||||
},
|
||||
unfreezeContent: {
|
||||
id: 'app.instance.mods.unfreeze-content',
|
||||
defaultMessage: 'Unfreeze version',
|
||||
},
|
||||
contentTypeProject: {
|
||||
id: 'app.instance.mods.content-type-project',
|
||||
defaultMessage: 'project',
|
||||
@@ -218,7 +196,7 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
let savedModalState: ManagedContentModalState | null = null
|
||||
let savedModalState: ModpackContentModalState | null = null
|
||||
|
||||
function contentOwnerLink(owner: ContentOwner): NonNullable<ContentOwner['link']> {
|
||||
if (owner.type === 'user') return `/user/${encodeURIComponent(owner.id)}`
|
||||
@@ -241,7 +219,6 @@ const skipNonEssentialWarnings = computed(() =>
|
||||
)
|
||||
|
||||
const instancePage = injectInstancePage()
|
||||
const sharedInstanceState = injectSharedInstance()
|
||||
const instance = instancePage.instance
|
||||
const isServerInstance = instancePage.isServerInstance
|
||||
const openSettings = () => instancePage.openSettings(1)
|
||||
@@ -306,12 +283,15 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
const linkedModpackProject = ref<ManagedContentProject | null>(null)
|
||||
const linkedModpackVersion = ref<ManagedContentVersion | null>(null)
|
||||
const linkedModpackProject = ref<ContentModpackCardProject | null>(null)
|
||||
const linkedModpackVersion = ref<ContentModpackCardVersion | null>(null)
|
||||
const linkedModpackOwner = ref<ContentOwner | null>(null)
|
||||
const linkedModpackCategories = ref<ContentModpackCardCategory[]>([])
|
||||
const linkedModpackHasUpdate = ref(false)
|
||||
const linkedModpackUpdateVersionId = ref<string | null>(null)
|
||||
const localImportedModpackUnlinked = ref(false)
|
||||
|
||||
const localImportedModpackProject = computed<ManagedContentProject | null>(() => {
|
||||
const localImportedModpackProject = computed<ContentModpackCardProject | null>(() => {
|
||||
const link = instance.value.link
|
||||
if (localImportedModpackUnlinked.value || link?.type !== 'imported_modpack') return null
|
||||
|
||||
@@ -320,6 +300,7 @@ const localImportedModpackProject = computed<ManagedContentProject | null>(() =>
|
||||
slug: link.filename ?? instance.value.id,
|
||||
title: link.name ?? instance.value.name,
|
||||
icon_url: instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : undefined,
|
||||
description: '',
|
||||
filename: link.filename ?? undefined,
|
||||
}
|
||||
})
|
||||
@@ -338,7 +319,6 @@ watch(
|
||||
const isModpackUpdating = ref(false)
|
||||
const isBulkOperating = ref(false)
|
||||
const isInstanceBusy = computed(() => instance.value?.install_stage !== 'installed')
|
||||
const showSharedContentFilter = computed(() => instance.value.shared_instance?.role === 'member')
|
||||
const isPackLocked = computed(
|
||||
() =>
|
||||
instance.value.quarantined ||
|
||||
@@ -349,10 +329,10 @@ const isPackLocked = computed(
|
||||
const shareModal = ref<InstanceType<typeof ShareModalWrapper> | null>()
|
||||
const exportModal = ref(null)
|
||||
const contentUpdaterModal = ref<InstanceType<typeof ContentUpdaterModal> | null>()
|
||||
const managedContentModal = ref<InstanceType<typeof ManagedContentModal> | null>()
|
||||
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal> | null>()
|
||||
const modpackUpdateConfirmModal = ref<InstanceType<typeof ConfirmModpackUpdateModal> | null>()
|
||||
const sharedDisableConfirmModal = ref<InstanceType<typeof ConfirmDisableModal> | null>()
|
||||
const pendingManagedContentDisableItems = ref<ContentItem[]>([])
|
||||
const pendingModpackDisableItems = ref<ContentItem[]>([])
|
||||
const unknownFileWarningModal = ref<InstanceType<typeof UnknownFileWarningModal> | null>()
|
||||
const unknownFileName = ref('')
|
||||
let resolveUnknownFileConfirmation: ((confirmed: boolean) => void) | null = null
|
||||
@@ -369,134 +349,6 @@ const modpackContentQuery = useQuery({
|
||||
),
|
||||
})
|
||||
|
||||
const hasSharedManagedContent = computed(() => {
|
||||
if (instance.value.shared_instance?.role === 'owner') return false
|
||||
|
||||
const linkType = instance.value.link?.type
|
||||
return (
|
||||
!!instance.value.shared_instance ||
|
||||
linkType === 'server_project' ||
|
||||
linkType === 'server_project_modpack'
|
||||
)
|
||||
})
|
||||
|
||||
const managedContentItems = computed(() => {
|
||||
const linkedContent = modpackContentQuery.data.value ?? []
|
||||
const sourcedContent = hasSharedManagedContent.value
|
||||
? projects.value.filter((item) =>
|
||||
['server_project', 'shared_instance'].includes(item.source_kind ?? ''),
|
||||
)
|
||||
: []
|
||||
|
||||
return dedupeManagedContentItems([...linkedContent, ...sourcedContent])
|
||||
})
|
||||
|
||||
const managedContentSummary = computed(() =>
|
||||
modpackContentQuery.isLoading.value && modpackContentQuery.data.value === undefined
|
||||
? undefined
|
||||
: summarizeManagedContent(managedContentItems.value),
|
||||
)
|
||||
|
||||
const managedContent = computed<ManagedContentData | null>(() => {
|
||||
const attachment = instance.value.shared_instance
|
||||
const sharedManager = sharedInstanceState.manager.value
|
||||
const linkedProject = instancePage.linkedProject.value
|
||||
const linkType = instance.value.link?.type
|
||||
const isSharedOwner = attachment?.role === 'owner'
|
||||
|
||||
if (
|
||||
!isSharedOwner &&
|
||||
(attachment || linkType === 'server_project' || linkType === 'server_project_modpack')
|
||||
) {
|
||||
const serverManaged =
|
||||
sharedManager?.type === 'server' ||
|
||||
!!attachment?.server_manager_name ||
|
||||
linkType === 'server_project' ||
|
||||
linkType === 'server_project_modpack' ||
|
||||
(!attachment && isServerInstance.value)
|
||||
const managerName = serverManaged
|
||||
? (sharedManager?.name ??
|
||||
attachment?.server_manager_name ??
|
||||
linkedProject?.name ??
|
||||
instance.value.name)
|
||||
: (sharedManager?.name ?? instance.value.name)
|
||||
const managerIcon = serverManaged
|
||||
? (sharedManager?.avatarUrl ??
|
||||
attachment?.server_manager_icon_url ??
|
||||
linkedProject?.icon_url ??
|
||||
undefined)
|
||||
: (sharedManager?.avatarUrl ??
|
||||
(instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : undefined))
|
||||
const managerLink = serverManaged
|
||||
? linkedProject
|
||||
? {
|
||||
path: `/project/${linkedProject.slug ?? linkedProject.id}`,
|
||||
query: { i: instancePage.instanceId.value },
|
||||
}
|
||||
: undefined
|
||||
: sharedManager?.type === 'user'
|
||||
? `/user/${encodeURIComponent(sharedManager.name)}`
|
||||
: undefined
|
||||
|
||||
return {
|
||||
card: {
|
||||
kind: serverManaged ? 'server' : 'shared-instance',
|
||||
installing: isInstanceBusy.value,
|
||||
manager: {
|
||||
name: managerName,
|
||||
iconUrl: managerIcon,
|
||||
link: managerLink,
|
||||
},
|
||||
summary: managedContentSummary.value,
|
||||
syncedAt: sharedInstanceState.lastUpdateCheckAt.value,
|
||||
updateAvailable: instancePage.sharedInstanceUpdateAvailable.value,
|
||||
},
|
||||
disabled: attachment?.status === 'applying' || isInstanceBusy.value,
|
||||
disabledText: formatMessage(commonMessages.updatingLabel),
|
||||
}
|
||||
}
|
||||
|
||||
const project = displayedModpackProject.value
|
||||
if (!project) return null
|
||||
|
||||
return {
|
||||
card: {
|
||||
kind: 'modpack',
|
||||
installing: isInstanceBusy.value,
|
||||
manager: {
|
||||
name: project.title,
|
||||
iconUrl: project.icon_url ?? undefined,
|
||||
link: linkedModpackProject.value
|
||||
? {
|
||||
path: `/project/${project.slug ?? project.id}`,
|
||||
query: { i: instancePage.instanceId.value },
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
summary: managedContentSummary.value,
|
||||
versionNumber: linkedModpackVersion.value?.version_number,
|
||||
versionLink:
|
||||
linkedModpackProject.value && linkedModpackVersion.value
|
||||
? {
|
||||
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}/version/${linkedModpackVersion.value.id}`,
|
||||
query: { i: instancePage.instanceId.value },
|
||||
}
|
||||
: undefined,
|
||||
updatedAt: linkedModpackVersion.value?.date_published,
|
||||
},
|
||||
disabled: isModpackUpdating.value || isInstanceBusy.value,
|
||||
disabledText: formatMessage(commonMessages.updatingLabel),
|
||||
}
|
||||
})
|
||||
|
||||
const managedContentModalHeader = computed(() =>
|
||||
formatMessage(
|
||||
managedContent.value?.card.kind === 'modpack'
|
||||
? messages.modpackContentHeader
|
||||
: messages.sharedContentHeader,
|
||||
),
|
||||
)
|
||||
|
||||
// TODO: Extract content operation and updater modal state into composables; this page currently owns file mutations, dependency installs, busy flags, and version selection flow.
|
||||
const updatingProject = ref<ContentItem | null>(null)
|
||||
const updatingProjectVersions = ref<Labrinth.Versions.v2.Version[]>([])
|
||||
@@ -566,30 +418,14 @@ function canDeleteContent(item: ContentItem) {
|
||||
return canMutateContent(item)
|
||||
}
|
||||
|
||||
function canToggleContent(item: ContentItem) {
|
||||
return canMutateContent(item)
|
||||
}
|
||||
|
||||
function canChangeContentVersion(item: ContentItem) {
|
||||
return canMutateContent(item) && !item.locked
|
||||
}
|
||||
|
||||
async function reconcileSharedInstancePublishState() {
|
||||
if (instance.value.shared_instance?.role !== 'owner') return
|
||||
|
||||
await get_shared_instance_publish_preview(instance.value.id).catch((error) => {
|
||||
debug('Failed to reconcile shared instance publish state', { error })
|
||||
})
|
||||
}
|
||||
|
||||
function setContentItemBusy(item: ContentItem, busy: boolean, originalFileName = item.file_name) {
|
||||
item.installing = busy
|
||||
managedContentModal.value?.updateItem(originalFileName, {
|
||||
modpackContentModal.value?.updateItem(originalFileName, {
|
||||
installing: busy,
|
||||
disabled: busy,
|
||||
})
|
||||
if (item.file_name !== originalFileName) {
|
||||
managedContentModal.value?.updateItem(item.file_name, {
|
||||
modpackContentModal.value?.updateItem(item.file_name, {
|
||||
installing: busy,
|
||||
disabled: busy,
|
||||
})
|
||||
@@ -791,12 +627,8 @@ async function handleUnknownFileContinue(dontShowAgain: boolean) {
|
||||
resolveUnknownFileWarning(true)
|
||||
}
|
||||
|
||||
async function toggleDisableMod(
|
||||
mod: ContentItem,
|
||||
desiredEnabled?: boolean,
|
||||
reconcileSharedState = true,
|
||||
) {
|
||||
if (!mod.file_path || !canToggleContent(mod)) return
|
||||
async function toggleDisableMod(mod: ContentItem, desiredEnabled?: boolean) {
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
const originalFilePath = mod.file_path
|
||||
@@ -808,7 +640,7 @@ async function toggleDisableMod(
|
||||
mod.file_path = newPath
|
||||
mod.file_name = newFileName
|
||||
mod.enabled = enabled
|
||||
managedContentModal.value?.updateItem(operation.originalFileName, {
|
||||
modpackContentModal.value?.updateItem(operation.originalFileName, {
|
||||
file_path: newPath,
|
||||
file_name: newFileName,
|
||||
enabled,
|
||||
@@ -827,10 +659,6 @@ async function toggleDisableMod(
|
||||
project_type: mod.project_type,
|
||||
disabled: !enabled,
|
||||
})
|
||||
|
||||
if (reconcileSharedState) {
|
||||
await reconcileSharedInstancePublishState()
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
} finally {
|
||||
@@ -841,7 +669,7 @@ async function toggleDisableMod(
|
||||
const toggleDisableDebounced = toggleDisableMod
|
||||
|
||||
async function removeMod(mod: ContentItem) {
|
||||
if (!mod.file_path || !canDeleteContent(mod)) return
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
|
||||
@@ -972,7 +800,7 @@ async function bulkUpdateAllProjects(onProgress?: (status: BulkOperationStatus)
|
||||
}
|
||||
|
||||
async function updateProject(mod: ContentItem) {
|
||||
if (!canUpdateProject(mod) || mod.locked) return
|
||||
if (!canUpdateProject(mod)) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
|
||||
@@ -1001,7 +829,7 @@ async function updateProject(mod: ContentItem) {
|
||||
}
|
||||
|
||||
async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions.v2.Version) {
|
||||
if (!canChangeContentVersion(mod)) return
|
||||
if (!canMutateContent(mod)) return
|
||||
if (!mod.file_path) return
|
||||
const operation = beginContentOperation(mod)
|
||||
if (!operation) return
|
||||
@@ -1028,8 +856,7 @@ async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions
|
||||
|
||||
async function handleUpdate(id: string) {
|
||||
const item = projects.value.find((p) => getContentItemId(p) === id)
|
||||
if (!item || item.locked || !canUpdateProject(item) || !item.project?.id || !item.version?.id)
|
||||
return
|
||||
if (!item || !canUpdateProject(item) || !item.project?.id || !item.version?.id) return
|
||||
|
||||
const requestId = beginUpdateRequest()
|
||||
const itemId = getContentItemId(item)
|
||||
@@ -1139,7 +966,7 @@ async function handleUpdate(id: string) {
|
||||
}
|
||||
|
||||
async function handleSwitchVersion(item: ContentItem) {
|
||||
if (!canChangeContentVersion(item)) return
|
||||
if (!canMutateContent(item)) return
|
||||
if (!item.project?.id || !item.version?.id) return
|
||||
|
||||
const requestId = beginUpdateRequest()
|
||||
@@ -1166,9 +993,9 @@ async function handleSwitchVersion(item: ContentItem) {
|
||||
updatingProjectVersions.value = versions
|
||||
}
|
||||
|
||||
async function handleManagedContentToggle(item: ContentItem, enabled: boolean) {
|
||||
async function handleModpackContentToggle(item: ContentItem, enabled: boolean) {
|
||||
if (!enabled && managedContentPolicy.disableWarning([item])) {
|
||||
pendingManagedContentDisableItems.value = [item]
|
||||
pendingModpackDisableItems.value = [item]
|
||||
sharedDisableConfirmModal.value?.show()
|
||||
return
|
||||
}
|
||||
@@ -1176,48 +1003,47 @@ async function handleManagedContentToggle(item: ContentItem, enabled: boolean) {
|
||||
await toggleDisableDebounced(item, enabled)
|
||||
}
|
||||
|
||||
async function handleManagedContentBulkToggle(items: ContentItem[], enabled: boolean) {
|
||||
async function handleModpackContentBulkToggle(items: ContentItem[], enabled: boolean) {
|
||||
if (!enabled && managedContentPolicy.disableWarning(items)) {
|
||||
pendingManagedContentDisableItems.value = items
|
||||
pendingModpackDisableItems.value = items
|
||||
sharedDisableConfirmModal.value?.show()
|
||||
return
|
||||
}
|
||||
|
||||
await setManagedContentEnabled(items, enabled)
|
||||
await setModpackContentEnabled(items, enabled)
|
||||
}
|
||||
|
||||
async function confirmPendingManagedContentDisable() {
|
||||
const items = [...pendingManagedContentDisableItems.value]
|
||||
pendingManagedContentDisableItems.value = []
|
||||
await setManagedContentEnabled(items, false)
|
||||
async function confirmPendingModpackContentDisable() {
|
||||
const items = [...pendingModpackDisableItems.value]
|
||||
pendingModpackDisableItems.value = []
|
||||
await setModpackContentEnabled(items, false)
|
||||
}
|
||||
|
||||
async function setManagedContentEnabled(items: ContentItem[], enabled: boolean) {
|
||||
await Promise.all(items.map((item) => toggleDisableMod(item, enabled, false)))
|
||||
await reconcileSharedInstancePublishState()
|
||||
async function setModpackContentEnabled(items: ContentItem[], enabled: boolean) {
|
||||
await Promise.all(items.map((item) => toggleDisableMod(item, enabled)))
|
||||
}
|
||||
|
||||
async function handleManagedContent() {
|
||||
async function handleModpackContent() {
|
||||
if (!instance.value?.id) return
|
||||
|
||||
if (modpackContentQuery.data.value !== undefined) {
|
||||
managedContentModal.value?.show(managedContentItems.value)
|
||||
if (modpackContentQuery.data.value?.length) {
|
||||
modpackContentModal.value?.show(modpackContentQuery.data.value)
|
||||
return
|
||||
}
|
||||
|
||||
managedContentModal.value?.showLoading()
|
||||
modpackContentModal.value?.showLoading()
|
||||
|
||||
const { data, error } = await modpackContentQuery.refetch()
|
||||
|
||||
if (data !== undefined) {
|
||||
managedContentModal.value?.show(managedContentItems.value)
|
||||
modpackContentModal.value?.show(data)
|
||||
} else {
|
||||
if (error) handleError(error)
|
||||
managedContentModal.value?.hide()
|
||||
modpackContentModal.value?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshManagedContentItems(cacheBehaviour?: CacheBehaviour) {
|
||||
async function refreshModpackContentItems(cacheBehaviour?: CacheBehaviour) {
|
||||
if (!instance.value?.id) return
|
||||
|
||||
const contentItems = await queryClient
|
||||
@@ -1228,13 +1054,13 @@ async function refreshManagedContentItems(cacheBehaviour?: CacheBehaviour) {
|
||||
.catch(handleError)
|
||||
|
||||
if (contentItems) {
|
||||
managedContentModal.value?.setItems(managedContentItems.value)
|
||||
modpackContentModal.value?.setItems(contentItems)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshContentState(cacheBehaviour?: CacheBehaviour) {
|
||||
await initProjects(cacheBehaviour)
|
||||
await refreshManagedContentItems(cacheBehaviour)
|
||||
await refreshModpackContentItems(cacheBehaviour)
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -1267,6 +1093,7 @@ async function handleModpackUpdate() {
|
||||
linkedModpackUpdateVersionId: linkedModpackUpdateVersionId.value,
|
||||
linkedModpackProject: linkedModpackProject.value,
|
||||
linkedModpackVersion: linkedModpackVersion.value,
|
||||
linkedModpackHasUpdate: linkedModpackHasUpdate.value,
|
||||
instance: {
|
||||
path: instance.value.id,
|
||||
name: instance.value.name,
|
||||
@@ -1435,6 +1262,8 @@ async function unpairInstance() {
|
||||
})
|
||||
linkedModpackProject.value = null
|
||||
linkedModpackVersion.value = null
|
||||
linkedModpackOwner.value = null
|
||||
linkedModpackHasUpdate.value = false
|
||||
linkedModpackUpdateVersionId.value = null
|
||||
localImportedModpackUnlinked.value = true
|
||||
await initProjects()
|
||||
@@ -1497,41 +1326,9 @@ function getOverflowOptions(item: ContentItem): OverflowMenuOption[] {
|
||||
})
|
||||
}
|
||||
|
||||
if (canMutateContent(item)) {
|
||||
options.push(
|
||||
{ type: 'divider' },
|
||||
{
|
||||
id: item.locked ? 'unfreeze-content' : 'freeze-content',
|
||||
label: formatMessage(item.locked ? messages.unfreezeContent : messages.freezeContent),
|
||||
icon: item.locked ? LockOpenIcon : LockIcon,
|
||||
action: () => handleContentFreeze(item, !item.locked),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
async function handleContentFreeze(item: ContentItem, frozen: boolean) {
|
||||
if (!item.file_path || !canMutateContent(item)) return
|
||||
const operation = beginContentOperation(item)
|
||||
if (!operation) return
|
||||
const originalFilePath = item.file_path
|
||||
|
||||
try {
|
||||
await set_project_locked(instance.value.id, item.file_path, frozen)
|
||||
item.locked = frozen
|
||||
managedContentModal.value?.updateItem(operation.originalFileName, { locked: frozen })
|
||||
updateLinkedModpackContentCache(item, operation.originalFileName, originalFilePath, {
|
||||
locked: frozen,
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err as Error)
|
||||
} finally {
|
||||
finishContentOperation(item, operation)
|
||||
}
|
||||
}
|
||||
|
||||
async function initProjects(cacheBehaviour?: CacheBehaviour, staleTime = 0) {
|
||||
if (!instance.value) return
|
||||
|
||||
@@ -1561,10 +1358,16 @@ function applyContentData(contentData: InstanceContentData) {
|
||||
if (contentData.modpack) {
|
||||
linkedModpackProject.value = contentData.modpack.project
|
||||
linkedModpackVersion.value = contentData.modpack.version
|
||||
linkedModpackOwner.value = contentData.modpack.owner
|
||||
linkedModpackCategories.value = contentData.modpack.categories
|
||||
linkedModpackHasUpdate.value = contentData.modpack.hasUpdate
|
||||
linkedModpackUpdateVersionId.value = contentData.modpack.updateVersionId
|
||||
} else {
|
||||
linkedModpackProject.value = null
|
||||
linkedModpackVersion.value = null
|
||||
linkedModpackOwner.value = null
|
||||
linkedModpackCategories.value = []
|
||||
linkedModpackHasUpdate.value = false
|
||||
linkedModpackUpdateVersionId.value = null
|
||||
}
|
||||
|
||||
@@ -1572,16 +1375,55 @@ function applyContentData(contentData: InstanceContentData) {
|
||||
return true
|
||||
}
|
||||
|
||||
function contentVersionLabel(item: ContentItem): string {
|
||||
if (item.embedded_metadata?.version) return item.embedded_metadata.version
|
||||
return formatMessage(commonMessages.unknownLabel)
|
||||
}
|
||||
|
||||
provideContentManager({
|
||||
items: mergedProjects,
|
||||
loading,
|
||||
error: ref(null),
|
||||
managedContent,
|
||||
modpack: computed(() => {
|
||||
if (linkedModpackProject.value) {
|
||||
return {
|
||||
project: linkedModpackProject.value,
|
||||
projectLink: {
|
||||
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}`,
|
||||
query: { i: instancePage.instanceId.value },
|
||||
},
|
||||
version: linkedModpackVersion.value ?? undefined,
|
||||
versionLink:
|
||||
linkedModpackProject.value && linkedModpackVersion.value
|
||||
? {
|
||||
path: `/project/${linkedModpackProject.value.slug ?? linkedModpackProject.value.id}/version/${linkedModpackVersion.value.id}`,
|
||||
query: { i: instancePage.instanceId.value },
|
||||
}
|
||||
: undefined,
|
||||
owner: linkedModpackOwner.value
|
||||
? {
|
||||
...linkedModpackOwner.value,
|
||||
link: contentOwnerLink(linkedModpackOwner.value),
|
||||
}
|
||||
: undefined,
|
||||
categories: linkedModpackCategories.value,
|
||||
hasUpdate: linkedModpackHasUpdate.value,
|
||||
disabled: isModpackUpdating.value,
|
||||
disabledText: isModpackUpdating.value
|
||||
? formatMessage(commonMessages.updatingLabel)
|
||||
: formatMessage(commonMessages.installingLabel),
|
||||
}
|
||||
}
|
||||
|
||||
if (localImportedModpackProject.value) {
|
||||
return {
|
||||
project: localImportedModpackProject.value,
|
||||
categories: [],
|
||||
hasUpdate: false,
|
||||
disabled: isModpackUpdating.value,
|
||||
disabledText: isModpackUpdating.value
|
||||
? formatMessage(commonMessages.updatingLabel)
|
||||
: formatMessage(commonMessages.installingLabel),
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}),
|
||||
isPackLocked,
|
||||
isBusy: isInstanceBusy,
|
||||
disableAddContent: isQuarantined,
|
||||
@@ -1590,27 +1432,23 @@ provideContentManager({
|
||||
skipNonEssentialWarnings,
|
||||
contentTypeLabel: ref(formatMessage(messages.contentTypeProject)),
|
||||
toggleEnabled: toggleDisableDebounced,
|
||||
bulkEnableItems: async (items: ContentItem[]) => {
|
||||
await Promise.all(
|
||||
bulkEnableItems: (items: ContentItem[]) =>
|
||||
Promise.all(
|
||||
items
|
||||
.filter((item) => canToggleContent(item) && !item.enabled)
|
||||
.map((item) => toggleDisableMod(item, true, false)),
|
||||
)
|
||||
await reconcileSharedInstancePublishState()
|
||||
},
|
||||
bulkDisableItems: async (items: ContentItem[]) => {
|
||||
await Promise.all(
|
||||
.filter((item) => canMutateContent(item) && !item.enabled)
|
||||
.map((item) => toggleDisableMod(item, true)),
|
||||
).then(() => {}),
|
||||
bulkDisableItems: (items: ContentItem[]) =>
|
||||
Promise.all(
|
||||
items
|
||||
.filter((item) => canToggleContent(item) && item.enabled)
|
||||
.map((item) => toggleDisableMod(item, false, false)),
|
||||
)
|
||||
await reconcileSharedInstancePublishState()
|
||||
},
|
||||
.filter((item) => canMutateContent(item) && item.enabled)
|
||||
.map((item) => toggleDisableMod(item, false)),
|
||||
).then(() => {}),
|
||||
deleteItem: removeMod,
|
||||
bulkDeleteItems: (items: ContentItem[]) =>
|
||||
Promise.all(items.filter(canDeleteContent).map((item) => removeMod(item))).then(() => {}),
|
||||
Promise.all(items.filter(canMutateContent).map((item) => removeMod(item))).then(() => {}),
|
||||
canDeleteItem: canDeleteContent,
|
||||
canToggleItem: canToggleContent,
|
||||
canToggleItem: canMutateContent,
|
||||
getDeleteWarning: managedContentPolicy.deleteWarning,
|
||||
getDisableWarning: managedContentPolicy.disableWarning,
|
||||
getDeleteDependencyWarning,
|
||||
@@ -1621,15 +1459,13 @@ provideContentManager({
|
||||
updateItem: handleUpdate,
|
||||
bulkUpdateAll: bulkUpdateAllProjects,
|
||||
bulkUpdateItem: updateProject,
|
||||
runManagedContentPrimaryAction:
|
||||
instance.value.shared_instance?.role === 'member'
|
||||
? instancePage.reviewSharedInstanceUpdate
|
||||
: instance.value.link?.type === 'modrinth_modpack' && !isQuarantined.value
|
||||
? handleModpackUpdate
|
||||
: undefined,
|
||||
viewManagedContent: handleManagedContent,
|
||||
updateModpack:
|
||||
isServerInstance.value || isSharedMember.value || isQuarantined.value
|
||||
? undefined
|
||||
: handleModpackUpdate,
|
||||
viewModpackContent: handleModpackContent,
|
||||
unlinkModpack: unpairInstance,
|
||||
openManagedContentSettings: openSettings,
|
||||
openSettings: openSettings,
|
||||
switchVersion: handleSwitchVersion,
|
||||
getOverflowOptions,
|
||||
shareItems: handleShareItems,
|
||||
@@ -1639,15 +1475,15 @@ provideContentManager({
|
||||
project: item.project ?? {
|
||||
id: item.file_name,
|
||||
slug: null,
|
||||
title: item.embedded_metadata?.name ?? item.file_name.replace('.disabled', ''),
|
||||
icon_url: item.embedded_metadata?.icon_url ?? null,
|
||||
title: item.file_name.replace('.disabled', ''),
|
||||
icon_url: null,
|
||||
},
|
||||
projectLink: item.project?.id
|
||||
? { path: `/project/${item.project.id}`, query: { i: instancePage.instanceId.value } }
|
||||
: undefined,
|
||||
version: item.version ?? {
|
||||
id: item.file_name,
|
||||
version_number: contentVersionLabel(item),
|
||||
version_number: formatMessage(commonMessages.unknownLabel),
|
||||
file_name: item.file_name,
|
||||
},
|
||||
versionLink:
|
||||
@@ -1663,22 +1499,19 @@ provideContentManager({
|
||||
link: contentOwnerLink(item.owner),
|
||||
}
|
||||
: undefined,
|
||||
external: item.external ?? !item.project,
|
||||
enabled: canMutateContent(item) ? item.enabled : undefined,
|
||||
locked: item.locked,
|
||||
installing: item.installing,
|
||||
hideDelete: !canDeleteContent(item),
|
||||
hideSwitchVersion: !canChangeContentVersion(item) || !item.project?.id || !item.version?.id,
|
||||
hasUpdate: canUpdateProject(item) && !item.locked,
|
||||
hideSwitchVersion: !canMutateContent(item) || !item.project?.id || !item.version?.id,
|
||||
hasUpdate: canUpdateProject(item),
|
||||
}),
|
||||
showSharedContentFilter,
|
||||
filterPersistKey: instance.value.id,
|
||||
})
|
||||
|
||||
type UnlistenFn = () => void
|
||||
|
||||
const initialContentReady = loadInitialContent()
|
||||
void initialContentReady.then(restoreManagedContentModalState).catch(handleError)
|
||||
void initialContentReady.then(restoreModpackContentModalState).catch(handleError)
|
||||
|
||||
function getInstallRevision() {
|
||||
return installRevisionByInstance.value.get(instance.value.id) ?? 0
|
||||
@@ -1708,18 +1541,18 @@ watch(contentQuery.error, (error) => {
|
||||
}
|
||||
})
|
||||
|
||||
async function restoreManagedContentModalState() {
|
||||
async function restoreModpackContentModalState() {
|
||||
if (!savedModalState) return
|
||||
|
||||
const stateToRestore = savedModalState
|
||||
savedModalState = null
|
||||
await nextTick()
|
||||
managedContentModal.value?.restore(stateToRestore)
|
||||
modpackContentModal.value?.restore(stateToRestore)
|
||||
}
|
||||
|
||||
// Save modal state when navigating away so it can be restored on back
|
||||
const removeBeforeEach = router.beforeEach(() => {
|
||||
const state = managedContentModal.value?.getState()
|
||||
const state = modpackContentModal.value?.getState()
|
||||
savedModalState = state ?? null
|
||||
})
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface InstancePageContext {
|
||||
readonly instance: ComputedRef<GameInstance>
|
||||
readonly linkedProject: ComputedRef<Labrinth.Projects.v3.Project | undefined>
|
||||
readonly isServerInstance: ComputedRef<boolean>
|
||||
readonly sharedInstanceUpdateAvailable: ComputedRef<boolean>
|
||||
readonly offline: Readonly<Ref<boolean>>
|
||||
readonly playing: ComputedRef<boolean>
|
||||
readonly loading: Readonly<Ref<boolean>>
|
||||
@@ -22,7 +21,6 @@ export interface InstancePageContext {
|
||||
openSettings: (tab?: number) => void
|
||||
browseContent: (projectType?: string) => Promise<void>
|
||||
browseServers: () => Promise<void>
|
||||
reviewSharedInstanceUpdate: (event?: MouseEvent) => void
|
||||
}
|
||||
|
||||
export const [injectInstancePage, provideInstancePage] =
|
||||
|
||||
@@ -37,8 +37,11 @@
|
||||
:loading-server-ping="loadingServerPing"
|
||||
:players-online="playersOnline"
|
||||
:status-online="statusOnline"
|
||||
:recent-plays="recentPlays"
|
||||
:ping="ping"
|
||||
:minecraft-server="minecraftServer"
|
||||
:linked-project-v3="linkedProjectV3"
|
||||
:shared-instance-manager="sharedInstanceManager"
|
||||
@repair="() => repairInstance()"
|
||||
@stop="() => stopInstance('InstancePage')"
|
||||
@play="() => startInstance('InstancePage')"
|
||||
@@ -61,8 +64,10 @@
|
||||
:shared-instance-expected-user-id="sharedInstanceExpectedUserId"
|
||||
:shared-instance-role="instance.shared_instance?.role"
|
||||
:shared-instance-signed-out="sharedInstanceSignedOut"
|
||||
:shared-instance-update-available="showSharedInstanceUpdateAdmonition"
|
||||
@published="refreshInstance"
|
||||
@delete="requestInstanceDeletion"
|
||||
@review-update="reviewSharedInstanceUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div :class="['p-6 pt-4', { 'min-h-0 flex-1 overflow-y-auto': isFixedRender }]">
|
||||
@@ -206,9 +211,7 @@ useQuery(
|
||||
retry: false,
|
||||
})),
|
||||
)
|
||||
const linkedProjectId = computed(
|
||||
() => instance.value?.link?.server_project_id ?? instance.value?.link?.project_id ?? '',
|
||||
)
|
||||
const linkedProjectId = computed(() => instance.value?.link?.project_id ?? '')
|
||||
const linkedProjectQuery = useQuery(
|
||||
computed(() => ({
|
||||
...instanceLinkedProjectQueryOptions(linkedProjectId.value),
|
||||
@@ -299,6 +302,9 @@ const minecraftServer = computed(() => linkedProjectV3.value?.minecraft_server)
|
||||
const javaServerPingData = computed(() => linkedProjectV3.value?.minecraft_java_server?.ping?.data)
|
||||
const liveServerStatusOnline = ref(false)
|
||||
const statusOnline = computed(() => liveServerStatusOnline.value || !!javaServerPingData.value)
|
||||
const recentPlays = computed(
|
||||
() => linkedProjectV3.value?.minecraft_java_server?.verified_plays_2w ?? undefined,
|
||||
)
|
||||
const playersOnline = ref<number | undefined>(undefined)
|
||||
const ping = ref<number | undefined>(undefined)
|
||||
const loadingServerPing = ref(false)
|
||||
@@ -311,6 +317,7 @@ provideSharedInstance(sharedInstanceState)
|
||||
const {
|
||||
actionsLocked: sharedInstanceActionsLocked,
|
||||
expectedUserId: sharedInstanceExpectedUserId,
|
||||
manager: sharedInstanceManager,
|
||||
refreshUpdatePreview: refreshSharedInstanceUpdatePreview,
|
||||
setUnavailable: setSharedInstanceUnavailable,
|
||||
signedOut: sharedInstanceSignedOut,
|
||||
@@ -324,7 +331,7 @@ const sharedInstanceUpdateKey = computed(() => {
|
||||
const latestVersion = sharedInstanceUpdatePreview.value?.latestVersion
|
||||
return instanceId && latestVersion !== undefined ? `${instanceId}:${latestVersion}` : null
|
||||
})
|
||||
const sharedInstanceUpdateAvailable = computed(
|
||||
const showSharedInstanceUpdateAdmonition = computed(
|
||||
() =>
|
||||
sharedInstanceUpdatePreview.value?.updateAvailable === true &&
|
||||
sharedInstanceUpdateKey.value !== hiddenSharedInstanceUpdateKey.value,
|
||||
@@ -508,7 +515,7 @@ async function handleSharedInstanceUnavailable(
|
||||
setSharedInstanceUnavailable(reason)
|
||||
}
|
||||
|
||||
function reviewSharedInstanceUpdate(event?: MouseEvent) {
|
||||
function reviewSharedInstanceUpdate(event: MouseEvent) {
|
||||
const currentInstance = instance.value
|
||||
const preview = sharedInstanceUpdatePreview.value
|
||||
if (
|
||||
@@ -785,7 +792,6 @@ provideInstancePage({
|
||||
instance: instance as ComputedRef<GameInstance>,
|
||||
linkedProject: linkedProjectV3,
|
||||
isServerInstance,
|
||||
sharedInstanceUpdateAvailable,
|
||||
offline,
|
||||
playing,
|
||||
loading,
|
||||
@@ -798,7 +804,6 @@ provideInstancePage({
|
||||
openSettings,
|
||||
browseContent,
|
||||
browseServers,
|
||||
reviewSharedInstanceUpdate,
|
||||
})
|
||||
provideInstanceBackup(() => instance.value!)
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ export function createSharedInstanceContext(
|
||||
enabled: computed(
|
||||
() =>
|
||||
!!instance.value?.id &&
|
||||
instance.value.install_stage === 'installed' &&
|
||||
!!instance.value.shared_instance &&
|
||||
!actionsLocked.value &&
|
||||
!offline.value &&
|
||||
@@ -129,7 +128,6 @@ export function createSharedInstanceContext(
|
||||
const updatePreview = computed(() =>
|
||||
unavailableReason.value ? null : (updatePreviewQuery.data.value ?? null),
|
||||
)
|
||||
const lastUpdateCheckAt = computed(() => updatePreviewQuery.dataUpdatedAt.value || undefined)
|
||||
|
||||
watch(
|
||||
() => instance.value?.id,
|
||||
@@ -164,7 +162,6 @@ export function createSharedInstanceContext(
|
||||
unavailableManager,
|
||||
manager,
|
||||
updatePreview,
|
||||
lastUpdateCheckAt,
|
||||
expectedUserId,
|
||||
wrongAccount,
|
||||
signedOut,
|
||||
|
||||
@@ -22,7 +22,6 @@ export const DEFAULT_FEATURE_FLAGS = {
|
||||
friends_online_collapsed: false,
|
||||
friends_offline_collapsed: true,
|
||||
friends_pending_collapsed: true,
|
||||
dismissed_photosensitivity_filter_warning: false,
|
||||
}
|
||||
|
||||
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'retro', 'system'] as const
|
||||
|
||||
@@ -217,7 +217,6 @@ fn main() {
|
||||
"instance_add_project_from_path",
|
||||
"instance_is_file_on_modrinth",
|
||||
"instance_toggle_disable_project",
|
||||
"instance_set_project_locked",
|
||||
"instance_remove_project",
|
||||
"instance_update_managed_modrinth_version",
|
||||
"instance_repair_managed_modrinth",
|
||||
|
||||
+1
-10
@@ -187,19 +187,10 @@ async fn sync_ads_occlusion<R: Runtime>(app: &tauri::AppHandle<R>) {
|
||||
|
||||
state.occluded = occluded;
|
||||
let visible = should_show_ads_webview(&state);
|
||||
let consent_overlay_shown = state.consent_overlay_shown;
|
||||
drop(state);
|
||||
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
let is_minimized = app
|
||||
.get_window("main")
|
||||
.and_then(|window| window.is_minimized().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
set_webview_visible(
|
||||
webview,
|
||||
visible && !is_minimized && (!occluded || consent_overlay_shown),
|
||||
);
|
||||
set_webview_visible_for_window(app, webview, visible);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
instance_add_project_from_path,
|
||||
instance_is_file_on_modrinth,
|
||||
instance_toggle_disable_project,
|
||||
instance_set_project_locked,
|
||||
instance_remove_project,
|
||||
instance_update_managed_modrinth_version,
|
||||
instance_repair_managed_modrinth,
|
||||
@@ -703,17 +702,6 @@ pub async fn instance_toggle_disable_project(
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_set_project_locked(
|
||||
instance_id: &str,
|
||||
project_path: &str,
|
||||
locked: bool,
|
||||
) -> Result<()> {
|
||||
theseus::instance::set_project_locked(instance_id, project_path, locked)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_remove_project(
|
||||
instance_id: &str,
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"wrangler": "^4.115.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@contentauth/c2pa-web": "^0.13.1",
|
||||
"@formatjs/intl-localematcher": "^0.5.4",
|
||||
"@ltd/j-toml": "^1.38.0",
|
||||
"@modrinth/api-client": "workspace:*",
|
||||
|
||||
@@ -40,8 +40,6 @@ html {
|
||||
|
||||
interpolate-size: allow-keywords;
|
||||
scrollbar-gutter: stable;
|
||||
|
||||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.light-mode {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<NewModal
|
||||
ref="modal"
|
||||
:header="formatMessage(messages.title)"
|
||||
:closable="false"
|
||||
fade="warning"
|
||||
max-width="544px"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<p class="mb-4 mt-0 leading-normal">
|
||||
{{ formatMessage(messages.body1) }}
|
||||
</p>
|
||||
<p class="mb-4 mt-0 leading-normal">
|
||||
<IntlFormatted :message-id="messages.body2">
|
||||
<template #rules="{ children }">
|
||||
<nuxt-link
|
||||
to="/legal/rules#generative-ai"
|
||||
target="_blank"
|
||||
class="font-semibold text-orange hover:underline"
|
||||
>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<div class="flex justify-end">
|
||||
<Button type="colored" color="brand" native-type="button" @click="hide">
|
||||
<CheckCircleIcon class="size-4" />
|
||||
{{ formatMessage(commonMessages.iUnderstandButton) }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CheckCircleIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
IntlFormatted,
|
||||
NewModal,
|
||||
normalizeChildren,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const modal = useTemplateRef('modal')
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.ai-image-warning-modal.title',
|
||||
defaultMessage: 'AI-generated images not allowed',
|
||||
},
|
||||
body1: {
|
||||
id: 'project.ai-image-warning-modal.body.1',
|
||||
defaultMessage:
|
||||
'Using AI-generated images to represent your project is not allowed. Attempting to work around detection may lead to your Modrinth account being suspended.',
|
||||
},
|
||||
body2: {
|
||||
id: 'project.ai-image-warning-modal.body.2',
|
||||
defaultMessage: `See section 6 of <rules>Modrinth's Content Rules</rules> for more information on our generative AI policy.`,
|
||||
},
|
||||
})
|
||||
|
||||
function show() {
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
@@ -278,13 +278,11 @@ import {
|
||||
CollapsibleRegion,
|
||||
type ContentItem,
|
||||
CopyCode,
|
||||
formatReportType,
|
||||
getProjectTypeIcon,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
@@ -299,7 +297,6 @@ import SharedInstanceReportContext, {
|
||||
} from './SharedInstanceReportContext.vue'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
const auth = await useAuth()
|
||||
|
||||
@@ -790,9 +787,13 @@ const reportItemUrl = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const formattedReportType = computed(() =>
|
||||
formatReportType(formatMessage, props.report.report_type),
|
||||
)
|
||||
const formattedReportType = computed(() => {
|
||||
const reportType = props.report.report_type
|
||||
|
||||
// some are split by -, some are split by " "
|
||||
const words = reportType.includes('-') ? reportType.split('-') : reportType.split(' ')
|
||||
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')
|
||||
})
|
||||
|
||||
function copyId() {
|
||||
navigator.clipboard.writeText(props.report.id).then(() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
ConfirmLeaveModal,
|
||||
type ContentItem,
|
||||
injectModrinthClient,
|
||||
ManagedContentModal,
|
||||
ModpackContentModal,
|
||||
Table,
|
||||
type TableColumn,
|
||||
useFormatDateTime,
|
||||
@@ -76,7 +76,7 @@ const emit = defineEmits<{
|
||||
contentError: [error: unknown]
|
||||
}>()
|
||||
|
||||
const contentModal = ref<InstanceType<typeof ManagedContentModal> | null>(null)
|
||||
const contentModal = ref<InstanceType<typeof ModpackContentModal> | null>(null)
|
||||
const banModal = ref<InstanceType<typeof ConfirmLeaveModal> | null>(null)
|
||||
const client = injectModrinthClient()
|
||||
const contentByVersion = new Map<string, ContentItem[]>()
|
||||
@@ -406,11 +406,11 @@ function formattedLoader(version: SharedInstanceReportVersion) {
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<ManagedContentModal
|
||||
<ModpackContentModal
|
||||
ref="contentModal"
|
||||
header="Shared instance content"
|
||||
:source-name="contentInstance.name"
|
||||
:source-icon-url="contentInstance.icon_url ?? undefined"
|
||||
:modpack-name="contentInstance.name"
|
||||
:modpack-icon-url="contentInstance.icon_url ?? undefined"
|
||||
/>
|
||||
<ConfirmLeaveModal
|
||||
ref="banModal"
|
||||
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { MegaphoneIcon } from '@modrinth/assets'
|
||||
import {
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
IntlFormatted,
|
||||
normalizeChildren,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type { DisclosureCardMetaProps, DisclosureLockStatus, NoteDisclosure } from './types'
|
||||
|
||||
const model = defineModel<NoteDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.advertising.title',
|
||||
defaultMessage: 'Contains advertisements',
|
||||
},
|
||||
description1: {
|
||||
id: 'project.settings.disclosures.advertising.description.1',
|
||||
defaultMessage: `You must enable this if your project contains advertisements, sponsorships, or promotions of other works.`,
|
||||
},
|
||||
description2: {
|
||||
id: 'project.settings.disclosures.advertising.description.2',
|
||||
defaultMessage: `If the promotion has no direct monetary value <emphasis>and</emphasis> it is for something that the average person would consider <italic>relevant</italic> and <italic>unobtrusive</italic> (such as a link to your Modrinth profile in the corner of the settings page for your own mod), we would not consider that an advertisement.`,
|
||||
},
|
||||
notePlaceholder: {
|
||||
id: 'project.settings.disclosures.advertising.note-placeholder',
|
||||
defaultMessage: 'e.g. Adds the Modrinth SMP server to your server list automatically.',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="MegaphoneIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<p>{{ formatMessage(messages.description1) }}</p>
|
||||
<p>
|
||||
<IntlFormatted :message-id="messages.description2">
|
||||
<template #italic="{ children }">
|
||||
<span class="italic">
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</span>
|
||||
</template>
|
||||
<template #emphasis="{ children }">
|
||||
<span class="font-bold italic">
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</span>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(commonMessages.explanationLabel)"
|
||||
title-for="advertising-disclosure-note"
|
||||
>
|
||||
<StyledInput
|
||||
id="advertising-disclosure-note"
|
||||
v-model="model.note"
|
||||
multiline
|
||||
:rows="3"
|
||||
class="max-w-[40rem]"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.notePlaceholder)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
@@ -1,125 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { SparklesIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
disclosureAiUsageMessages,
|
||||
IntlFormatted,
|
||||
normalizeChildren,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type { AiDisclosure, AiUsage, DisclosureCardMetaProps, DisclosureLockStatus } from './types'
|
||||
|
||||
const model = defineModel<AiDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const AI_USES: AiUsage[] = ['code', 'assets', 'text', 'functionality']
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.ai.title',
|
||||
defaultMessage: 'Contains AI-generated content',
|
||||
},
|
||||
description: {
|
||||
id: 'project.settings.disclosures.ai.description',
|
||||
defaultMessage: `You must enable this if this project contains a substantial amount of AI-generated code, any
|
||||
assets that are substantially AI-generated, the project's functionality relies on the use of
|
||||
generative AI, or if any element of your project's page such as description or publishing
|
||||
relies on generative AI.`,
|
||||
},
|
||||
contentRules: {
|
||||
id: 'project.settings.disclosures.ai.content-rules',
|
||||
defaultMessage:
|
||||
"Please refer to Section 6 of <rules>Modrinth's Content Rules</rules> for more information.",
|
||||
},
|
||||
typesDescription: {
|
||||
id: 'project.settings.disclosures.ai.types-description',
|
||||
defaultMessage: 'What is generative AI being used for?',
|
||||
},
|
||||
notePlaceholder: {
|
||||
id: 'project.settings.disclosures.ai.note-placeholder',
|
||||
defaultMessage: 'e.g. The Chinese and Arabic translations are AI-generated.',
|
||||
},
|
||||
})
|
||||
|
||||
function hasUse(use: AiUsage): boolean {
|
||||
return model.value.uses.includes(use)
|
||||
}
|
||||
|
||||
function setUse(use: AiUsage, enabled: boolean) {
|
||||
if (enabled) {
|
||||
if (!model.value.uses.includes(use)) {
|
||||
model.value.uses = [...model.value.uses, use]
|
||||
}
|
||||
return
|
||||
}
|
||||
model.value.uses = model.value.uses.filter((entry) => entry !== use)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="SparklesIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<p class="text-secondary">
|
||||
<IntlFormatted :message-id="messages.contentRules">
|
||||
<template #rules="{ children }">
|
||||
<nuxt-link
|
||||
to="/legal/rules#generative-ai"
|
||||
target="_blank"
|
||||
class="smart-clickable:allow-pointer-events underline hover:text-primary"
|
||||
>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup :title="formatMessage(messages.typesDescription)">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Checkbox
|
||||
v-for="use in AI_USES"
|
||||
:key="use"
|
||||
:model-value="hasUse(use)"
|
||||
:disabled="disabled"
|
||||
@update:model-value="(enabled) => setUse(use, enabled)"
|
||||
>
|
||||
{{ formatMessage(disclosureAiUsageMessages[use]) }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</SettingsFormGroup>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(commonMessages.explanationLabel)"
|
||||
title-for="ai-disclosure-note"
|
||||
optional
|
||||
>
|
||||
<StyledInput
|
||||
id="ai-disclosure-note"
|
||||
v-model="model.note"
|
||||
multiline
|
||||
:rows="3"
|
||||
class="max-w-[40rem]"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.notePlaceholder)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ArchiveIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ArchivedProjectBanner,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type { DisclosureCardMetaProps, DisclosureLockStatus, NoteDisclosure } from './types'
|
||||
|
||||
const model = defineModel<NoteDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<
|
||||
DisclosureCardMetaProps & {
|
||||
projectTitle: string
|
||||
}
|
||||
>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.archived.title',
|
||||
defaultMessage: 'Archive project',
|
||||
},
|
||||
description1: {
|
||||
id: 'project.settings.disclosures.archived.description.1',
|
||||
defaultMessage:
|
||||
'Mark your project as archived to let users know that you are no longer working on it.',
|
||||
},
|
||||
description2: {
|
||||
id: 'project.settings.disclosures.archived.description.2',
|
||||
defaultMessage:
|
||||
'Archived projects remain discoverable when their visibility is set to Public. If you wish to de-list your project, set its visibility to Unlisted.',
|
||||
},
|
||||
notePlaceholder: {
|
||||
id: 'project.settings.disclosures.archived.note-placeholder',
|
||||
defaultMessage: `e.g. I don't have time to maintain this project anymore, feel free to fork it!`,
|
||||
},
|
||||
bannerPreview: {
|
||||
id: 'project.settings.disclosures.archived.banner-preview',
|
||||
defaultMessage: 'Banner preview',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="ArchiveIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<p>{{ formatMessage(messages.description1) }}</p>
|
||||
<p>{{ formatMessage(messages.description2) }}</p>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(commonMessages.explanationLabel)"
|
||||
title-for="archived-disclosure-note"
|
||||
optional
|
||||
>
|
||||
<StyledInput
|
||||
id="archived-disclosure-note"
|
||||
v-model="model.note"
|
||||
multiline
|
||||
:rows="3"
|
||||
class="max-w-[40rem]"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.notePlaceholder)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
<SettingsFormGroup :title="formatMessage(messages.bannerPreview)">
|
||||
<ArchivedProjectBanner :title="projectTitle" :reason="model.note" />
|
||||
</SettingsFormGroup>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { GitForkIcon, PlusIcon, TrashIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type {
|
||||
DerivativeDisclosure,
|
||||
DerivativeSource,
|
||||
DisclosureCardMetaProps,
|
||||
DisclosureLockStatus,
|
||||
} from './types'
|
||||
|
||||
const model = defineModel<DerivativeDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.derivative.title',
|
||||
defaultMessage: 'Contains derivative content',
|
||||
},
|
||||
description: {
|
||||
id: 'project.settings.disclosures.derivative.description',
|
||||
defaultMessage:
|
||||
'You must enable this if your project is derivative of another project, such as being a fork or containing a substantial amount of someone else’s work.',
|
||||
},
|
||||
nameLabel: {
|
||||
id: 'project.settings.disclosures.derivative.name-label',
|
||||
defaultMessage: 'Name of original work',
|
||||
},
|
||||
namePlaceholder: {
|
||||
id: 'project.settings.disclosures.derivative.name-placeholder',
|
||||
defaultMessage: 'Example project',
|
||||
},
|
||||
linkLabel: {
|
||||
id: 'project.settings.disclosures.derivative.link-label',
|
||||
defaultMessage: 'Link to original work',
|
||||
},
|
||||
linkPlaceholder: {
|
||||
id: 'project.settings.disclosures.derivative.link-placeholder',
|
||||
defaultMessage: 'https://example.com',
|
||||
},
|
||||
noteLabel: {
|
||||
id: 'project.settings.disclosures.derivative.note-label',
|
||||
defaultMessage: 'Explain how your project is based on the original work',
|
||||
},
|
||||
})
|
||||
|
||||
function emptyDerivativeSource(): DerivativeSource {
|
||||
return { label: '' }
|
||||
}
|
||||
|
||||
function updateSource(index: number, patch: Partial<DerivativeSource>) {
|
||||
model.value.sources = model.value.sources.map((source, i) =>
|
||||
i === index ? { ...source, ...patch } : source,
|
||||
)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => model.value.enabled,
|
||||
(enabled) => {
|
||||
if (enabled && model.value.sources.length === 0) {
|
||||
model.value.sources = [emptyDerivativeSource()]
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function addSource() {
|
||||
model.value.sources = [...model.value.sources, emptyDerivativeSource()]
|
||||
}
|
||||
|
||||
function removeSource(index: number) {
|
||||
if (model.value.sources.length <= 1) return
|
||||
model.value.sources = model.value.sources.filter((_, i) => i !== index)
|
||||
}
|
||||
|
||||
function setOptionalField(
|
||||
index: number,
|
||||
field: 'link' | 'note',
|
||||
value: string | number | undefined,
|
||||
) {
|
||||
updateSource(index, {
|
||||
[field]: typeof value === 'string' && value ? value : null,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="GitForkIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<template #expanded>
|
||||
<div
|
||||
v-for="(source, index) in model.sources"
|
||||
:key="index"
|
||||
class="relative flex flex-col gap-4 rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4"
|
||||
>
|
||||
<div class="absolute right-3 top-3">
|
||||
<Button
|
||||
v-if="model.sources.length > 1"
|
||||
type="quiet"
|
||||
color="red"
|
||||
:disabled="disabled"
|
||||
@click="removeSource(index)"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.removeButton) }}
|
||||
</Button>
|
||||
</div>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(messages.nameLabel)"
|
||||
:title-for="`derivative-name-${index}`"
|
||||
class="max-w-[40rem]"
|
||||
>
|
||||
<StyledInput
|
||||
:id="`derivative-name-${index}`"
|
||||
:model-value="source.label"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.namePlaceholder)"
|
||||
@update:model-value="
|
||||
(value) => updateSource(index, { label: typeof value === 'string' ? value : '' })
|
||||
"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(messages.linkLabel)"
|
||||
:title-for="`derivative-link-${index}`"
|
||||
class="max-w-[40rem]"
|
||||
>
|
||||
<StyledInput
|
||||
:id="`derivative-link-${index}`"
|
||||
:model-value="source.link ?? undefined"
|
||||
type="url"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.linkPlaceholder)"
|
||||
@update:model-value="(value) => setOptionalField(index, 'link', value)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(messages.noteLabel)"
|
||||
:title-for="`derivative-note-${index}`"
|
||||
class="max-w-[40rem]"
|
||||
>
|
||||
<StyledInput
|
||||
:id="`derivative-note-${index}`"
|
||||
:model-value="source.note ?? undefined"
|
||||
multiline
|
||||
:rows="3"
|
||||
:disabled="disabled"
|
||||
@update:model-value="(value) => setOptionalField(index, 'note', value)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
</div>
|
||||
<Button class="w-fit" :disabled="disabled" @click="addSource">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(commonMessages.addAnotherButton) }}
|
||||
</Button>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { CircleSlashIcon, LockIcon, LockOpenIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
defineMessages,
|
||||
IntlFormatted,
|
||||
normalizeChildren,
|
||||
SettingsInlineWarning,
|
||||
SettingsToggleCard,
|
||||
} from '@modrinth/ui'
|
||||
import type { Component } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import DisclosureUpdatedBy from './DisclosureUpdatedBy.vue'
|
||||
import type { DisclosureCardMetaProps, DisclosureLockStatus } from './types'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = defineProps<
|
||||
DisclosureCardMetaProps & {
|
||||
title: string
|
||||
icon?: Component
|
||||
description?: string
|
||||
}
|
||||
>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const enabled = defineModel<boolean>({ required: true })
|
||||
|
||||
const resolvedLockStatus = computed(() => props.lockStatus ?? 'unlocked')
|
||||
|
||||
const LOCK_STATUSES = [
|
||||
{ status: 'unlocked', label: 'Unlocked' },
|
||||
{ status: 'cannot_disable', label: 'Cannot disable' },
|
||||
{ status: 'fully_locked', label: 'Fully locked' },
|
||||
] as const
|
||||
|
||||
const messages = defineMessages({
|
||||
cannotDisableWarning: {
|
||||
id: 'project.settings.disclosures.lock-status.cannot-disable.warning',
|
||||
defaultMessage:
|
||||
'Please <contact-support-link>contact support</contact-support-link> if this disclosure needs to be removed.',
|
||||
},
|
||||
fullyLockedWarning: {
|
||||
id: 'project.settings.disclosures.lock-status.fully-locked.warning',
|
||||
defaultMessage:
|
||||
'Please <contact-support-link>contact support</contact-support-link> if this disclosure needs to be edited or removed.',
|
||||
},
|
||||
disabledLockedWarning: {
|
||||
id: 'project.settings.disclosures.lock-status.disabled.warning',
|
||||
defaultMessage:
|
||||
'This disclosure has been locked by the moderators. Please <contact-support-link>contact support</contact-support-link> if you believe this is in error.',
|
||||
},
|
||||
})
|
||||
|
||||
const lockWarningMessage = computed(() => {
|
||||
if (resolvedLockStatus.value === 'cannot_disable') {
|
||||
return enabled.value ? messages.cannotDisableWarning : null
|
||||
}
|
||||
if (resolvedLockStatus.value === 'fully_locked') {
|
||||
return enabled.value ? messages.fullyLockedWarning : messages.disabledLockedWarning
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const showLockWarning = computed(() => !!lockWarningMessage.value)
|
||||
|
||||
const showModeratorLockControls = computed(() => !!props.showLockControls)
|
||||
|
||||
const showFooter = computed(
|
||||
() => !!props.updatedAt || showModeratorLockControls.value || showLockWarning.value,
|
||||
)
|
||||
|
||||
function setLockStatus(status: DisclosureLockStatus) {
|
||||
if (resolvedLockStatus.value === status) return
|
||||
emit('setLockStatus', status)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SettingsToggleCard
|
||||
v-model="enabled"
|
||||
:disabled="disabled"
|
||||
:toggle-disabled="toggleDisabled"
|
||||
:icon="icon"
|
||||
:title="title"
|
||||
:description="description"
|
||||
>
|
||||
<slot />
|
||||
<template v-if="$slots.expanded" #expanded>
|
||||
<slot name="expanded" />
|
||||
</template>
|
||||
<template v-if="showFooter" #footer>
|
||||
<div class="flex flex-col gap-3">
|
||||
<SettingsInlineWarning v-if="showLockWarning && lockWarningMessage">
|
||||
<IntlFormatted :message-id="lockWarningMessage">
|
||||
<template #contact-support-link="{ children }">
|
||||
<a
|
||||
class="smart-clickable:allow-pointer-events text-orange underline hover:brightness-110"
|
||||
href="https://support.modrinth.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</SettingsInlineWarning>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<DisclosureUpdatedBy
|
||||
v-if="updatedAt"
|
||||
:updated-at="updatedAt"
|
||||
:updated-by="updatedBy"
|
||||
:set-by-moderator="setByModerator"
|
||||
/>
|
||||
<span v-else />
|
||||
<ButtonGroup v-if="showModeratorLockControls" class="ml-auto" label="Lock status">
|
||||
<Button
|
||||
v-for="{ status, label } in LOCK_STATUSES"
|
||||
:key="status"
|
||||
:color="
|
||||
status === 'cannot_disable'
|
||||
? 'orange'
|
||||
: status === 'fully_locked'
|
||||
? 'red'
|
||||
: undefined
|
||||
"
|
||||
:type="status !== 'unlocked' ? 'colored-text' : undefined"
|
||||
:disabled="disabled || resolvedLockStatus === status"
|
||||
@click="setLockStatus(status)"
|
||||
>
|
||||
<LockOpenIcon v-if="status === 'unlocked'" />
|
||||
<CircleSlashIcon v-else-if="status === 'cannot_disable'" />
|
||||
<LockIcon v-else />
|
||||
{{ label }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsToggleCard>
|
||||
</template>
|
||||
@@ -1,74 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ScaleIcon } from '@modrinth/assets'
|
||||
import {
|
||||
AutoLink,
|
||||
Avatar,
|
||||
defineMessages,
|
||||
IntlFormatted,
|
||||
useFormatDateTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { DisclosureUpdatedByUser } from './types'
|
||||
|
||||
const props = defineProps<{
|
||||
updatedAt: string
|
||||
updatedBy?: DisclosureUpdatedByUser | null
|
||||
setByModerator?: boolean
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||
const formatDateTime = useFormatDateTime({
|
||||
dateStyle: 'long',
|
||||
timeStyle: 'short',
|
||||
})
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const showModeratorLabel = computed(
|
||||
() => !!props.setByModerator && (!props.updatedBy || flags.value.showModeratorProjectMemberUi),
|
||||
)
|
||||
|
||||
const messages = defineMessages({
|
||||
lastUpdated: {
|
||||
id: 'project.settings.disclosures.last-updated',
|
||||
defaultMessage: 'Last updated on {date} by {user}',
|
||||
},
|
||||
updatedByModerator: {
|
||||
id: 'project.settings.disclosures.last-updated.moderator',
|
||||
defaultMessage: 'Moderator',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex flex-wrap items-center gap-1 text-secondary">
|
||||
<IntlFormatted :message-id="messages.lastUpdated">
|
||||
<template #date>
|
||||
<span v-tooltip="formatDateTime(updatedAt)">{{ formatDate(updatedAt) }}</span>
|
||||
</template>
|
||||
<template #user>
|
||||
<span v-if="showModeratorLabel" class="inline-flex items-center gap-1 text-orange">
|
||||
<ScaleIcon class="size-5 shrink-0" />
|
||||
{{ formatMessage(messages.updatedByModerator) }}
|
||||
</span>
|
||||
<AutoLink
|
||||
v-else-if="updatedBy"
|
||||
:to="`/user/${updatedBy.username}`"
|
||||
class="inline-flex min-w-0 max-w-full items-center gap-1 text-contrast hover:underline"
|
||||
>
|
||||
<Avatar
|
||||
v-if="updatedBy.avatar_url"
|
||||
:src="updatedBy.avatar_url"
|
||||
:alt="updatedBy.username"
|
||||
size="20px"
|
||||
class="shrink-0"
|
||||
circle
|
||||
/>
|
||||
<span class="truncate">{{ updatedBy.username }}</span>
|
||||
</AutoLink>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</div>
|
||||
</template>
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { CircleDollarSignIcon, ListPlusIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
IconButton,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type { DisclosureCardMetaProps, DisclosureLockStatus, PaidFeaturesDisclosure } from './types'
|
||||
|
||||
const model = defineModel<PaidFeaturesDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.paid-features.title',
|
||||
defaultMessage: 'Contains paid features',
|
||||
},
|
||||
description: {
|
||||
id: 'project.settings.disclosures.paid-features.description',
|
||||
defaultMessage:
|
||||
'You must enable this if your project contains features that can be obtained by spending real-world money.',
|
||||
},
|
||||
featuresDescription: {
|
||||
id: 'project.settings.disclosures.paid-features.features-description',
|
||||
defaultMessage: 'List the types of paid features',
|
||||
},
|
||||
featurePlaceholder: {
|
||||
id: 'project.settings.disclosures.paid-features.feature-placeholder',
|
||||
defaultMessage: 'e.g. Cosmetics available as Patreon reward',
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => model.value.enabled,
|
||||
(enabled) => {
|
||||
if (enabled && model.value.features.length === 0) {
|
||||
model.value.features = ['']
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function addFeature() {
|
||||
model.value.features = [...model.value.features, '']
|
||||
}
|
||||
|
||||
function removeFeature(index: number) {
|
||||
if (model.value.features.length <= 1) return
|
||||
model.value.features = model.value.features.filter((_, i) => i !== index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="CircleDollarSignIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup :title="formatMessage(messages.featuresDescription)">
|
||||
<div v-for="(_, index) in model.features" :key="index" class="flex items-center gap-2">
|
||||
<StyledInput
|
||||
v-model="model.features[index]"
|
||||
class="min-w-0 flex-1"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.featurePlaceholder)"
|
||||
/>
|
||||
<IconButton
|
||||
v-if="model.features.length > 1"
|
||||
:label="formatMessage(commonMessages.removeButton)"
|
||||
:disabled="disabled"
|
||||
@click="removeFeature(index)"
|
||||
>
|
||||
<XIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</SettingsFormGroup>
|
||||
<Button class="w-fit" :disabled="disabled" @click="addFeature">
|
||||
<ListPlusIcon />
|
||||
{{ formatMessage(commonMessages.addAnotherButton) }}
|
||||
</Button>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { EyeIcon } from '@modrinth/assets'
|
||||
import {
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type { DisclosureCardMetaProps, DisclosureLockStatus, NoteDisclosure } from './types'
|
||||
|
||||
const model = defineModel<NoteDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.photosensitivity.title',
|
||||
defaultMessage: 'Photosensitivity warning',
|
||||
},
|
||||
description: {
|
||||
id: 'project.settings.disclosures.photosensitivity.description',
|
||||
defaultMessage:
|
||||
'Enable this if your project contains anything that you think may be dangerous to certain people who are sensitive to flashing lights or patterns.',
|
||||
},
|
||||
notePlaceholder: {
|
||||
id: 'project.settings.disclosures.photosensitivity.note-placeholder',
|
||||
defaultMessage:
|
||||
'e.g. It adds a flashlight item that has a strobe mode. It can be disabled in Accessibility settings in-game.',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="EyeIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(commonMessages.explanationLabel)"
|
||||
title-for="photosensitivity-disclosure-note"
|
||||
>
|
||||
<StyledInput
|
||||
id="photosensitivity-disclosure-note"
|
||||
v-model="model.note"
|
||||
multiline
|
||||
:rows="3"
|
||||
class="max-w-[40rem]"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.notePlaceholder)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { CircuitBoardIcon } from '@modrinth/assets'
|
||||
import { defineMessages, SettingsFormGroup, StyledInput, useVIntl } from '@modrinth/ui'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type {
|
||||
DisclosureCardMetaProps,
|
||||
DisclosureLockStatus,
|
||||
SystemInteractionsDisclosure,
|
||||
} from './types'
|
||||
|
||||
const model = defineModel<SystemInteractionsDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.system-interactions.title',
|
||||
defaultMessage: 'External system interactions',
|
||||
},
|
||||
description: {
|
||||
id: 'project.settings.disclosures.system-interactions.description',
|
||||
defaultMessage:
|
||||
"You must enable this if your project reads or edits things on the user's system outside of the game.",
|
||||
},
|
||||
noteLabel: {
|
||||
id: 'project.settings.disclosures.system-interactions.note-label',
|
||||
defaultMessage: 'Describe the external system interactions',
|
||||
},
|
||||
notePlaceholder: {
|
||||
id: 'project.settings.disclosures.system-interactions.note-placeholder',
|
||||
defaultMessage: 'e.g. It adds a file to the desktop called wake_up.txt',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="CircuitBoardIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(messages.noteLabel)"
|
||||
title-for="system-interactions-disclosure-note"
|
||||
>
|
||||
<StyledInput
|
||||
id="system-interactions-disclosure-note"
|
||||
v-model="model.note"
|
||||
multiline
|
||||
:rows="3"
|
||||
class="max-w-[40rem]"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.notePlaceholder)"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ListPlusIcon, RadioTowerIcon, TrashIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
Chips,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
disclosureTelemetryConsentMessages,
|
||||
IconButton,
|
||||
SettingsFormGroup,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import DisclosureToggleCard from './DisclosureToggleCard.vue'
|
||||
import type {
|
||||
DisclosureCardMetaProps,
|
||||
DisclosureLockStatus,
|
||||
TelemetryConsent,
|
||||
TelemetryDisclosure,
|
||||
} from './types'
|
||||
|
||||
const model = defineModel<TelemetryDisclosure>({ required: true })
|
||||
|
||||
const props = defineProps<DisclosureCardMetaProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
setLockStatus: [status: DisclosureLockStatus]
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const CONSENT_MODELS: TelemetryConsent[] = ['opt_in', 'opt_out', 'always_active']
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'project.settings.disclosures.telemetry.title',
|
||||
defaultMessage: 'Contains telemetry',
|
||||
},
|
||||
description: {
|
||||
id: 'project.settings.disclosures.telemetry.description',
|
||||
defaultMessage:
|
||||
'You must enable this if your project sends usage data back to yourself or a third party.',
|
||||
},
|
||||
consentDescription: {
|
||||
id: 'project.settings.disclosures.telemetry.consent-description',
|
||||
defaultMessage: 'What is the consent model of your telemetry?',
|
||||
},
|
||||
dataLabel: {
|
||||
id: 'project.settings.disclosures.telemetry.data-label',
|
||||
defaultMessage: 'What data is collected?',
|
||||
},
|
||||
dataDescription: {
|
||||
id: 'project.settings.disclosures.telemetry.data-description',
|
||||
defaultMessage:
|
||||
'You can either add a privacy policy, or a list of types of data that are collected. Remember to mention if it is anonymous or contains personally identifiable information (PII).',
|
||||
},
|
||||
dataPlaceholder: {
|
||||
id: 'project.settings.disclosures.telemetry.data-placeholder',
|
||||
defaultMessage:
|
||||
'e.g. Anonymous launch analytics to track Minecraft version and mod loader usage.',
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => model.value.enabled,
|
||||
(enabled) => {
|
||||
if (enabled && model.value.entries.length === 0) {
|
||||
model.value.entries = ['']
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function addEntry() {
|
||||
model.value.entries = [...model.value.entries, '']
|
||||
}
|
||||
|
||||
function removeEntry(index: number) {
|
||||
if (model.value.entries.length <= 1) return
|
||||
model.value.entries = model.value.entries.filter((_, i) => i !== index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DisclosureToggleCard
|
||||
v-bind="props"
|
||||
v-model="model.enabled"
|
||||
:icon="RadioTowerIcon"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
@set-lock-status="emit('setLockStatus', $event)"
|
||||
>
|
||||
<template #expanded>
|
||||
<SettingsFormGroup :title="formatMessage(messages.consentDescription)">
|
||||
<Chips
|
||||
v-model="model.consent"
|
||||
:items="CONSENT_MODELS"
|
||||
:capitalize="false"
|
||||
:format-label="
|
||||
(item: TelemetryConsent) => formatMessage(disclosureTelemetryConsentMessages[item])
|
||||
"
|
||||
/>
|
||||
</SettingsFormGroup>
|
||||
<SettingsFormGroup
|
||||
:title="formatMessage(messages.dataLabel)"
|
||||
:description="formatMessage(messages.dataDescription)"
|
||||
>
|
||||
<div v-for="(_, index) in model.entries" :key="index" class="flex items-center gap-2">
|
||||
<StyledInput
|
||||
v-model="model.entries[index]"
|
||||
class="min-w-0 flex-1"
|
||||
:disabled="disabled"
|
||||
:placeholder="formatMessage(messages.dataPlaceholder)"
|
||||
/>
|
||||
<IconButton
|
||||
v-if="model.entries.length > 1"
|
||||
:label="formatMessage(commonMessages.removeButton)"
|
||||
:disabled="disabled"
|
||||
@click="removeEntry(index)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</SettingsFormGroup>
|
||||
<Button class="w-fit" :disabled="disabled" @click="addEntry">
|
||||
<ListPlusIcon />
|
||||
{{ formatMessage(commonMessages.addAnotherButton) }}
|
||||
</Button>
|
||||
</template>
|
||||
</DisclosureToggleCard>
|
||||
</template>
|
||||
@@ -1,322 +0,0 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
isActiveDisclosure,
|
||||
isDisclosureCompatibleWithProjectTypes,
|
||||
PROJECT_DISCLOSURE_TYPES,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import type {
|
||||
DisclosureFormState,
|
||||
DisclosureLockStatus,
|
||||
DisclosureOf,
|
||||
DisclosureType,
|
||||
NoteDisclosure,
|
||||
ProjectDisclosure,
|
||||
ProjectDisclosureData,
|
||||
} from './types'
|
||||
|
||||
function findDisclosure<T extends DisclosureType>(
|
||||
disclosures: ProjectDisclosureData[],
|
||||
type: T,
|
||||
): DisclosureOf<T> | undefined {
|
||||
return disclosures.find((disclosure): disclosure is DisclosureOf<T> => disclosure.type === type)
|
||||
}
|
||||
|
||||
type NoteDisclosureType = 'advertisements' | 'epilepsy_triggers' | 'archived'
|
||||
|
||||
function createNoteModel(
|
||||
disclosures: ProjectDisclosureData[],
|
||||
type: NoteDisclosureType,
|
||||
): NoteDisclosure {
|
||||
const disclosure = findDisclosure(disclosures, type)
|
||||
return {
|
||||
enabled: isActiveDisclosure(disclosure),
|
||||
note: disclosure?.note ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
function nonemptyOrPlaceholder(values: string[]): string[] {
|
||||
return values.length > 0 ? [...values] : ['']
|
||||
}
|
||||
|
||||
function createLockStatuses(
|
||||
disclosures: ProjectDisclosureData[],
|
||||
): Record<DisclosureType, DisclosureLockStatus> {
|
||||
const lockStatuses = Object.fromEntries(
|
||||
PROJECT_DISCLOSURE_TYPES.map((type) => [type, 'unlocked']),
|
||||
) as Record<DisclosureType, DisclosureLockStatus>
|
||||
|
||||
for (const disclosure of disclosures) {
|
||||
lockStatuses[disclosure.type] = disclosure.lock_status
|
||||
}
|
||||
|
||||
return lockStatuses
|
||||
}
|
||||
|
||||
export function disclosuresToForm(disclosures: ProjectDisclosureData[]): DisclosureFormState {
|
||||
const ai = findDisclosure(disclosures, 'ai_content')
|
||||
const paidFeatures = findDisclosure(disclosures, 'paid_features')
|
||||
const telemetry = findDisclosure(disclosures, 'telemetry')
|
||||
const derivative = findDisclosure(disclosures, 'derivative_work')
|
||||
const systemInteractions = findDisclosure(disclosures, 'system_interactions')
|
||||
|
||||
return {
|
||||
ai: {
|
||||
enabled: isActiveDisclosure(ai),
|
||||
uses: ai ? [...(ai.uses ?? [])] : [],
|
||||
note: ai?.note ?? '',
|
||||
},
|
||||
advertising: createNoteModel(disclosures, 'advertisements'),
|
||||
paidFeatures: {
|
||||
enabled: isActiveDisclosure(paidFeatures),
|
||||
features: paidFeatures ? nonemptyOrPlaceholder(paidFeatures.features) : [],
|
||||
},
|
||||
telemetry: {
|
||||
enabled: isActiveDisclosure(telemetry),
|
||||
consent: telemetry?.consent ?? 'opt_in',
|
||||
entries: telemetry ? nonemptyOrPlaceholder(telemetry.data_collected) : [],
|
||||
},
|
||||
derivative: {
|
||||
enabled: isActiveDisclosure(derivative),
|
||||
sources: derivative ? derivative.sources.map((source) => ({ ...source })) : [],
|
||||
},
|
||||
photosensitivity: createNoteModel(disclosures, 'epilepsy_triggers'),
|
||||
systemInteractions: {
|
||||
enabled: isActiveDisclosure(systemInteractions),
|
||||
note: systemInteractions?.note ?? '',
|
||||
interactions: systemInteractions ? [...systemInteractions.interactions] : [],
|
||||
},
|
||||
archived: createNoteModel(disclosures, 'archived'),
|
||||
lockStatuses: createLockStatuses(disclosures),
|
||||
}
|
||||
}
|
||||
|
||||
export function findDisclosureData<T extends DisclosureType>(
|
||||
disclosures: ProjectDisclosureData[] | undefined,
|
||||
type: T,
|
||||
): DisclosureOf<T> | undefined {
|
||||
if (!disclosures) return undefined
|
||||
return findDisclosure(disclosures, type)
|
||||
}
|
||||
|
||||
export function formToDisclosure(
|
||||
form: DisclosureFormState,
|
||||
type: DisclosureType,
|
||||
): ProjectDisclosure {
|
||||
switch (type) {
|
||||
case 'ai_content':
|
||||
return {
|
||||
type: 'ai_content',
|
||||
uses: [...form.ai.uses],
|
||||
note: form.ai.note.trim() || null,
|
||||
}
|
||||
case 'advertisements':
|
||||
return { type: 'advertisements', note: form.advertising.note.trim() || null }
|
||||
case 'paid_features':
|
||||
return {
|
||||
type: 'paid_features',
|
||||
features: form.paidFeatures.features.map((feature) => feature.trim()).filter(Boolean),
|
||||
}
|
||||
case 'telemetry':
|
||||
return {
|
||||
type: 'telemetry',
|
||||
consent: form.telemetry.consent,
|
||||
data_collected: form.telemetry.entries.map((entry) => entry.trim()).filter(Boolean),
|
||||
}
|
||||
case 'derivative_work':
|
||||
return {
|
||||
type: 'derivative_work',
|
||||
sources: form.derivative.sources.map((source) => ({
|
||||
label: source.label.trim(),
|
||||
link: source.link?.trim() || null,
|
||||
note: source.note?.trim() || null,
|
||||
})),
|
||||
}
|
||||
case 'epilepsy_triggers':
|
||||
return { type: 'epilepsy_triggers', note: form.photosensitivity.note.trim() || null }
|
||||
case 'system_interactions':
|
||||
return {
|
||||
type: 'system_interactions',
|
||||
interactions: [...form.systemInteractions.interactions],
|
||||
note: form.systemInteractions.note.trim() || null,
|
||||
}
|
||||
case 'archived':
|
||||
return { type: 'archived', note: form.archived.note.trim() || null }
|
||||
}
|
||||
}
|
||||
|
||||
export function formToDisclosures(form: DisclosureFormState): ProjectDisclosure[] {
|
||||
const set: ProjectDisclosure[] = []
|
||||
|
||||
if (form.ai.enabled) {
|
||||
set.push(formToDisclosure(form, 'ai_content'))
|
||||
}
|
||||
if (form.advertising.enabled) {
|
||||
set.push(formToDisclosure(form, 'advertisements'))
|
||||
}
|
||||
if (form.paidFeatures.enabled) {
|
||||
set.push(formToDisclosure(form, 'paid_features'))
|
||||
}
|
||||
if (form.telemetry.enabled) {
|
||||
set.push(formToDisclosure(form, 'telemetry'))
|
||||
}
|
||||
if (form.derivative.enabled) {
|
||||
set.push(formToDisclosure(form, 'derivative_work'))
|
||||
}
|
||||
if (form.photosensitivity.enabled) {
|
||||
set.push(formToDisclosure(form, 'epilepsy_triggers'))
|
||||
}
|
||||
if (form.systemInteractions.enabled) {
|
||||
set.push(formToDisclosure(form, 'system_interactions'))
|
||||
}
|
||||
if (form.archived.enabled) {
|
||||
set.push(formToDisclosure(form, 'archived'))
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
export function toModifyRequests(
|
||||
form: DisclosureFormState,
|
||||
previous: DisclosureFormState,
|
||||
): Labrinth.Projects.v3.ModifyProjectDisclosures[] {
|
||||
const next = formToDisclosures(form)
|
||||
const previousDisclosures = formToDisclosures(previous)
|
||||
const previousByType = new Map(
|
||||
previousDisclosures.map((disclosure) => [disclosure.type, disclosure]),
|
||||
)
|
||||
const nextTypes = new Set(next.map((disclosure) => disclosure.type))
|
||||
|
||||
const remove: DisclosureType[] = []
|
||||
const contentOnly: ProjectDisclosure[] = []
|
||||
const lockChangeGroups = new Map<DisclosureLockStatus, ProjectDisclosure[]>()
|
||||
const disabledLockGroups = new Map<DisclosureLockStatus, ProjectDisclosure[]>()
|
||||
|
||||
for (const disclosure of next) {
|
||||
const existing = previousByType.get(disclosure.type)
|
||||
const contentChanged = !existing || JSON.stringify(existing) !== JSON.stringify(disclosure)
|
||||
const previousLock = previous.lockStatuses[disclosure.type] ?? 'unlocked'
|
||||
const nextLock = form.lockStatuses[disclosure.type] ?? 'unlocked'
|
||||
const lockChanged = previousLock !== nextLock
|
||||
|
||||
if (!contentChanged && !lockChanged) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (lockChanged) {
|
||||
const group = lockChangeGroups.get(nextLock) ?? []
|
||||
group.push(disclosure)
|
||||
lockChangeGroups.set(nextLock, group)
|
||||
} else {
|
||||
contentOnly.push(disclosure)
|
||||
}
|
||||
}
|
||||
|
||||
for (const type of PROJECT_DISCLOSURE_TYPES) {
|
||||
if (nextTypes.has(type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const previousLock = previous.lockStatuses[type] ?? 'unlocked'
|
||||
const nextLock = form.lockStatuses[type] ?? 'unlocked'
|
||||
const lockChanged = previousLock !== nextLock
|
||||
const wasEnabled = previousByType.has(type)
|
||||
|
||||
if (wasEnabled && !lockChanged) {
|
||||
remove.push(type)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!lockChanged) {
|
||||
continue
|
||||
}
|
||||
|
||||
const group = disabledLockGroups.get(nextLock) ?? []
|
||||
group.push(formToDisclosure(form, type))
|
||||
disabledLockGroups.set(nextLock, group)
|
||||
}
|
||||
|
||||
const requests: Labrinth.Projects.v3.ModifyProjectDisclosures[] = []
|
||||
|
||||
if (remove.length > 0) {
|
||||
requests.push({ set: [], remove })
|
||||
}
|
||||
|
||||
if (contentOnly.length > 0) {
|
||||
requests.push({ set: contentOnly, remove: [] })
|
||||
}
|
||||
|
||||
for (const [lockStatus, set] of lockChangeGroups) {
|
||||
requests.push({ set, remove: [], lock_status: lockStatus })
|
||||
}
|
||||
|
||||
for (const [lockStatus, set] of disabledLockGroups) {
|
||||
requests.push({ set, remove: [], lock_status: lockStatus })
|
||||
requests.push({ set: [], remove: set.map((disclosure) => disclosure.type) })
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
||||
|
||||
export function getDisclosureFormSnapshot(form: DisclosureFormState) {
|
||||
return {
|
||||
disclosures: formToDisclosures(form),
|
||||
lockStatuses: { ...form.lockStatuses },
|
||||
}
|
||||
}
|
||||
|
||||
export type DisclosureFormIssue =
|
||||
| 'advertising-note'
|
||||
| 'paid-features-empty'
|
||||
| 'telemetry-empty'
|
||||
| 'derivative-empty'
|
||||
| 'derivative-source-label'
|
||||
| 'photosensitivity-note'
|
||||
| 'system-interactions-note'
|
||||
|
||||
export function getDisclosureFormIssues(
|
||||
form: DisclosureFormState,
|
||||
projectTypes?: readonly string[],
|
||||
): DisclosureFormIssue[] {
|
||||
const issues: DisclosureFormIssue[] = []
|
||||
const missingNote = (model: NoteDisclosure) => model.enabled && !model.note.trim()
|
||||
const compatible = (type: DisclosureType) =>
|
||||
!projectTypes || isDisclosureCompatibleWithProjectTypes(type, projectTypes)
|
||||
|
||||
if (compatible('advertisements') && missingNote(form.advertising)) {
|
||||
issues.push('advertising-note')
|
||||
}
|
||||
if (
|
||||
compatible('paid_features') &&
|
||||
form.paidFeatures.enabled &&
|
||||
!form.paidFeatures.features.some((feature) => feature.trim())
|
||||
) {
|
||||
issues.push('paid-features-empty')
|
||||
}
|
||||
if (
|
||||
compatible('telemetry') &&
|
||||
form.telemetry.enabled &&
|
||||
!form.telemetry.entries.some((entry) => entry.trim())
|
||||
) {
|
||||
issues.push('telemetry-empty')
|
||||
}
|
||||
if (compatible('derivative_work') && form.derivative.enabled) {
|
||||
if (form.derivative.sources.length === 0) {
|
||||
issues.push('derivative-empty')
|
||||
} else if (form.derivative.sources.some((source) => !source.label?.trim())) {
|
||||
issues.push('derivative-source-label')
|
||||
}
|
||||
}
|
||||
if (compatible('epilepsy_triggers') && missingNote(form.photosensitivity)) {
|
||||
issues.push('photosensitivity-note')
|
||||
}
|
||||
if (
|
||||
compatible('system_interactions') &&
|
||||
form.systemInteractions.enabled &&
|
||||
!form.systemInteractions.note.trim()
|
||||
) {
|
||||
issues.push('system-interactions-note')
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export { default as AdvertisingDisclosureCard } from './AdvertisingDisclosureCard.vue'
|
||||
export { default as AiDisclosureCard } from './AiDisclosureCard.vue'
|
||||
export { default as ArchivedDisclosureCard } from './ArchivedDisclosureCard.vue'
|
||||
export { default as DerivativeDisclosureCard } from './DerivativeDisclosureCard.vue'
|
||||
export { default as DisclosureToggleCard } from './DisclosureToggleCard.vue'
|
||||
export { default as DisclosureUpdatedBy } from './DisclosureUpdatedBy.vue'
|
||||
export * from './form'
|
||||
export { default as PaidFeaturesDisclosureCard } from './PaidFeaturesDisclosureCard.vue'
|
||||
export { default as PhotosensitivityDisclosureCard } from './PhotosensitivityDisclosureCard.vue'
|
||||
export { default as SystemInteractionsDisclosureCard } from './SystemInteractionsDisclosureCard.vue'
|
||||
export { default as TelemetryDisclosureCard } from './TelemetryDisclosureCard.vue'
|
||||
export * from './types'
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
|
||||
export type TelemetryConsent = Labrinth.Projects.v3.TelemetryConsent
|
||||
export type AiUsage = Labrinth.Projects.v3.AiUsage
|
||||
export type DerivativeSource = Labrinth.Projects.v3.DerivativeSource
|
||||
export type DisclosureLockStatus = Labrinth.Projects.v3.DisclosureLockStatus
|
||||
export type ProjectDisclosure = Labrinth.Projects.v3.ProjectDisclosure
|
||||
export type ProjectDisclosureData = Labrinth.Projects.v3.ProjectDisclosureData
|
||||
export type DisclosureType = Labrinth.Projects.v3.ProjectDisclosureType
|
||||
export type DisclosureOf<T extends DisclosureType> = Labrinth.Projects.v3.ProjectDisclosureOf<T>
|
||||
|
||||
export type NoteDisclosure = {
|
||||
enabled: boolean
|
||||
note: string
|
||||
}
|
||||
|
||||
export type SystemInteractionsDisclosure = {
|
||||
enabled: boolean
|
||||
note: string
|
||||
interactions: string[]
|
||||
}
|
||||
|
||||
export type AiDisclosure = {
|
||||
enabled: boolean
|
||||
uses: AiUsage[]
|
||||
note: string
|
||||
}
|
||||
|
||||
export type PaidFeaturesDisclosure = {
|
||||
enabled: boolean
|
||||
features: string[]
|
||||
}
|
||||
|
||||
export type TelemetryDisclosure = {
|
||||
enabled: boolean
|
||||
consent: TelemetryConsent
|
||||
entries: string[]
|
||||
}
|
||||
|
||||
export type DerivativeDisclosure = {
|
||||
enabled: boolean
|
||||
sources: DerivativeSource[]
|
||||
}
|
||||
|
||||
export type DisclosureFormState = {
|
||||
ai: AiDisclosure
|
||||
advertising: NoteDisclosure
|
||||
paidFeatures: PaidFeaturesDisclosure
|
||||
telemetry: TelemetryDisclosure
|
||||
derivative: DerivativeDisclosure
|
||||
photosensitivity: NoteDisclosure
|
||||
systemInteractions: SystemInteractionsDisclosure
|
||||
archived: NoteDisclosure
|
||||
lockStatuses: Record<DisclosureType, DisclosureLockStatus>
|
||||
}
|
||||
|
||||
export type DisclosureUpdatedByUser = {
|
||||
id: string
|
||||
username: string
|
||||
avatar_url?: string | null
|
||||
}
|
||||
|
||||
export type DisclosureCardMetaProps = {
|
||||
disabled?: boolean
|
||||
toggleDisabled?: boolean
|
||||
updatedAt?: string | null
|
||||
updatedBy?: DisclosureUpdatedByUser | null
|
||||
setByModerator?: boolean
|
||||
lockStatus?: DisclosureLockStatus | null
|
||||
showLockControls?: boolean
|
||||
}
|
||||
@@ -90,14 +90,7 @@
|
||||
</div>
|
||||
<div class="report-type">
|
||||
<Badge v-if="report.closed" type="closed" />
|
||||
<Badge
|
||||
:type="
|
||||
formatMessage(messages.reportedFor, {
|
||||
type: formatReportType(formatMessage, report.report_type),
|
||||
})
|
||||
"
|
||||
color="orange"
|
||||
/>
|
||||
<Badge :type="`Reported for ${report.report_type}`" color="orange" />
|
||||
</div>
|
||||
<div v-if="showMessage" class="markdown-body" v-html="renderHighlightedString(report.body)" />
|
||||
<ThreadSummary
|
||||
@@ -132,22 +125,12 @@
|
||||
|
||||
<script setup>
|
||||
import { BoxesIcon, ReportIcon, UnknownIcon, VersionIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
CopyCode,
|
||||
defineMessages,
|
||||
formatReportType,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { Avatar, Badge, CopyCode, useFormatDateTime, useRelativeTime } from '@modrinth/ui'
|
||||
import { formatProjectType, renderHighlightedString } from '@modrinth/utils'
|
||||
|
||||
import ThreadSummary from '~/components/ui/thread/ThreadSummary.vue'
|
||||
import { getProjectTypeForUrl } from '~/helpers/projects.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
@@ -182,13 +165,6 @@ defineProps({
|
||||
})
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const messages = defineMessages({
|
||||
reportedFor: {
|
||||
id: 'report.reported-for',
|
||||
defaultMessage: 'Reported for {type}',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
<template>
|
||||
<template v-if="moderation">
|
||||
<Chips
|
||||
v-model="reasonFilter"
|
||||
:items="reasons"
|
||||
:format-label="formatReasonLabel"
|
||||
:capitalize="false"
|
||||
/>
|
||||
<Chips v-model="reasonFilter" :items="reasons" />
|
||||
<p v-if="reports.length === MAX_REPORTS" class="text-red">
|
||||
There are at least {{ MAX_REPORTS }} open reports. This page is at its max reports and will
|
||||
not show any more recent ones.
|
||||
</p>
|
||||
<p v-else-if="reasonFilter === 'All'">There are {{ filteredReports.length }} open reports.</p>
|
||||
<p v-else>
|
||||
There are {{ filteredReports.length }}/{{ reports.length }} open '{{
|
||||
formatReportType(formatMessage, reasonFilter)
|
||||
}}' reports.
|
||||
There are {{ filteredReports.length }}/{{ reports.length }} open '{{ reasonFilter }}' reports.
|
||||
</p>
|
||||
</template>
|
||||
<ReportInfo
|
||||
@@ -31,7 +24,7 @@
|
||||
<p v-if="filteredReports.length === 0">You don't have any active reports.</p>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Chips, formatReportType, injectModrinthClient, useVIntl } from '@modrinth/ui'
|
||||
import { Chips, injectModrinthClient } from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
@@ -50,16 +43,10 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
const viewMode = ref('open')
|
||||
const reasonFilter = ref('All')
|
||||
|
||||
const formatReasonLabel = (reason) => {
|
||||
if (reason === 'All') return reason
|
||||
return formatReportType(formatMessage, reason)
|
||||
}
|
||||
|
||||
const MAX_REPORTS = 1500
|
||||
|
||||
const { data: rawReportsData } = useQuery({
|
||||
|
||||
@@ -38,6 +38,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
showProjectPageCreateServersTooltip: true,
|
||||
showProjectPageQuickServerButton: false,
|
||||
newProjectGeneralSettings: false,
|
||||
newProjectEnvironmentSettings: true,
|
||||
serverRamAsBytesAlwaysOn: false,
|
||||
archonSentryCapture: false,
|
||||
hideRussiaCensorshipBanner: false,
|
||||
@@ -57,7 +58,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
versionDevInfoCollapsed: true,
|
||||
alwaysShowVersionDevInfo: false,
|
||||
advancedFiltersCollapsed: true,
|
||||
dismissedPhotosensitivityFilterWarning: false,
|
||||
} as const)
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import {
|
||||
defineMessages,
|
||||
injectProjectPageContext,
|
||||
type MessageDescriptor,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
const messages = defineMessages({
|
||||
headTitle: {
|
||||
id: 'project.settings.head-title',
|
||||
defaultMessage: '⚙️ {section} - {project}',
|
||||
},
|
||||
})
|
||||
|
||||
export function useProjectSettingsHeadTitle(section: MessageDescriptor) {
|
||||
const { formatMessage } = useVIntl()
|
||||
const { projectV2: project } = injectProjectPageContext()
|
||||
|
||||
useHead({
|
||||
title: () =>
|
||||
`${formatMessage(messages.headTitle, {
|
||||
section: formatMessage(section),
|
||||
project: project.value.title,
|
||||
})}`,
|
||||
})
|
||||
}
|
||||
@@ -1,57 +1,15 @@
|
||||
import type { AbstractModrinthClient } from '@modrinth/api-client'
|
||||
import type { QueryClient } from '@tanstack/query-core'
|
||||
|
||||
export const STALE_TIME = 1000 * 60 * 5 // 5 minutes
|
||||
export const STALE_TIME_LONG = 1000 * 60 * 10 // 10 minutes
|
||||
|
||||
/** Anything with a canonical project ID and optional slug (search hits, full projects, etc.). */
|
||||
export type ProjectCheckIdentity = {
|
||||
id?: string | null
|
||||
project_id?: string | null
|
||||
slug?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Warm `['project', 'check', …]` so navigating to a project by slug skips the check network round-trip.
|
||||
* Safe to call with search hits, full project objects, or mixed lists.
|
||||
*/
|
||||
export function warmProjectCheckCaches(
|
||||
queryClient: QueryClient,
|
||||
projects: ProjectCheckIdentity | readonly ProjectCheckIdentity[] | null | undefined,
|
||||
) {
|
||||
const list = projects == null ? [] : Array.isArray(projects) ? projects : [projects]
|
||||
for (const project of list) {
|
||||
const id = project.id ?? project.project_id
|
||||
if (!id) continue
|
||||
queryClient.setQueryData(['project', 'check', id], { id })
|
||||
if (project.slug) {
|
||||
queryClient.setQueryData(['project', 'check', project.slug], { id })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Project query options.
|
||||
*
|
||||
* `v2` / `v3` / members / etc. must be keyed by the **canonical project ID**.
|
||||
* When you only have a route slug (or unknown id-or-slug), resolve with `check` first.
|
||||
*/
|
||||
export const projectQueryOptions = {
|
||||
/** Resolve a slug or ID to the canonical project ID. */
|
||||
check: (idOrSlug: string, client: AbstractModrinthClient) => ({
|
||||
queryKey: ['project', 'check', idOrSlug] as const,
|
||||
queryFn: () => client.labrinth.projects_v2.check(idOrSlug),
|
||||
staleTime: STALE_TIME,
|
||||
}),
|
||||
|
||||
/** @param projectId Canonical project ID (not slug) */
|
||||
v2: (projectId: string, client: AbstractModrinthClient) => ({
|
||||
queryKey: ['project', 'v2', projectId] as const,
|
||||
queryFn: () => client.labrinth.projects_v2.get(projectId),
|
||||
staleTime: STALE_TIME,
|
||||
}),
|
||||
|
||||
/** @param projectId Canonical project ID (not slug) */
|
||||
v3: (projectId: string, client: AbstractModrinthClient) => ({
|
||||
queryKey: ['project', 'v3', projectId] as const,
|
||||
queryFn: () => client.labrinth.projects_v3.get(projectId),
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { C2paSdk } from '@contentauth/c2pa-web'
|
||||
import { createC2pa, isSupportedReaderFormat } from '@contentauth/c2pa-web/inline'
|
||||
|
||||
// https://cv.iptc.org/newscodes/digitalsourcetype/
|
||||
// only detecting generative AI types
|
||||
const AI_DIGITAL_SOURCE_TYPES = [
|
||||
'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia',
|
||||
'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicData',
|
||||
'http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia',
|
||||
'http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic',
|
||||
]
|
||||
|
||||
let c2paInstance: Promise<C2paSdk> | null = null
|
||||
|
||||
function getC2pa() {
|
||||
if (!c2paInstance) {
|
||||
c2paInstance = createC2pa()
|
||||
}
|
||||
return c2paInstance
|
||||
}
|
||||
|
||||
// reference: https://github.com/contentauth/c2pa-js/blob/main/packages/c2pa-web/README.md
|
||||
export async function fileDeclaresAi(file: File): Promise<boolean> {
|
||||
try {
|
||||
if (!isSupportedReaderFormat(file.type)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const c2pa = await getC2pa()
|
||||
|
||||
const reader = await c2pa.reader.fromBlob(file.type, file)
|
||||
const manifestStore = await reader.manifestStore()
|
||||
|
||||
await reader.free()
|
||||
|
||||
const manifestJson = JSON.stringify(manifestStore)
|
||||
return AI_DIGITAL_SOURCE_TYPES.some((type) => manifestJson.includes(type))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -2375,6 +2375,12 @@
|
||||
"project.settings.permissions.sort.status": {
|
||||
"message": "Stav"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Nastavení"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Přejít na přehled projektů"
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "Verze"
|
||||
},
|
||||
|
||||
@@ -2480,6 +2480,18 @@
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Upload venligst en version først for at kunne vælge tags!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Indstillinger"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Gå til projekt kontrolpanel"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} er blevet arkiveret. {title} vil ikke modtage flere opdateringer medmindre forfatteren beslutter sig for at fjerne projektet fra arkivet."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "Kopier ID"
|
||||
},
|
||||
|
||||
@@ -3971,6 +3971,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "Du hast {count} Tags ausgewählt. Wir empfehlen dir, die Anzahl auf 12 oder weniger zu reduzieren, damit dein Server übersichtlich bleibt und leichter gefunden werden kann."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Bitte lade zuerst eine Version hoch, um Tags auswählen zu können!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Einstellungen"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Projekt-Dashboard besuchen"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} wurde archiviert. {title} wird keine weiteren Updates erhalten, es sei denn, der Autor entscheided sich, das Projekt zu Dearchivieren."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "ID kopieren"
|
||||
},
|
||||
|
||||
@@ -3971,6 +3971,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "Du hast {count} Tags ausgewählt. Wir empfehlen dir, die Anzahl auf 12 oder weniger zu reduzieren, damit dein Server übersichtlich bleibt und leichter gefunden werden kann."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Bitte lade zuerst eine Version hoch, um Tags auswählen zu können!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Einstellungen"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Projekt-Dashboard besuchen"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} wurde archiviert. {title} wird keine weiteren Updates erhalten, es sei denn, der Autor entscheidet sich das Projekt zu dearchivieren."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "ID kopieren"
|
||||
},
|
||||
|
||||
@@ -3452,12 +3452,6 @@
|
||||
"project-type.shader.singular": {
|
||||
"message": "Shader"
|
||||
},
|
||||
"project.actions.back-to-all-projects": {
|
||||
"message": "Back to all projects"
|
||||
},
|
||||
"project.actions.back-to-project-page": {
|
||||
"message": "Back to project page"
|
||||
},
|
||||
"project.actions.create-server": {
|
||||
"message": "Create a server"
|
||||
},
|
||||
@@ -3470,9 +3464,6 @@
|
||||
"project.actions.edit-project": {
|
||||
"message": "Edit project"
|
||||
},
|
||||
"project.actions.project-page": {
|
||||
"message": "Project page"
|
||||
},
|
||||
"project.actions.rescan-modpack": {
|
||||
"message": "Rescan modpack"
|
||||
},
|
||||
@@ -3488,15 +3479,6 @@
|
||||
"project.actions.servers-promo.title": {
|
||||
"message": "Create a server"
|
||||
},
|
||||
"project.ai-image-warning-modal.body.1": {
|
||||
"message": "Using AI-generated images to represent your project is not allowed. Attempting to work around detection may lead to your Modrinth account being suspended."
|
||||
},
|
||||
"project.ai-image-warning-modal.body.2": {
|
||||
"message": "See section 6 of <rules>Modrinth's Content Rules</rules> for more information on our generative AI policy."
|
||||
},
|
||||
"project.ai-image-warning-modal.title": {
|
||||
"message": "AI-generated images not allowed"
|
||||
},
|
||||
"project.collections.create-new": {
|
||||
"message": "Create new collection"
|
||||
},
|
||||
@@ -3758,201 +3740,6 @@
|
||||
"project.notification.updated.title": {
|
||||
"message": "Project updated"
|
||||
},
|
||||
"project.settings.back-to-all-projects": {
|
||||
"message": "Back to all projects"
|
||||
},
|
||||
"project.settings.back-to-project-page": {
|
||||
"message": "Back to project page"
|
||||
},
|
||||
"project.settings.danger-zone": {
|
||||
"message": "Danger zone"
|
||||
},
|
||||
"project.settings.delete-project.button": {
|
||||
"message": "Delete project"
|
||||
},
|
||||
"project.settings.delete-project.confirmation.description": {
|
||||
"message": "If you proceed, all of this project's information and all of its versions will be immediately deleted from our database. None of it is recoverable later if you change your mind. Consider just setting your project to be Private for a less permanent option."
|
||||
},
|
||||
"project.settings.delete-project.confirmation.proceed-text": {
|
||||
"message": "Permanently delete project"
|
||||
},
|
||||
"project.settings.delete-project.confirmation.title": {
|
||||
"message": "Are you sure you want to delete this project?"
|
||||
},
|
||||
"project.settings.delete-project.description.1": {
|
||||
"message": "Permanently deletes this project from Modrinth. Deleted projects <emphasis>cannot be recovered</emphasis> by Modrinth staff or support."
|
||||
},
|
||||
"project.settings.delete-project.description.2": {
|
||||
"message": "Files uploaded to this project that are actively used in Modpacks hosted on Modrinth may continue to exist."
|
||||
},
|
||||
"project.settings.delete-project.title": {
|
||||
"message": "Delete project"
|
||||
},
|
||||
"project.settings.disclosures.advertising.description.1": {
|
||||
"message": "You must enable this if your project contains advertisements, sponsorships, or promotions of other works."
|
||||
},
|
||||
"project.settings.disclosures.advertising.description.2": {
|
||||
"message": "If the promotion has no direct monetary value <emphasis>and</emphasis> it is for something that the average person would consider <italic>relevant</italic> and <italic>unobtrusive</italic> (such as a link to your Modrinth profile in the corner of the settings page for your own mod), we would not consider that an advertisement."
|
||||
},
|
||||
"project.settings.disclosures.advertising.note-placeholder": {
|
||||
"message": "e.g. Adds the Modrinth SMP server to your server list automatically."
|
||||
},
|
||||
"project.settings.disclosures.advertising.title": {
|
||||
"message": "Contains advertisements"
|
||||
},
|
||||
"project.settings.disclosures.ai.content-rules": {
|
||||
"message": "Please refer to Section 6 of <rules>Modrinth's Content Rules</rules> for more information."
|
||||
},
|
||||
"project.settings.disclosures.ai.description": {
|
||||
"message": "You must enable this if this project contains a substantial amount of AI-generated code, any\n\t\t\t\tassets that are substantially AI-generated, the project's functionality relies on the use of\n\t\t\t\tgenerative AI, or if any element of your project's page such as description or publishing\n\t\t\t\trelies on generative AI."
|
||||
},
|
||||
"project.settings.disclosures.ai.note-placeholder": {
|
||||
"message": "e.g. The Chinese and Arabic translations are AI-generated."
|
||||
},
|
||||
"project.settings.disclosures.ai.title": {
|
||||
"message": "Contains AI-generated content"
|
||||
},
|
||||
"project.settings.disclosures.ai.types-description": {
|
||||
"message": "What is generative AI being used for?"
|
||||
},
|
||||
"project.settings.disclosures.archived.banner-preview": {
|
||||
"message": "Banner preview"
|
||||
},
|
||||
"project.settings.disclosures.archived.description.1": {
|
||||
"message": "Mark your project as archived to let users know that you are no longer working on it."
|
||||
},
|
||||
"project.settings.disclosures.archived.description.2": {
|
||||
"message": "Archived projects remain discoverable when their visibility is set to Public. If you wish to de-list your project, set its visibility to Unlisted."
|
||||
},
|
||||
"project.settings.disclosures.archived.note-placeholder": {
|
||||
"message": "e.g. I don't have time to maintain this project anymore, feel free to fork it!"
|
||||
},
|
||||
"project.settings.disclosures.archived.title": {
|
||||
"message": "Archive project"
|
||||
},
|
||||
"project.settings.disclosures.content-disclosures": {
|
||||
"message": "Content disclosures"
|
||||
},
|
||||
"project.settings.disclosures.derivative.description": {
|
||||
"message": "You must enable this if your project is derivative of another project, such as being a fork or containing a substantial amount of someone else’s work."
|
||||
},
|
||||
"project.settings.disclosures.derivative.link-label": {
|
||||
"message": "Link to original work"
|
||||
},
|
||||
"project.settings.disclosures.derivative.link-placeholder": {
|
||||
"message": "https://example.com"
|
||||
},
|
||||
"project.settings.disclosures.derivative.name-label": {
|
||||
"message": "Name of original work"
|
||||
},
|
||||
"project.settings.disclosures.derivative.name-placeholder": {
|
||||
"message": "Example project"
|
||||
},
|
||||
"project.settings.disclosures.derivative.note-label": {
|
||||
"message": "Explain how your project is based on the original work"
|
||||
},
|
||||
"project.settings.disclosures.derivative.title": {
|
||||
"message": "Contains derivative content"
|
||||
},
|
||||
"project.settings.disclosures.description": {
|
||||
"message": "You must add any applicable content disclosures to your project in compliance with <rules>Modrinth's Content Rules</rules>."
|
||||
},
|
||||
"project.settings.disclosures.last-updated": {
|
||||
"message": "Last updated on {date} by {user}"
|
||||
},
|
||||
"project.settings.disclosures.last-updated.moderator": {
|
||||
"message": "Moderator"
|
||||
},
|
||||
"project.settings.disclosures.lock-status.cannot-disable.warning": {
|
||||
"message": "Please <contact-support-link>contact support</contact-support-link> if this disclosure needs to be removed."
|
||||
},
|
||||
"project.settings.disclosures.lock-status.disabled.warning": {
|
||||
"message": "This disclosure has been locked by the moderators. Please <contact-support-link>contact support</contact-support-link> if you believe this is in error."
|
||||
},
|
||||
"project.settings.disclosures.lock-status.fully-locked.warning": {
|
||||
"message": "Please <contact-support-link>contact support</contact-support-link> if this disclosure needs to be edited or removed."
|
||||
},
|
||||
"project.settings.disclosures.paid-features.description": {
|
||||
"message": "You must enable this if your project contains features that can be obtained by spending real-world money."
|
||||
},
|
||||
"project.settings.disclosures.paid-features.feature-placeholder": {
|
||||
"message": "e.g. Cosmetics available as Patreon reward"
|
||||
},
|
||||
"project.settings.disclosures.paid-features.features-description": {
|
||||
"message": "List the types of paid features"
|
||||
},
|
||||
"project.settings.disclosures.paid-features.title": {
|
||||
"message": "Contains paid features"
|
||||
},
|
||||
"project.settings.disclosures.photosensitivity.description": {
|
||||
"message": "Enable this if your project contains anything that you think may be dangerous to certain people who are sensitive to flashing lights or patterns."
|
||||
},
|
||||
"project.settings.disclosures.photosensitivity.note-placeholder": {
|
||||
"message": "e.g. It adds a flashlight item that has a strobe mode. It can be disabled in Accessibility settings in-game."
|
||||
},
|
||||
"project.settings.disclosures.photosensitivity.title": {
|
||||
"message": "Photosensitivity warning"
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.advertising-note": {
|
||||
"message": "Advertising disclosure requires explanation."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.derivative-empty": {
|
||||
"message": "Derivative works disclosure must list at least one source work."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.derivative-source-label": {
|
||||
"message": "Derivative work sources must have a name."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.no-permission": {
|
||||
"message": "You don't have permission to edit this project's disclosures."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.paid-features-empty": {
|
||||
"message": "Paid features disclosure must list at least one paid feature."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.photosensitivity-note": {
|
||||
"message": "Photosensitivity warning disclosure must include a description."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.system-interactions-note": {
|
||||
"message": "External system interactions disclosure must include a description."
|
||||
},
|
||||
"project.settings.disclosures.save-blocked.telemetry-empty": {
|
||||
"message": "Telemetry disclosure must list at least one type of data collected."
|
||||
},
|
||||
"project.settings.disclosures.system-interactions.description": {
|
||||
"message": "You must enable this if your project reads or edits things on the user's system outside of the game."
|
||||
},
|
||||
"project.settings.disclosures.system-interactions.note-label": {
|
||||
"message": "Describe the external system interactions"
|
||||
},
|
||||
"project.settings.disclosures.system-interactions.note-placeholder": {
|
||||
"message": "e.g. It adds a file to the desktop called wake_up.txt"
|
||||
},
|
||||
"project.settings.disclosures.system-interactions.title": {
|
||||
"message": "External system interactions"
|
||||
},
|
||||
"project.settings.disclosures.telemetry.consent-description": {
|
||||
"message": "What is the consent model of your telemetry?"
|
||||
},
|
||||
"project.settings.disclosures.telemetry.data-description": {
|
||||
"message": "You can either add a privacy policy, or a list of types of data that are collected. Remember to mention if it is anonymous or contains personally identifiable information (PII)."
|
||||
},
|
||||
"project.settings.disclosures.telemetry.data-label": {
|
||||
"message": "What data is collected?"
|
||||
},
|
||||
"project.settings.disclosures.telemetry.data-placeholder": {
|
||||
"message": "e.g. Anonymous launch analytics to track Minecraft version and mod loader usage."
|
||||
},
|
||||
"project.settings.disclosures.telemetry.description": {
|
||||
"message": "You must enable this if your project sends usage data back to yourself or a third party."
|
||||
},
|
||||
"project.settings.disclosures.telemetry.title": {
|
||||
"message": "Contains telemetry"
|
||||
},
|
||||
"project.settings.disclosures.upload-version-first.heading": {
|
||||
"message": "Upload versions before adding disclosures"
|
||||
},
|
||||
"project.settings.editing-project": {
|
||||
"message": "Editing {projectType} project"
|
||||
},
|
||||
"project.settings.general.name.description": {
|
||||
"message": "Avoid prefixes, suffixes, parentheticals, or added descriptions—just the project's actual name."
|
||||
},
|
||||
@@ -3998,18 +3785,6 @@
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.head-title": {
|
||||
"message": "⚙️ {section} - {project}"
|
||||
},
|
||||
"project.settings.monetization.description": {
|
||||
"message": "Projects on Modrinth are automatically enrolled in the <rewards-program-link>Rewards Program</rewards-program-link>. If you don't want to (or can't for legal reasons) earn revenue from this project, you can turn it off here."
|
||||
},
|
||||
"project.settings.monetization.disabled-description": {
|
||||
"message": "This project is not eligible for monetization. If you think this is a mistake, please <contact-support-link>contact support</contact-support-link>."
|
||||
},
|
||||
"project.settings.monetization.title": {
|
||||
"message": "Monetization"
|
||||
},
|
||||
"project.settings.permissions.attention-needed.description.proj-approved": {
|
||||
"message": "Please provide proof that you have permission to redistribute all of the following files. Once completed, withheld versions will be automatically published."
|
||||
},
|
||||
@@ -4202,8 +3977,17 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "You've selected {count} tags. Consider reducing to 12 or fewer to keep your server focused and easier to discover."
|
||||
},
|
||||
"project.settings.tags.upload-version-first.heading": {
|
||||
"message": "Upload versions before adding tags"
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Please upload a version first in order to select tags!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Settings"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Visit projects dashboard"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} has been archived. {title} will not receive any further updates unless the author decides to unarchive the project."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "Copy ID"
|
||||
@@ -4313,12 +4097,6 @@
|
||||
"report.note.malicious.2": {
|
||||
"message": "Summaries from Microsoft Defender, VirusTotal, or AI malware detection are not sufficient forms of evidence and will not be accepted."
|
||||
},
|
||||
"report.note.missing-disclosure.1": {
|
||||
"message": "Content disclosures are a new feature we've just added. We're giving creators a grace period to get their disclosures up-to-date before we begin accepting reports for missing or incorrect disclosures."
|
||||
},
|
||||
"report.note.missing-disclosure.2": {
|
||||
"message": "Check back later once the grace period has ended. Reports of this type are not being accepted at this time."
|
||||
},
|
||||
"report.please-report": {
|
||||
"message": "Please report:"
|
||||
},
|
||||
@@ -4337,9 +4115,6 @@
|
||||
"report.report-item": {
|
||||
"message": "Report {title} to moderators"
|
||||
},
|
||||
"report.reported-for": {
|
||||
"message": "Reported for {type}"
|
||||
},
|
||||
"report.submit": {
|
||||
"message": "Submit report"
|
||||
},
|
||||
|
||||
@@ -3971,6 +3971,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "Has seleccionado {count} etiquetas. Considera reducirlas a 12 o menos para mantener tu servidor enfocado y que sea más fácil de descubrir."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Por favor, ¡sube una versión primero para poder seleccionar etiquetas!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Configuración"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Ver el panel de control de proyectos"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} se ha archivado. {title} no recibirá más actualizaciones hasta que el autor decida desarchivar el proyecto."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "Copiar ID"
|
||||
},
|
||||
|
||||
@@ -3338,6 +3338,15 @@
|
||||
"project.settings.permissions.sort.status": {
|
||||
"message": "Estado"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Ajustes"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Visita el panel de proyectos"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} ha sido archivado. {title} no recibirá ninguna futura actualización excepto que el autor decida desarchivar el proyecto."
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "Versiones"
|
||||
},
|
||||
|
||||
@@ -977,6 +977,9 @@
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Asetukset"
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "Versiot"
|
||||
},
|
||||
|
||||
@@ -2408,6 +2408,15 @@
|
||||
"project.settings.permissions.sort.status": {
|
||||
"message": "Estado"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Mga Setting"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Bisitahin ang dashboard ng mga proyekto"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "Inarkibo ang {title}. Ang {title} ay hindi na tatanggap ng mga update maliban kung iwawala nila ang pagkaarkibo ng proyekto."
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "Mga bersiyon"
|
||||
},
|
||||
|
||||
@@ -3971,6 +3971,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "Vous avez sélectionné {count} tags. Considérez réduire à 12 ou moins pour garder votre serveur concis et plus facile à découvrir."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Merci d'upload une version en premier afin de pouvoir sélectionner des tags !"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Paramètres"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Visiter le tableau de contrôle des projets"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} a été archivé. {title} ne recevra plus de mises à jour jusqu'à ce que l'auteur du projet décide de désarchiver le projet."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "Copier l'ID"
|
||||
},
|
||||
|
||||
@@ -2021,6 +2021,15 @@
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "הגדרות"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "ללוח הבקרה של הפרויקטים"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "הפרויקט {title} הועבר לארכיון. {title} לא יקבל עדכונים נוספים, אלא אם המחבר יחליט להוציא את הפרויקט מהארכיון."
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "גרסאות"
|
||||
},
|
||||
|
||||
@@ -3677,6 +3677,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "{count} címkét választottál ki. Fontold meg, hogy 12-re vagy annál kevesebbre csökkentsd a számukat, hogy a szervered célzott maradjon és könnyebben megtalálható legyen."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "A címkék kiválasztásához előbb tölts fel egy verziót!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Beállítások"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Projektek irányítópult megnyitása"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "A(z) {title} archiválásra került. A(z) {title} nem kap további frissítéseket, kivéve, ha a fejlesztő úgy dönt, hogy visszavonja a projekt archiválását."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "Azonosító másolása"
|
||||
},
|
||||
|
||||
@@ -2411,6 +2411,15 @@
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Pengaturan"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Kunjungi dasbor proyek"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} telah diarsip. {title} tidak akan menerima pembaruan di masa mendatang kecuali pembuat memutuskan untuk membuka arsip proyek ini."
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "Versi"
|
||||
},
|
||||
|
||||
@@ -3950,6 +3950,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "Hai selezionato {count} tag. Dovresti tenerne al massimo 12, così che il tuo server rimanga più facile da individuare."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Carica una vesione prima di selezionare i tag!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Impostazioni"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Visita bacheca del progetto"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} è stato archiviato. {title} non riceverà più aggiornamenti a meno che l'autore decida di rimuoverlo dall'archivio."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "Copia ID"
|
||||
},
|
||||
|
||||
@@ -3062,6 +3062,15 @@
|
||||
"project.settings.permissions.sort.status": {
|
||||
"message": "ステータス"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "設定"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "プロジェクトダッシュボードを見る"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} はアーカイブされました。作者がプロジェクトのアーカイブを解除しない限り、今後 {title} に更新はありません。"
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "バージョン"
|
||||
},
|
||||
|
||||
@@ -3953,6 +3953,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "선택한 태그가 {count}개입니다. 서버를 더 집중적이고 검색하기 쉽게 만들려면 태그를 12개 이하로 줄이는 것이 좋습니다."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "먼저 버전을 업로드해야 태그를 선택할 수 있습니다!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "설정"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "프로젝트 대시보드 방문"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} 은(는) 보관되었습니다. 작성자가 프로젝트 보관 해제를 결정하지 않는 한 더 이상 업데이트가 제공되지 않습니다."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "ID 복사"
|
||||
},
|
||||
|
||||
@@ -3059,6 +3059,15 @@
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tag"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Tetapan"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Kunjungi papan pemuka projek"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} telah diarkibkan. {title} tidak akan menerima sebarang kemas kini lanjut melainkan pengarang memutuskan untuk menyaharkibkan projek."
|
||||
},
|
||||
"project.versions.share-option": {
|
||||
"message": "Kongsi"
|
||||
},
|
||||
|
||||
@@ -3968,6 +3968,18 @@
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "Je hebt {count} tags geselecteerd. Overweeg om dit aantal terug te brengen tot 12 of minder, zodat je project gericht blijft en gemakkelijker te vinden is."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Upload eerst een versie om tags te kunnen selecteren!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Instellingen"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Bezoek het project dashboard"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} is gearchiveerd. {title} zal geen verdere updates krijgen tenzij de auteur het project de-archiveerd."
|
||||
},
|
||||
"project.versions.copy-id-option": {
|
||||
"message": "ID kopiëren"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user