mirror of
https://github.com/modrinth/code.git
synced 2026-09-04 22:10:15 +00:00
fix: project middleware startLoading
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useGeneratedState } from '~/composables/generated'
|
import { useGeneratedState } from '~/composables/generated'
|
||||||
import { projectQueryOptions } from '~/composables/queries/project'
|
import { projectQueryOptions } from '~/composables/queries/project'
|
||||||
import { useAppQueryClient } from '~/composables/query-client'
|
import { useAppQueryClient } from '~/composables/query-client'
|
||||||
|
import { createModrinthClient } from '~/helpers/api.ts'
|
||||||
import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js'
|
import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js'
|
||||||
import { useServerModrinthClient } from '~/server/utils/api-client'
|
import { useServerModrinthClient } from '~/server/utils/api-client'
|
||||||
|
|
||||||
@@ -18,9 +19,6 @@ const PROJECT_TYPES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
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 routeProjectParam = to.params.project
|
||||||
const projectId = Array.isArray(routeProjectParam) ? routeProjectParam[0] : routeProjectParam
|
const projectId = Array.isArray(routeProjectParam) ? routeProjectParam[0] : routeProjectParam
|
||||||
const routeType = Array.isArray(to.params.type) ? to.params.type[0] : to.params.type
|
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 queryClient = useAppQueryClient()
|
||||||
const authToken = useCookie('auth-token')
|
const client = await getProjectMiddlewareClient()
|
||||||
const client = useServerModrinthClient({ authToken: authToken.value || undefined })
|
|
||||||
const tags = useGeneratedState()
|
const tags = useGeneratedState()
|
||||||
|
|
||||||
|
if (import.meta.client) startLoading()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch v2 and v3 in parallel — cache both for the page's useQuery calls
|
// Fetch v2 and v3 in parallel — cache both for the page's useQuery calls
|
||||||
const [project, projectV3] = await Promise.all([
|
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)
|
// Cache by slug if we looked up by ID (or vice versa)
|
||||||
if (projectId !== project.slug) {
|
if (projectId !== project.slug) {
|
||||||
queryClient.setQueryData(['project', 'v2', project.slug], project)
|
queryClient.setQueryData(['project', 'v2', project.slug], project)
|
||||||
|
queryClient.setQueryData(['project', 'v3', project.slug], projectV3)
|
||||||
}
|
}
|
||||||
if (projectId !== project.id) {
|
if (projectId !== project.id) {
|
||||||
queryClient.setQueryData(['project', 'v2', project.id], project)
|
queryClient.setQueryData(['project', 'v2', project.id], project)
|
||||||
|
queryClient.setQueryData(['project', 'v3', project.id], projectV3)
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectType = projectV3.minecraft_server != null ? 'server' : project.project_type
|
const projectType = projectV3.minecraft_server != null ? 'server' : project.project_type
|
||||||
@@ -81,5 +82,23 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Let the page handle 404s and other errors
|
// 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>
|
</template>
|
||||||
</NewModal>
|
</NewModal>
|
||||||
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
<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
|
<div
|
||||||
class="new-page sidebar"
|
class="new-page sidebar"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -435,7 +447,10 @@
|
|||||||
!flags.alwaysShowChecklistAsPopup,
|
!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">
|
<div class="mb-6">
|
||||||
<ModerationProjectNags
|
<ModerationProjectNags
|
||||||
v-if="
|
v-if="
|
||||||
@@ -721,6 +736,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Admonition,
|
Admonition,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
BrowseInstallHeader,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
@@ -743,12 +759,14 @@ import {
|
|||||||
ProjectSidebarTags,
|
ProjectSidebarTags,
|
||||||
provideProjectPageContext,
|
provideProjectPageContext,
|
||||||
ScrollablePanel,
|
ScrollablePanel,
|
||||||
|
SelectedProjectsFloatingBar,
|
||||||
ServersPromo,
|
ServersPromo,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
useFormatDateTime,
|
useFormatDateTime,
|
||||||
useFormatPrice,
|
useFormatPrice,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useStickyObserver,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
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 { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||||
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
|
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
|
||||||
import { versionQueryOptions } from '~/composables/queries/version'
|
import { versionQueryOptions } from '~/composables/queries/version'
|
||||||
|
import { useServerInstallContent } from '~/composables/use-server-install-content'
|
||||||
import { userCollectProject, userFollowProject } from '~/composables/user.js'
|
import { userCollectProject, userFollowProject } from '~/composables/user.js'
|
||||||
import {
|
import {
|
||||||
loadChecklistOpenState,
|
loadChecklistOpenState,
|
||||||
@@ -843,6 +862,11 @@ const versionFilter = ref('')
|
|||||||
|
|
||||||
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
||||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||||
|
const stickyInstallHeaderRef = ref(null)
|
||||||
|
const { isStuck: isInstallHeaderStuck } = useStickyObserver(
|
||||||
|
stickyInstallHeaderRef,
|
||||||
|
'ProjectInstallHeader',
|
||||||
|
)
|
||||||
|
|
||||||
const projectEnvironmentModal = useTemplateRef('projectEnvironmentModal')
|
const projectEnvironmentModal = useTemplateRef('projectEnvironmentModal')
|
||||||
const modalCollection = useTemplateRef('modal_collection')
|
const modalCollection = useTemplateRef('modal_collection')
|
||||||
@@ -987,6 +1011,10 @@ const messages = defineMessages({
|
|||||||
id: 'project.navigation.changelog',
|
id: 'project.navigation.changelog',
|
||||||
defaultMessage: 'Changelog',
|
defaultMessage: 'Changelog',
|
||||||
},
|
},
|
||||||
|
backToDiscover: {
|
||||||
|
id: 'project.install-context.back-to-discover',
|
||||||
|
defaultMessage: 'Back to discover',
|
||||||
|
},
|
||||||
createNewCollection: {
|
createNewCollection: {
|
||||||
id: 'project.collections.create-new',
|
id: 'project.collections.create-new',
|
||||||
defaultMessage: 'Create new collection',
|
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)
|
// Use actual project ID for dependent queries (ensures cache consistency)
|
||||||
const projectId = computed(() => projectRaw.value?.id)
|
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) {
|
async function deleteVersion(id) {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
@@ -2517,16 +2625,16 @@ const navLinks = computed(() => {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: formatMessage(messages.descriptionTab),
|
label: formatMessage(messages.descriptionTab),
|
||||||
href: projectUrl,
|
href: withInstallContextQuery(projectUrl),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(messages.galleryTab),
|
label: formatMessage(messages.galleryTab),
|
||||||
href: `${projectUrl}/gallery`,
|
href: withInstallContextQuery(`${projectUrl}/gallery`),
|
||||||
shown: galleryCount > 0 || !!currentMember.value,
|
shown: galleryCount > 0 || !!currentMember.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(messages.changelogTab),
|
label: formatMessage(messages.changelogTab),
|
||||||
href: `${projectUrl}/changelog`,
|
href: withInstallContextQuery(`${projectUrl}/changelog`),
|
||||||
shown:
|
shown:
|
||||||
hasVersions.value &&
|
hasVersions.value &&
|
||||||
projectV3Loaded.value &&
|
projectV3Loaded.value &&
|
||||||
@@ -2535,7 +2643,7 @@ const navLinks = computed(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(messages.versionsTab),
|
label: formatMessage(messages.versionsTab),
|
||||||
href: `${projectUrl}/versions`,
|
href: withInstallContextQuery(`${projectUrl}/versions`),
|
||||||
shown:
|
shown:
|
||||||
(hasVersions.value || !!currentMember.value) &&
|
(hasVersions.value || !!currentMember.value) &&
|
||||||
projectV3Loaded.value &&
|
projectV3Loaded.value &&
|
||||||
@@ -2545,7 +2653,7 @@ const navLinks = computed(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(messages.moderationTab),
|
label: formatMessage(messages.moderationTab),
|
||||||
href: `${projectUrl}/moderation`,
|
href: withInstallContextQuery(`${projectUrl}/moderation`),
|
||||||
shown: !!currentMember.value,
|
shown: !!currentMember.value,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { cycleValue } from '@modrinth/utils'
|
|||||||
import { useQueryClient } from '@tanstack/vue-query'
|
import { useQueryClient } from '@tanstack/vue-query'
|
||||||
import { useTimeoutFn } from '@vueuse/core'
|
import { useTimeoutFn } from '@vueuse/core'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import type { LocationQueryRaw } from 'vue-router'
|
||||||
|
|
||||||
import LogoAnimated from '~/components/brand/LogoAnimated.vue'
|
import LogoAnimated from '~/components/brand/LogoAnimated.vue'
|
||||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.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) {
|
async function fetchSearch(requestParams: string) {
|
||||||
debug('search() called', {
|
debug('search() called', {
|
||||||
requestParams: requestParams.substring(0, 100),
|
requestParams: requestParams.substring(0, 100),
|
||||||
@@ -468,9 +491,11 @@ provideBrowseManager({
|
|||||||
projectType: projectTypeId,
|
projectType: projectTypeId,
|
||||||
...searchState,
|
...searchState,
|
||||||
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) =>
|
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) =>
|
getServerProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) =>
|
||||||
`/server/${result.slug ?? result.project_id}`,
|
withHostingContext(`/server/${result.slug ?? result.project_id}`),
|
||||||
selectableProjectTypes: computed(() => []),
|
selectableProjectTypes: computed(() => []),
|
||||||
showProjectTypeTabs: computed(() => false),
|
showProjectTypeTabs: computed(() => false),
|
||||||
variant: 'web',
|
variant: 'web',
|
||||||
|
|||||||
Reference in New Issue
Block a user