refactor: content tab styling (#7091)

* refactor: managed content card

* fix: update filter pill styling to be chips-like

* fix: modpack exporting

* fixed: vanilla server projs + header

* refactor: filters

* refactor: detect external file icons + other metadata inside

* refactor: support json5 metadata

* fix: qa

* fix: qa

* fix: fmt + prepr

* fix: fmt

* fix: qa

* fix: headers

* feat: locking

* fix: clippy

* fix: qa

* fix: frozen loc

* fix: shared instances diff calc break

* fix: env filter + remove categories
This commit is contained in:
Calum H.
2026-08-13 14:55:35 +00:00
committed by GitHub
parent e087e571f3
commit 06b7c44dcc
126 changed files with 3654 additions and 3473 deletions
@@ -33,19 +33,15 @@ import {
getStoredServerAddonInstallQueue,
getTargetInstallPreferences,
} from '../../../shared/browse-tab/composables/install-logic'
import ManagedContentModal from '../../../shared/content-tab/components/managed-content-modal/index.vue'
import ConfirmModpackUpdateModal from '../../../shared/content-tab/components/modals/ConfirmModpackUpdateModal.vue'
import ConfirmUnlinkModal from '../../../shared/content-tab/components/modals/ConfirmUnlinkModal.vue'
import ContentUpdaterModal from '../../../shared/content-tab/components/modals/content-updater-modal/index.vue'
import ModpackContentModal from '../../../shared/content-tab/components/modals/ModpackContentModal.vue'
import ContentPageLayout from '../../../shared/content-tab/layout.vue'
import type { ContentModpackData } from '../../../shared/content-tab/providers/content-manager'
import type { ManagedContentData } from '../../../shared/content-tab/providers/content-manager'
import { provideContentManager } from '../../../shared/content-tab/providers/content-manager'
import type {
ContentItem,
ContentModpackCardCategory,
ContentModpackCardProject,
ContentModpackCardVersion,
} from '../../../shared/content-tab/types'
import type { ContentItem } from '../../../shared/content-tab/types'
import { summarizeManagedContent } from '../../../shared/content-tab/utils/managed-content'
type AddonWithUiState = Archon.Content.v1.Addon & { installing?: boolean }
type ContentOwnerAvatarSource = {
@@ -65,6 +61,10 @@ const props = withDefaults(
const { formatMessage } = useVIntl()
const messages = defineMessages({
modpackContent: {
id: 'hosting.content.managed-content.modpack-header',
defaultMessage: 'Modpack content',
},
failedToRemoveContent: {
id: 'hosting.content.failed-to-remove',
defaultMessage: 'Failed to remove content',
@@ -172,19 +172,21 @@ const modpackContentQuery = useQuery({
})
const setupActionDisabled = computed(() => !canSetup.value || busyReasons.value.length > 0)
const setupActionBusyMessage = computed(() => {
if (!canSetup.value) return permissionDeniedMessage.value
const bannerCoversInstalling =
const isInstallingContent = computed(
() =>
server.value?.status === 'installing' ||
isSyncingContent.value ||
busyReasons.value.some(
(r) =>
r.reason.id === 'servers.busy.installing' || r.reason.id === 'servers.busy.syncing-content',
)
),
)
const setupActionBusyMessage = computed(() => {
if (!canSetup.value) return permissionDeniedMessage.value
const filteredReasons = busyReasons.value.filter((r) => {
if (
bannerCoversInstalling &&
isInstallingContent.value &&
(r.reason.id === 'servers.busy.installing' || r.reason.id === 'servers.busy.syncing-content')
)
return false
@@ -256,54 +258,38 @@ const newestModpackUpdateVersion = computed(() => {
)
})
const modpack = computed<ContentModpackData | null>(() => {
const managedContent = computed<ManagedContentData | null>(() => {
const mp = contentQuery.data.value?.modpack
if (!mp) return null
const isLocal = mp.spec.platform === 'local_file'
const project = projectQuery.data.value
const projectId = isLocal ? null : mp.spec.project_id
return {
project: {
id: projectId ?? mp.title ?? '',
slug: project?.slug ?? projectId ?? '',
title: mp.title ?? (isLocal ? mp.spec.name : projectId) ?? '',
icon_url: mp.icon_url ?? undefined,
description: mp.description ?? '',
downloads: mp.downloads,
followers: mp.followers,
filename: isLocal ? mp.spec.filename : undefined,
} as ContentModpackCardProject,
projectLink: projectId ? `/project/${project?.slug ?? projectId}` : undefined,
version: isLocal
const addons = modpackContentQuery.data.value?.addons
const summary = addons
? summarizeManagedContent(addons.map(addonToContentItem))
: modpackContentQuery.isLoading.value
? undefined
: ({
id: mp.spec.version_id,
version_number: mp.version_number ?? '',
date_published: mp.date_published ?? '',
} as ContentModpackCardVersion),
versionLink:
projectId && !isLocal
? `/project/${project?.slug ?? projectId}/version/${mp.spec.version_id}`
: undefined,
owner: mp.owner
? {
id: mp.owner.id,
name: mp.owner.name,
type: mp.owner.type,
avatar_url: getContentOwnerAvatarUrl(mp.owner),
link:
mp.owner.type === 'organization'
? `/organization/${mp.owner.id}`
: `/user/${mp.owner.id}`,
}
: undefined,
categories: (project?.categories ?? []).map((name) => ({
name,
icon: name,
project_type: 'modpack',
header: 'categories',
})) as ContentModpackCardCategory[],
hasUpdate: !!mp.has_update || !!newestModpackUpdateVersion.value,
: []
const title = isLocal
? (mp.title ?? mp.spec.name)
: (project?.title ?? mp.title ?? projectId ?? '')
return {
card: {
kind: 'modpack',
installing: isInstallingContent.value,
manager: {
name: title,
iconUrl: (isLocal ? mp.icon_url : (project?.icon_url ?? mp.icon_url)) ?? undefined,
link: projectId ? `/project/${project?.slug ?? projectId}` : undefined,
},
summary,
versionNumber: isLocal ? undefined : (mp.version_number ?? undefined),
versionLink:
projectId && mp.spec.platform === 'modrinth'
? `/project/${project?.slug ?? projectId}/version/${mp.spec.version_id}`
: undefined,
updatedAt: isLocal ? undefined : (mp.date_published ?? undefined),
},
}
})
@@ -329,6 +315,31 @@ const addonLookup = computed(() => {
})
const pendingServerContentInstalls = ref<PendingServerContentInstall[]>([])
const projectMetadataBatchSize = 800
const contentProjectIds = computed(() =>
[...(contentQuery.data.value?.addons ?? []), ...modpackAddons.value]
.map((addon) => addon.project_id)
.concat(pendingServerContentInstalls.value.map((item) => item.projectId))
.filter((id): id is string => !!id)
.filter((id, index, ids) => ids.indexOf(id) === index)
.sort(),
)
const contentProjectsQuery = useQuery({
queryKey: computed(() => ['labrinth', 'projects', 'v2', contentProjectIds.value]),
queryFn: async () => {
const batches = []
for (let index = 0; index < contentProjectIds.value.length; index += projectMetadataBatchSize) {
batches.push(contentProjectIds.value.slice(index, index + projectMetadataBatchSize))
}
return (
await Promise.all(batches.map((ids) => client.labrinth.projects_v2.getMultiple(ids)))
).flat()
},
enabled: computed(() => contentProjectIds.value.length > 0),
})
const contentProjectsById = computed(
() => new Map((contentProjectsQuery.data.value ?? []).map((project) => [project.id, project])),
)
const lastStableContentKeys = ref<Set<string>>(new Set())
const contentInstallBaselineKeys = ref<Set<string> | null>(null)
const contentInstallAddedKeys = ref<Set<string>>(new Set())
@@ -506,12 +517,14 @@ async function flushStoredServerInstalls() {
}
function pendingInstallToContentItem(item: PendingServerContentInstall): ContentItem {
const projectMetadata = contentProjectsById.value.get(item.projectId)
return {
project: {
...(projectMetadata ?? {}),
id: item.projectId,
slug: item.slug ?? item.projectId,
title: item.title,
icon_url: item.iconUrl ?? undefined,
slug: item.slug ?? projectMetadata?.slug ?? item.projectId,
title: projectMetadata?.title ?? item.title,
icon_url: item.iconUrl ?? projectMetadata?.icon_url ?? undefined,
},
version: {
id: item.versionId,
@@ -854,7 +867,7 @@ async function handleBulkDisable(items: ContentItem[]) {
}
const modpackUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal>>()
const modpackContentModal = ref<InstanceType<typeof ManagedContentModal>>()
const contentUpdaterModal = ref<InstanceType<typeof ContentUpdaterModal>>()
const updatingProject = ref<ContentItem | null>(null)
@@ -1002,12 +1015,16 @@ function handleUnknownFileContinue(dontShowAgain: boolean) {
}
function addonToContentItem(addon: AddonWithUiState): ContentItem {
const projectMetadata = addon.project_id
? contentProjectsById.value.get(addon.project_id)
: undefined
return {
project: {
...(projectMetadata ?? {}),
id: addon.project_id ?? addon.filename,
slug: addon.project_id ?? addon.filename,
title: friendlyAddonName(addon),
icon_url: addon.icon_url ?? undefined,
slug: projectMetadata?.slug ?? addon.project_id ?? addon.filename,
title: projectMetadata?.title ?? friendlyAddonName(addon),
icon_url: addon.icon_url ?? projectMetadata?.icon_url ?? undefined,
},
version: {
id: addon.version?.id ?? addon.filename,
@@ -1378,7 +1395,7 @@ provideContentManager({
items: contentItems,
loading: computed(() => contentQuery.isLoading.value),
error: computed(() => contentQuery.error.value ?? null),
modpack,
managedContent,
isPackLocked: ref(false),
isBusy: setupActionDisabled,
busyMessage: setupActionBusyMessage,
@@ -1396,13 +1413,14 @@ provideContentManager({
browse: handleBrowseContent,
uploadFiles: handleUploadFiles,
deletionContext: 'server',
showEnvironmentWarnings: true,
hasUpdateSupport: true,
updateItem: handleUpdateItem,
bulkUpdateItems: handleBulkUpdate,
updateModpack: handleModpackUpdate,
viewModpackContent: handleViewModpackContent,
runManagedContentPrimaryAction: handleModpackUpdate,
viewManagedContent: handleViewModpackContent,
unlinkModpack: handleModpackUnlink,
openSettings: () => openServerSettings({ tabId: 'installation' }),
openManagedContentSettings: () => openServerSettings({ tabId: 'installation' }),
switchVersion: handleSwitchVersion,
getOverflowOptions,
getItemId: getContentItemId,
@@ -1423,6 +1441,7 @@ provideContentManager({
owner: item.owner
? { ...item.owner, link: item.owner.link ?? `/${item.owner.type}/${item.owner.id}` }
: undefined,
external: item.external ?? !hasModrinthProject,
enabled: item.enabled,
}
},
@@ -1448,11 +1467,13 @@ provideContentManager({
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
@unlink="handleModpackUnlinkConfirm"
/>
<ModpackContentModal
<ManagedContentModal
ref="modpackContentModal"
:modpack-name="modpack?.project.title"
:modpack-icon-url="modpack?.project.icon_url"
:source-name="managedContent?.card.manager.name"
:source-icon-url="managedContent?.card.manager.iconUrl"
:header="formatMessage(messages.modpackContent)"
enable-toggle
show-environment-warnings
:action-disabled="setupActionDisabled"
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
@update:enabled="handleModpackContentToggle"
@@ -1476,11 +1497,13 @@ provideContentManager({
:is-app="false"
:project-type="updatingModpack ? 'modpack' : updatingProject?.project_type"
:project-icon-url="
updatingModpack ? modpack?.project.icon_url : updatingProject?.project?.icon_url
updatingModpack
? managedContent?.card.manager.iconUrl
: updatingProject?.project?.icon_url
"
:project-name="
updatingModpack
? (modpack?.project.title ?? formatMessage(commonMessages.modpackLabel))
? (managedContent?.card.manager.name ?? formatMessage(commonMessages.modpackLabel))
: (updatingProject?.project?.title ?? updatingProject?.file_name)
"
:loading="loadingVersions"
@@ -1499,7 +1522,7 @@ provideContentManager({
ref="modpackUpdateModal"
:downgrade="isModpackUpdateDowngrade"
:backup-tip="
[modpack?.project.title, pendingModpackUpdateVersion?.version_number]
[managedContent?.card.manager.name, pendingModpackUpdateVersion?.version_number]
.filter(Boolean)
.join(' ')
"