mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 02:24:56 +00:00
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:
@@ -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(' ')
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user