mirror of
https://github.com/modrinth/code.git
synced 2026-08-30 19:46:33 +00:00
* better default state handling
* make next stage button work for going to next project
* fix skill issue
* actually fix initial values this time,
make single line inputs with varying lengths (text input and dropdown) use their widest possible length instead of whatever the hell they used to be
* loaders in progress
* loaders and versions test (probably dont use them as the messages suck and idrk what they should do quick fix/message wise)
* WIP the wip
* good enough for now slug taken improvements
* trim all those extra newlines
* viewedness
* batch prefetch queue + technically slightly faster checklist init
* show all valid alternative project types regardless of current project type
* Hide moderation permissions stage when it should be, and I'm sure this won't cause any issues
* Hide moderation permissions stage when it should be, and I'm sure this won't cause any issues
* gimme them versions
* better moderation permission stage shown check?
* betterer moderation permission stage shown check?
* bettererer moderation permission stage shown check? + also began deleting legacy checklist types
* fix certain stages being able to be considered viewed before being viewed
* more removals + i forgot to remove an import last commit
* changed nothing™️
* remove severities + bandaid post-approval message priorities + dependencies
* fix message preview tooltips
* next stage = next project when done
* insufficient description custom fix
* fix that thing coolbot told me was broken I forgot what it was oops
* oh there was a second bug there
* make alternate versions and incorrect project type required cuz they are
* re-navigate button also damn that's a lot of other stuff that pnpm fix... fixed?
* you cant just quick fix a slug into a taken one
* maybe lets not just try and set a slug to something entirely invalid?
* what if we just didn't drop ur queue :smart:
* more queue upgrades
* tweaks
* I love unbreaking things
* un-nuked renavigate button
* prepr
* prepr worked this time i think
* undo more mistakes
* i love intellij refactoring
* ok i think we're good
302 lines
8.5 KiB
Vue
302 lines
8.5 KiB
Vue
<template>
|
|
<div v-if="visibleNags.length > 0" class="universal-card my-4">
|
|
<div class="flex max-w-full flex-wrap items-center gap-x-6 gap-y-4">
|
|
<div class="flex flex-auto flex-wrap items-center gap-x-6 gap-y-4">
|
|
<h2 class="my-0 mr-auto">
|
|
{{ getFormattedMessage(messages.publishingChecklist) }}
|
|
</h2>
|
|
<div class="flex flex-row gap-2">
|
|
<div class="flex items-center gap-1">
|
|
<AsteriskIcon class="size-4 shrink-0 text-red" />
|
|
<span class="text-secondary">{{ getFormattedMessage(messages.required) }}</span>
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-1">
|
|
<TriangleAlertIcon class="size-4 shrink-0 text-orange" />
|
|
<span class="text-secondary">{{ getFormattedMessage(messages.warning) }}</span>
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-1">
|
|
<LightBulbIcon class="size-4 shrink-0 text-purple" />
|
|
<span class="text-secondary">{{ getFormattedMessage(messages.suggestion) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="input-group">
|
|
<IconButton
|
|
label="Toggle details"
|
|
:class="{ '[&>svg]:rotate-180': !collapsed }"
|
|
@click="$emit('toggleCollapsed')"
|
|
>
|
|
<DropdownIcon class="duration-250 transition-transform ease-in-out" />
|
|
</IconButton>
|
|
</div>
|
|
</div>
|
|
<div v-if="!collapsed" class="mt-4 grid grid-cols-[repeat(auto-fit,minmax(15rem,1fr))] gap-2">
|
|
<div
|
|
v-for="nag in visibleNags"
|
|
:key="nag.id"
|
|
class="flex flex-col gap-3 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
|
|
>
|
|
<span class="flex items-center gap-2 font-medium text-contrast">
|
|
<component
|
|
:is="nag.icon || getDefaultIcon(nag.status)"
|
|
v-tooltip="getStatusTooltip(nag.status)"
|
|
:class="[
|
|
'size-4',
|
|
nag.status === 'required' && 'text-red',
|
|
nag.status === 'warning' && 'text-orange',
|
|
nag.status === 'suggestion' && 'text-purple',
|
|
]"
|
|
:aria-label="getStatusTooltip(nag.status)"
|
|
/>
|
|
{{ getFormattedMessage(nag.title) }}
|
|
</span>
|
|
{{ getNagDescription(nag) }}
|
|
<NuxtLink
|
|
v-if="nag.link && shouldShowLink(nag)"
|
|
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/${
|
|
nag.link.path
|
|
}`"
|
|
class="goto-link mt-auto"
|
|
>
|
|
{{ getFormattedMessage(nag.link.title) }}
|
|
<ChevronRightIcon aria-hidden="true" class="featured-header-chevron" />
|
|
</NuxtLink>
|
|
<Button
|
|
v-if="nag.status === 'special-submit-action' && nag.id === 'submit-for-review'"
|
|
v-tooltip="
|
|
!canSubmitForReview ? getFormattedMessage(messages.submitChecklistTooltip) : undefined
|
|
"
|
|
type="colored"
|
|
color="orange"
|
|
:disabled="!canSubmitForReview"
|
|
@click="submitForReview"
|
|
>
|
|
<SendIcon />
|
|
{{ getFormattedMessage(messages.submitForReviewButton) }}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Labrinth } from '@modrinth/api-client'
|
|
import {
|
|
AsteriskIcon,
|
|
ChevronRightIcon,
|
|
DropdownIcon,
|
|
LightBulbIcon,
|
|
ScaleIcon,
|
|
SendIcon,
|
|
TriangleAlertIcon,
|
|
} from '@modrinth/assets'
|
|
import type { Nag, NagContext, NagStatus } from '@modrinth/moderation'
|
|
import { nags } from '@modrinth/moderation'
|
|
import { Button, IconButton } from '@modrinth/ui'
|
|
import { defineMessages, type MessageDescriptor, useVIntl } from '@modrinth/ui'
|
|
import type { Component } from 'vue'
|
|
import { computed } from 'vue'
|
|
|
|
interface Tags {
|
|
rejectedStatuses: string[]
|
|
}
|
|
|
|
interface Props {
|
|
project: Labrinth.Projects.v2.Project
|
|
projectV3: Labrinth.Projects.v3.Project
|
|
versions?: Labrinth.Versions.v3.Version[]
|
|
currentMember?: Labrinth.Projects.v3.TeamMember | null
|
|
collapsed?: boolean
|
|
routeName?: string
|
|
tags: Tags
|
|
}
|
|
|
|
const messages = defineMessages({
|
|
publishingChecklist: {
|
|
id: 'project-moderation-nags.publishing-checklist',
|
|
defaultMessage: 'Publishing checklist',
|
|
},
|
|
submitForReview: {
|
|
id: 'project-moderation-nags.submit-for-review',
|
|
defaultMessage: 'Submit for review',
|
|
},
|
|
submitForReviewDesc: {
|
|
id: 'project-moderation-nags.submit-for-review-desc',
|
|
defaultMessage:
|
|
'Your project is only viewable by members of the project. It must be reviewed by moderators in order to be published.',
|
|
},
|
|
submitForReviewButton: {
|
|
id: 'project-moderation-nags.submit-for-review-button',
|
|
defaultMessage: 'Submit for review',
|
|
},
|
|
resubmitForReview: {
|
|
id: 'project-moderation-nags.resubmit-for-review',
|
|
defaultMessage: 'Resubmit for review',
|
|
},
|
|
resubmitForReviewDesc: {
|
|
id: 'project-moderation-nags.resubmit-for-review-desc',
|
|
defaultMessage:
|
|
"Your project has been {status, select, rejected {rejected} withheld {withheld} other {{status}}} by Modrinth's staff. In most cases, you can resubmit for review after addressing the staff's message.",
|
|
},
|
|
visitModerationPage: {
|
|
id: 'project-moderation-nags.visit-moderation-page',
|
|
defaultMessage: 'Visit moderation page',
|
|
},
|
|
submitChecklistTooltip: {
|
|
id: 'project-moderation-nags.submit-checklist-tooltip',
|
|
defaultMessage: 'You must complete the required steps in the publishing checklist!',
|
|
},
|
|
required: {
|
|
id: 'project-moderation-nags.required',
|
|
defaultMessage: 'Required',
|
|
},
|
|
warning: {
|
|
id: 'project-moderation-nags.warning',
|
|
defaultMessage: 'Warning',
|
|
},
|
|
suggestion: {
|
|
id: 'project-moderation-nags.suggestion',
|
|
defaultMessage: 'Suggestion',
|
|
},
|
|
})
|
|
|
|
const { formatMessage } = useVIntl()
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
versions: () => [],
|
|
currentMember: null,
|
|
collapsed: false,
|
|
routeName: '',
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
toggleCollapsed: []
|
|
setProcessing: [processing: boolean]
|
|
}>()
|
|
|
|
const nagContext = computed<NagContext>(() => ({
|
|
project: props.project,
|
|
projectV3: props.projectV3,
|
|
versions: props.versions,
|
|
currentMember: props.currentMember?.user as Labrinth.Users.v2.User,
|
|
currentRoute: props.routeName,
|
|
tags: props.tags,
|
|
submitProject: submitForReview,
|
|
}))
|
|
|
|
const canSubmitForReview = computed(() => {
|
|
return (
|
|
applicableNags.value.filter((nag) => nag.status === 'required' && !isNagComplete(nag))
|
|
.length === 0
|
|
)
|
|
})
|
|
|
|
async function submitForReview() {
|
|
if (canSubmitForReview.value) {
|
|
emit('setProcessing', true)
|
|
}
|
|
}
|
|
|
|
const applicableNags = computed<Nag[]>(() => {
|
|
return nags.filter((nag) => {
|
|
return nag.shouldShow(nagContext.value)
|
|
})
|
|
})
|
|
|
|
function isNagComplete(nag: Nag): boolean {
|
|
const context = nagContext.value
|
|
return !nag.shouldShow(context)
|
|
}
|
|
|
|
const visibleNags = computed<Nag[]>(() => {
|
|
const finalNags = applicableNags.value.filter((nag) => !isNagComplete(nag))
|
|
|
|
if (props.project.status === 'draft') {
|
|
finalNags.push({
|
|
id: 'submit-for-review',
|
|
title: messages.submitForReview,
|
|
description: () => formatMessage(messages.submitForReviewDesc),
|
|
status: 'special-submit-action',
|
|
shouldShow: (ctx) => ctx.project.status === 'draft',
|
|
})
|
|
}
|
|
|
|
if (props.tags.rejectedStatuses.includes(props.project.status)) {
|
|
finalNags.push({
|
|
id: 'resubmit-for-review',
|
|
title: messages.resubmitForReview,
|
|
description: (ctx) =>
|
|
formatMessage(messages.resubmitForReviewDesc, { status: ctx.project.status }),
|
|
status: 'special-submit-action',
|
|
shouldShow: (ctx) => ctx.tags.rejectedStatuses.includes(ctx.project.status),
|
|
link: {
|
|
path: 'moderation',
|
|
title: messages.visitModerationPage,
|
|
shouldShow: () => props.routeName !== 'type-project-moderation',
|
|
},
|
|
})
|
|
}
|
|
|
|
finalNags.sort((a, b) => {
|
|
const statusOrder = { required: 0, warning: 1, suggestion: 2, 'special-submit-action': 3 }
|
|
return statusOrder[a.status] - statusOrder[b.status]
|
|
})
|
|
|
|
return finalNags
|
|
})
|
|
|
|
function shouldShowLink(nag: Nag): boolean {
|
|
return nag.link?.shouldShow ? nag.link.shouldShow(nagContext.value) : false
|
|
}
|
|
|
|
function getDefaultIcon(status: NagStatus): Component {
|
|
switch (status) {
|
|
case 'required':
|
|
return AsteriskIcon
|
|
case 'warning':
|
|
return TriangleAlertIcon
|
|
case 'suggestion':
|
|
return LightBulbIcon
|
|
case 'special-submit-action':
|
|
return ScaleIcon
|
|
default:
|
|
return AsteriskIcon
|
|
}
|
|
}
|
|
|
|
function getStatusTooltip(status: NagStatus): string {
|
|
switch (status) {
|
|
case 'required':
|
|
return formatMessage(messages.required)
|
|
case 'warning':
|
|
return formatMessage(messages.warning)
|
|
case 'suggestion':
|
|
return formatMessage(messages.suggestion)
|
|
default:
|
|
return formatMessage(messages.required)
|
|
}
|
|
}
|
|
|
|
function getNagDescription(nag: Nag): string {
|
|
if (typeof nag.description === 'function') {
|
|
return nag.description(nagContext.value)
|
|
}
|
|
return formatMessage(nag.description)
|
|
}
|
|
|
|
function getFormattedMessage(message: string | MessageDescriptor): string {
|
|
if (typeof message === 'string') {
|
|
return message
|
|
}
|
|
return formatMessage(message)
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.duration-250 {
|
|
transition-duration: 250ms;
|
|
}
|
|
</style>
|