diff --git a/apps/app-frontend/src/components/ui/instance-page-header/index.vue b/apps/app-frontend/src/components/ui/instance-page-header/index.vue
index 872db1ae5b..15da280355 100644
--- a/apps/app-frontend/src/components/ui/instance-page-header/index.vue
+++ b/apps/app-frontend/src/components/ui/instance-page-header/index.vue
@@ -15,12 +15,12 @@
Shared
+
diff --git a/apps/app-frontend/src/components/ui/instance_settings/SharingSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/SharingSettings.vue
index 8a870096c2..0886c82e3c 100644
--- a/apps/app-frontend/src/components/ui/instance_settings/SharingSettings.vue
+++ b/apps/app-frontend/src/components/ui/instance_settings/SharingSettings.vue
@@ -19,7 +19,7 @@
@@ -172,11 +172,11 @@ const messages = defineMessages({
},
activeInvitesDescription: {
id: 'instance.settings.sharing.active-invites.description',
- defaultMessage: 'Anyone with one of these invite codes can join while it remains active.',
+ defaultMessage: 'Anyone with one of these invite links can join while it remains active.',
},
inviteCodeLabel: {
id: 'instance.settings.sharing.active-invites.code',
- defaultMessage: 'Invite code',
+ defaultMessage: 'Invite link',
},
usesLabel: {
id: 'instance.settings.sharing.active-invites.uses',
diff --git a/apps/app-frontend/src/components/ui/shared-instances/ConfirmRevokeSharedInstanceInviteModal.vue b/apps/app-frontend/src/components/ui/shared-instances/ConfirmRevokeSharedInstanceInviteModal.vue
index 37db5502af..e373ce7b93 100644
--- a/apps/app-frontend/src/components/ui/shared-instances/ConfirmRevokeSharedInstanceInviteModal.vue
+++ b/apps/app-frontend/src/components/ui/shared-instances/ConfirmRevokeSharedInstanceInviteModal.vue
@@ -70,7 +70,7 @@ const messages = defineMessages({
admonitionBody: {
id: 'instance.settings.sharing.revoke-invite.admonition-body',
defaultMessage:
- 'The invite code {code} will stop working immediately. People who already joined will keep access.',
+ 'The invite link {code} will stop working immediately. People who already joined will keep access.',
},
revokeButton: {
id: 'instance.settings.sharing.revoke-invite.confirm',
diff --git a/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/index.vue b/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/index.vue
index a3fe639bf3..19a498942b 100644
--- a/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/index.vue
+++ b/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/index.vue
@@ -70,10 +70,7 @@
:values="{ username: creator.username }"
>
-
+
-
+
diff --git a/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/use-shared-instance-preview-content.ts b/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/use-shared-instance-preview-content.ts
index 671b4b1899..3646f87592 100644
--- a/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/use-shared-instance-preview-content.ts
+++ b/apps/app-frontend/src/components/ui/shared-instances/shared-instance-install-modal/use-shared-instance-preview-content.ts
@@ -1,7 +1,7 @@
import type { Labrinth } from '@modrinth/api-client'
import type { ContentItem } from '@modrinth/ui'
-import { get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
+import { get_project, get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import type { SharedInstanceInstallPreview } from '@/helpers/install'
type VersionDependency = Labrinth.Versions.v2.Dependency & { version_id?: string }
@@ -20,7 +20,18 @@ export function useSharedInstancePreviewContent() {
async function modpackContentItems(preview: SharedInstanceInstallPreview) {
if (!preview.modpackVersionId) return []
const version = await get_version(preview.modpackVersionId, 'must_revalidate')
- return await contentItemsFromDependencies(version?.dependencies ?? [])
+ if (!version) return []
+
+ const [project, contentItems] = await Promise.all([
+ get_project(version.project_id, 'must_revalidate'),
+ contentItemsFromDependencies(version.dependencies ?? []),
+ ])
+ if (!project) return contentItems
+
+ return contentItems.map((item) => ({
+ ...item,
+ source: { project },
+ }))
}
async function contentItemsFromDependencies(dependencies: Labrinth.Versions.v2.Dependency[]) {
diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json
index 85f1397043..da2ca26ef4 100644
--- a/apps/app-frontend/src/locales/en-US/index.json
+++ b/apps/app-frontend/src/locales/en-US/index.json
@@ -623,6 +623,9 @@
"app.modal.install-to-play.invite-warning": {
"message": "This invite was created by another Modrinth user, not Modrinth. Only accept invites from people you trust."
},
+ "app.modal.install-to-play.invite-warning-with-creator": {
+ "message": "This invite was created by {username}, not Modrinth. Only accept invites from people you trust."
+ },
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
@@ -1194,10 +1197,10 @@
"message": "Actions"
},
"instance.settings.sharing.active-invites.code": {
- "message": "Invite code"
+ "message": "Invite link"
},
"instance.settings.sharing.active-invites.description": {
- "message": "Anyone with one of these invite codes can join while it remains active."
+ "message": "Anyone with one of these invite links can join while it remains active."
},
"instance.settings.sharing.active-invites.empty": {
"message": "There are no active invites."
@@ -1218,7 +1221,7 @@
"message": "Uses"
},
"instance.settings.sharing.revoke-invite.admonition-body": {
- "message": "The invite code {code} will stop working immediately. People who already joined will keep access."
+ "message": "The invite link {code} will stop working immediately. People who already joined will keep access."
},
"instance.settings.sharing.revoke-invite.admonition-header": {
"message": "This action cannot be undone"
diff --git a/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue b/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue
index 247ba590e3..6e2a91f743 100644
--- a/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue
+++ b/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue
@@ -604,13 +604,18 @@ async function loadSharedInstanceVersionContent(
dependency.project_id ? [dependency.project_id] : [],
)
const projectIds = [
- ...new Set([...versions.map((version) => version.project_id), ...dependencyProjectIds]),
+ ...new Set([
+ ...versions.map((version) => version.project_id),
+ ...dependencyProjectIds,
+ ...(modpackVersion ? [modpackVersion.project_id] : []),
+ ]),
]
const projects = projectIds.length
? await client.labrinth.projects_v2.getMultiple(projectIds)
: []
const versionsById = new Map(versions.map((version) => [version.id, version]))
const projectsById = new Map(projects.map((project) => [project.id, project]))
+ const modpackProject = modpackVersion ? projectsById.get(modpackVersion.project_id) : undefined
const directContent: ContentItem[] = [...new Set(directVersionIds)].flatMap((versionId) => {
const version = versionsById.get(versionId)
@@ -633,13 +638,19 @@ async function loadSharedInstanceVersionContent(
const fileName =
primaryFile?.filename ?? dependency.file_name ?? project?.title ?? version?.name ?? 'Unknown'
- return sharedInstanceContentItem(
+ const item = sharedInstanceContentItem(
version,
project,
fileName,
dependency.project_id ?? fileName,
!project && !version,
)
+ return modpackProject
+ ? {
+ ...item,
+ source: { project: modpackProject },
+ }
+ : item
})
const externalContent: ContentItem[] = instanceVersion.external_files.map((file, index) => ({
diff --git a/packages/app-lib/src/api/instance/icon.rs b/packages/app-lib/src/api/instance/icon.rs
index e56dd9fd0b..f6d13b33e2 100644
--- a/packages/app-lib/src/api/instance/icon.rs
+++ b/packages/app-lib/src/api/instance/icon.rs
@@ -224,12 +224,11 @@ fn normalize_svg_from_path(icon_path: &Path) -> crate::Result {
))
})?;
if metadata.len() > INSTANCE_ICON_MAX_SVG_SOURCE_BYTES {
- return Err(crate::ErrorKind::InputError(format!(
- "SVG instance icons cannot exceed {} bytes before normalization",
- INSTANCE_ICON_MAX_SVG_SOURCE_BYTES
- ))
- .into());
- }
+ return Err(crate::ErrorKind::InputError(format!(
+ "SVG instance icons cannot exceed {INSTANCE_ICON_MAX_SVG_SOURCE_BYTES} bytes before normalization"
+ ))
+ .into());
+ }
let bytes = std::fs::read(icon_path).map_err(|error| {
crate::ErrorKind::InputError(format!(
@@ -245,15 +244,16 @@ fn normalize_svg(
resources_dir: Option<&Path>,
) -> crate::Result {
if bytes.len() as u64 > INSTANCE_ICON_MAX_SVG_SOURCE_BYTES {
- return Err(crate::ErrorKind::InputError(format!(
- "SVG instance icons cannot exceed {} bytes before normalization",
- INSTANCE_ICON_MAX_SVG_SOURCE_BYTES
- ))
- .into());
- }
+ return Err(crate::ErrorKind::InputError(format!(
+ "SVG instance icons cannot exceed {INSTANCE_ICON_MAX_SVG_SOURCE_BYTES} bytes before normalization"
+ ))
+ .into());
+ }
- let mut options = resvg::usvg::Options::default();
- options.resources_dir = resources_dir.map(Path::to_path_buf);
+ let mut options = resvg::usvg::Options {
+ resources_dir: resources_dir.map(Path::to_path_buf),
+ ..Default::default()
+ };
options.fontdb_mut().load_system_fonts();
let tree =
resvg::usvg::Tree::from_data(bytes, &options).map_err(|error| {
@@ -308,9 +308,8 @@ fn has_svg_extension(path: &Path) -> bool {
}
fn icon_too_large_error() -> crate::Error {
- crate::ErrorKind::InputError(format!(
- "Instance icons cannot exceed {} bytes",
- INSTANCE_ICON_MAX_BYTES
- ))
+ crate::ErrorKind::InputError(format!(
+ "Instance icons cannot exceed {INSTANCE_ICON_MAX_BYTES} bytes"
+ ))
.into()
}
diff --git a/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue b/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue
index 831866e867..930615c2f5 100644
--- a/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue
+++ b/packages/ui/src/layouts/shared/content-tab/components/ContentCardItem.vue
@@ -29,6 +29,7 @@ import type {
ContentCardProject,
ContentCardVersion,
ContentOwner,
+ ContentSource,
} from '../types'
const { formatMessage } = useVIntl()
@@ -46,6 +47,7 @@ interface Props {
version?: ContentCardVersion
versionLink?: string | RouteLocationRaw
owner?: ContentOwner
+ source?: ContentSource
enabled?: boolean
installing?: boolean
hasUpdate?: boolean
@@ -68,6 +70,7 @@ const props = withDefaults(defineProps(), {
version: undefined,
versionLink: undefined,
owner: undefined,
+ source: undefined,
enabled: undefined,
installing: false,
hasUpdate: false,
@@ -196,8 +199,32 @@ const deleteHovered = ref(false)
+
+
+
+
+ {{ source.project.title }}
+
+
+
(() =>
link: `https://modrinth.com/${item.owner.type}/${item.owner.id}`,
}
: undefined,
+ source: item.source
+ ? {
+ ...item.source,
+ link: item.source.link ?? sourceProjectLink(item.source.project),
+ }
+ : undefined,
...(props.enableToggle ? { enabled: item.enabled } : {}),
installing: item.installing === true,
toggleDisabled: props.actionDisabled,
@@ -290,7 +298,7 @@ const tableItems = computed(() =>
})),
)
const externalItemIds = computed(
- () => new Set(items.value.filter((item) => item.external).map((item) => item.id)),
+ () => new Set(items.value.filter((item) => item.external && !item.source).map((item) => item.id)),
)
const externalSlicerUrls = computed(() => {
const urls: Record = {}
@@ -332,6 +340,12 @@ function itemDisplayName(item: ContentItem) {
return item.project?.title ?? item.file_name
}
+function sourceProjectLink(project: ContentCardProject) {
+ const projectId = project.slug ?? project.id
+ const url = `https://modrinth.com/modpack/${encodeURIComponent(projectId)}`
+ return pageContext ? () => pageContext.openExternalUrl(url) : url
+}
+
function handleEnabledChange(id: string, value: boolean) {
if (props.actionDisabled) return
const item = items.value.find((item) => item.id === id)
diff --git a/packages/ui/src/layouts/shared/content-tab/types.ts b/packages/ui/src/layouts/shared/content-tab/types.ts
index c92f8216a9..b643787f58 100644
--- a/packages/ui/src/layouts/shared/content-tab/types.ts
+++ b/packages/ui/src/layouts/shared/content-tab/types.ts
@@ -21,6 +21,11 @@ export interface ContentOwner {
link?: string | RouteLocationRaw | (() => void)
}
+export interface ContentSource {
+ project: ContentCardProject
+ link?: string | RouteLocationRaw | (() => void)
+}
+
export type ClientWarningType = 'retained' | 'depends' | 'environment'
export type ContentSourceKind =
@@ -44,6 +49,7 @@ export interface ContentCardTableItem {
version?: ContentCardVersion
versionLink?: string | RouteLocationRaw
owner?: ContentOwner
+ source?: ContentSource
enabled?: boolean
disabled?: boolean
disabledTooltip?: string | null