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
@@ -1,17 +1,10 @@
import type {
ContentItem,
ContentModpackCardCategory,
ContentModpackCardProject,
ContentModpackCardVersion,
ContentOwner,
} from '@modrinth/ui'
import type { ContentItem, ManagedContentProject, ManagedContentVersion } 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 = {
@@ -21,11 +14,8 @@ export type InstanceContentData = {
}
export type InstanceContentModpackData = {
project: ContentModpackCardProject
version: ContentModpackCardVersion
owner: ContentOwner | null
categories: ContentModpackCardCategory[]
hasUpdate: boolean
project: ManagedContentProject
version: ManagedContentVersion
updateVersionId: string | null
}
@@ -34,19 +24,15 @@ export async function loadInstanceContentData(
cacheBehaviour?: CacheBehaviour,
onError?: (error: Error) => unknown,
): Promise<InstanceContentData> {
const [contentItems, modpackInfo, allCategories] = await Promise.all([
const [contentItems, modpackInfo] = 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,
allCategories as ContentModpackCardCategory[] | null | undefined,
),
modpack: normalizeLinkedModpackInfo(modpackInfo as LinkedModpackInfo | null | undefined),
}
}
@@ -58,7 +44,6 @@ function handleLoadError(error: unknown, onError?: (error: Error) => unknown) {
function normalizeLinkedModpackInfo(
modpackInfo: LinkedModpackInfo | null | undefined,
allCategories: ContentModpackCardCategory[] | null | undefined,
): InstanceContentModpackData | null {
if (!modpackInfo) return null
@@ -72,30 +57,6 @@ 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
})
}
+42 -6
View File
@@ -5,7 +5,7 @@
*/
import type { Labrinth } from '@modrinth/api-client'
import type { ContentItem, ContentOwner } from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core'
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
import type { InstallJobSnapshot, SharedInstanceUpdateDiff } from './install'
import type {
@@ -74,7 +74,11 @@ export async function get_content_items(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_content_items', { instanceId, cacheBehaviour })
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_content_items', {
instanceId,
cacheBehaviour,
})
return adaptContentItems(items)
}
export async function refresh_content_updates(instanceId: string): Promise<void> {
@@ -111,10 +115,11 @@ export async function get_linked_modpack_content(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_linked_modpack_content', {
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_linked_modpack_content', {
instanceId,
cacheBehaviour,
})
return adaptContentItems(items)
}
// Convert a list of dependencies into ContentItems with rich metadata
@@ -122,9 +127,28 @@ export async function get_dependencies_as_content_items(
dependencies: Labrinth.Versions.v3.Dependency[],
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_dependencies_as_content_items', {
dependencies,
cacheBehaviour,
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),
},
}
})
}
@@ -264,6 +288,18 @@ 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
View File
@@ -128,6 +128,7 @@ export type ContentSourceKind =
type ContentFile = {
enabled: boolean
locked: boolean
source_kind?: ContentSourceKind | null
metadata?: {
project_id: string