mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
fix navtabs issues and unify project lists (#7245)
* fix navtabs issues and unify project lists * move page navtab logic to component
This commit is contained in:
@@ -25,8 +25,8 @@ export async function get_user_profile(userId: string): Promise<Labrinth.Users.v
|
||||
return await invoke<Labrinth.Users.v3.User>('plugin:users|get_user_profile', { userId })
|
||||
}
|
||||
|
||||
export async function get_user_projects(userId: string): Promise<Labrinth.Projects.v2.Project[]> {
|
||||
return await invoke<Labrinth.Projects.v2.Project[]>('plugin:users|get_user_projects', {
|
||||
export async function get_user_projects(userId: string): Promise<Labrinth.Projects.v3.Project[]> {
|
||||
return await invoke<Labrinth.Projects.v3.Project[]>('plugin:users|get_user_projects', {
|
||||
userId,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
>
|
||||
<template #project-actions="{ project }">
|
||||
<Button
|
||||
v-if="project.minecraft_server == null"
|
||||
type="outlined"
|
||||
class="!text-brand [&>svg]:!text-brand !shadow-[inset_0_0_0_1px_var(--color-brand)]"
|
||||
:disabled="isProjectInstalling(project.id)"
|
||||
@click.stop="installProject(project)"
|
||||
>
|
||||
<SpinnerIcon v-if="isProjectInstalling(project.id)" class="animate-spin" />
|
||||
<DownloadIcon v-else-if="project.project_type === 'modpack'" />
|
||||
<DownloadIcon v-else-if="project.project_types.includes('modpack')" />
|
||||
<PlusIcon v-else />
|
||||
{{
|
||||
formatMessage(
|
||||
isProjectInstalling(project.id)
|
||||
? commonMessages.installingLabel
|
||||
: project.project_type === 'modpack'
|
||||
: project.project_types.includes('modpack')
|
||||
? commonMessages.installButton
|
||||
: messages.installToInstance,
|
||||
)
|
||||
@@ -96,7 +97,7 @@ function setProjectInstalling(projectId: string, installing: boolean): void {
|
||||
installingProjectIds.value = next
|
||||
}
|
||||
|
||||
async function installProject(project: Labrinth.Projects.v2.Project): Promise<void> {
|
||||
async function installProject(project: Labrinth.Projects.v3.Project): Promise<void> {
|
||||
if (isProjectInstalling(project.id)) return
|
||||
|
||||
setProjectInstalling(project.id, true)
|
||||
@@ -130,14 +131,19 @@ const userProfile = provideUserProfile({
|
||||
unblockUser: unblock_user,
|
||||
})
|
||||
|
||||
const userId = computed(() => {
|
||||
const value = route.params.user
|
||||
return Array.isArray(value) ? (value[0] ?? '') : (value ?? '')
|
||||
})
|
||||
const projectType = computed(() => {
|
||||
const value = route.params.projectType
|
||||
return Array.isArray(value) ? value[0] : value
|
||||
})
|
||||
function readRouteParam(param: unknown): string | undefined {
|
||||
const value = Array.isArray(param) ? param[0] : param
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined
|
||||
}
|
||||
|
||||
const userId = ref(readRouteParam(route.params.user) ?? '')
|
||||
watch(
|
||||
() => readRouteParam(route.params.user),
|
||||
(next) => {
|
||||
if (next) userId.value = next
|
||||
},
|
||||
)
|
||||
const projectType = computed(() => readRouteParam(route.params.projectType))
|
||||
|
||||
function getCachedUserSummary(id: string) {
|
||||
return queryClient.getQueryData<Labrinth.Users.v3.User>(['users', 'summary', id])
|
||||
|
||||
@@ -532,7 +532,7 @@
|
||||
</div>
|
||||
|
||||
<div class="normal-page__content">
|
||||
<div class="mb-3 overflow-x-auto"><NavTabs :links="navLinks" replace class="mb-1" /></div>
|
||||
<NavTabs :links="navLinks" replace page-nav />
|
||||
<NuxtPage @on-download="triggerDownloadAnimation" @delete-version="deleteVersion" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,64 +307,32 @@
|
||||
}
|
||||
}),
|
||||
]"
|
||||
class="mb-4"
|
||||
replace
|
||||
page-nav
|
||||
/>
|
||||
|
||||
<ProjectCardList
|
||||
<ProjectList
|
||||
v-if="projects && projects?.length > 0"
|
||||
:projects="displayedProjects"
|
||||
:layout="cosmetics.searchDisplayMode.collection"
|
||||
>
|
||||
<ProjectCard
|
||||
v-for="project in (route.params.projectType !== undefined
|
||||
? projects.filter(
|
||||
(x) =>
|
||||
x.project_type ===
|
||||
route.params.projectType.substr(0, route.params.projectType.length - 1),
|
||||
)
|
||||
: projects
|
||||
)
|
||||
.slice()
|
||||
.sort((a, b) => b.downloads - a.downloads)"
|
||||
:key="project.id"
|
||||
:link="`/${project.project_type}/${project.slug ?? project.id}`"
|
||||
:title="project.title"
|
||||
:icon-url="project.icon_url"
|
||||
:banner="project.gallery.find((element) => element.featured)?.url"
|
||||
:summary="project.description"
|
||||
:date-updated="project.updated"
|
||||
:downloads="project.downloads ?? 0"
|
||||
:followers="project.followers ?? 0"
|
||||
:tags="project.categories"
|
||||
:environment="{
|
||||
clientSide: project.client_side,
|
||||
serverSide: project.server_side,
|
||||
}"
|
||||
:color="project.color"
|
||||
:layout="
|
||||
cosmetics.searchDisplayMode.collection === 'grid' ||
|
||||
cosmetics.searchDisplayMode.collection === 'gallery'
|
||||
? 'grid'
|
||||
: 'list'
|
||||
"
|
||||
>
|
||||
<template v-if="canEdit || collection.id === 'following'" #actions>
|
||||
<Button
|
||||
v-if="canEdit"
|
||||
class="remove-btn"
|
||||
:disabled="removing"
|
||||
@click="() => removeProject(project)"
|
||||
>
|
||||
<SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" />
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.removeProjectButton) }}
|
||||
</Button>
|
||||
<Button v-if="collection.id === 'following'" @click="unfollowProject(project)">
|
||||
<HeartMinusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.unfollowProjectButton) }}
|
||||
</Button>
|
||||
</template>
|
||||
</ProjectCard>
|
||||
</ProjectCardList>
|
||||
<template v-if="canEdit || collection.id === 'following'" #actions="{ project }">
|
||||
<Button
|
||||
v-if="canEdit"
|
||||
class="remove-btn"
|
||||
:disabled="removing"
|
||||
@click="() => removeProject(project)"
|
||||
>
|
||||
<SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" />
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.removeProjectButton) }}
|
||||
</Button>
|
||||
<Button v-if="collection.id === 'following'" @click="unfollowProject(project)">
|
||||
<HeartMinusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.unfollowProjectButton) }}
|
||||
</Button>
|
||||
</template>
|
||||
</ProjectList>
|
||||
<EmptyState v-else type="empty-inbox" :heading="formatMessage(messages.noProjectsLabel)">
|
||||
<template #actions>
|
||||
<ButtonLink
|
||||
@@ -405,6 +373,7 @@ import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonLink,
|
||||
catalogProjectTypes,
|
||||
commonMessages,
|
||||
commonProjectTypeCategoryMessages,
|
||||
commonProjectTypeSentenceMessages,
|
||||
@@ -413,6 +382,7 @@ import {
|
||||
defineMessages,
|
||||
EmptyState,
|
||||
FileInput,
|
||||
filterProjectsByType,
|
||||
HorizontalRule,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
@@ -421,11 +391,10 @@ import {
|
||||
NewModal,
|
||||
normalizeChildren,
|
||||
NormalPage,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
parseProjectTypeRouteParam,
|
||||
ProjectList,
|
||||
RadioButtons,
|
||||
SidebarCard,
|
||||
sortProjectTypes,
|
||||
StyledInput,
|
||||
TeleportOverflowMenu,
|
||||
useCompactNumber,
|
||||
@@ -463,21 +432,13 @@ async function fetchProjectsByIds(projectIds) {
|
||||
segments.push(projectIds.slice(i, i + segmentSize))
|
||||
}
|
||||
const results = await Promise.all(
|
||||
segments.map((ids) => api.labrinth.projects_v2.getMultiple(ids)),
|
||||
segments.map((ids) => api.labrinth.projects_v3.getMultiple(ids)),
|
||||
)
|
||||
const projects = results.flat()
|
||||
for (const project of projects) {
|
||||
project.categories = project.categories.concat(project.loaders)
|
||||
}
|
||||
return projects
|
||||
return results.flat()
|
||||
}
|
||||
|
||||
async function fetchFollowedProjects(userId) {
|
||||
const projects = await api.labrinth.users_v2.getFollowedProjects(userId)
|
||||
for (const project of projects) {
|
||||
project.categories = project.categories.concat(project.loaders)
|
||||
}
|
||||
return projects
|
||||
return api.labrinth.users_v3.getFollowedProjects(userId)
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -765,13 +726,13 @@ const canEdit = computed(
|
||||
collection.value.id !== 'following',
|
||||
)
|
||||
|
||||
const projectTypes = computed(() => {
|
||||
const projectSet = new Set(
|
||||
projects.value?.map((project) => project?.project_type).filter((x) => x !== undefined) || [],
|
||||
)
|
||||
projectSet.delete('project')
|
||||
return sortProjectTypes(projectSet)
|
||||
})
|
||||
const projectTypes = computed(() => catalogProjectTypes(projects.value ?? []))
|
||||
|
||||
const displayedProjects = computed(() =>
|
||||
filterProjectsByType(projects.value ?? [], parseProjectTypeRouteParam(route.params.projectType))
|
||||
.slice()
|
||||
.sort((a, b) => b.downloads - a.downloads),
|
||||
)
|
||||
|
||||
function getProjectTypeSentenceMessage(type) {
|
||||
return commonProjectTypeSentenceMessages[type] ?? commonProjectTypeSentenceMessages.project
|
||||
|
||||
@@ -621,7 +621,7 @@ const { isStuck: isInstallHeaderStuck } = useStickyObserver(
|
||||
"
|
||||
>
|
||||
<section
|
||||
class="flex min-w-0 flex-col gap-3"
|
||||
class="mt-6 flex min-w-0 flex-col gap-3 sm:mt-0"
|
||||
:class="cosmetics.rightSearchLayout ? 'lg:order-1' : 'lg:order-2'"
|
||||
>
|
||||
<BrowsePageLayout>
|
||||
|
||||
@@ -122,74 +122,12 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||
<NavTabs :links="navLinks" replace />
|
||||
</div>
|
||||
<ProjectCardList v-if="projects && projects.length > 0">
|
||||
<template
|
||||
v-for="project in (route.params.projectType !== undefined
|
||||
? (projects ?? []).filter((x) =>
|
||||
x.project_types.includes(
|
||||
typeof route.params.projectType === 'string'
|
||||
? route.params.projectType.slice(0, route.params.projectType.length - 1)
|
||||
: route.params.projectType[0]?.slice(
|
||||
0,
|
||||
route.params.projectType[0].length - 1,
|
||||
) || '',
|
||||
),
|
||||
)
|
||||
: (projects ?? [])
|
||||
)
|
||||
.slice()
|
||||
.sort(projectUserSorting)"
|
||||
:key="project.id"
|
||||
>
|
||||
<ProjectCard
|
||||
v-if="isProjectServer(project)"
|
||||
:link="`/server/${project.slug || project.id}`"
|
||||
:title="project.name"
|
||||
:icon-url="project.icon_url"
|
||||
:summary="project.summary"
|
||||
:tags="project.categories"
|
||||
:server-online-players="
|
||||
project.minecraft_java_server?.ping?.data?.players_online ?? 0
|
||||
"
|
||||
:server-recent-plays="project.minecraft_java_server?.verified_plays_2w ?? 0"
|
||||
:server-region="project.minecraft_server?.region"
|
||||
:server-status-online="!!project.minecraft_java_server?.ping?.data"
|
||||
:server-modpack-content="getServerModpackContent(project)"
|
||||
:status="
|
||||
auth.user && (auth.user.id! === user.id || tags.staffRoles.includes(auth.user.role))
|
||||
? (project.status as ProjectStatus)
|
||||
: undefined
|
||||
"
|
||||
:max-tags="2"
|
||||
layout="list"
|
||||
is-server-project
|
||||
exclude-loaders
|
||||
/>
|
||||
<ProjectCard
|
||||
v-else
|
||||
:link="`/${project.project_types[0] ?? 'project'}/${project.slug || project.id}`"
|
||||
:title="project.name"
|
||||
:icon-url="project.icon_url"
|
||||
:banner="project.gallery.find((element) => element.featured)?.url"
|
||||
:summary="project.summary"
|
||||
:date-updated="project.updated"
|
||||
:downloads="project.downloads"
|
||||
:followers="project.followers"
|
||||
:tags="project.categories"
|
||||
:environment="project.environment?.[0]"
|
||||
:status="
|
||||
auth.user && (auth.user.id! === user.id || tags.staffRoles.includes(auth.user.role))
|
||||
? (project.status as ProjectStatus)
|
||||
: undefined
|
||||
"
|
||||
:color="project.color"
|
||||
layout="list"
|
||||
/>
|
||||
</template>
|
||||
</ProjectCardList>
|
||||
<NavTabs v-if="navLinks.length > 2" :links="navLinks" replace page-nav />
|
||||
<ProjectList
|
||||
v-if="projects && projects.length > 0"
|
||||
:projects="displayedProjects"
|
||||
:show-status="showProjectStatus"
|
||||
/>
|
||||
<div v-else-if="true" class="error">
|
||||
<UpToDate class="icon" />
|
||||
<br />
|
||||
@@ -226,21 +164,21 @@ import {
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
catalogProjectTypes,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
filterProjectsByType,
|
||||
injectModrinthClient,
|
||||
IntlFormatted,
|
||||
NavTabs,
|
||||
normalizeChildren,
|
||||
PROJECT_DEP_MARKER_QUERY,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
parseProjectTypeRouteParam,
|
||||
ProjectList,
|
||||
SidebarCard,
|
||||
sortProjectTypes,
|
||||
useCompactNumber,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import type { Organization, ProjectStatus, ProjectType } from '@modrinth/utils'
|
||||
import type { Organization, ProjectType } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
|
||||
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
|
||||
@@ -332,19 +270,7 @@ const {
|
||||
isFetching: projectsIsFetching,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['organization', orgId, 'projects']),
|
||||
queryFn: async () => {
|
||||
// @ts-expect-error
|
||||
const rawProjects = (await client.labrinth.organizations_v3.getProjects(orgId)) as ProjectV3[]
|
||||
|
||||
return rawProjects.map((project) => {
|
||||
let categories = project.categories.concat(project.loaders)
|
||||
if (project.mrpack_loaders) {
|
||||
categories = categories.concat(project.mrpack_loaders as string[])
|
||||
}
|
||||
|
||||
return { ...project, categories }
|
||||
})
|
||||
},
|
||||
queryFn: () => client.labrinth.organizations_v3.getProjects(orgId),
|
||||
placeholderData: [],
|
||||
})
|
||||
|
||||
@@ -386,42 +312,17 @@ const isInvited = computed(() => {
|
||||
return currentMember.value?.accepted === false
|
||||
})
|
||||
|
||||
const projectTypes = computed(() => {
|
||||
const types = new Set<string>()
|
||||
const projectTypes = computed(() => catalogProjectTypes(projects.value ?? []))
|
||||
|
||||
for (const project of projects.value ?? []) {
|
||||
const type = project.project_types[0] ?? 'project'
|
||||
if (type !== 'project') types.add(type)
|
||||
}
|
||||
const displayedProjects = computed(() =>
|
||||
filterProjectsByType(projects.value ?? [], parseProjectTypeRouteParam(route.params.projectType))
|
||||
.slice()
|
||||
.sort(projectUserSorting),
|
||||
)
|
||||
|
||||
return sortProjectTypes(types)
|
||||
})
|
||||
function isProjectServer(project: ProjectV3): boolean {
|
||||
return project.minecraft_server != null
|
||||
}
|
||||
|
||||
function getServerModpackContent(project: ProjectV3) {
|
||||
const content = project.minecraft_java_server?.content
|
||||
if (content?.kind === 'modpack') {
|
||||
const { project_name, project_icon, project_id } = content
|
||||
if (!project_name) return undefined
|
||||
return {
|
||||
name: project_name,
|
||||
icon: project_icon,
|
||||
onclick:
|
||||
project_id !== project.id
|
||||
? () => {
|
||||
navigateTo({
|
||||
path: `/project/${project_id}`,
|
||||
query: { ...PROJECT_DEP_MARKER_QUERY },
|
||||
})
|
||||
}
|
||||
: undefined,
|
||||
showCustomModpackTooltip: project_id === project.id,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
const showProjectStatus = computed(
|
||||
() => !!(auth.user && (auth.user.id === user.id || tags.staffRoles.includes(auth.user.role))),
|
||||
)
|
||||
|
||||
const sumDownloads = computed(() => {
|
||||
let sum = 0
|
||||
|
||||
@@ -31,7 +31,7 @@ const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const userProfile = provideUserProfile({
|
||||
getUser: (userId) => client.labrinth.users_v3.get(userId),
|
||||
getProjects: (userId) => client.labrinth.users_v2.getProjects(userId),
|
||||
getProjects: (userId) => client.labrinth.users_v3.getProjects(userId),
|
||||
getOrganizations: (userId) => client.labrinth.users_v2.getOrganizations(userId),
|
||||
getCollections: (userId) => client.labrinth.users_v2.getCollections(userId),
|
||||
patchUser: (userId, patch) => client.labrinth.users_v2.patch(userId, patch),
|
||||
@@ -46,11 +46,19 @@ const auth = await useAuth()
|
||||
const cosmetics = useCosmetics()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const userId = computed(() => String(route.params.user))
|
||||
const projectType = computed(() => {
|
||||
const value = route.params.projectType
|
||||
return Array.isArray(value) ? value[0] : value
|
||||
})
|
||||
function readRouteParam(param: unknown): string | undefined {
|
||||
const value = Array.isArray(param) ? param[0] : param
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined
|
||||
}
|
||||
|
||||
const userId = ref(readRouteParam(route.params.user) ?? '')
|
||||
watch(
|
||||
() => readRouteParam(route.params.user),
|
||||
(next) => {
|
||||
if (next) userId.value = next
|
||||
},
|
||||
)
|
||||
const projectType = computed(() => readRouteParam(route.params.projectType))
|
||||
|
||||
let prefetchedUser: Labrinth.Users.v3.User | undefined
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user