mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
fix: project middleware startLoading
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useGeneratedState } from '~/composables/generated'
|
||||
import { projectQueryOptions } from '~/composables/queries/project'
|
||||
import { useAppQueryClient } from '~/composables/query-client'
|
||||
import { createModrinthClient } from '~/helpers/api.ts'
|
||||
import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js'
|
||||
import { useServerModrinthClient } from '~/server/utils/api-client'
|
||||
|
||||
@@ -18,9 +19,6 @@ const PROJECT_TYPES = [
|
||||
]
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
// Only run this middleware on the server - it relies on server-only runtime config
|
||||
if (import.meta.client) return
|
||||
|
||||
const routeProjectParam = to.params.project
|
||||
const projectId = Array.isArray(routeProjectParam) ? routeProjectParam[0] : routeProjectParam
|
||||
const routeType = Array.isArray(to.params.type) ? to.params.type[0] : to.params.type
|
||||
@@ -31,10 +29,11 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
}
|
||||
|
||||
const queryClient = useAppQueryClient()
|
||||
const authToken = useCookie('auth-token')
|
||||
const client = useServerModrinthClient({ authToken: authToken.value || undefined })
|
||||
const client = await getProjectMiddlewareClient()
|
||||
const tags = useGeneratedState()
|
||||
|
||||
if (import.meta.client) startLoading()
|
||||
|
||||
try {
|
||||
// Fetch v2 and v3 in parallel — cache both for the page's useQuery calls
|
||||
const [project, projectV3] = await Promise.all([
|
||||
@@ -48,9 +47,11 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
// Cache by slug if we looked up by ID (or vice versa)
|
||||
if (projectId !== project.slug) {
|
||||
queryClient.setQueryData(['project', 'v2', project.slug], project)
|
||||
queryClient.setQueryData(['project', 'v3', project.slug], projectV3)
|
||||
}
|
||||
if (projectId !== project.id) {
|
||||
queryClient.setQueryData(['project', 'v2', project.id], project)
|
||||
queryClient.setQueryData(['project', 'v3', project.id], projectV3)
|
||||
}
|
||||
|
||||
const projectType = projectV3.minecraft_server != null ? 'server' : project.project_type
|
||||
@@ -81,5 +82,23 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
}
|
||||
} catch {
|
||||
// Let the page handle 404s and other errors
|
||||
} finally {
|
||||
if (import.meta.client) stopLoading()
|
||||
}
|
||||
})
|
||||
|
||||
async function getProjectMiddlewareClient() {
|
||||
if (import.meta.server) {
|
||||
const authToken = useCookie('auth-token')
|
||||
return useServerModrinthClient({ authToken: authToken.value || undefined })
|
||||
}
|
||||
|
||||
const auth = await useAuth()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
return createModrinthClient(auth, {
|
||||
apiBaseUrl: config.public.apiBaseUrl.replace('/v2/', '/'),
|
||||
archonBaseUrl: config.public.pyroBaseUrl.replace('/v2/', '/'),
|
||||
rateLimitKey: config.rateLimitKey,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -421,6 +421,18 @@
|
||||
</template>
|
||||
</NewModal>
|
||||
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
||||
<div
|
||||
v-if="projectInstallContext && !isSettings"
|
||||
ref="stickyInstallHeaderRef"
|
||||
class="sticky top-0 z-20 mx-auto max-w-[80rem] border-0 border-solid border-divider bg-surface-1 px-6 pt-4"
|
||||
:class="[isInstallHeaderStuck ? 'border-t' : '']"
|
||||
>
|
||||
<BrowseInstallHeader :install-context="projectHeaderInstallContext" divider bottom-padding />
|
||||
</div>
|
||||
<SelectedProjectsFloatingBar
|
||||
v-if="projectInstallContext && !isSettings"
|
||||
:install-context="projectInstallContext"
|
||||
/>
|
||||
<div
|
||||
class="new-page sidebar"
|
||||
:class="{
|
||||
@@ -435,7 +447,10 @@
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
}"
|
||||
>
|
||||
<div class="normal-page__header relative my-4">
|
||||
<div
|
||||
class="normal-page__header relative mb-4"
|
||||
:class="projectInstallContext && !isSettings ? 'mt-0' : 'mt-4'"
|
||||
>
|
||||
<div class="mb-6">
|
||||
<ModerationProjectNags
|
||||
v-if="
|
||||
@@ -721,6 +736,7 @@ import {
|
||||
import {
|
||||
Admonition,
|
||||
Avatar,
|
||||
BrowseInstallHeader,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
@@ -743,12 +759,14 @@ import {
|
||||
ProjectSidebarTags,
|
||||
provideProjectPageContext,
|
||||
ScrollablePanel,
|
||||
SelectedProjectsFloatingBar,
|
||||
ServersPromo,
|
||||
StyledInput,
|
||||
useDebugLogger,
|
||||
useFormatDateTime,
|
||||
useFormatPrice,
|
||||
useRelativeTime,
|
||||
useStickyObserver,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
||||
@@ -772,6 +790,7 @@ import { getSignInRouteObj } from '~/composables/auth.ts'
|
||||
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
|
||||
import { versionQueryOptions } from '~/composables/queries/version'
|
||||
import { useServerInstallContent } from '~/composables/use-server-install-content'
|
||||
import { userCollectProject, userFollowProject } from '~/composables/user.js'
|
||||
import {
|
||||
loadChecklistOpenState,
|
||||
@@ -843,6 +862,11 @@ const versionFilter = ref('')
|
||||
|
||||
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||
const stickyInstallHeaderRef = ref(null)
|
||||
const { isStuck: isInstallHeaderStuck } = useStickyObserver(
|
||||
stickyInstallHeaderRef,
|
||||
'ProjectInstallHeader',
|
||||
)
|
||||
|
||||
const projectEnvironmentModal = useTemplateRef('projectEnvironmentModal')
|
||||
const modalCollection = useTemplateRef('modal_collection')
|
||||
@@ -987,6 +1011,10 @@ const messages = defineMessages({
|
||||
id: 'project.navigation.changelog',
|
||||
defaultMessage: 'Changelog',
|
||||
},
|
||||
backToDiscover: {
|
||||
id: 'project.install-context.back-to-discover',
|
||||
defaultMessage: 'Back to discover',
|
||||
},
|
||||
createNewCollection: {
|
||||
id: 'project.collections.create-new',
|
||||
defaultMessage: 'Create new collection',
|
||||
@@ -1340,6 +1368,60 @@ const project = computed(() => {
|
||||
),
|
||||
}
|
||||
})
|
||||
const routeProjectType = computed(() =>
|
||||
Array.isArray(route.params.type) ? route.params.type[0] : route.params.type,
|
||||
)
|
||||
const projectInstallType = computed(() => ({
|
||||
id: project.value?.actualProjectType ?? routeProjectType.value,
|
||||
}))
|
||||
const serverInstallModalRef = ref(null)
|
||||
const serverInstallDebug = useDebugLogger('ProjectServerInstall')
|
||||
const { installContext: serverBrowseInstallContext } = useServerInstallContent({
|
||||
projectType: projectInstallType,
|
||||
onboardingModalRef: serverInstallModalRef,
|
||||
debug: serverInstallDebug,
|
||||
})
|
||||
const projectDiscoverBackUrl = computed(() => {
|
||||
const browsePath = route.query.b
|
||||
if (typeof browsePath === 'string' && browsePath.startsWith('/discover/')) {
|
||||
return browsePath
|
||||
}
|
||||
|
||||
const discoverType =
|
||||
routeProjectType.value === 'project'
|
||||
? (project.value?.actualProjectType ?? project.value?.project_type ?? 'mod')
|
||||
: (routeProjectType.value ?? project.value?.actualProjectType ?? 'mod')
|
||||
|
||||
return `/discover/${discoverType}s${getInstallContextQueryString([
|
||||
'sid',
|
||||
'wid',
|
||||
'from',
|
||||
'shi',
|
||||
])}`
|
||||
})
|
||||
const projectInstallContext = computed(() => {
|
||||
const context = serverBrowseInstallContext.value
|
||||
if (!context) return null
|
||||
return {
|
||||
...context,
|
||||
backUrl: projectDiscoverBackUrl.value,
|
||||
backLabel: formatMessage(messages.backToDiscover),
|
||||
discardSelectedAndBack: async () => {
|
||||
await (context.clearSelected ?? context.clearQueued)?.()
|
||||
await navigateTo(projectDiscoverBackUrl.value)
|
||||
},
|
||||
}
|
||||
})
|
||||
const projectHeaderInstallContext = computed(() => {
|
||||
const context = projectInstallContext.value
|
||||
if (!context) return null
|
||||
return {
|
||||
...context,
|
||||
onBack: undefined,
|
||||
selectedProjects: [],
|
||||
isInstallingSelected: false,
|
||||
}
|
||||
})
|
||||
|
||||
// Use actual project ID for dependent queries (ensures cache consistency)
|
||||
const projectId = computed(() => projectRaw.value?.id)
|
||||
@@ -2493,6 +2575,32 @@ function onVersionNavigate(url) {
|
||||
})
|
||||
}
|
||||
|
||||
const INSTALL_CONTEXT_QUERY_KEYS = ['sid', 'wid', 'from', 'shi', 'b']
|
||||
|
||||
function getInstallContextQueryString(keys = INSTALL_CONTEXT_QUERY_KEYS) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
for (const key of keys) {
|
||||
const value = route.query[key]
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
if (item != null) {
|
||||
params.append(key, item)
|
||||
}
|
||||
}
|
||||
} else if (value != null) {
|
||||
params.append(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = params.toString()
|
||||
return queryString ? `?${queryString}` : ''
|
||||
}
|
||||
|
||||
function withInstallContextQuery(path) {
|
||||
return `${path}${getInstallContextQueryString()}`
|
||||
}
|
||||
|
||||
async function deleteVersion(id) {
|
||||
if (!id) return
|
||||
|
||||
@@ -2517,16 +2625,16 @@ const navLinks = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: formatMessage(messages.descriptionTab),
|
||||
href: projectUrl,
|
||||
href: withInstallContextQuery(projectUrl),
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.galleryTab),
|
||||
href: `${projectUrl}/gallery`,
|
||||
href: withInstallContextQuery(`${projectUrl}/gallery`),
|
||||
shown: galleryCount > 0 || !!currentMember.value,
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.changelogTab),
|
||||
href: `${projectUrl}/changelog`,
|
||||
href: withInstallContextQuery(`${projectUrl}/changelog`),
|
||||
shown:
|
||||
hasVersions.value &&
|
||||
projectV3Loaded.value &&
|
||||
@@ -2535,7 +2643,7 @@ const navLinks = computed(() => {
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.versionsTab),
|
||||
href: `${projectUrl}/versions`,
|
||||
href: withInstallContextQuery(`${projectUrl}/versions`),
|
||||
shown:
|
||||
(hasVersions.value || !!currentMember.value) &&
|
||||
projectV3Loaded.value &&
|
||||
@@ -2545,7 +2653,7 @@ const navLinks = computed(() => {
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.moderationTab),
|
||||
href: `${projectUrl}/moderation`,
|
||||
href: withInstallContextQuery(`${projectUrl}/moderation`),
|
||||
shown: !!currentMember.value,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -33,6 +33,7 @@ import { cycleValue } from '@modrinth/utils'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { LocationQueryRaw } from 'vue-router'
|
||||
|
||||
import LogoAnimated from '~/components/brand/LogoAnimated.vue'
|
||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||
@@ -219,6 +220,28 @@ function mapV3ProjectHit(hit: Labrinth.Search.v3.ResultSearchProject): DiscoverP
|
||||
}
|
||||
}
|
||||
|
||||
const hostingContextQuery = computed(() => {
|
||||
const query: LocationQueryRaw = {}
|
||||
const hasHostingContext = route.query.sid != null
|
||||
|
||||
for (const key of ['sid', 'wid', 'from', 'shi']) {
|
||||
const value = route.query[key]
|
||||
if (value != null) {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHostingContext) {
|
||||
query.b = route.fullPath
|
||||
}
|
||||
|
||||
return Object.keys(query).length > 0 ? query : undefined
|
||||
})
|
||||
|
||||
function withHostingContext(path: string) {
|
||||
return hostingContextQuery.value ? { path, query: hostingContextQuery.value } : path
|
||||
}
|
||||
|
||||
async function fetchSearch(requestParams: string) {
|
||||
debug('search() called', {
|
||||
requestParams: requestParams.substring(0, 100),
|
||||
@@ -468,9 +491,11 @@ provideBrowseManager({
|
||||
projectType: projectTypeId,
|
||||
...searchState,
|
||||
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) =>
|
||||
`/${projectType.value?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`,
|
||||
withHostingContext(
|
||||
`/${projectType.value?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`,
|
||||
),
|
||||
getServerProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) =>
|
||||
`/server/${result.slug ?? result.project_id}`,
|
||||
withHostingContext(`/server/${result.slug ?? result.project_id}`),
|
||||
selectableProjectTypes: computed(() => []),
|
||||
showProjectTypeTabs: computed(() => false),
|
||||
variant: 'web',
|
||||
|
||||
Reference in New Issue
Block a user