mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
feat: qa + app routing bugs
This commit is contained in:
@@ -116,7 +116,7 @@
|
||||
class="flex flex-1 flex-col gap-3 border-0 border-y bg-surface-2 border-solid border-surface-5 my-auto px-5 py-4"
|
||||
>
|
||||
<div
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,70%)] items-center gap-4 text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,70%)] items-center gap-4 text-base text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
>
|
||||
<span>{{ formatMessage(commonMessages.modpackLabel) }}</span>
|
||||
<div
|
||||
@@ -146,22 +146,22 @@
|
||||
</span>
|
||||
</AutoLink>
|
||||
</div>
|
||||
<span v-else class="font-semibold text-contrast">{{ formatMessage(messages.none) }}</span>
|
||||
<span v-else class="font-semibold text-contrast">{{ formatMessage(messages.noModpack) }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,25%)] items-center gap-4 text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,25%)] items-center gap-4 text-base text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
>
|
||||
<span>{{ formatMessage(messages.installedContent) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ installedContentLabel }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,45%)] items-center gap-4 text-base font-medium text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,45%)] items-center gap-4 text-base text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
>
|
||||
<span>{{ formatMessage(messages.lastActive) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ lastActiveLabel }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,45%)] items-center gap-4 text-base font-medium text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,45%)] items-center gap-4 text-base text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
>
|
||||
<span>{{ formatMessage(messages.created) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ createdLabel }}</span>
|
||||
@@ -191,6 +191,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PencilIcon, PlusIcon, Settings2Icon } from '@modrinth/assets'
|
||||
import { capitalizeString } from '@modrinth/utils'
|
||||
import { computed, useId, useTemplateRef } from 'vue'
|
||||
|
||||
import AutoLink from '#ui/components/base/AutoLink.vue'
|
||||
@@ -215,9 +216,9 @@ const messages = defineMessages({
|
||||
id: 'servers.manage.instances.card.active',
|
||||
defaultMessage: 'Active',
|
||||
},
|
||||
none: {
|
||||
id: 'servers.manage.instances.card.none',
|
||||
defaultMessage: 'None',
|
||||
noModpack: {
|
||||
id: 'servers.manage.instances.card.no-modpack',
|
||||
defaultMessage: '—',
|
||||
},
|
||||
installedContent: {
|
||||
id: 'servers.manage.instances.card.installed-content',
|
||||
@@ -298,7 +299,7 @@ const installedContentLabel = computed(() => {
|
||||
const lastActiveLabel = computed(() => {
|
||||
if (props.world.type === 'empty') return ''
|
||||
return props.world.lastActiveAt
|
||||
? formatRelativeTime(props.world.lastActiveAt)
|
||||
? capitalizeString(formatRelativeTime(props.world.lastActiveAt))
|
||||
: formatMessage(messages.notTrackedYet)
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { ComputedRef, Ref, ShallowRef } from 'vue'
|
||||
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { useDebugLogger } from '#ui/composables/debug-logger'
|
||||
import type { FilterType, FilterValue, ProjectType, SortType } from '#ui/utils/search'
|
||||
import { LOADER_FILTER_TYPES, useSearch } from '#ui/utils/search'
|
||||
import { useServerSearch } from '#ui/utils/server-search'
|
||||
@@ -18,6 +17,7 @@ export interface UseBrowseSearchOptions {
|
||||
categories: Labrinth.Tags.v2.Category[]
|
||||
}>
|
||||
providedFilters?: ComputedRef<FilterValue[]>
|
||||
active?: ComputedRef<boolean>
|
||||
search: (params: string) => Promise<BrowseSearchResponse>
|
||||
persistentQueryParams: string[]
|
||||
getExtraQueryParams?: () => Record<string, string | undefined>
|
||||
@@ -61,12 +61,10 @@ export interface BrowseSearchState {
|
||||
}
|
||||
|
||||
export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchState {
|
||||
const debug = useDebugLogger('BrowseSearch')
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
debug('init, projectType:', options.projectType.value)
|
||||
|
||||
const active = computed(() => options.active?.value ?? true)
|
||||
const projectTypes = computed(() => [options.projectType.value] as ProjectType[])
|
||||
const isServerType = computed(() => options.projectType.value === 'server')
|
||||
|
||||
@@ -172,6 +170,13 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
let searchVersion = 0
|
||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function clearSearchDebounce() {
|
||||
if (searchDebounceTimer) {
|
||||
clearTimeout(searchDebounceTimer)
|
||||
searchDebounceTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const providedFiltersOrEmpty = computed(() => options.providedFilters?.value ?? [])
|
||||
|
||||
watch(
|
||||
@@ -192,24 +197,29 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(effectiveRequestParams, (newVal, oldVal) => {
|
||||
debug('effectiveRequestParams changed', {
|
||||
from: oldVal?.substring(0, 80),
|
||||
to: newVal?.substring(0, 80),
|
||||
})
|
||||
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
|
||||
watch(effectiveRequestParams, () => {
|
||||
clearSearchDebounce()
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
searchDebounceTimer = setTimeout(() => {
|
||||
refreshSearch()
|
||||
}, 200)
|
||||
})
|
||||
|
||||
watch(active, (isActive, wasActive) => {
|
||||
clearSearchDebounce()
|
||||
if (isActive && wasActive === false) {
|
||||
void refreshSearch()
|
||||
}
|
||||
})
|
||||
|
||||
async function refreshSearch() {
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const version = ++searchVersion
|
||||
debug('refreshSearch start', {
|
||||
version,
|
||||
projectType: options.projectType.value,
|
||||
params: effectiveRequestParams.value.substring(0, 100),
|
||||
})
|
||||
|
||||
const currentHitsEmpty = isServerType.value
|
||||
? serverHits.value.length === 0
|
||||
@@ -221,8 +231,11 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
try {
|
||||
const response = await options.search(effectiveRequestParams.value)
|
||||
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (version !== searchVersion) {
|
||||
debug('refreshSearch stale, discarding', { version, current: searchVersion })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -232,17 +245,10 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
projectHits.value = response.projectHits
|
||||
}
|
||||
totalHits.value = response.total_hits
|
||||
debug('refreshSearch complete', {
|
||||
version,
|
||||
hits: response.total_hits,
|
||||
projectHits: response.projectHits.length,
|
||||
serverHits: response.serverHits.length,
|
||||
})
|
||||
|
||||
updateUrlParams()
|
||||
loading.value = false
|
||||
} catch (err) {
|
||||
debug('refreshSearch error', err)
|
||||
console.error('Browse search error:', err)
|
||||
if (version === searchVersion) {
|
||||
loading.value = false
|
||||
@@ -251,7 +257,9 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
}
|
||||
|
||||
function updateUrlParams() {
|
||||
debug('updateUrlParams', { path: route.path })
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
const persistentParams: Record<string, string | (string | null)[] | null | undefined> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(route.query)) {
|
||||
@@ -292,8 +300,7 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
|
||||
watch(
|
||||
() => options.projectType.value,
|
||||
(newType, oldType) => {
|
||||
debug('projectType changed', { from: oldType, to: newType })
|
||||
() => {
|
||||
effectiveCurrentSortType.value =
|
||||
effectiveSortTypes.value.find((sortType) => sortType.name === 'relevance') ??
|
||||
effectiveSortTypes.value[0]
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { Component } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import Admonition from '#ui/components/base/Admonition.vue'
|
||||
import PageHeader from '#ui/components/base/PageHeader.vue'
|
||||
import LoaderIcon from '#ui/components/servers/icons/LoaderIcon.vue'
|
||||
import { useServerImage } from '#ui/composables/servers/use-server-image.ts'
|
||||
@@ -171,8 +170,5 @@ async function handleSelectedProjectsLeaveResult(
|
||||
title-class="leading-8"
|
||||
truncate-title
|
||||
/>
|
||||
<Admonition v-if="installContext.warning" type="warning" class="mb-1">
|
||||
{{ installContext.warning }}
|
||||
</Admonition>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Labrinth } from '@modrinth/api-client'
|
||||
import { SearchIcon } from '@modrinth/assets'
|
||||
import { computed, toValue } from 'vue'
|
||||
|
||||
import Admonition from '#ui/components/base/Admonition.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import Combobox, { type ComboboxOption } from '#ui/components/base/Combobox.vue'
|
||||
import LoadingIndicator from '#ui/components/base/LoadingIndicator.vue'
|
||||
@@ -21,6 +22,7 @@ import { injectBrowseManager } from './providers/browse-manager'
|
||||
const ctx = injectBrowseManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const lockedMessages = computed(() => toValue(ctx.lockedFilterMessages))
|
||||
const installWarning = computed(() => ctx.installContext?.value?.warning)
|
||||
|
||||
const sortOptions = computed<ComboboxOption<SortType>[]>(() =>
|
||||
ctx.effectiveSortTypes.value.map((st) => ({
|
||||
@@ -61,6 +63,10 @@ const messages = defineMessages({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Admonition v-if="installWarning" type="warning">
|
||||
{{ installWarning }}
|
||||
</Admonition>
|
||||
|
||||
<NavTabs v-if="ctx.showProjectTypeTabs.value" :links="ctx.selectableProjectTypes.value" />
|
||||
|
||||
<StyledInput
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<Admonition
|
||||
<div
|
||||
v-if="!instanceInfoAdmonitionDismissed"
|
||||
type="info"
|
||||
:header="formatMessage(messages.instanceInfoHeader)"
|
||||
dismissible
|
||||
@dismiss="dismissInstanceInfoAdmonition"
|
||||
class="grid grid-cols-[1.5rem_minmax(0,1fr)_auto] items-start gap-x-3 rounded-2xl border border-solid border-brand-blue bg-bg-blue p-5 pr-4 text-contrast"
|
||||
>
|
||||
<ul class="m-0 pl-4">
|
||||
<li>{{ formatMessage(messages.instanceInfoDefinition) }}</li>
|
||||
<li>{{ formatMessage(messages.instanceInfoSwitching) }}</li>
|
||||
<li>{{ formatMessage(messages.instanceInfoFiles) }}</li>
|
||||
</ul>
|
||||
</Admonition>
|
||||
<InfoIcon class="mt-0.5 size-6 text-brand-blue" aria-hidden="true" />
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<h2 class="m-0 text-xl font-bold leading-7">
|
||||
{{ formatMessage(messages.instanceInfoHeader) }}
|
||||
</h2>
|
||||
<p class="m-0 text-lg leading-7 text-contrast/85">
|
||||
{{ formatMessage(messages.instanceInfoBody) }}
|
||||
</p>
|
||||
</div>
|
||||
<ButtonStyled circular type="transparent" color="blue" hover-color-fill="background">
|
||||
<button
|
||||
type="button"
|
||||
class="mt-0.5"
|
||||
:aria-label="formatMessage(messages.instanceInfoDismiss)"
|
||||
@click="dismissInstanceInfoAdmonition"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="worldsPending"
|
||||
@@ -39,12 +50,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Archon } from '@modrinth/api-client'
|
||||
import { InfoIcon, XIcon } from '@modrinth/assets'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import Admonition from '#ui/components/base/Admonition.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import InstanceCard from '#ui/components/servers/instances/InstanceCard.vue'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import {
|
||||
@@ -61,20 +73,16 @@ const messages = defineMessages({
|
||||
},
|
||||
instanceInfoHeader: {
|
||||
id: 'servers.manage.instances.info.header',
|
||||
defaultMessage: 'What is an instance?',
|
||||
defaultMessage: 'What is a server instance?',
|
||||
},
|
||||
instanceInfoDefinition: {
|
||||
id: 'servers.manage.instances.info.definition',
|
||||
defaultMessage: 'An instance is a separate server setup.',
|
||||
},
|
||||
instanceInfoSwitching: {
|
||||
id: 'servers.manage.instances.info.switching',
|
||||
defaultMessage: 'You can switch which instance your server runs.',
|
||||
},
|
||||
instanceInfoFiles: {
|
||||
id: 'servers.manage.instances.info.files',
|
||||
instanceInfoBody: {
|
||||
id: 'servers.manage.instances.info.body',
|
||||
defaultMessage:
|
||||
'Each instance has its own server files, worlds, installed content, and settings.',
|
||||
'An instance is a separate setup of your server with its own content, files, worlds, and settings. You can switch which instance your server runs at any time.',
|
||||
},
|
||||
instanceInfoDismiss: {
|
||||
id: 'servers.manage.instances.info.dismiss',
|
||||
defaultMessage: "Don't show this again",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user