update tags page + sort project types consistently (#7044)

This commit is contained in:
Prospector
2026-08-07 13:19:36 -07:00
committed by GitHub
parent a71a361e0f
commit 75335647ee
24 changed files with 452 additions and 470 deletions
@@ -15,20 +15,25 @@ const emit = defineEmits<{
(e: 'reset' | 'save', event: MouseEvent): void
}>()
type SaveDisabledReason = MessageDescriptor | string
const props = withDefaults(
defineProps<{
canReset?: boolean
canSave?: boolean
original: T
modified: Partial<T>
saving?: boolean
text?: MessageDescriptor | string
saveLabel?: MessageDescriptor | string
savingLabel?: MessageDescriptor | string
saveDisabledReason?: SaveDisabledReason | SaveDisabledReason[]
saveIcon?: Component
inline?: boolean
}>(),
{
canReset: true,
canSave: true,
saving: false,
text: () =>
defineMessage({
@@ -37,6 +42,7 @@ const props = withDefaults(
}),
saveLabel: () => commonMessages.saveButton,
savingLabel: () => commonMessages.savingButton,
saveDisabledReason: undefined,
saveIcon: SaveIcon,
inline: false,
},
@@ -50,6 +56,22 @@ function localizeIfPossible(message: MessageDescriptor | string) {
return typeof message === 'string' ? message : formatMessage(message)
}
const saveDisabled = computed(() => props.saving || !props.canSave)
const saveDisabledTooltip = computed(() => {
if (!saveDisabled.value || props.saving || !props.saveDisabledReason) {
return undefined
}
const reasons = (
Array.isArray(props.saveDisabledReason) ? props.saveDisabledReason : [props.saveDisabledReason]
).map(localizeIfPossible)
return reasons.length > 0
? { content: reasons.join('\n'), popperClass: 'unsaved-changes-save-tooltip' }
: undefined
})
const actionBar = ref<InstanceType<typeof FloatingActionBar> | null>(null)
function nudge(): void {
@@ -66,11 +88,32 @@ defineExpose({ nudge })
<Button v-if="canReset" type="quiet" :disabled="saving" @click="(e) => emit('reset', e)">
<HistoryIcon /> {{ formatMessage(commonMessages.resetButton) }}
</Button>
<Button type="colored" color="brand" :disabled="saving" @click="(e) => emit('save', e)">
<SpinnerIcon v-if="saving" class="animate-spin" />
<component :is="saveIcon" v-else />
{{ localizeIfPossible(saving ? savingLabel : saveLabel) }}
</Button>
<span
v-tooltip="saveDisabledTooltip"
class="flex"
:class="{ 'cursor-not-allowed': saveDisabled }"
>
<Button
type="colored"
color="brand"
:disabled="saveDisabled"
:class="{ 'pointer-events-none': saveDisabled }"
@click="(e) => emit('save', e)"
>
<SpinnerIcon v-if="saving" class="animate-spin" />
<component :is="saveIcon" v-else />
{{ localizeIfPossible(saving ? savingLabel : saveLabel) }}
</Button>
</span>
</div>
</FloatingActionBar>
</template>
<style lang="scss">
/* unscoped because floating-vue teleports the tooltip outside of scope */
.v-popper__popper.v-popper--theme-tooltip.unsaved-changes-save-tooltip .v-popper__inner {
max-width: 22rem;
white-space: pre-line;
line-height: 1.4;
}
</style>
@@ -496,7 +496,7 @@ import {
injectPageContext,
injectTags,
} from '#ui/providers'
import { commonMessages, getProjectTypeTitleMessage } from '#ui/utils'
import { commonMessages, getProjectTypeTitleMessage, sortProjectTypes } from '#ui/utils'
import { blockedUsersQueryKey, injectUserProfile } from './providers'
import {
@@ -840,7 +840,7 @@ const projectTypes = computed(() => {
const types = new Set(projects.value.map((project) => project.resolvedProjectType))
if (collections.value.length > 0) types.add('collection')
types.delete('project')
return [...types]
return sortProjectTypes(types)
})
const navLinks = computed(() => {
@@ -851,15 +851,13 @@ const navLinks = computed(() => {
label: formatMessage(commonMessages.allProjectType),
href: profilePath,
},
...projectTypes.value
.map((projectType) => ({
label:
projectType === 'collection'
? formatMessage(messages.collectionsLabel)
: formatMessage(getProjectTypeTitleMessage(projectType), { count: 2 }),
href: `${profilePath}/${projectType}s`,
}))
.sort((first, second) => first.label.localeCompare(second.label)),
...projectTypes.value.map((projectType) => ({
label:
projectType === 'collection'
? formatMessage(messages.collectionsLabel)
: formatMessage(getProjectTypeTitleMessage(projectType), { count: 2 }),
href: `${profilePath}/${projectType}s`,
})),
]
})
+1
View File
@@ -6,6 +6,7 @@ export * from './file-extensions'
export * from './game-modes'
export * from './loaders'
export * from './notices'
export * from './project-types'
export * from './savable'
export * from './search'
export * from './server-content-installing'
+31
View File
@@ -0,0 +1,31 @@
export const PROJECT_TYPE_ORDER = [
'mod',
'resourcepack',
'datapack',
'shader',
'modpack',
'plugin',
'server',
'collection',
] as const
export type OrderedProjectType = (typeof PROJECT_TYPE_ORDER)[number]
const PROJECT_TYPE_SORT_ALIASES: Record<string, string> = {
minecraft_java_server: 'server',
shaderpack: 'shader',
}
export function getProjectTypeSortIndex(type: string): number {
const normalized = PROJECT_TYPE_SORT_ALIASES[type] ?? type
const index = (PROJECT_TYPE_ORDER as readonly string[]).indexOf(normalized)
return index === -1 ? PROJECT_TYPE_ORDER.length : index
}
export function compareProjectTypes(a: string, b: string): number {
return getProjectTypeSortIndex(a) - getProjectTypeSortIndex(b)
}
export function sortProjectTypes<T extends string>(types: Iterable<T>): T[] {
return [...types].sort(compareProjectTypes)
}
+2 -2
View File
@@ -93,10 +93,10 @@ export type ProjectType =
const ALL_PROJECT_TYPES: ProjectType[] = [
'mod',
'modpack',
'resourcepack',
'shader',
'datapack',
'shader',
'modpack',
'plugin',
'server',
]