You've already forked AstralRinth
forked from didirus/AstralRinth
Projects overhaul for creators (#827)
* Projects page * Continue work on bulk edit * editLinks is now bulkEdit * Bulk Edit Links completed * Edit URL clear fields. * Create project button + other bulk buttons. * Pagination (w/o reactivity.) * Apply suggestions from code review Co-authored-by: triphora <emmaffle@modrinth.com> * Sorting fixed, broken page count though? * Only make editable projects selectable + remove delete button * Shorthand * Start using computed * Fix pagination * Add Pagination Switching * Final Style Changes * Cleanup * Action Affects dropdown * Switch to checkbox swizzle * Projects dashboard, the most hellish thing I have ever worked on * Rewrite project dashboard without tables * why's that there * Fix mod message icon * New project settings page * Remove extra slash * Bulk project route and improve styling of links UI * Remove beta label from Monetization * Relevant page links in project settings * Don't vertically center header rows * Improve error messages, add remove project icon button, add saving feedback, begin project checklist, fix license settings * Remove contextual link from project settings, disable WIP checklist * Fix bulk edit * Project checklist, add featured gallery image to project pages, fix random bugs * Remove old check * Remove icon border on grid mode and hide project status card when unnecessary * Fix build * Make checklist progress smaller and add collapsing * Remove uneven gap on nav cards * Improve wrapping of checklist * Replace project settings header link with status * Fix bugs + status stuff * Fix warns + compile error * Update wording * Hide environment type nag for project types without it * Make member dropdown match Co-authored-by: mineblock11 <93472213+mineblock11@users.noreply.github.com> Co-authored-by: triphora <emmaffle@modrinth.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -2,14 +2,18 @@
|
||||
<img
|
||||
v-if="src"
|
||||
ref="img"
|
||||
:class="`avatar size-${size} ${circle ? 'circle' : ''}`"
|
||||
:class="`avatar size-${size} ${circle ? 'circle' : ''} ${
|
||||
noShadow ? 'no-shadow' : ''
|
||||
}`"
|
||||
:src="src"
|
||||
:alt="alt"
|
||||
:loading="loading"
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
:class="`avatar size-${size} ${circle ? 'circle' : ''}`"
|
||||
:class="`avatar size-${size} ${circle ? 'circle' : ''} ${
|
||||
noShadow ? 'no-shadow' : ''
|
||||
}`"
|
||||
xml:space="preserve"
|
||||
fill-rule="evenodd"
|
||||
stroke-linecap="round"
|
||||
@@ -52,6 +56,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
noShadow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: String,
|
||||
default: 'eager',
|
||||
@@ -112,5 +120,9 @@ export default {
|
||||
&.circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.no-shadow {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<template>
|
||||
<span :class="'version-badge ' + color + ' type--' + type">
|
||||
<template v-if="color"
|
||||
><span class="circle" /> {{ $capitalizeString(type) }}</template
|
||||
>
|
||||
<template v-else-if="type === 'admin'"
|
||||
><ModrinthIcon /> Modrinth Team</template
|
||||
>
|
||||
<template v-else-if="type === 'moderator'"
|
||||
><ModeratorIcon /> Moderator</template
|
||||
>
|
||||
<template v-if="color">
|
||||
<span class="circle" /> {{ $capitalizeString(type) }}
|
||||
</template>
|
||||
<template v-else-if="type === 'admin'">
|
||||
<ModrinthIcon /> Modrinth Team
|
||||
</template>
|
||||
<template v-else-if="type === 'moderator'">
|
||||
<ModeratorIcon /> Moderator
|
||||
</template>
|
||||
<template v-else-if="type === 'creator'"><CreatorIcon /> Creator</template>
|
||||
<template v-else-if="type === 'approved'"><ListIcon /> Listed</template>
|
||||
<template v-else-if="type === 'unlisted'"><EyeOffIcon /> Unlisted</template>
|
||||
<template v-else-if="type === 'draft'"><DraftIcon /> Draft</template>
|
||||
<template v-else-if="type === 'archived'"
|
||||
><ArchiveIcon /> Archived</template
|
||||
>
|
||||
<template v-else-if="type === 'archived'">
|
||||
<ArchiveIcon /> Archived
|
||||
</template>
|
||||
<template v-else-if="type === 'rejected'"><CrossIcon /> Rejected</template>
|
||||
<template v-else-if="type === 'processing'"
|
||||
><ProcessingIcon /> Under review</template
|
||||
>
|
||||
<template v-else
|
||||
><span class="circle" /> {{ $capitalizeString(type) }}</template
|
||||
>
|
||||
<template v-else-if="type === 'processing'">
|
||||
<ProcessingIcon /> Under review
|
||||
</template>
|
||||
<template v-else-if="type === 'accepted'"><CheckIcon /> Accepted</template>
|
||||
<template v-else-if="type === 'pending'">
|
||||
<ProcessingIcon /> Pending
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="circle" /> {{ $capitalizeString(type) }}
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -36,6 +40,7 @@ import DraftIcon from '~/assets/images/utils/file-text.svg?inline'
|
||||
import CrossIcon from '~/assets/images/utils/x.svg?inline'
|
||||
import ArchiveIcon from '~/assets/images/utils/archive.svg?inline'
|
||||
import ProcessingIcon from '~/assets/images/utils/updated.svg?inline'
|
||||
import CheckIcon from '~/assets/images/utils/check.svg?inline'
|
||||
|
||||
export default {
|
||||
name: 'Badge',
|
||||
@@ -49,6 +54,7 @@ export default {
|
||||
CrossIcon,
|
||||
ArchiveIcon,
|
||||
ProcessingIcon,
|
||||
CheckIcon,
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
@@ -90,12 +96,14 @@ export default {
|
||||
--badge-color: var(--color-special-red);
|
||||
}
|
||||
|
||||
&.type--pending,
|
||||
&.type--moderator,
|
||||
&.type--processing,
|
||||
&.orange {
|
||||
--badge-color: var(--color-special-orange);
|
||||
}
|
||||
|
||||
&.type--accepted,
|
||||
&.type--admin,
|
||||
&.green {
|
||||
--badge-color: var(--color-special-green);
|
||||
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
|
||||
p {
|
||||
user-select: none;
|
||||
padding: 0.2rem 0rem;
|
||||
padding: 0.2rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
116
components/ui/EnvironmentIndicator.vue
Normal file
116
components/ui/EnvironmentIndicator.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<span v-if="typeOnly" class="environment">
|
||||
<InfoIcon aria-hidden="true" />
|
||||
A {{ type }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="
|
||||
!['resourcepack', 'shader'].includes(type) &&
|
||||
!(type === 'plugin' && search) &&
|
||||
!categories.some((x) => $tag.loaderData.dataPackLoaders.includes(x))
|
||||
"
|
||||
class="environment"
|
||||
>
|
||||
<template v-if="clientSide === 'optional' && serverSide === 'optional'">
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
Client or server
|
||||
</template>
|
||||
<template
|
||||
v-else-if="clientSide === 'required' && serverSide === 'required'"
|
||||
>
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
Client and server
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
(clientSide === 'optional' || clientSide === 'required') &&
|
||||
(serverSide === 'optional' || serverSide === 'unsupported')
|
||||
"
|
||||
>
|
||||
<ClientIcon aria-hidden="true" />
|
||||
Client
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
(serverSide === 'optional' || serverSide === 'required') &&
|
||||
(clientSide === 'optional' || clientSide === 'unsupported')
|
||||
"
|
||||
>
|
||||
<ServerIcon aria-hidden="true" />
|
||||
Server
|
||||
</template>
|
||||
<template
|
||||
v-else-if="serverSide === 'unsupported' && clientSide === 'unsupported'"
|
||||
>
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
Unsupported
|
||||
</template>
|
||||
<template v-else-if="alwaysShow">
|
||||
<InfoIcon aria-hidden="true" />
|
||||
A {{ type }}
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
import InfoIcon from '~/assets/images/utils/info.svg?inline'
|
||||
import ClientIcon from '~/assets/images/utils/client.svg?inline'
|
||||
import GlobeIcon from '~/assets/images/utils/globe.svg?inline'
|
||||
import ServerIcon from '~/assets/images/utils/server.svg?inline'
|
||||
export default {
|
||||
name: 'EnvironmentIndicator',
|
||||
components: {
|
||||
InfoIcon,
|
||||
ClientIcon,
|
||||
ServerIcon,
|
||||
GlobeIcon,
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'mod',
|
||||
},
|
||||
serverSide: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
clientSide: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
typeOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
alwaysShow: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
search: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
categories: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.environment {
|
||||
display: flex;
|
||||
color: var(--color-text) !important;
|
||||
font-weight: bold;
|
||||
svg {
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -85,7 +85,6 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
label {
|
||||
flex-direction: unset;
|
||||
margin-bottom: 0;
|
||||
max-height: unset;
|
||||
|
||||
svg {
|
||||
|
||||
@@ -9,15 +9,17 @@
|
||||
@click="hide"
|
||||
/>
|
||||
<div class="modal-body" :class="{ shown: shown }">
|
||||
<div v-if="header" class="header">
|
||||
<h1>{{ header }}</h1>
|
||||
<button class="iconified-button icon-only transparent" @click="hide">
|
||||
<CrossIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<template v-if="shown">
|
||||
<div v-if="header" class="header">
|
||||
<h1>{{ header }}</h1>
|
||||
<button class="iconified-button icon-only transparent" @click="hide">
|
||||
<CrossIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<nav class="navigation">
|
||||
<slot />
|
||||
<nav>
|
||||
<ul>
|
||||
<slot />
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -11,10 +13,18 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navigation {
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-gap: var(--spacing-card-xs);
|
||||
flex-wrap: wrap;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: unset;
|
||||
text-align: unset;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,21 +1,44 @@
|
||||
<template>
|
||||
<NuxtLink class="nav-link button-base" :to="link">
|
||||
<NuxtLink v-if="link !== null" class="nav-link button-base" :to="link">
|
||||
<div class="nav-content">
|
||||
<slot />
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="beta" class="beta-badge">BETA</span>
|
||||
<span v-if="chevron" class="chevron"><ChevronRightIcon /></span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<button
|
||||
v-else-if="action"
|
||||
class="nav-link button-base"
|
||||
:class="{ 'danger-button': danger }"
|
||||
@click="action"
|
||||
>
|
||||
<span class="nav-content">
|
||||
<slot />
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="beta" class="beta-badge">BETA</span>
|
||||
</span>
|
||||
</button>
|
||||
<span v-else>i forgor 💀</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChevronRightIcon from '~/assets/images/utils/chevron-right.svg?inline'
|
||||
|
||||
export default {
|
||||
name: 'NavStackItem',
|
||||
components: {
|
||||
ChevronRightIcon,
|
||||
},
|
||||
props: {
|
||||
link: {
|
||||
required: true,
|
||||
default: null,
|
||||
type: String,
|
||||
},
|
||||
action: {
|
||||
default: null,
|
||||
type: Function,
|
||||
},
|
||||
label: {
|
||||
required: true,
|
||||
type: String,
|
||||
@@ -24,6 +47,14 @@ export default {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
chevron: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
danger: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -31,12 +62,20 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.nav-link {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text);
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.25rem;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
|
||||
:where(.nav-link) {
|
||||
--text-color: var(--color-text);
|
||||
--background-color: var(--color-raised-bg);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
box-sizing: border-box;
|
||||
@@ -46,7 +85,7 @@ export default {
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
flex-grow: 1;
|
||||
background-color: var(--color-raised-bg);
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
&.nuxt-link-exact-active {
|
||||
@@ -60,5 +99,9 @@ export default {
|
||||
.beta-badge {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
tabindex="-1"
|
||||
:to="`/${$getProjectTypeForUrl(type, categories)}/${id}`"
|
||||
>
|
||||
<Avatar :src="iconUrl" :alt="name" size="md" loading="lazy" />
|
||||
<Avatar :src="iconUrl" :alt="name" size="md" no-shadow loading="lazy" />
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
class="gallery"
|
||||
@@ -49,59 +49,14 @@
|
||||
:type="type"
|
||||
class="tags"
|
||||
>
|
||||
<span v-if="moderation" class="environment">
|
||||
<InfoIcon aria-hidden="true" />
|
||||
A {{ projectTypeDisplay }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="
|
||||
!['resourcepack', 'shader'].includes(type) &&
|
||||
!(projectTypeDisplay === 'plugin' && search) &&
|
||||
!categories.some((x) => $tag.loaderData.dataPackLoaders.includes(x))
|
||||
"
|
||||
class="environment"
|
||||
>
|
||||
<template v-if="clientSide === 'optional' && serverSide === 'optional'">
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
Client or server
|
||||
</template>
|
||||
<template
|
||||
v-else-if="clientSide === 'required' && serverSide === 'required'"
|
||||
>
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
Client and server
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
(clientSide === 'optional' || clientSide === 'required') &&
|
||||
(serverSide === 'optional' || serverSide === 'unsupported')
|
||||
"
|
||||
>
|
||||
<ClientIcon aria-hidden="true" />
|
||||
Client
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
(serverSide === 'optional' || serverSide === 'required') &&
|
||||
(clientSide === 'optional' || clientSide === 'unsupported')
|
||||
"
|
||||
>
|
||||
<ServerIcon aria-hidden="true" />
|
||||
Server
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
serverSide === 'unsupported' && clientSide === 'unsupported'
|
||||
"
|
||||
>
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
Unsupported
|
||||
</template>
|
||||
<template v-else-if="moderation">
|
||||
<InfoIcon aria-hidden="true" />
|
||||
A {{ projectTypeDisplay }}
|
||||
</template>
|
||||
</span>
|
||||
<EnvironmentIndicator
|
||||
:type-only="moderation"
|
||||
:client-side="clientSide"
|
||||
:server-side="serverSide"
|
||||
:type="projectTypeDisplay"
|
||||
:search="search"
|
||||
:categories="categories"
|
||||
/>
|
||||
</Categories>
|
||||
<div class="stats">
|
||||
<div v-if="downloads" class="stat">
|
||||
@@ -150,11 +105,8 @@
|
||||
<script>
|
||||
import Categories from '~/components/ui/search/Categories'
|
||||
import Badge from '~/components/ui/Badge'
|
||||
import EnvironmentIndicator from '~/components/ui/EnvironmentIndicator'
|
||||
|
||||
import InfoIcon from '~/assets/images/utils/info.svg?inline'
|
||||
import ClientIcon from '~/assets/images/utils/client.svg?inline'
|
||||
import GlobeIcon from '~/assets/images/utils/globe.svg?inline'
|
||||
import ServerIcon from '~/assets/images/utils/server.svg?inline'
|
||||
import CalendarIcon from '~/assets/images/utils/calendar.svg?inline'
|
||||
import EditIcon from '~/assets/images/utils/updated.svg?inline'
|
||||
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
|
||||
@@ -164,13 +116,10 @@ import Avatar from '~/components/ui/Avatar'
|
||||
export default {
|
||||
name: 'ProjectCard',
|
||||
components: {
|
||||
EnvironmentIndicator,
|
||||
Avatar,
|
||||
Categories,
|
||||
Badge,
|
||||
InfoIcon,
|
||||
ClientIcon,
|
||||
ServerIcon,
|
||||
GlobeIcon,
|
||||
CalendarIcon,
|
||||
EditIcon,
|
||||
DownloadIcon,
|
||||
@@ -364,7 +313,7 @@ export default {
|
||||
img,
|
||||
svg {
|
||||
border-radius: var(--size-rounded-lg);
|
||||
border: 0.25rem solid var(--color-raised-bg);
|
||||
border: 4px solid var(--color-raised-bg);
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -427,6 +376,11 @@ export default {
|
||||
|
||||
.icon {
|
||||
margin-top: calc(var(--spacing-card-bg) - var(--spacing-card-sm));
|
||||
|
||||
img,
|
||||
svg {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
440
components/ui/ProjectPublishingChecklist.vue
Normal file
440
components/ui/ProjectPublishingChecklist.vue
Normal file
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="
|
||||
$auth.user &&
|
||||
currentMember &&
|
||||
nags.filter((x) => x.condition).length > 0 &&
|
||||
project.status === 'draft'
|
||||
"
|
||||
class="author-actions universal-card"
|
||||
>
|
||||
<div class="header__row">
|
||||
<div class="header__title">
|
||||
<h2>Publishing checklist</h2>
|
||||
<div class="checklist">
|
||||
<span class="checklist__title">Progress:</span>
|
||||
<div class="checklist__items">
|
||||
<div
|
||||
v-for="nag in nags"
|
||||
:key="`checklist-${nag.id}`"
|
||||
v-tooltip="nag.title"
|
||||
class="circle"
|
||||
:class="'circle ' + (!nag.condition ? 'done ' : '') + nag.status"
|
||||
>
|
||||
<CheckIcon v-if="!nag.condition" />
|
||||
<RequiredIcon v-else-if="nag.status === 'required'" />
|
||||
<SuggestionIcon v-else-if="nag.status === 'suggestion'" />
|
||||
<ModerationIcon v-else-if="nag.status === 'review'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button
|
||||
class="square-button"
|
||||
:class="{ 'not-collapsed': !collapsed }"
|
||||
@click="toggleCollapsed()"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!collapsed" class="grid-display width-16">
|
||||
<div
|
||||
v-for="nag in nags.filter((x) => x.condition)"
|
||||
:key="nag.id"
|
||||
class="grid-display__item"
|
||||
>
|
||||
<span class="label">
|
||||
<RequiredIcon
|
||||
v-if="nag.status === 'required'"
|
||||
v-tooltip="'Required'"
|
||||
:class="nag.status"
|
||||
/>
|
||||
<SuggestionIcon
|
||||
v-else-if="nag.status === 'suggestion'"
|
||||
v-tooltip="'Suggestion'"
|
||||
:class="nag.status"
|
||||
/>
|
||||
<ModerationIcon
|
||||
v-else-if="nag.status === 'review'"
|
||||
v-tooltip="'Review'"
|
||||
:class="nag.status"
|
||||
/>{{ nag.title }}</span
|
||||
>
|
||||
{{ nag.description }}
|
||||
<NuxtLink
|
||||
v-if="nag.link"
|
||||
:class="{ invisible: nag.link.hide }"
|
||||
class="goto-link"
|
||||
:to="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/${nag.link.path}`"
|
||||
>
|
||||
{{ nag.link.title }}
|
||||
<ChevronRightIcon
|
||||
class="featured-header-chevron"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NuxtLink>
|
||||
<button
|
||||
v-else-if="nag.action"
|
||||
class="iconified-button moderation-button"
|
||||
:disabled="nag.action.disabled()"
|
||||
@click="nag.action.onClick"
|
||||
>
|
||||
<SendIcon />
|
||||
{{ nag.action.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChevronRightIcon from '~/assets/images/utils/chevron-right.svg?inline'
|
||||
import DropdownIcon from '~/assets/images/utils/dropdown.svg?inline'
|
||||
import CheckIcon from '~/assets/images/utils/check.svg?inline'
|
||||
import RequiredIcon from '~/assets/images/utils/asterisk.svg?inline'
|
||||
import SuggestionIcon from '~/assets/images/utils/lightbulb.svg?inline'
|
||||
import ModerationIcon from '~/assets/images/sidebar/admin.svg?inline'
|
||||
import SendIcon from '~/assets/images/utils/send.svg?inline'
|
||||
|
||||
export default {
|
||||
name: 'ProjectPublishingChecklist',
|
||||
components: {
|
||||
ChevronRightIcon,
|
||||
DropdownIcon,
|
||||
CheckIcon,
|
||||
RequiredIcon,
|
||||
SuggestionIcon,
|
||||
ModerationIcon,
|
||||
SendIcon,
|
||||
},
|
||||
props: {
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
currentMember: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isSettings: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
routeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
setProcessing: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'setProcessing function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
toggleCollapsed: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'toggleCollapsed function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
featuredGalleryImage() {
|
||||
return this.project.gallery.find((img) => img.featured)
|
||||
},
|
||||
nags() {
|
||||
return [
|
||||
{
|
||||
condition:
|
||||
this.project.body === '' ||
|
||||
this.project.body.startsWith('# Placeholder description'),
|
||||
title: 'Add a description',
|
||||
id: 'add-description',
|
||||
description:
|
||||
"A description that clearly describes the project's purpose and function is required.",
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: 'Visit description settings',
|
||||
hide: this.routeName === 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !this.project.icon_url,
|
||||
title: 'Add an icon',
|
||||
id: 'add-icon',
|
||||
description:
|
||||
'Your project should have a nice-looking icon to uniquely identify your project at a glance.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: 'Visit general settings',
|
||||
hide: this.routeName === 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !this.featuredGalleryImage,
|
||||
title: 'Feature a gallery image',
|
||||
id: 'feature-gallery-image',
|
||||
description:
|
||||
'Featured gallery images may be the first impression of many users.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: 'Visit gallery page',
|
||||
hide: this.routeName === 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: this.versions.length < 1,
|
||||
title: 'Upload a version',
|
||||
id: 'upload-version',
|
||||
description:
|
||||
'At least one version is required for a project to be submitted for review.',
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'versions',
|
||||
title: 'Visit versions page',
|
||||
hide: this.routeName === 'type-id-versions',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: this.project.categories.length < 1,
|
||||
title: 'Select tags',
|
||||
id: 'select-tags',
|
||||
description: 'Select all tags that apply to your project.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: 'Visit tag settings',
|
||||
hide: this.routeName === 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
hide:
|
||||
this.project.project_type === 'resourcepack' ||
|
||||
this.project.project_type === 'plugin' ||
|
||||
this.project.project_type === 'shader' ||
|
||||
this.project.project_type === 'datapack',
|
||||
condition:
|
||||
this.project.client_side === 'unknown' ||
|
||||
this.project.server_side === 'unknown',
|
||||
title: 'Select supported environments',
|
||||
id: 'select-environments',
|
||||
description: `Select if the ${this.$formatProjectType(
|
||||
this.project.project_type
|
||||
).toLowerCase()} functions on the client-side and/or server-side.`,
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: 'Visit general settings',
|
||||
hide: this.routeName === 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: this.project.status === 'draft',
|
||||
title: 'Submit for review',
|
||||
id: 'submit-for-review',
|
||||
description:
|
||||
'Your project is only viewable by members of the project. It must be reviewed by moderators in order to be published.',
|
||||
status: 'review',
|
||||
link: null,
|
||||
action: {
|
||||
onClick: this.submitForReview,
|
||||
title: 'Submit for review',
|
||||
disabled: () =>
|
||||
this.nags.filter((x) => x.condition && x.status === 'required')
|
||||
.length > 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
.filter((x) => !x.hide)
|
||||
.sort((a, b) =>
|
||||
this.sortByTrue(
|
||||
!a.condition,
|
||||
!b.condition,
|
||||
this.sortByTrue(
|
||||
a.status === 'required',
|
||||
b.status === 'required',
|
||||
this.sortByFalse(a.status === 'review', b.status === 'review')
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sortByTrue(a, b, ifEqual = 0) {
|
||||
if (a === b) {
|
||||
return ifEqual
|
||||
} else if (a) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
},
|
||||
sortByFalse(a, b, ifEqual = 0) {
|
||||
if (a === b) {
|
||||
return ifEqual
|
||||
} else if (b) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
},
|
||||
async submitForReview() {
|
||||
if (
|
||||
this.nags.filter((x) => x.condition && x.status === 'required')
|
||||
.length === 0
|
||||
) {
|
||||
await this.setProcessing()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.author-actions {
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.header__row {
|
||||
align-items: center;
|
||||
column-gap: var(--spacing-card-lg);
|
||||
row-gap: var(--spacing-card-md);
|
||||
max-width: 100%;
|
||||
|
||||
.header__title {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
column-gap: var(--spacing-card-lg);
|
||||
row-gap: var(--spacing-card-md);
|
||||
flex-basis: min-content;
|
||||
|
||||
h2 {
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
svg {
|
||||
transition: transform 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
&.not-collapsed svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-display__item .label {
|
||||
display: flex;
|
||||
gap: var(--spacing-card-xs);
|
||||
align-items: center;
|
||||
|
||||
.required {
|
||||
color: var(--color-special-red);
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
color: var(--color-special-purple);
|
||||
}
|
||||
|
||||
.review {
|
||||
color: var(--color-special-orange);
|
||||
}
|
||||
}
|
||||
|
||||
.checklist {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--spacing-card-xs);
|
||||
width: fit-content;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
|
||||
.checklist__title {
|
||||
font-weight: bold;
|
||||
margin-right: var(--spacing-card-xs);
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
.checklist__items {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--spacing-card-xs);
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.circle {
|
||||
--circle-size: 2rem;
|
||||
--background-color: var(--color-bg);
|
||||
--content-color: var(--color-special-gray);
|
||||
width: var(--circle-size);
|
||||
height: var(--circle-size);
|
||||
border-radius: 50%;
|
||||
background-color: var(--background-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
color: var(--content-color);
|
||||
width: calc(var(--circle-size) / 2);
|
||||
height: calc(var(--circle-size) / 2);
|
||||
}
|
||||
|
||||
&.required {
|
||||
--content-color: var(--color-special-red);
|
||||
}
|
||||
|
||||
&.suggestion {
|
||||
--content-color: var(--color-special-purple);
|
||||
}
|
||||
|
||||
&.review {
|
||||
--content-color: var(--color-special-orange);
|
||||
}
|
||||
|
||||
&.done {
|
||||
--background-color: var(--color-special-green);
|
||||
--content-color: var(--color-brand-inverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user