Lots of fixes - see trello (#347)

* A ton of fixes

* Fix project deletion message
This commit is contained in:
Geometrically
2022-01-28 18:11:34 -07:00
committed by GitHub
parent 643cd87706
commit 86f37863a7
25 changed files with 1132 additions and 741 deletions

View File

@@ -1,7 +1,7 @@
export default function (to, from, savedPosition) { export default function (to, from, savedPosition) {
if (to.name.startsWith('type-id') && !from.name.startsWith('type-id')) { if (to.name.startsWith('type-id') && from.name.startsWith('type-id')) {
return { x: 0, y: 0 }
} else {
return savedPosition return savedPosition
} else {
return { x: 0, y: 0 }
} }
} }

View File

@@ -31,8 +31,6 @@
box-shadow: inset 0px -1px 1px rgba(17, 24, 39, 0.1); box-shadow: inset 0px -1px 1px rgba(17, 24, 39, 0.1);
max-height: 2rem;
color: var(--color-button-text); color: var(--color-button-text);
background-color: var(--color-button-bg); background-color: var(--color-button-bg);
text-decoration: none; text-decoration: none;
@@ -385,23 +383,19 @@
.multiselect { .multiselect {
color: var(--color-text) !important; color: var(--color-text) !important;
max-height: 40px;
input { input {
background: transparent; background: transparent;
} }
&.top-margin {
.multiselect__tags {
padding-top: 10px;
}
}
.multiselect__tags { .multiselect__tags {
border-radius: 1.25rem; border-radius: 1.25rem;
background: var(--color-dropdown-bg); background: var(--color-dropdown-bg);
border: none; border: none;
cursor: pointer; cursor: pointer;
padding-left: 1rem; padding-left: 1rem;
padding-top: 10px;
&:active, &:active,
&:hover { &:hover {
@@ -436,6 +430,8 @@
background: var(--color-dropdown-bg); background: var(--color-dropdown-bg);
border: none; border: none;
overflow-x: hidden; overflow-x: hidden;
border-bottom-left-radius: var(--size-rounded-card);
border-bottom-right-radius: var(--size-rounded-card);
.multiselect__element { .multiselect__element {
.multiselect__option--highlight { .multiselect__option--highlight {
@@ -470,6 +466,10 @@ label {
span { span {
flex: 2; flex: 2;
padding-right: var(--spacing-card-lg); padding-right: var(--spacing-card-lg);
&.no-padding {
padding-right: 0;
}
} }
input, input,
@@ -518,8 +518,10 @@ label {
} }
.stylized-toggle { .stylized-toggle {
min-height: 32px;
height: 32px; height: 32px;
width: 52px; width: 52px;
max-width: 52px;
border-radius: 16px; border-radius: 16px;
display: inline-block; display: inline-block;
position: relative; position: relative;
@@ -563,8 +565,14 @@ label {
height: 1.75rem; height: 1.75rem;
width: 1.75rem; width: 1.75rem;
border-radius: 1.5rem; border-radius: 1.5rem;
background-color: var(--color-button-bg); color: var(--color-brand-inverted);
background-color: var(--color-brand);
margin-right: var(--spacing-card-sm); margin-right: var(--spacing-card-sm);
&:hover {
background-color: var(--color-brand-hover);
}
svg { svg {
width: 1.25rem; width: 1.25rem;
margin: auto; margin: auto;
@@ -682,3 +690,29 @@ label {
// box-shadow: var(--shadow-card); // box-shadow: var(--shadow-card);
} }
.vue-notification-group {
right: 25px !important;
.vue-notification-template {
border-radius: var(--size-rounded-card);
margin: 0 0 25px 0;
.notification-title {
font-size: var(--font-size-lg);
margin-right: auto;
}
.notification-content {
font-size: var(--font-size-md);
}
}
}
.card-divider {
background-color: var(--color-divider);
border: none;
color: var(--color-divider);
height: 1px;
margin: var(--spacing-card-bg) 0;
}

View File

@@ -2,10 +2,6 @@ html {
@extend .light-mode; @extend .light-mode;
} }
body {
overflow-y: scroll;
}
.light-mode { .light-mode {
--color-icon: #6b7280; --color-icon: #6b7280;
--color-text: hsl(221, 39%, 11%); --color-text: hsl(221, 39%, 11%);
@@ -30,7 +26,7 @@ body {
--color-brand-3: #30b27b; --color-brand-3: #30b27b;
--color-brand-disabled: #e2e8f0; --color-brand-disabled: #e2e8f0;
--color-button-bg: #e6e7eb; --color-button-bg: #e0e0e5;
--color-button-text: var(--color-text-dark); --color-button-text: var(--color-text-dark);
--color-button-bg-hover: #d9dce0; --color-button-bg-hover: #d9dce0;
--color-button-text-hover: #1b1e24; --color-button-text-hover: #1b1e24;
@@ -259,15 +255,9 @@ textarea {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: 2px solid transparent; border: 2px solid transparent;
&:focus, &:hover:not([disabled]):not(:focus) {
&:hover:not([disabled]) {
background: var(--color-button-bg-hover); background: var(--color-button-bg-hover);
color: var(--color-text); color: var(--color-text);
//outline: none; Bad for accessibility
&::placeholder {
color: var(--color-text);
}
} }
&:focus { &:focus {
@@ -275,10 +265,6 @@ textarea {
border-color: var(--color-divider-dark); border-color: var(--color-divider-dark);
} }
&::placeholder {
color: var(--color-color-text);
}
&:disabled, &:disabled,
&[disabled] { &[disabled] {
opacity: 0.6; opacity: 0.6;

View File

@@ -48,6 +48,11 @@
flex-direction: row; flex-direction: row;
margin: 0 auto; margin: 0 auto;
max-width: 80rem; max-width: 80rem;
column-gap: 0.75rem;
&.alt-layout {
flex-direction: row-reverse;
}
} }
.normal-page__sidebar { .normal-page__sidebar {
@@ -55,7 +60,6 @@
} }
.normal-page__content { .normal-page__content {
padding-left: 0.75rem;
width: 60rem; width: 60rem;
} }
} }

View File

@@ -6,6 +6,7 @@
width: 100%; width: 100%;
} }
html { body {
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden;
} }

View File

@@ -198,8 +198,15 @@ export default {
}, },
}, },
methods: { methods: {
formatNumber(x) { formatNumber(y) {
const x = +y
if (x >= 1000000) {
return (x / 1000000).toFixed(2).toString() + 'M'
} else if (x >= 10000) {
return (x / 1000).toFixed(1).toString() + 'K'
} else {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
}, },
}, },
} }
@@ -213,7 +220,6 @@ export default {
.project-card { .project-card {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-direction: column;
padding: var(--spacing-card-bg); padding: var(--spacing-card-bg);
width: calc(100% - 2 * var(--spacing-card-bg)); width: calc(100% - 2 * var(--spacing-card-bg));
@@ -300,8 +306,9 @@ export default {
margin-right: 2rem; margin-right: 2rem;
svg { svg {
width: 1.25rem;
height: 1.25rem; height: 1.25rem;
margin-right: 0.125rem; margin-right: 0.25rem;
} }
} }
} }
@@ -359,6 +366,15 @@ export default {
@media screen and (max-width: 560px) { @media screen and (max-width: 560px) {
.card-content { .card-content {
flex-direction: column; flex-direction: column;
.info {
.dates {
.date {
margin-bottom: 0.5rem;
}
}
}
.right-side { .right-side {
padding-top: var(--spacing-card-sm); padding-top: var(--spacing-card-sm);
@@ -368,10 +384,6 @@ export default {
margin-left: 0; margin-left: 0;
} }
.buttons {
flex-direction: row;
}
.buttons button, .buttons button,
a { a {
margin-left: unset; margin-left: unset;

View File

@@ -226,7 +226,7 @@
<p>modrinth/knossos {{ version }}</p> <p>modrinth/knossos {{ version }}</p>
<p>© Guavy LLC</p> <p>© Guavy LLC</p>
</div> </div>
<div class="links" role="region" aria-label="Legal"> <div class="links links-1" role="region" aria-label="Legal">
<h4 aria-hidden="true">Legal</h4> <h4 aria-hidden="true">Legal</h4>
<nuxt-link to="/legal/terms">Terms</nuxt-link> <nuxt-link to="/legal/terms">Terms</nuxt-link>
<nuxt-link to="/legal/privacy">Privacy</nuxt-link> <nuxt-link to="/legal/privacy">Privacy</nuxt-link>
@@ -238,7 +238,7 @@
License License
</a> </a>
</div> </div>
<div class="links" role="region" aria-label="Resources"> <div class="links links-2" role="region" aria-label="Resources">
<h4 aria-hidden="true">Resources</h4> <h4 aria-hidden="true">Resources</h4>
<a target="_blank" href="https://blog.modrinth.com">Blog</a> <a target="_blank" href="https://blog.modrinth.com">Blog</a>
<a target="_blank" href="https://discord.gg/EUHuJHt">Discord</a> <a target="_blank" href="https://discord.gg/EUHuJHt">Discord</a>
@@ -275,26 +275,22 @@ import HomeIcon from '~/assets/images/sidebar/home.svg?inline'
import ModIcon from '~/assets/images/sidebar/mod.svg?inline' import ModIcon from '~/assets/images/sidebar/mod.svg?inline'
import ModpackIcon from '~/assets/images/sidebar/modpack.svg?inline' import ModpackIcon from '~/assets/images/sidebar/modpack.svg?inline'
// import DropdownIcon from '~/assets/images/utils/dropdown.svg?inline'
import MoonIcon from '~/assets/images/utils/moon.svg?inline' import MoonIcon from '~/assets/images/utils/moon.svg?inline'
import SunIcon from '~/assets/images/utils/sun.svg?inline' import SunIcon from '~/assets/images/utils/sun.svg?inline'
import PlusIcon from '~/assets/images/utils/plus.svg?inline' import PlusIcon from '~/assets/images/utils/plus.svg?inline'
// import UserIcon from '~/assets/images/utils/user.svg?inline'
import LogOutIcon from '~/assets/images/utils/log-out.svg?inline' import LogOutIcon from '~/assets/images/utils/log-out.svg?inline'
import GitHubIcon from '~/assets/images/utils/github.svg?inline' import GitHubIcon from '~/assets/images/utils/github.svg?inline'
import CookieConsent from '~/components/ads/CookieConsent' import CookieConsent from '~/components/ads/CookieConsent'
const overflowStyle = 'overlay' const overflowStyle = 'scroll'
export default { export default {
components: { components: {
ModrinthLogo, ModrinthLogo,
// DropdownIcon,
MoonIcon, MoonIcon,
SunIcon, SunIcon,
// UserIcon,
LogOutIcon, LogOutIcon,
GitHubIcon, GitHubIcon,
NotificationIcon, NotificationIcon,
@@ -322,6 +318,7 @@ export default {
await Promise.all([ await Promise.all([
this.$store.dispatch('user/fetchAll', { force: true }), this.$store.dispatch('user/fetchAll', { force: true }),
this.$store.dispatch('tag/fetchAllTags'), this.$store.dispatch('tag/fetchAllTags'),
this.$store.dispatch('cosmetics/fetchCosmetics', this.$cookies),
]) ])
}, },
computed: { computed: {
@@ -335,8 +332,7 @@ export default {
this.isMobileMenuOpen = this.isMobileMenuOpen =
this.$refs.mobileMenu.className === 'mobile-menu active' this.$refs.mobileMenu.className === 'mobile-menu active'
document.documentElement.style.overflow = overflowStyle document.body.style.overflowY = overflowStyle
document.body.style.overflow = overflowStyle
this.$store.dispatch('user/fetchAll') this.$store.dispatch('user/fetchAll')
}, },
@@ -356,12 +352,8 @@ export default {
}` }`
document.body.scrollTop = 0 document.body.scrollTop = 0
document.documentElement.style.overflow = document.body.style.overflowY =
document.documentElement.style.overflow !== 'hidden' document.body.style.overflowY !== 'hidden' ? 'hidden' : overflowStyle
? 'hidden'
: overflowStyle
document.body.style.overflow =
document.body.style.overflow !== 'hidden' ? 'hidden' : overflowStyle
this.isMobileMenuOpen = !currentlyActive this.isMobileMenuOpen = !currentlyActive
}, },
@@ -393,12 +385,6 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
html {
overflow: auto;
//noinspection CssInvalidPropertyValue
overflow: overlay;
}
.layout { .layout {
min-height: 100vh; min-height: 100vh;
background-color: var(--color-bg); background-color: var(--color-bg);
@@ -850,14 +836,20 @@ html {
footer { footer {
margin: 6rem 0 2rem 0; margin: 6rem 0 2rem 0;
flex-wrap: wrap;
text-align: center; text-align: center;
display: grid;
grid-template:
'logo-info logo-info' auto
'links-1 links-2' auto
'buttons buttons' auto
/ 1fr 1fr;
.logo-info { .logo-info {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: 22rem; max-width: 20rem;
margin-bottom: 1rem; margin-bottom: 1rem;
grid-area: logo-info;
.text-logo { .text-logo {
width: 10rem; width: 10rem;
@@ -878,11 +870,20 @@ html {
a { a {
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
} }
&.links-1 {
grid-area: links-1;
}
&.links-2 {
grid-area: links-2;
}
} }
.buttons { .buttons {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
grid-area: buttons;
button, button,
a { a {

View File

@@ -174,7 +174,6 @@ export default {
'~/plugins/vue-notification.js', '~/plugins/vue-notification.js',
'~/plugins/xss.js', '~/plugins/xss.js',
'~/plugins/vue-syntax.js', '~/plugins/vue-syntax.js',
'~/plugins/auth.js',
'~/plugins/shorthands.js', '~/plugins/shorthands.js',
], ],
/* /*

View File

@@ -1,7 +1,11 @@
<template> <template>
<div class="page-container"> <div class="page-container">
<div class="page-contents"> <div
<section class="project-info"> :class="{
'page-contents': true,
'alt-layout': $store.state.cosmetics.projectLayout,
}"
>
<div class="header card"> <div class="header card">
<nuxt-link <nuxt-link
:to=" :to="
@@ -68,7 +72,7 @@
{{ project.description }} {{ project.description }}
</p> </p>
<Categories :categories="project.categories" class="categories" /> <Categories :categories="project.categories" class="categories" />
<hr /> <hr class="card-divider" />
<div class="stats"> <div class="stats">
<span class="stat">{{ formatNumber(project.downloads) }}</span> <span class="stat">{{ formatNumber(project.downloads) }}</span>
<span class="label" <span class="label"
@@ -83,9 +87,7 @@
<div class="date"> <div class="date">
<CalendarIcon aria-hidden="true" /> <CalendarIcon aria-hidden="true" />
<span class="label">Created</span> <span class="label">Created</span>
<span class="value">{{ <span class="value">{{ $dayjs(project.published).fromNow() }}</span>
$dayjs(project.published).fromNow()
}}</span>
</div> </div>
<div class="date"> <div class="date">
<UpdateIcon aria-hidden="true" /> <UpdateIcon aria-hidden="true" />
@@ -93,7 +95,7 @@
<span class="value">{{ $dayjs(project.updated).fromNow() }}</span> <span class="value">{{ $dayjs(project.updated).fromNow() }}</span>
</div> </div>
</div> </div>
<hr v-if="$auth.user" /> <hr v-if="$auth.user" class="card-divider" />
<div class="buttons"> <div class="buttons">
<nuxt-link <nuxt-link
v-if="$auth.user" v-if="$auth.user"
@@ -104,9 +106,7 @@
Report Report
</nuxt-link> </nuxt-link>
<button <button
v-if=" v-if="$auth.user && !$user.follows.find((x) => x.id === project.id)"
$auth.user && !$user.follows.find((x) => x.id === project.id)
"
class="iconified-button" class="iconified-button"
@click="$store.dispatch('user/followProject', project)" @click="$store.dispatch('user/followProject', project)"
> >
@@ -114,9 +114,7 @@
Follow Follow
</button> </button>
<button <button
v-if=" v-if="$auth.user && $user.follows.find((x) => x.id === project.id)"
$auth.user && $user.follows.find((x) => x.id === project.id)
"
class="iconified-button" class="iconified-button"
@click="$store.dispatch('user/unfollowProject', project)" @click="$store.dispatch('user/unfollowProject', project)"
> >
@@ -133,9 +131,9 @@
(project.moderator_message.message || (project.moderator_message.message ||
project.moderator_message.body))) project.moderator_message.body)))
" "
class="card" class="project-status card"
> >
<h3>Project status</h3> <h3 class="card-header">Project status</h3>
<div class="status-info"></div> <div class="status-info"></div>
<p> <p>
Your project is currently: Your project is currently:
@@ -160,14 +158,14 @@
</p> </p>
<div class="message"> <div class="message">
<p v-if="project.status === 'processing'"> <p v-if="project.status === 'processing'">
Your project is currently not viewable by people who are not part Your project is currently not viewable by people who are not part of
of your team. Please wait for our moderators to manually review your team. Please wait for our moderators to manually review your
your project to see if it abides by our project rules! project to see if it abides by our project rules!
</p> </p>
<p v-if="project.status === 'draft'"> <p v-if="project.status === 'draft'">
Your project is currently not viewable by people who are not part Your project is currently not viewable by people who are not part of
of your team. If your project is ready for review, click the your team. If your project is ready for review, click the button
button below to make your mod public! below to make your mod public!
</p> </p>
<p v-if="project.moderator_message"> <p v-if="project.moderator_message">
{{ project.moderator_message.message }} {{ project.moderator_message.message }}
@@ -198,7 +196,7 @@
</button> </button>
</div> </div>
</div> </div>
<div class="card"> <div class="extra-info card">
<template <template
v-if=" v-if="
project.issues_url || project.issues_url ||
@@ -207,7 +205,7 @@
project.discord_url project.discord_url
" "
> >
<h3>External resources</h3> <h3 class="card-header">External resources</h3>
<div class="links"> <div class="links">
<a <a
v-if="project.issues_url" v-if="project.issues_url"
@@ -317,16 +315,14 @@
<span v-else-if="donation.id === 'patreon'">Patreon</span> <span v-else-if="donation.id === 'patreon'">Patreon</span>
<span v-else-if="donation.id === 'paypal'">PayPal</span> <span v-else-if="donation.id === 'paypal'">PayPal</span>
<span v-else-if="donation.id === 'ko-fi'">Ko-fi</span> <span v-else-if="donation.id === 'ko-fi'">Ko-fi</span>
<span v-else-if="donation.id === 'github'" <span v-else-if="donation.id === 'github'">GitHub Sponsors</span>
>GitHub Sponsors</span
>
<span v-else>Donate</span> <span v-else>Donate</span>
</a> </a>
</div> </div>
<hr /> <hr class="card-divider" />
</template> </template>
<template v-if="featuredVersions.length > 0"> <template v-if="featuredVersions.length > 0">
<h3>Featured versions</h3> <h3 class="card-header">Featured versions</h3>
<div <div
v-for="version in featuredVersions" v-for="version in featuredVersions"
:key="version.id" :key="version.id"
@@ -363,7 +359,7 @@
.map((x) => x.charAt(0).toUpperCase() + x.slice(1)) .map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join(', ') .join(', ')
}} }}
{{ version.game_versions[version.game_versions.length - 1] }} {{ $formatVersion(version.game_versions) }}
</div> </div>
<VersionBadge <VersionBadge
v-if="version.version_type === 'release'" v-if="version.version_type === 'release'"
@@ -382,9 +378,9 @@
/> />
</div> </div>
</div> </div>
<hr /> <hr class="card-divider" />
</template> </template>
<h3>Project members</h3> <h3 class="card-header">Project members</h3>
<div <div
v-for="member in members" v-for="member in members"
:key="member.user.id" :key="member.user.id"
@@ -398,8 +394,8 @@
<p class="role">{{ member.role }}</p> <p class="role">{{ member.role }}</p>
</div> </div>
</div> </div>
<hr /> <hr class="card-divider" />
<h3>Technical information</h3> <h3 class="card-header">Technical information</h3>
<div class="infos"> <div class="infos">
<div class="info"> <div class="info">
<div class="key">License</div> <div class="key">License</div>
@@ -431,10 +427,10 @@
</div> </div>
<Advertisement <Advertisement
v-if="project.status === 'approved' || project.status === 'unlisted'" v-if="project.status === 'approved' || project.status === 'unlisted'"
class="small-advertisement"
type="square" type="square"
small-screen="destroy" small-screen="destroy"
/> />
</section>
<div class="content"> <div class="content">
<div class="project-main"> <div class="project-main">
<div class="card styled-tabs"> <div class="card styled-tabs">
@@ -699,7 +695,7 @@ export default {
try { try {
await this.$axios.patch( await this.$axios.patch(
`project/${this.currentProject.id}`, `project/${this.project.id}`,
{ {
moderation_message: null, moderation_message: null,
moderation_message_body: null, moderation_message_body: null,
@@ -747,14 +743,43 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
hr { .page-contents {
background-color: var(--color-divider); display: grid;
border: none;
color: var(--color-divider); grid-template:
height: 1px; 'header'
margin: var(--spacing-card-bg) 0; 'project-status'
'content'
'extra-info'
'small-advert'
/ 100%;
@media screen and (min-width: 1024px) {
grid-template:
'header content' auto
'project-status content' auto
'extra-info content' auto
'small-advert content' auto
'dummy content' 1fr
/ 20rem calc(100% - 20rem);
&.alt-layout {
grid-template:
'content header' auto
'content project-status' auto
'content extra-info' auto
'content small-advert' auto
'content dummy' 1fr
/ 1fr calc(100% - 20rem);
} }
}
column-gap: var(--spacing-card-md);
}
.header { .header {
grid-area: header;
.icon { .icon {
width: 6rem; width: 6rem;
height: 6rem; height: 6rem;
@@ -836,17 +861,28 @@ hr {
} }
} }
.project-status {
grid-area: project-status;
}
.extra-info {
grid-area: extra-info;
}
.small-advertisement {
grid-area: small-advert;
}
.content {
grid-area: content;
}
.project-info { .project-info {
height: auto; height: auto;
overflow: hidden; overflow: hidden;
@media screen and (min-width: 1024px) {
min-width: 21rem;
max-width: 21rem;
margin-right: var(--spacing-card-md);
} }
h3 { .card-header {
font-weight: bold; font-weight: bold;
color: var(--color-heading); color: var(--color-heading);
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
@@ -914,7 +950,7 @@ hr {
&:not(:last-child)::after { &:not(:last-child)::after {
content: '•'; content: '•';
margin: 0 0.5rem; margin: 0 0.25rem;
} }
} }
} }
@@ -960,7 +996,6 @@ hr {
} }
} }
} }
}
@media screen and (max-width: 550px) { @media screen and (max-width: 550px) {
.title a { .title a {

View File

@@ -120,7 +120,7 @@ export default {
} }
.filters { .filters {
margin-bottom: 0.5rem; margin-bottom: 1rem;
} }
.version-header { .version-header {

View File

@@ -5,7 +5,7 @@
<nuxt-link <nuxt-link
:to="`/${project.project_type}/${ :to="`/${project.project_type}/${
project.slug ? project.slug : project.id project.slug ? project.slug : project.id
}`" }/settings`"
class="iconified-button column" class="iconified-button column"
> >
Back Back
@@ -65,8 +65,9 @@
</label> </label>
<h3>Categories</h3> <h3>Categories</h3>
<label> <label>
<span> <span class="no-padding">
Select up to 3 categories that will help others find your project. Select up to 3 categories that will help others <br />
find your project.
</span> </span>
<Multiselect <Multiselect
id="categories" id="categories"
@@ -298,7 +299,7 @@
v-model="license" v-model="license"
placeholder="Select one" placeholder="Select one"
track-by="short" track-by="short"
label="name" label="short"
:options="$tag.licenses" :options="$tag.licenses"
:searchable="true" :searchable="true"
:close-on-select="true" :close-on-select="true"
@@ -616,7 +617,6 @@ label {
span { span {
flex: 2; flex: 2;
padding-right: var(--spacing-card-lg);
} }
input, input,
@@ -651,6 +651,19 @@ label {
.page-contents { .page-contents {
display: grid; display: grid;
grid-template:
'header' auto
'essentials' auto
'project-icon' auto
'game-sides' auto
'description' auto
'extra-links' auto
'license' auto
'donations' auto
'footer' auto
/ 1fr;
@media screen and (min-width: 1024px) {
grid-template: grid-template:
'header header header' auto 'header header header' auto
'essentials essentials project-icon' auto 'essentials essentials project-icon' auto
@@ -661,6 +674,7 @@ label {
'donations donations donations' auto 'donations donations donations' auto
'footer footer footer' auto 'footer footer footer' auto
/ 4fr 1fr 2fr; / 4fr 1fr 2fr;
}
column-gap: var(--spacing-card-md); column-gap: var(--spacing-card-md);
row-gap: var(--spacing-card-md); row-gap: var(--spacing-card-md);
} }
@@ -712,6 +726,10 @@ section.game-sides {
.labeled-control { .labeled-control {
flex: 2; flex: 2;
margin-left: var(--spacing-card-lg); margin-left: var(--spacing-card-lg);
h3 {
margin-bottom: var(--spacing-card-sm);
}
} }
} }
} }
@@ -766,6 +784,10 @@ section.donations {
flex: 1; flex: 1;
} }
} }
button {
margin: 0.5rem 0;
}
} }
.footer { .footer {

View File

@@ -437,7 +437,7 @@ export default {
this.$notify({ this.$notify({
group: 'main', group: 'main',
title: 'Action Success', title: 'Action Success',
text: 'Your _type has been successfully deleted.', text: 'Your project has been successfully deleted.',
type: 'success', type: 'success',
}) })
}, },
@@ -458,6 +458,13 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.card {
h3 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
}
.member { .member {
margin-bottom: var(--spacing-card-md); margin-bottom: var(--spacing-card-md);
@@ -594,7 +601,7 @@ section {
} }
h3 { h3 {
margin-right: auto; margin: auto auto auto 0;
} }
> div { > div {

View File

@@ -14,7 +14,13 @@
class="iconified-button back-button" class="iconified-button back-button"
:to="`/${project.project_type}/${ :to="`/${project.project_type}/${
project.slug ? project.slug : project.id project.slug ? project.slug : project.id
}/versions`" }/${
$nuxt.context.from
? $nuxt.context.from.name === 'type-id-changelog'
? 'changelog'
: 'versions'
: 'versions'
}`"
> >
<BackIcon aria-hidden="true" /> <BackIcon aria-hidden="true" />
Back to list Back to list
@@ -125,7 +131,7 @@
placeholder="Enter the version name..." placeholder="Enter the version name..."
/> />
<Checkbox v-model="version.featured" label="Featured" /> <Checkbox v-model="version.featured" label="Featured" />
<hr /> <hr class="card-divider" />
</div> </div>
<section v-if="mode === 'edit' || mode === 'create'"> <section v-if="mode === 'edit' || mode === 'create'">
<h3>Changelog</h3> <h3>Changelog</h3>
@@ -158,7 +164,7 @@
: 'No changelog specified.' : 'No changelog specified.'
" "
></div> ></div>
<hr /> <hr class="card-divider" />
</section> </section>
<section> <section>
<h3>Metadata</h3> <h3>Metadata</h3>
@@ -187,7 +193,8 @@
class="value" class="value"
type="beta" type="beta"
color="yellow" color="yellow"
/><VersionBadge />
<VersionBadge
v-else-if="version.version_type === 'alpha'" v-else-if="version.version_type === 'alpha'"
class="value" class="value"
type="alpha" type="alpha"
@@ -259,11 +266,7 @@
placeholder="Choose versions..." placeholder="Choose versions..."
/> />
<p v-else class="value"> <p v-else class="value">
{{ {{ $formatVersion(version.game_versions) }}
version.game_versions
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join(', ')
}}
</p> </p>
</div> </div>
<div v-if="mode === 'version'" class="data"> <div v-if="mode === 'version'" class="data">
@@ -292,7 +295,7 @@
<p class="value">{{ version.id }}</p> <p class="value">{{ version.id }}</p>
</div> </div>
</div> </div>
<hr /> <hr class="card-divider" />
</section> </section>
<section <section
v-if=" v-if="
@@ -394,7 +397,7 @@
</button> </button>
</div> </div>
</div> </div>
<hr /> <hr class="card-divider" />
</section> </section>
<section <section
v-if="version.files.length > 0 || mode === 'edit' || mode === 'create'" v-if="version.files.length > 0 || mode === 'edit' || mode === 'create'"
@@ -819,6 +822,8 @@ export default {
}) })
).data ).data
this.$emit('update:project', this.versions.concat([data]))
await this.$router.push( await this.$router.push(
`/${this.project.project_type}/${ `/${this.project.project_type}/${
this.project.slug ? this.project.slug : data.project_id this.project.slug ? this.project.slug : data.project_id
@@ -850,14 +855,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
hr {
background-color: var(--color-divider);
border: none;
color: var(--color-divider);
height: 1px;
margin: var(--spacing-card-bg) 0;
}
.content { .content {
max-width: calc(100% - (2 * var(--spacing-card-lg))); max-width: calc(100% - (2 * var(--spacing-card-lg)));
} }
@@ -906,6 +903,11 @@ section {
.data-wrapper { .data-wrapper {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
flex-direction: column;
@media screen and (min-width: 800px) {
flex-direction: row;
}
.data { .data {
flex-basis: calc(33.333333% - 0.5rem); flex-basis: calc(33.333333% - 0.5rem);

View File

@@ -71,7 +71,7 @@
.map((x) => x.charAt(0).toUpperCase() + x.slice(1)) .map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join(', ') + .join(', ') +
' ' + ' ' +
version.game_versions[version.game_versions.length - 1] $formatVersion(version.game_versions)
}} }}
</p> </p>
<p></p> <p></p>
@@ -96,7 +96,7 @@
.join(', ') .join(', ')
}} }}
</p> </p>
<p>{{ version.game_versions[version.game_versions.length - 1] }}</p> <p>{{ $formatVersion(version.game_versions) }}</p>
</td> </td>
<td> <td>
<p> <p>

View File

@@ -43,8 +43,9 @@
</label> </label>
<h3>Categories</h3> <h3>Categories</h3>
<label> <label>
<span> <span class="no-padding">
Select up to 3 categories that will help others find your project. Select up to 3 categories that will help others <br />
find your project.
</span> </span>
<multiselect <multiselect
id="categories" id="categories"
@@ -81,7 +82,7 @@
</label> </label>
<h3>Project type</h3> <h3>Project type</h3>
<label> <label>
<span>The project type of your project.</span> <span class="no-padding">The project type of your project.</span>
<Multiselect <Multiselect
v-model="projectType" v-model="projectType"
placeholder="Select one" placeholder="Select one"
@@ -379,19 +380,6 @@
placeholder="Choose versions..." placeholder="Choose versions..."
/> />
</label> </label>
<h3>Files</h3>
<label>
<span>
You must upload at least one file, however, you are allowed to
upload multiple files.
</span>
<FileInput
accept=".jar,application/java-archive,.zip,application/zip"
multiple
prompt="Choose files or drag them here"
@change="updateVersionFiles"
/>
</label>
</div> </div>
<div class="dependencies"> <div class="dependencies">
<h3>Dependencies</h3> <h3>Dependencies</h3>
@@ -470,6 +458,35 @@
</div> </div>
</div> </div>
</div> </div>
<div class="files">
<h3>Files</h3>
<SmartFileInput
class="file-input"
multiple
accept=".jar,application/java-archive,.zip,application/zip,.mrpack"
prompt="Upload files"
@change="
(x) =>
x.forEach((y) => versions[currentVersionIndex].files.push(y))
"
/>
<div class="uploaded-files">
<div
v-for="(file, index) in versions[currentVersionIndex].files"
:key="index + 'new'"
class="file"
>
<p class="filename">{{ file.name }}</p>
<button
class="action iconified-button"
@click="versions[currentVersionIndex].files.splice(index, 1)"
>
<TrashIcon aria-hidden="true" />
Delete
</button>
</div>
</div>
</div>
<div class="changelog"> <div class="changelog">
<h3>Changes</h3> <h3>Changes</h3>
<span> <span>
@@ -630,7 +647,6 @@
<div class="title"> <div class="title">
<div class="text"> <div class="text">
<h3>License</h3> <h3>License</h3>
<i>— this section is optional</i>
</div> </div>
</div> </div>
<label> <label>
@@ -837,10 +853,18 @@ export default {
async createProject() { async createProject() {
this.$nuxt.$loading.start() this.$nuxt.$loading.start()
for (const version of this.versions) { for (let i = 0; i < this.versions.length; i++) {
const version = this.versions[i]
if (!version.version_title) { if (!version.version_title) {
version.version_title = version.version_number version.version_title = version.version_number
} }
const newFileParts = []
for (let j = 0; j < version.files.length; j++) {
newFileParts.push(`version-${i}-${j}`)
}
version.file_parts = newFileParts
} }
const formData = new FormData() const formData = new FormData()
@@ -903,11 +927,11 @@ export default {
for (let i = 0; i < this.versions.length; i++) { for (let i = 0; i < this.versions.length; i++) {
const version = this.versions[i] const version = this.versions[i]
for (let j = 0; j < version.raw_files.length; j++) { for (let j = 0; j < version.files.length; j++) {
formData.append( formData.append(
`version-${i}-${j}`, `version-${i}-${j}`,
new Blob([version.raw_files[j]]), new Blob([version.files[j]]),
version.raw_files[j].name version.files[j].name
) )
} }
} }
@@ -939,6 +963,7 @@ export default {
title: 'An error occurred', title: 'An error occurred',
text: description, text: description,
type: 'error', type: 'error',
duration: 10000,
}) })
window.scrollTo({ top: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, behavior: 'smooth' })
@@ -976,21 +1001,9 @@ export default {
} }
}, },
updateVersionFiles(files) {
this.versions[this.currentVersionIndex].raw_files = files
const newFileParts = []
for (let i = 0; i < files.length; i++) {
newFileParts.push(`version-${this.currentVersionIndex}-${i}`)
}
this.versions[this.currentVersionIndex].file_parts = newFileParts
},
createVersion() { createVersion() {
this.versions.push({ this.versions.push({
raw_files: [], files: [],
file_parts: [],
version_number: '', version_number: '',
version_title: '', version_title: '',
version_body: '', version_body: '',
@@ -1125,7 +1138,6 @@ section.project-icon {
} }
.iconified-button { .iconified-button {
width: 9rem;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
} }
@@ -1143,6 +1155,10 @@ section.game-sides {
.labeled-control { .labeled-control {
flex: 2; flex: 2;
margin-left: var(--spacing-card-lg); margin-left: var(--spacing-card-lg);
h3 {
margin-bottom: var(--spacing-card-sm);
}
} }
} }
} }
@@ -1213,6 +1229,23 @@ section.versions {
&:last-child { &:last-child {
display: flex; display: flex;
} }
@media screen and (max-width: 800px) {
+ &:nth-child(4),
&:nth-child(3) {
display: none;
}
&:first-child,
&:nth-child(5) {
width: unset;
}
}
@media screen and (max-width: 1024px) {
&:nth-child(2) {
display: none;
}
}
} }
th { th {
@@ -1248,19 +1281,20 @@ section.versions {
'controls controls' auto 'controls controls' auto
'main main' auto 'main main' auto
'dependencies dependencies' auto 'dependencies dependencies' auto
'files files'
'changelog changelog' 'changelog changelog'
/ 5fr 4fr; / 5fr 4fr;
column-gap: var(--spacing-card-md);
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
grid-template: grid-template:
'controls controls' auto 'controls controls' auto
'main dependencies' auto 'main dependencies' auto
'main files' 1fr
'changelog changelog' 'changelog changelog'
/ 5fr 4fr; / 5fr 4fr;
} }
column-gap: var(--spacing-card-sm);
.controls { .controls {
grid-area: controls; grid-area: controls;
display: flex; display: flex;
@@ -1342,6 +1376,32 @@ section.versions {
} }
} }
.files {
grid-area: files;
.file-input {
margin-top: 1rem;
}
.uploaded-files {
.file {
display: flex;
align-items: center;
margin-bottom: 0.25rem;
flex-wrap: wrap;
row-gap: 0.25rem;
* {
margin-left: 0.25rem;
}
.filename {
margin: 0;
font-weight: bold;
}
}
}
}
.changelog { .changelog {
grid-area: changelog; grid-area: changelog;
display: flex; display: flex;
@@ -1464,6 +1524,10 @@ section.donations {
flex: 1; flex: 1;
} }
} }
button {
margin: 0.5rem 0;
}
} }
.footer { .footer {

View File

@@ -25,7 +25,9 @@
</label> </label>
<h3>Item type</h3> <h3>Item type</h3>
<label> <label>
<span>The type of the item that is being reported.</span> <span class="no-padding"
>The type of the item that is being reported.</span
>
<multiselect <multiselect
id="item-type" id="item-type"
v-model="itemType" v-model="itemType"
@@ -39,7 +41,7 @@
</label> </label>
<h3>Report type</h3> <h3>Report type</h3>
<label> <label>
<span> <span class="no-padding">
The type of report. This is the category that this report falls The type of report. This is the category that this report falls
under. under.
</span> </span>
@@ -195,22 +197,6 @@ export default {
} }
} }
label {
display: flex;
span {
flex: 2;
padding-right: var(--spacing-card-lg);
}
input,
.multiselect,
.input-group {
flex: 3;
height: fit-content;
}
}
.textarea-wrapper { .textarea-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -112,10 +112,6 @@ export default {
input { input {
box-sizing: content-box; box-sizing: content-box;
} }
.iconified-button {
padding: 1.25rem 1rem;
}
} }
@media screen and (max-width: 750px) { @media screen and (max-width: 750px) {

View File

@@ -5,6 +5,10 @@
<h1>Notifications</h1> <h1>Notifications</h1>
<div class="divider card"> <div class="divider card">
<ThisOrThat
v-model="selectedNotificationType"
:items="notificationTypes"
/>
<button class="iconified-button" @click="clearNotifications"> <button class="iconified-button" @click="clearNotifications">
<ClearIcon /> <ClearIcon />
Clear all Clear all
@@ -12,17 +16,21 @@
</div> </div>
<div class="notifications"> <div class="notifications">
<div <div
v-for="notification in $user.notifications" v-for="notification in selectedNotificationType !== 'all'
? $user.notifications.filter(
(x) => x.type === NOTIFICATION_TYPES[selectedNotificationType]
)
: $user.notifications"
:key="notification.id" :key="notification.id"
class="card notification" class="card notification"
> >
<div class="icon"> <div class="icon">
<UpdateIcon v-if="notification.type === 'project-update'" /> <UpdateIcon v-if="notification.type === 'project_update'" />
<UsersIcon v-else-if="notification.type === 'team_invite'" /> <UsersIcon v-else-if="notification.type === 'team_invite'" />
</div> </div>
<div class="text"> <div class="text">
<nuxt-link :to="notification.link" class="top"> <nuxt-link :to="notification.link" class="top">
<h3>{{ notification.title }}</h3> <h3 v-html="$xss($md.render(notification.title))" />
<span> <span>
Notified {{ $dayjs(notification.created).fromNow() }}</span Notified {{ $dayjs(notification.created).fromNow() }}</span
> >
@@ -41,7 +49,7 @@
{{ action.title }} {{ action.title }}
</button> </button>
<button <button
v-if="$user.notifications.length === 0" v-if="notification.actions.length === 0"
class="iconified-button" class="iconified-button"
@click="performAction(notification, notificationIndex, null)" @click="performAction(notification, notificationIndex, null)"
> >
@@ -65,21 +73,51 @@ import ClearIcon from '~/assets/images/utils/trash.svg?inline'
import UpdateIcon from '~/assets/images/utils/updated.svg?inline' import UpdateIcon from '~/assets/images/utils/updated.svg?inline'
import UsersIcon from '~/assets/images/utils/users.svg?inline' import UsersIcon from '~/assets/images/utils/users.svg?inline'
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?inline' import UpToDate from '~/assets/images/illustrations/up_to_date.svg?inline'
import ThisOrThat from '~/components/ui/ThisOrThat'
const NOTIFICATION_TYPES = {
'Team Invites': 'team_invite',
'Project Updates': 'project_update',
}
export default { export default {
name: 'Notifications', name: 'Notifications',
components: { components: {
ThisOrThat,
ClearIcon, ClearIcon,
UpdateIcon, UpdateIcon,
UsersIcon, UsersIcon,
UpToDate, UpToDate,
}, },
data() {
return {
selectedNotificationType: 'all',
}
},
async fetch() { async fetch() {
await this.$store.dispatch('user/fetchNotifications') await this.$store.dispatch('user/fetchNotifications')
}, },
head: { head: {
title: 'Notifications - Modrinth', title: 'Notifications - Modrinth',
}, },
computed: {
notificationTypes() {
const obj = { all: true }
for (const notification of this.$user.notifications) {
obj[
Object.keys(NOTIFICATION_TYPES).find(
(key) => NOTIFICATION_TYPES[key] === notification.type
)
] = true
}
return Object.keys(obj)
},
},
created() {
this.NOTIFICATION_TYPES = NOTIFICATION_TYPES
},
methods: { methods: {
async clearNotifications() { async clearNotifications() {
try { try {
@@ -142,25 +180,32 @@ h1 {
} }
.divider { .divider {
button { align-items: center;
margin-left: auto; display: flex;
} justify-content: space-between;
flex-wrap: wrap;
row-gap: 0.5rem;
} }
.notifications { .notifications {
.notification { .notification {
display: flex; display: flex;
max-height: 4rem; flex-wrap: wrap;
padding: var(--spacing-card-sm) var(--spacing-card-lg); padding: var(--spacing-card-sm) var(--spacing-card-lg);
.icon svg { .icon {
height: calc(4rem - var(--spacing-card-sm)); display: flex;
flex-direction: column;
justify-content: center;
svg {
height: calc(3rem - var(--spacing-card-sm));
width: auto; width: auto;
margin-right: 1rem; margin-right: 1rem;
} }
}
.text { .text {
max-height: calc(4rem - var(--spacing-card-sm));
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@@ -168,16 +213,21 @@ h1 {
.top { .top {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
flex-direction: column;
h3 { h3 ::v-deep {
font-size: var(--font-size-lg); font-size: var(--font-size-lg);
margin: 0 0.5rem 0 0; margin: 0 0.5rem 0 0;
p {
margin: 0;
strong { strong {
color: var(--color-brand); color: var(--color-brand);
} }
} }
} }
}
p { p {
padding: 0; padding: 0;
@@ -186,6 +236,9 @@ h1 {
} }
.buttons { .buttons {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: auto; margin-left: auto;
text-align: right; text-align: right;
@@ -201,5 +254,19 @@ h1 {
.page-contents { .page-contents {
max-width: calc(1280px - 20rem) !important; max-width: calc(1280px - 20rem) !important;
} }
.notifications {
.notification {
flex-wrap: nowrap;
.text {
flex-direction: column;
.top {
flex-direction: row;
}
}
}
}
} }
</style> </style>

View File

@@ -1,5 +1,10 @@
<template> <template>
<div class="normal-page"> <div
:class="{
'normal-page': true,
'alt-layout': $store.state.cosmetics.searchLayout,
}"
>
<aside class="normal-page__sidebar" aria-label="Filters"> <aside class="normal-page__sidebar" aria-label="Filters">
<section class="card" role="presentation"> <section class="card" role="presentation">
<button <button
@@ -133,7 +138,6 @@
/> />
</div> </div>
</section> </section>
<Advertisement type="square" small-screen="destroy" />
</aside> </aside>
<section class="normal-page__content"> <section class="normal-page__content">
<div class="card search-controls"> <div class="card search-controls">
@@ -309,6 +313,8 @@ export default {
} }
if (this.$route.query.v) if (this.$route.query.v)
this.selectedVersions = this.$route.query.v.split(',') this.selectedVersions = this.$route.query.v.split(',')
if (this.$route.query.l)
this.selectedLicenses = this.$route.query.l.split(',')
if (this.$route.query.h) this.showSnapshots = this.$route.query.h === 'true' if (this.$route.query.h) this.showSnapshots = this.$route.query.h === 'true'
if (this.$route.query.e) if (this.$route.query.e)
this.selectedEnvironments = this.$route.query.e.split(',') this.selectedEnvironments = this.$route.query.e.split(',')
@@ -529,6 +535,8 @@ export default {
queryItems.push(`f=${encodeURIComponent(this.facets)}`) queryItems.push(`f=${encodeURIComponent(this.facets)}`)
if (this.selectedVersions.length > 0) if (this.selectedVersions.length > 0)
queryItems.push(`v=${encodeURIComponent(this.selectedVersions)}`) queryItems.push(`v=${encodeURIComponent(this.selectedVersions)}`)
if (this.selectedLicenses.length > 0)
queryItems.push(`l=${encodeURIComponent(this.selectedLicenses)}`)
if (this.showSnapshots) url += `h=true` if (this.showSnapshots) url += `h=true`
if (this.selectedEnvironments.length > 0) if (this.selectedEnvironments.length > 0)
queryItems.push( queryItems.push(
@@ -560,7 +568,7 @@ export default {
} }
</script> </script>
<style scoped> <style lang="scss" scoped>
.sidebar-menu { .sidebar-menu {
display: none; display: none;
margin-top: 1rem; margin-top: 1rem;
@@ -577,6 +585,14 @@ export default {
.search-controls { .search-controls {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.iconified-input {
margin-left: 6px;
input {
width: calc(100% + 8px);
}
}
} }
.search-controls__sorting { .search-controls__sorting {

View File

@@ -9,7 +9,7 @@
:created-at="project.published" :created-at="project.published"
:updated-at="project.updated" :updated-at="project.updated"
:description="project.description" :description="project.description"
:downloads="project.downloads.toString()" :downloads="project.downloads ? project.downloads.toString() : '0'"
:icon-url="project.icon_url" :icon-url="project.icon_url"
:name="project.title" :name="project.title"
:client-side="project.client_side" :client-side="project.client_side"

View File

@@ -23,29 +23,28 @@
Reset Reset
</button> </button>
</div> </div>
<div class="recap"> <div class="recap card">
<section> <section>
<h2>Quick recap of you</h2> <h2>Profile Recap</h2>
<div> <div>
<Badge <Badge
v-if="$auth.user.role === 'admin'" v-if="$auth.user.role === 'admin'"
type="You are an admin" type="Admin"
color="red" color="red"
/> />
<Badge <Badge
v-else-if="$auth.user.role === 'moderator'" v-else-if="$auth.user.role === 'moderator'"
type="You are a moderator" type="Moderator"
color="yellow" color="yellow"
/> />
<Badge v-else type="You are a developer" color="green" /> <Badge v-else type="Developer" color="green" />
<div class="stat"> <div class="stat">
<SunriseIcon /> <SunriseIcon />
<span>You joined {{ $dayjs($auth.user.created).fromNow() }}</span> <span>Joined {{ $dayjs($auth.user.created).fromNow() }}</span>
</div> </div>
</div> </div>
</section> </section>
<section> <section>
<h2>You have</h2>
<div class="stat"> <div class="stat">
<DownloadIcon /> <DownloadIcon />
<span> <span>
@@ -105,6 +104,33 @@
:allow-empty="false" :allow-empty="false"
/> />
</label> </label>
<h3>Search Layout</h3>
<label>
<span>
Sets the sidebar direction for the search page. Enabling this will
put the search bar on the right side
</span>
<input
v-model="projectLayout"
class="switch stylized-toggle"
type="checkbox"
@change="changeLayout"
/>
</label>
<h3>Project Layout</h3>
<label>
<span>
Sets the sidebar direction for project pages. Enabling this will be
close to the legacy layout with project information on the right
side
</span>
<input
v-model="searchLayout"
class="switch stylized-toggle"
type="checkbox"
@change="changeLayout"
/>
</label>
</section> </section>
</div> </div>
</div> </div>
@@ -141,9 +167,14 @@ export default {
return { return {
icon: null, icon: null,
previewImage: null, previewImage: null,
searchLayout: false,
projectLayout: false,
} }
}, },
fetch() { fetch() {
this.searchLayout = this.$store.state.cosmetics.searchLayout
this.projectLayout = this.$store.state.cosmetics.projectLayout
this.$emit('update:action-button', 'Save') this.$emit('update:action-button', 'Save')
this.$emit('update:action-button-callback', this.saveChanges) this.$emit('update:action-button-callback', this.saveChanges)
}, },
@@ -198,6 +229,13 @@ export default {
return this.formatNumber(sum) return this.formatNumber(sum)
}, },
async changeLayout() {
await this.$store.dispatch('cosmetics/save', {
searchLayout: this.searchLayout,
projectLayout: this.projectLayout,
$cookies: this.$cookies,
})
},
async saveChanges() { async saveChanges() {
this.$nuxt.$loading.start() this.$nuxt.$loading.start()
try { try {
@@ -248,6 +286,10 @@ export default {
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
flex-direction: row; flex-direction: row;
.left-side {
margin-right: var(--spacing-card-bg);
}
} }
} }
@@ -255,8 +297,6 @@ export default {
min-width: 20rem; min-width: 20rem;
.profile-picture { .profile-picture {
margin-right: var(--spacing-card-bg);
h3 { h3 {
font-size: var(--font-size-lg); font-size: var(--font-size-lg);
} }
@@ -277,13 +317,6 @@ export default {
.recap { .recap {
section { section {
padding: var(--spacing-card-md) var(--spacing-card-lg);
margin-bottom: 1rem;
@media screen and (min-width: 1024px) {
padding: 0;
}
h2 { h2 {
font-size: var(--font-size-lg); font-size: var(--font-size-lg);
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
@@ -304,6 +337,7 @@ export default {
.stat { .stat {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0.5rem 0;
svg { svg {
width: auto; width: auto;
@@ -315,7 +349,6 @@ export default {
span { span {
strong { strong {
font-weight: bolder; font-weight: bolder;
font-size: var(--font-size-xl);
} }
} }
} }

View File

@@ -1,21 +1,13 @@
<template> <template>
<div class="normal-page"> <div class="normal-page">
<div> <div class="normal-page__sidebar">
<aside class="card sidebar normal-page__sidebar"> <aside class="card sidebar">
<img <img
class="sidebar__item profile-picture" class="sidebar__item profile-picture"
:src="user.avatar_url" :src="user.avatar_url"
:alt="user.username" :alt="user.username"
/> />
<h1 class="sidebar__item username">{{ user.username }}</h1> <h1 class="sidebar__item username">{{ user.username }}</h1>
<nuxt-link
v-if="$auth.user && $auth.user.id !== user.id"
:to="`/create/report?id=${user.id}&t=user`"
class="sidebar__item report-button iconified-button"
>
<ReportIcon aria-hidden="true" />
Report
</nuxt-link>
<div class="sidebar__item"> <div class="sidebar__item">
<Badge v-if="user.role === 'admin'" type="admin" color="red" /> <Badge v-if="user.role === 'admin'" type="admin" color="red" />
<Badge <Badge
@@ -25,6 +17,7 @@
/> />
<Badge v-else type="developer" color="green" /> <Badge v-else type="developer" color="green" />
</div> </div>
<hr class="card-divider" />
<h3 class="sidebar__item">About me</h3> <h3 class="sidebar__item">About me</h3>
<span v-if="user.bio" class="sidebar__item bio">{{ user.bio }}</span> <span v-if="user.bio" class="sidebar__item bio">{{ user.bio }}</span>
<div class="sidebar__item stats-block"> <div class="sidebar__item stats-block">
@@ -48,6 +41,16 @@
</div> </div>
</div> </div>
</div> </div>
<template v-if="$auth.user && $auth.user.id !== user.id">
<hr class="card-divider" />
<nuxt-link
:to="`/create/report?id=${user.id}&t=user`"
class="sidebar__item report-button iconified-button"
>
<ReportIcon aria-hidden="true" />
Report
</nuxt-link>
</template>
</aside> </aside>
</div> </div>
<div class="normal-page__content"> <div class="normal-page__content">
@@ -71,7 +74,9 @@
<div v-if="projects.length > 0"> <div v-if="projects.length > 0">
<ProjectCard <ProjectCard
v-for="project in selectedProjectType !== 'all' v-for="project in selectedProjectType !== 'all'
? projects.filter((x) => x.project_type === selectedProjectType) ? projects.filter(
(x) => x.project_type === selectedProjectType.slice(0, -1)
)
: projects" : projects"
:id="project.slug || project.id" :id="project.slug || project.id"
:key="project.id" :key="project.id"
@@ -217,7 +222,7 @@ export default {
const obj = { all: true } const obj = { all: true }
for (const project of this.projects) { for (const project of this.projects) {
obj[project.project_type] = true obj[project.project_type + 's'] = true
} }
return Object.keys(obj) return Object.keys(obj)
@@ -245,6 +250,8 @@ export default {
align-items: center; align-items: center;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap;
row-gap: 0.5rem;
} }
.sidebar__item:not(:last-child) { .sidebar__item:not(:last-child) {
margin: 0 0 0.75rem 0; margin: 0 0 0.75rem 0;

View File

@@ -1,3 +0,0 @@
export default ({ store }, inject) => {
inject('auth', store.state.auth)
}

View File

@@ -1,4 +1,91 @@
export default ({ store }, inject) => { export default ({ store }, inject) => {
inject('user', store.state.user) inject('user', store.state.user)
inject('tag', store.state.tag) inject('tag', store.state.tag)
inject('auth', store.state.auth)
inject('formatVersion', (versionArray) => {
const allVersions = store.state.tag.gameVersions.slice().reverse()
const allReleases = allVersions.filter((x) => x.version_type === 'release')
const intervals = []
let currentInterval = 0
for (let i = 0; i < versionArray.length; i++) {
const index = allVersions.findIndex((x) => x.version === versionArray[i])
const releaseIndex = allReleases.findIndex(
(x) => x.version === versionArray[i]
)
if (i === 0) {
intervals.push([[versionArray[i], index, releaseIndex]])
} else {
const intervalBase = intervals[currentInterval]
if (
(index - intervalBase[intervalBase.length - 1][1] === 1 ||
releaseIndex - intervalBase[intervalBase.length - 1][2] === 1) &&
(allVersions[intervalBase[0][1]].version_type === 'release' ||
allVersions[index].version_type !== 'release')
) {
intervalBase[1] = [versionArray[i], index, releaseIndex]
} else {
currentInterval += 1
intervals[currentInterval] = [[versionArray[i], index, releaseIndex]]
}
}
}
const newIntervals = []
for (let i = 0; i < intervals.length; i++) {
const interval = intervals[i]
if (
interval.length === 2 &&
interval[0][2] !== -1 &&
interval[1][2] === -1
) {
let lastSnapshot = null
for (let j = interval[1][1]; j > interval[0][1]; j--) {
if (allVersions[j].version_type === 'release') {
newIntervals.push([
interval[0],
[
allVersions[j].version,
j,
allReleases.findIndex(
(x) => x.version === allVersions[j].version
),
],
])
if (lastSnapshot !== null && lastSnapshot !== j + 1) {
newIntervals.push([
[allVersions[lastSnapshot].version, lastSnapshot, -1],
interval[1],
])
} else {
newIntervals.push([interval[1]])
}
break
} else {
lastSnapshot = j
}
}
} else {
newIntervals.push(interval)
}
}
const output = []
for (const interval of newIntervals) {
if (interval.length === 2) {
output.push(`${interval[0][0]}${interval[1][0]}`)
} else {
output.push(interval[0][0])
}
}
return output.join(', ')
})
} }

35
store/cosmetics.js Normal file
View File

@@ -0,0 +1,35 @@
const parameters = {
maxAge: 60 * 60 * 24 * 365 * 10, // Ten years
sameSite: 'Strict',
secure: true,
httpOnly: false,
path: '/',
}
export const state = () => ({
searchLayout: false,
projectLayout: false,
})
export const mutations = {
SET_SEARCH_LAYOUT(state, searchLayout) {
state.searchLayout = searchLayout
},
SET_PROJECT_LAYOUT(state, projectLayout) {
state.projectLayout = projectLayout
},
}
export const actions = {
fetchCosmetics({ commit }, $cookies) {
commit('SET_PROJECT_LAYOUT', $cookies.get('project-layout'))
commit('SET_SEARCH_LAYOUT', $cookies.get('search-layout'))
},
save({ commit }, { projectLayout, searchLayout, $cookies }) {
commit('SET_PROJECT_LAYOUT', projectLayout)
commit('SET_SEARCH_LAYOUT', searchLayout)
$cookies.set('project-layout', projectLayout, parameters)
$cookies.set('search-layout', searchLayout, parameters)
},
}