mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b75ec80963 | ||
|
|
753e9e1fd8 | ||
|
|
227f94517c | ||
|
|
8b45415472 | ||
|
|
6d43db87da | ||
|
|
b802a56606 | ||
|
|
c343801b9e | ||
|
|
504605bad8 | ||
|
|
cce1206d01 | ||
|
|
31ec44dbf8 | ||
|
|
0565ee02ee | ||
|
|
ece828bc78 | ||
|
|
b0b2c59afb |
@@ -13,13 +13,9 @@ const preloadedFonts = [
|
||||
'inter/Inter-Bold.woff2',
|
||||
]
|
||||
|
||||
const favicons = {
|
||||
'(prefers-color-scheme:no-preference)': '/favicon-light.ico',
|
||||
'(prefers-color-scheme:light)': '/favicon-light.ico',
|
||||
'(prefers-color-scheme:dark)': '/favicon.ico',
|
||||
}
|
||||
const faviconHref = '/favicon.png'
|
||||
|
||||
const PROD_MODRINTH_URL = 'https://modrinth.com'
|
||||
const PROD_MODRINTH_URL = 'https://april-fools-2026.modrinth.com'
|
||||
const STAGING_MODRINTH_URL = 'https://staging.modrinth.com'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
@@ -30,6 +26,12 @@ export default defineNuxtConfig({
|
||||
lang: 'en',
|
||||
},
|
||||
title: 'Modrinth',
|
||||
meta: [
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=1024',
|
||||
},
|
||||
],
|
||||
link: [
|
||||
// The type is necessary because the linter can't always compare this very nested/complex type on itself
|
||||
...preloadedFonts.map((font): object => {
|
||||
@@ -41,12 +43,17 @@ export default defineNuxtConfig({
|
||||
crossorigin: 'anonymous',
|
||||
}
|
||||
}),
|
||||
...Object.entries(favicons).map(([media, href]): object => {
|
||||
return { rel: 'icon', type: 'image/x-icon', href, media }
|
||||
}),
|
||||
...Object.entries(favicons).map(([media, href]): object => {
|
||||
return { rel: 'apple-touch-icon', type: 'image/x-icon', href, media, sizes: '64x64' }
|
||||
}),
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: faviconHref,
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
type: 'image/x-icon',
|
||||
href: faviconHref,
|
||||
sizes: '64x64',
|
||||
},
|
||||
{
|
||||
rel: 'search',
|
||||
type: 'application/opensearchdescription+xml',
|
||||
@@ -193,10 +200,7 @@ export default defineNuxtConfig({
|
||||
|
||||
console.log('Tags generated!')
|
||||
|
||||
const robotsContent =
|
||||
getDomain() === PROD_MODRINTH_URL && process.env.PREVIEW !== 'true'
|
||||
? 'User-agent: *\nDisallow: /_internal/'
|
||||
: 'User-agent: *\nDisallow: /'
|
||||
const robotsContent = 'User-agent: *\nDisallow: /'
|
||||
|
||||
await fs.writeFile('./src/public/robots.txt', robotsContent)
|
||||
},
|
||||
@@ -294,12 +298,6 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
routeRules: {
|
||||
'/**': {
|
||||
headers: {
|
||||
'Accept-CH': 'Sec-CH-Prefers-Color-Scheme',
|
||||
'Critical-CH': 'Sec-CH-Prefers-Color-Scheme',
|
||||
},
|
||||
},
|
||||
'/dashboard/revenue/withdraw': {
|
||||
redirect: {
|
||||
to: '/dashboard/revenue',
|
||||
|
||||
@@ -57,19 +57,12 @@
|
||||
Cards and body styling
|
||||
*/
|
||||
.base-card {
|
||||
@extend .padding-lg;
|
||||
|
||||
position: relative;
|
||||
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: var(--size-rounded-card);
|
||||
|
||||
margin-bottom: var(--spacing-card-md);
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: -2px;
|
||||
|
||||
box-shadow: var(--shadow-card);
|
||||
|
||||
.card__overlay {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
@@ -213,7 +206,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
:where(input) {
|
||||
:where(input:not([type='radio']):not([type='checkbox'])) {
|
||||
box-sizing: border-box;
|
||||
max-height: 40px;
|
||||
max-width: 100%;
|
||||
@@ -392,7 +385,6 @@
|
||||
|
||||
&:focus-visible:not(&:disabled),
|
||||
&:hover:not(&:disabled) {
|
||||
cursor: pointer;
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
|
||||
@@ -436,7 +428,6 @@ tr.button-transparent {
|
||||
|
||||
&:focus-visible:not(&:disabled) > *,
|
||||
&:hover:not(&:disabled) > * {
|
||||
cursor: pointer;
|
||||
filter: brightness(0.85);
|
||||
background-color: var(--color-raised-bg);
|
||||
}
|
||||
@@ -507,7 +498,6 @@ tr.button-transparent {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-nm);
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
transition:
|
||||
@@ -1140,8 +1130,7 @@ svg.inline-svg {
|
||||
.list-style {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
font-weight: var(--weight-bold);
|
||||
gap: 6px;
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
html {
|
||||
--dark-color-text: #b0bac5;
|
||||
--dark-color-text-dark: #ecf9fb;
|
||||
--color-text-secondary: var(--color-icon);
|
||||
}
|
||||
|
||||
@@ -14,11 +12,11 @@ html {
|
||||
--gap-16: calc(2 * var(--gap-8));
|
||||
--gap-24: calc(3 * var(--gap-8));
|
||||
|
||||
--radius-card: 1rem;
|
||||
--radius-card: 4px;
|
||||
|
||||
--radius-btn: 0.75rem;
|
||||
--radius-btn-large: 1rem;
|
||||
--radius-btn-circle: var(--radius-max);
|
||||
--radius-btn: 4px;
|
||||
--radius-btn-large: 4px;
|
||||
--radius-btn-circle: 4px;
|
||||
|
||||
--text-14: 0.875rem;
|
||||
--text-16: 1rem;
|
||||
@@ -152,168 +150,11 @@ html {
|
||||
--banner-info-border: #3b82f6;
|
||||
}
|
||||
|
||||
.dark,
|
||||
.dark-mode,
|
||||
.oled-mode,
|
||||
.retro-mode {
|
||||
--color-icon: var(--color-secondary);
|
||||
--color-text: var(--dark-color-text);
|
||||
--color-text-inactive: #929aa3;
|
||||
--color-text-dark: var(--dark-color-text-dark);
|
||||
--color-heading: #c4cfdd;
|
||||
|
||||
--color-text-inverted: var(--color-bg);
|
||||
--color-bg-inverted: var(--color-text);
|
||||
|
||||
--color-brand-inverted: #000;
|
||||
|
||||
--tab-underline-hovered: #414146;
|
||||
|
||||
--color-button-text: var(--color-text);
|
||||
--color-button-bg-hover: #494f58;
|
||||
--color-button-text-hover: #ffffff;
|
||||
--color-button-bg-active: #616570;
|
||||
--color-button-text-active: var(--color-button-text-hover);
|
||||
|
||||
--color-dropdown-bg: var(--color-button-bg);
|
||||
--color-dropdown-text: var(--color-button-text);
|
||||
|
||||
--color-code-bg: var(--color-button-bg);
|
||||
--color-code-text: var(--color-text-dark);
|
||||
|
||||
--color-kbd-shadow: rgba(0, 0, 0, 0.35);
|
||||
|
||||
--color-warning-bg: hsl(355, 70%, 20%);
|
||||
--color-warning-text: hsl(342, 70%, 75%);
|
||||
|
||||
--color-warning-banner-text: hsl(0, 100%, 96%);
|
||||
--color-warning-banner-bg: hsl(356, 18%, 18%);
|
||||
--color-warning-banner-side: hsl(357, 78%, 40%);
|
||||
|
||||
--color-info-banner-text: var(--color-text);
|
||||
--color-info-banner-bg: var(--color-ad);
|
||||
--color-info-banner-side: var(--color-blue);
|
||||
|
||||
--color-block-quote: var(--color-code-bg);
|
||||
--color-header-underline: var(--color-divider-dark);
|
||||
--color-hr: var(--color-text);
|
||||
|
||||
--color-table-border: #4f5864;
|
||||
--color-table-alternate-row: #202228;
|
||||
|
||||
--landing-maze-bg: url('https://cdn.modrinth.com/landing-new/landing.webp');
|
||||
--landing-maze-gradient-bg:
|
||||
linear-gradient(0deg, #31375f 0%, rgba(8, 14, 55, 0) 100%),
|
||||
url('https://cdn.modrinth.com/landing-new/landing-lower.webp');
|
||||
--landing-maze-outer-bg: linear-gradient(180deg, #06060d 0%, #000000 100%);
|
||||
|
||||
--landing-color-heading: #fff;
|
||||
--landing-color-subheading: #afb6be;
|
||||
|
||||
--landing-transition-gradient-start: rgba(14, 16, 32, 0);
|
||||
--landing-transition-gradient-end: #060a1c;
|
||||
--landing-hover-card-gradient: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
#2c304f 0%,
|
||||
rgba(32, 35, 50, 0.77) 100%
|
||||
);
|
||||
--landing-border-gradient: linear-gradient(
|
||||
to bottom right,
|
||||
rgba(168, 177, 221, 0.75) 0%,
|
||||
rgba(168, 177, 221, 0.18) 100%
|
||||
);
|
||||
--landing-border-color: rgba(168, 177, 221, 0.55);
|
||||
--landing-creator-gradient: linear-gradient(180deg, #000000 0%, #0e101d 100%);
|
||||
|
||||
--landing-blob-gradient: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
rgba(44, 48, 79, 0.35) 0%,
|
||||
rgba(32, 35, 50, 0.2695) 100%
|
||||
);
|
||||
--landing-blob-shadow:
|
||||
2px 2px 12px rgba(0, 0, 0, 0.16), inset 2px 2px 64px rgba(57, 61, 94, 0.45);
|
||||
|
||||
--landing-card-bg: rgba(59, 63, 85, 0.15);
|
||||
--landing-card-shadow: 2px 2px 12px rgba(0, 0, 0, 0.16);
|
||||
|
||||
--landing-blue-label: #10c0e7;
|
||||
--landing-blue-label-bg: rgba(0, 177, 216, 0.15);
|
||||
--landing-green-label: #00d845;
|
||||
--landing-green-label-bg: rgba(0, 216, 69, 0.15);
|
||||
|
||||
--landing-raw-bg: #000;
|
||||
|
||||
--medal-promotion-bg: #000;
|
||||
--medal-promotion-bg-orange: rgba(208, 246, 255, 0.25);
|
||||
--medal-promotion-text-orange: #42abff;
|
||||
--medal-promotion-bg-gradient: linear-gradient(
|
||||
90deg,
|
||||
rgba(66, 170, 255, 0.15),
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
|
||||
--hover-filter: brightness(120%);
|
||||
--active-filter: brightness(140%);
|
||||
|
||||
--banner-error-bg: #45222c;
|
||||
--banner-error-text: #fee2e2;
|
||||
--banner-error-border: var(--color-red);
|
||||
|
||||
--banner-warning-bg: #453425;
|
||||
--banner-warning-text: #e4d9ca;
|
||||
--banner-warning-border: var(--color-orange);
|
||||
|
||||
--banner-info-bg: #28374b;
|
||||
--banner-info-text: #dbeafe;
|
||||
--banner-info-border: var(--color-blue);
|
||||
}
|
||||
|
||||
.oled-mode {
|
||||
--color-button-bg-hover: #2d2d32;
|
||||
--color-button-bg-active: #3c3c40;
|
||||
--color-table-alternate-row: #19191c;
|
||||
--color-divider-dark: #2c3134;
|
||||
|
||||
--color-warning-banner-bg: hsl(0, 45%, 11%);
|
||||
}
|
||||
|
||||
.retro-mode {
|
||||
--color-bg: #191917;
|
||||
--color-raised-bg: #1d1e1b;
|
||||
--color-button-bg: #3a3b38;
|
||||
--color-base: #c3c4b3;
|
||||
--color-secondary: #777a74;
|
||||
--color-contrast: #e6e2d1;
|
||||
|
||||
--color-brand: #4d9227;
|
||||
--color-brand-highlight: #25421e;
|
||||
--color-ad: var(--color-brand-highlight);
|
||||
--color-ad-raised: var(--color-brand);
|
||||
--color-ad-contrast: black;
|
||||
--color-ad-highlight: var(--color-brand);
|
||||
|
||||
--color-red: rgb(232, 32, 13);
|
||||
--color-orange: rgb(232, 141, 13);
|
||||
--color-green: rgb(60, 219, 54);
|
||||
--color-blue: rgb(9, 159, 239);
|
||||
--color-purple: rgb(139, 129, 230);
|
||||
--color-gray: #718096;
|
||||
|
||||
--color-red-highlight: rgba(232, 32, 13, 0.25);
|
||||
--color-orange-highlight: rgba(232, 141, 13, 0.25);
|
||||
--color-green-highlight: rgba(60, 219, 54, 0.25);
|
||||
--color-blue-highlight: rgba(9, 159, 239, 0.25);
|
||||
--color-purple-highlight: rgba(139, 129, 230, 0.25);
|
||||
--color-gray-highlight: rgba(113, 128, 150, 0.25);
|
||||
}
|
||||
|
||||
body {
|
||||
// Defaults
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
--font-standard:
|
||||
Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto, Cantarell,
|
||||
Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
--font-standard: Arial, Helvetica, Verdana, Tahoma, sans-serif;
|
||||
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||
font-family: var(--font-standard);
|
||||
font-size: 16px;
|
||||
@@ -394,58 +235,10 @@ h3 {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
input {
|
||||
border-radius: var(--size-rounded-sm);
|
||||
box-sizing: border-box;
|
||||
border: 2px solid transparent;
|
||||
// safari iOS rounds inputs by default
|
||||
// set the appearance to none to prevent this
|
||||
appearance: none !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
background: var(--color-button-bg);
|
||||
color: var(--color-text);
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
border: none;
|
||||
outline: 2px solid transparent;
|
||||
box-shadow:
|
||||
var(--shadow-inset-sm),
|
||||
0 0 0 0 transparent;
|
||||
transition: box-shadow 0.1s ease-in-out;
|
||||
min-height: 36px;
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow:
|
||||
inset 0 0 0 transparent,
|
||||
0 0 0 0.25rem var(--color-brand-shadow);
|
||||
color: var(--color-button-text-active);
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&[disabled='true'] {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:focus::placeholder {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-button-text);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: var(--color-code-bg);
|
||||
color: var(--color-code-text);
|
||||
@@ -465,7 +258,7 @@ kbd {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
input {
|
||||
input:not([type='radio']):not([type='checkbox']) {
|
||||
outline: 2px solid transparent !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@
|
||||
margin: 0 auto;
|
||||
max-width: 80rem;
|
||||
column-gap: 0.75rem;
|
||||
padding: 0 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
|
||||
grid-template:
|
||||
|
||||
@@ -1,100 +1,227 @@
|
||||
<template>
|
||||
<svg
|
||||
width="976"
|
||||
height="242"
|
||||
viewBox="0 0 976 242"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
fill-rule="evenodd"
|
||||
stroke-linejoin="round"
|
||||
stroke-miterlimit="2"
|
||||
clip-rule="evenodd"
|
||||
viewBox="0 0 3307 593"
|
||||
:class="{ animate: loading }"
|
||||
>
|
||||
<!-- modrinth -->
|
||||
<path
|
||||
v-if="api === 'prod'"
|
||||
fill="currentColor"
|
||||
fill-rule="nonzero"
|
||||
d="M1053.02 205.51c35.59 0 64.27 10.1 84.98 30.81 20.72 21.25 31.34 52.05 31.34 93.48v162.53h-66.4V338.3c0-24.96-5.3-43.55-16.46-56.3-11.15-12.22-26.55-18.6-47.27-18.6-22.3 0-40.37 7.45-53.65 21.79-13.27 14.87-20.18 36.11-20.18 63.2v143.94h-66.4V338.3c0-24.96-5.3-43.55-16.46-56.3-11.15-12.22-26.56-18.6-47.27-18.6-22.84 0-40.37 7.45-53.65 21.79-13.27 14.34-20.18 35.58-20.18 63.2v143.94h-66.4V208.7h63.21v36.12c10.63-12.75 23.9-22.3 39.84-29.21 15.93-6.9 33.46-10.1 53.11-10.1 21.25 0 40.37 3.72 56.84 11.69 16.46 8.5 29.21 20.18 38.77 35.59 11.69-14.88 26.56-26.56 45.15-35.06 18.59-7.97 38.77-12.22 61.08-12.22Zm329.84 290.54c-28.68 0-54.7-6.37-77.54-18.59a133.19 133.19 0 0 1-53.65-52.05c-13.28-21.78-19.65-46.74-19.65-74.9 0-28.14 6.37-53.1 19.65-74.88a135.4 135.4 0 0 1 53.65-51.53c22.84-12.21 48.86-18.59 77.54-18.59 29.22 0 55.24 6.38 78.08 18.6 22.84 12.21 40.9 29.74 54.18 51.52 12.75 21.77 19.12 46.74 19.12 74.89s-6.37 53.11-19.12 74.89c-13.28 22.3-31.34 39.83-54.18 52.05-22.84 12.22-48.86 18.6-78.08 18.6Zm0-56.83c24.44 0 44.62-7.97 60.55-24.43 15.94-16.47 23.9-37.72 23.9-64.27 0-26.56-7.96-47.8-23.9-64.27-15.93-16.47-36.11-24.43-60.55-24.43-24.43 0-44.61 7.96-60.02 24.43-15.93 16.46-23.9 37.71-23.9 64.27 0 26.55 7.97 47.8 23.9 64.27 15.4 16.46 35.6 24.43 60.02 24.43Zm491.32-341v394.11h-63.74v-36.65a108.02 108.02 0 0 1-40.37 30.28c-16.46 6.9-34 10.1-53.65 10.1-27.08 0-51.52-5.85-73.3-18.07-21.77-12.21-39.3-29.21-51.52-51.52-12.21-21.78-18.59-47.27-18.59-75.95s6.38-54.18 18.6-75.96c12.21-21.77 29.74-38.77 51.52-50.99 21.77-12.21 46.2-18.06 73.3-18.06 18.59 0 36.11 3.2 51.52 9.56a106.35 106.35 0 0 1 39.83 28.69V98.22h66.4Zm-149.79 341c15.94 0 30.28-3.72 43.03-11.16 12.74-6.9 22.83-17.52 30.27-30.8 7.44-13.28 11.15-29.21 11.15-46.74s-3.71-33.46-11.15-46.74c-7.44-13.28-17.53-23.9-30.27-31.34-12.75-6.9-27.1-10.62-43.03-10.62s-30.27 3.71-43.02 10.62c-12.75 7.43-22.84 18.06-30.28 31.34-7.43 13.28-11.15 29.2-11.15 46.74 0 17.53 3.72 33.46 11.15 46.74 7.44 13.28 17.53 23.9 30.28 30.8 12.75 7.44 27.09 11.16 43.02 11.16Zm298.51-189.09c19.12-29.74 52.58-44.62 100.92-44.62v63.21a84.29 84.29 0 0 0-15.4-1.6c-26.03 0-46.22 7.44-60.56 22.32-14.34 15.4-21.78 37.18-21.78 65.33v137.56h-66.39V208.7h63.2v41.43Zm155.63-41.43h66.39v283.63h-66.4V208.7Zm33.46-46.74c-12.22 0-22.31-3.72-30.28-11.68a37.36 37.36 0 0 1-12.21-28.16c0-11.15 4.25-20.71 12.21-28.68 7.97-7.43 18.06-11.15 30.28-11.15 12.21 0 22.3 3.72 30.27 10.62 7.97 7.44 12.22 16.47 12.22 27.62 0 11.69-3.72 21.25-11.69 29.21-7.96 7.97-18.59 12.22-30.8 12.22Zm279.38 43.55c35.59 0 64.27 10.63 86.05 31.34 21.78 20.72 32.4 52.05 32.4 92.95v162.53h-66.4V338.3c0-24.96-5.84-43.55-17.52-56.3-11.69-12.22-28.15-18.6-49.93-18.6-24.43 0-43.55 7.45-57.9 21.79-14.34 14.87-21.24 36.11-21.24 63.73v143.41h-66.4V208.7h63.21v36.65c11.16-13.28 24.97-22.84 41.43-29.74 16.47-6.9 35.59-10.1 56.3-10.1Zm371.81 271.42a78.34 78.34 0 0 1-28.15 14.34 130.83 130.83 0 0 1-35.6 4.78c-31.33 0-55.23-7.97-72.23-24.43-17-16.47-25.5-39.84-25.5-71.17V263.94h-46.73v-53.11h46.74v-64.8h66.4v64.8h75.95v53.11h-75.96v134.91c0 13.81 3.19 24.43 10.1 31.34 6.9 7.44 16.46 11.15 29.2 11.15 14.88 0 27.1-3.71 37.19-11.68l18.59 47.27Zm214.05-271.42c35.59 0 64.27 10.63 86.05 31.34 21.77 20.72 32.4 52.05 32.4 92.95v162.53h-66.4V338.3c0-24.96-5.84-43.55-17.53-56.3-11.68-12.22-28.15-18.6-49.92-18.6-24.44 0-43.56 7.45-57.9 21.79-14.34 14.87-21.24 36.11-21.24 63.73v143.41h-66.4V98.23h66.4v143.4c11.15-11.68 24.43-20.71 40.9-27.09 15.93-5.84 33.99-9.03 53.64-9.03Z"
|
||||
/>
|
||||
<!-- staging -->
|
||||
<path
|
||||
v-else-if="api === 'staging'"
|
||||
fill="currentColor"
|
||||
fill-rule="nonzero"
|
||||
d="M782.3,488.9c-23.6,0-46.3-3.1-68-9.4s-38.9-13.8-51.6-22.8l25.4-50.9c12.7,8.2,27.9,15,45.5,20.4 c17.6,5.4,35.3,8,52.9,8c20.8,0,35.9-2.9,45.2-8.6c9.3-5.7,14-13.4,14-23c0-7.8-3.2-13.8-9.5-17.9c-6.3-4.1-14.6-7.2-24.9-9.4 c-10.2-2.1-21.6-4.1-34.1-5.9c-12.5-1.8-25-4.2-37.6-7.2c-12.5-3-23.9-7.5-34.1-13.4c-10.2-5.9-18.5-13.8-24.9-23.8 c-6.3-10-9.5-23.2-9.5-39.6c0-18.2,5.1-34,15.3-47.4c10.2-13.4,24.6-23.7,43.1-31.1c18.5-7.3,40.5-11,65.9-11 c19,0,38.3,2.1,57.7,6.4c19.4,4.3,35.4,10.4,48.1,18.2l-25.4,50.9c-13.4-8.2-26.9-13.8-40.5-16.9c-13.6-3-27.1-4.6-40.5-4.6 c-20.1,0-35,3-44.7,9.1c-9.7,6.1-14.5,13.8-14.5,23c0,8.6,3.2,15,9.5,19.3c6.3,4.3,14.6,7.7,24.9,10.2c10.2,2.5,21.6,4.6,34.1,6.2 c12.5,1.6,24.9,4,37.3,7.2c12.3,3.2,23.7,7.6,34.1,13.1c10.4,5.5,18.8,13.3,25.1,23.3c6.3,10,9.5,23,9.5,39.1 c0,17.9-5.2,33.4-15.6,46.6c-10.4,13.2-25.1,23.5-44.2,30.8C831.5,485.2,808.7,488.9,782.3,488.9z M929.9,254.8v-53.6h188.3v53.6 H929.9z M1073.8,488.9c-31,0-55-8.1-71.9-24.4c-16.9-16.2-25.4-40.3-25.4-72V135.9h66.1v254.9c0,13.6,3.4,24.1,10.3,31.6 c6.9,7.5,16.5,11.2,28.8,11.2c14.8,0,27.2-3.9,37-11.8l18.5,47.7c-7.8,6.4-17.3,11.2-28.6,14.5 C1097.4,487.3,1085.8,488.9,1073.8,488.9z M1276.9,488.9c-21.2,0-39.7-3.7-55.5-11c-15.9-7.3-28.1-17.5-36.8-30.5 c-8.6-13-13-27.8-13-44.2c0-16.1,3.8-30.5,11.4-43.4c7.6-12.9,20-23,37.3-30.5c17.3-7.5,40.2-11.2,68.8-11.2h82v44.5h-77.2 c-22.6,0-37.7,3.7-45.5,11c-7.8,7.3-11.6,16.3-11.6,27c0,12.1,4.8,21.8,14.3,28.9c9.5,7.1,22.7,10.7,39.7,10.7 c16.2,0,30.8-3.7,43.6-11.2c12.9-7.5,22.1-18.6,27.8-33.2l11.1,40.2c-6.3,16.8-17.7,29.8-34.1,39.1 C1322.7,484.2,1302,488.9,1276.9,488.9z M1365.8,485.1v-57.8l-3.7-12.3V313.7c0-19.6-5.8-34.9-17.5-45.8 c-11.6-10.9-29.3-16.3-52.9-16.3c-15.9,0-31.5,2.5-46.8,7.5c-15.3,5-28.3,12-38.9,20.9l-25.9-48.7c15.2-11.8,33.2-20.6,54.2-26.5 c21-5.9,42.8-8.8,65.3-8.8c40.9,0,72.6,9.9,95,29.7c22.4,19.8,33.6,50.4,33.6,91.9v167.6H1365.8z M1633,472.3 c-26.8,0-51.1-5.8-72.7-17.4c-21.7-11.6-38.8-27.8-51.3-48.5c-12.5-20.7-18.8-45-18.8-72.8c0-27.5,6.3-51.6,18.8-72.3 c12.5-20.7,29.6-36.8,51.3-48.2c21.7-11.4,45.9-17.1,72.7-17.1c24,0,45.5,4.8,64.5,14.5s34.3,24.6,45.8,45 c11.5,20.4,17.2,46.4,17.2,78.2c0,31.8-5.7,57.9-17.2,78.5c-11.5,20.5-26.7,35.7-45.8,45.5C1678.4,467.4,1656.9,472.3,1633,472.3z M1641.4,592.8c-26.1,0-51.6-3.5-76.4-10.4c-24.9-7-45.2-17.1-61.1-30.3l29.6-50.3c12.3,10.4,27.9,18.7,46.8,24.9 c18.9,6.2,38,9.4,57.4,9.4c31,0,53.8-7.2,68.2-21.7c14.5-14.5,21.7-36.2,21.7-65.1v-50.9l5.3-64.8l-2.1-64.8v-69.6h63v242.6 c0,51.8-13.1,89.9-39.1,114.3C1728.5,580.5,1690.8,592.8,1641.4,592.8z M1643,415.5c16.6,0,31.4-3.5,44.4-10.4 c13-7,23.2-16.6,30.4-28.9c7.2-12.3,10.8-26.5,10.8-42.6c0-16.1-3.6-30.3-10.8-42.6c-7.2-12.3-17.4-21.8-30.4-28.4 c-13.1-6.6-27.9-9.9-44.4-9.9c-16.6,0-31.5,3.3-44.7,9.9c-13.2,6.6-23.5,16.1-30.7,28.4c-7.2,12.3-10.8,26.5-10.8,42.6 c0,16.1,3.6,30.3,10.8,42.6c7.2,12.3,17.5,22,30.7,28.9S1626.4,415.5,1643,415.5z M1913.9,152c-12.3,0-22.5-3.9-30.4-11.8 c-7.9-7.8-11.9-17.3-11.9-28.4c0-11.4,4-21,11.9-28.7c7.9-7.7,18.1-11.5,30.4-11.5c12.3,0,22.5,3.7,30.4,11 c7.9,7.3,11.9,16.5,11.9,27.6c0,11.8-3.9,21.7-11.6,29.7C1936.8,148,1926.6,152,1913.9,152z M1880.5,485.1v-286h66.1v286H1880.5z M2193.7,195.9c22.6,0,42.8,4.5,60.6,13.4c17.8,8.9,31.8,22.6,42.1,41c10.2,18.4,15.3,42,15.3,71v163.9h-66.1V329.8 c0-25.3-5.9-44.3-17.7-56.8c-11.8-12.5-28.3-18.7-49.5-18.7c-15.5,0-29.3,3.2-41.3,9.6c-12,6.4-21.3,16-27.8,28.7 c-6.5,12.7-9.8,28.7-9.8,47.9v144.6h-66.1v-286h63v77.1l-11.1-23.6c9.9-18.2,24.2-32.2,43.1-42 C2147.2,200.8,2169,195.9,2193.7,195.9z M2515.9,472.3c-26.8,0-51-5.8-72.7-17.4c-21.7-11.6-38.8-27.8-51.3-48.5 c-12.5-20.7-18.8-45-18.8-72.8c0-27.5,6.3-51.6,18.8-72.3c12.5-20.7,29.6-36.8,51.3-48.2c21.7-11.4,45.9-17.1,72.7-17.1 c24,0,45.5,4.8,64.5,14.5s34.3,24.6,45.8,45c11.5,20.4,17.2,46.4,17.2,78.2c0,31.8-5.7,57.9-17.2,78.5 c-11.5,20.5-26.7,35.7-45.8,45.5C2561.4,467.4,2539.9,472.3,2515.9,472.3z M2524.3,592.8c-26.1,0-51.6-3.5-76.4-10.4 c-24.9-7-45.2-17.1-61.1-30.3l29.6-50.3c12.3,10.4,27.9,18.7,46.8,24.9c18.9,6.2,38,9.4,57.4,9.4c31,0,53.8-7.2,68.2-21.7 c14.5-14.5,21.7-36.2,21.7-65.1v-50.9l5.3-64.8l-2.1-64.8v-69.6h63v242.6c0,51.8-13.1,89.9-39.1,114.3 C2611.4,580.5,2573.7,592.8,2524.3,592.8z M2525.9,415.5c16.6,0,31.4-3.5,44.4-10.4c13-7,23.2-16.6,30.4-28.9 c7.2-12.3,10.8-26.5,10.8-42.6c0-16.1-3.6-30.3-10.8-42.6c-7.2-12.3-17.4-21.8-30.4-28.4c-13.1-6.6-27.9-9.9-44.4-9.9 c-16.6,0-31.5,3.3-44.7,9.9c-13.2,6.6-23.5,16.1-30.7,28.4c-7.2,12.3-10.8,26.5-10.8,42.6c0,16.1,3.6,30.3,10.8,42.6 c7.2,12.3,17.5,22,30.7,28.9S2509.3,415.5,2525.9,415.5z"
|
||||
/>
|
||||
<!-- localhost -->
|
||||
<path
|
||||
v-else-if="api === 'localhost'"
|
||||
fill="currentColor"
|
||||
fill-rule="nonzero"
|
||||
d="M695,492.3V94.9h66.1v397.4H695z M974.9,496.1c-28.9,0-54.7-6.3-77.2-19c-22.6-12.7-40.4-30.1-53.4-52.2 c-13.1-22.1-19.6-47.3-19.6-75.5c0-28.6,6.5-53.8,19.6-75.8c13-22,30.9-39.2,53.4-51.7c22.6-12.5,48.3-18.7,77.2-18.7 c29.3,0,55.3,6.3,78,18.7c22.7,12.5,40.6,29.6,53.4,51.4c12.9,21.8,19.3,47.1,19.3,76.1c0,28.2-6.4,53.4-19.3,75.5 c-12.9,22.1-30.7,39.6-53.4,52.2C1030.1,489.7,1004.1,496.1,974.9,496.1z M974.9,438.8c16.2,0,30.7-3.6,43.4-10.7 c12.7-7.1,22.7-17.5,29.9-31.1c7.2-13.6,10.8-29.5,10.8-47.7c0-18.6-3.6-34.5-10.8-47.9c-7.2-13.4-17.2-23.6-29.9-30.8 c-12.7-7.1-27-10.7-42.9-10.7c-16.2,0-30.6,3.6-43.1,10.7c-12.5,7.1-22.5,17.4-29.9,30.8s-11.1,29.4-11.1,47.9 c0,18.2,3.7,34.1,11.1,47.7c7.4,13.6,17.4,23.9,29.9,31.1C944.8,435.2,959,438.8,974.9,438.8z M1318.7,496.1c-29.6,0-56-6.3-79.1-19 c-23.1-12.7-41.2-30.1-54.2-52.2c-13.1-22.1-19.6-47.3-19.6-75.5c0-28.6,6.5-53.8,19.6-75.8c13-22,31.1-39.2,54.2-51.7 c23.1-12.5,49.5-18.7,79.1-18.7c27.5,0,51.8,5.6,72.7,16.9c21,11.2,36.9,27.8,47.9,49.5l-50.8,30c-8.5-13.6-18.8-23.6-30.9-30 s-25.3-9.6-39.4-9.6c-16.2,0-30.9,3.6-43.9,10.7c-13.1,7.1-23.3,17.4-30.7,30.8s-11.1,29.4-11.1,47.9c0,18.6,3.7,34.5,11.1,47.9 c7.4,13.4,17.6,23.7,30.7,30.8c13,7.1,27.7,10.7,43.9,10.7c14.1,0,27.2-3.2,39.4-9.6s22.5-16.4,30.9-30l50.8,30 c-10.9,21.4-26.9,37.9-47.9,49.5C1370.5,490.3,1346.2,496.1,1318.7,496.1z M1581.6,496.1c-21.2,0-39.7-3.7-55.5-11 c-15.9-7.3-28.1-17.5-36.8-30.5c-8.6-13-13-27.8-13-44.2c0-16.1,3.8-30.5,11.4-43.4c7.6-12.9,20-23,37.3-30.5 c17.3-7.5,40.2-11.2,68.8-11.2h82v44.5h-77.2c-22.6,0-37.7,3.7-45.5,11c-7.8,7.3-11.6,16.3-11.6,27c0,12.1,4.8,21.8,14.3,28.9 c9.5,7.1,22.7,10.7,39.7,10.7c16.2,0,30.8-3.7,43.6-11.2c12.9-7.5,22.1-18.6,27.8-33.2l11.1,40.2c-6.3,16.8-17.7,29.8-34.1,39.1 C1627.4,491.4,1606.7,496.1,1581.6,496.1z M1670.5,492.3v-57.8l-3.7-12.3V320.9c0-19.6-5.8-34.9-17.5-45.8 c-11.6-10.9-29.3-16.3-52.9-16.3c-15.9,0-31.5,2.5-46.8,7.5c-15.3,5-28.3,12-38.9,20.9l-25.9-48.7c15.2-11.8,33.2-20.6,54.2-26.5 c21-5.9,42.8-8.8,65.3-8.8c40.9,0,72.6,9.9,95,29.7c22.4,19.8,33.6,50.4,33.6,91.9v167.6H1670.5z M1817.6,492.3V94.9h66.1v397.4 H1817.6z M2130.7,203.1c22.6,0,42.8,4.5,60.6,13.4c17.8,8.9,31.8,22.6,42.1,41c10.2,18.4,15.3,42,15.3,71v163.9h-66.1V337 c0-25.3-5.9-44.3-17.7-56.8c-11.8-12.5-28.3-18.7-49.5-18.7c-15.5,0-29.3,3.2-41.3,9.6c-12,6.4-21.3,16-27.8,28.7 c-6.5,12.7-9.8,28.7-9.8,47.9v144.6h-66.1V94.9h66.1v188.5l-14.3-23.6c9.9-18.2,24.2-32.2,43.1-42 C2084.3,208,2106.1,203.1,2130.7,203.1z M2460.3,496.1c-28.9,0-54.7-6.3-77.2-19c-22.6-12.7-40.4-30.1-53.4-52.2 c-13.1-22.1-19.6-47.3-19.6-75.5c0-28.6,6.5-53.8,19.6-75.8c13-22,30.9-39.2,53.4-51.7c22.6-12.5,48.3-18.7,77.2-18.7 c29.3,0,55.3,6.3,78,18.7c22.7,12.5,40.6,29.6,53.4,51.4c12.9,21.8,19.3,47.1,19.3,76.1c0,28.2-6.4,53.4-19.3,75.5 c-12.9,22.1-30.7,39.6-53.4,52.2C2515.6,489.7,2489.6,496.1,2460.3,496.1z M2460.3,438.8c16.2,0,30.7-3.6,43.4-10.7 c12.7-7.1,22.7-17.5,29.9-31.1c7.2-13.6,10.8-29.5,10.8-47.7c0-18.6-3.6-34.5-10.8-47.9c-7.2-13.4-17.2-23.6-29.9-30.8 c-12.7-7.1-27-10.7-42.9-10.7c-16.2,0-30.6,3.6-43.1,10.7c-12.5,7.1-22.5,17.4-29.9,30.8s-11.1,29.4-11.1,47.9 c0,18.2,3.7,34.1,11.1,47.7c7.4,13.6,17.4,23.9,29.9,31.1C2430.3,435.2,2444.5,438.8,2460.3,438.8z M2761.9,496.1 c-23.6,0-46.3-3.1-68-9.4c-21.7-6.2-38.9-13.8-51.6-22.8l25.4-50.9c12.7,8.2,27.9,15,45.5,20.4c17.6,5.4,35.3,8,52.9,8 c20.8,0,35.9-2.9,45.2-8.6c9.3-5.7,14-13.4,14-23c0-7.8-3.2-13.8-9.5-17.9c-6.3-4.1-14.6-7.2-24.9-9.4c-10.2-2.1-21.6-4.1-34.1-5.9 c-12.5-1.8-25-4.2-37.6-7.2c-12.5-3-23.9-7.5-34.1-13.4c-10.2-5.9-18.5-13.8-24.9-23.8c-6.3-10-9.5-23.2-9.5-39.6 c0-18.2,5.1-34,15.3-47.4c10.2-13.4,24.6-23.7,43.1-31.1c18.5-7.3,40.5-11,65.9-11c19,0,38.3,2.1,57.7,6.4 c19.4,4.3,35.4,10.4,48.1,18.2l-25.4,50.9c-13.4-8.2-26.9-13.8-40.5-16.9c-13.6-3-27.1-4.6-40.5-4.6c-20.1,0-35,3-44.7,9.1 c-9.7,6.1-14.5,13.8-14.5,23c0,8.6,3.2,15,9.5,19.3c6.3,4.3,14.6,7.7,24.9,10.2c10.2,2.5,21.6,4.6,34.1,6.2 c12.5,1.6,24.9,4,37.3,7.2c12.3,3.2,23.7,7.6,34.1,13.1c10.4,5.5,18.8,13.3,25.1,23.3c6.3,10,9.5,23,9.5,39.1 c0,17.9-5.2,33.4-15.6,46.6c-10.4,13.2-25.1,23.5-44.2,30.8S2788.3,496.1,2761.9,496.1z M2909.5,262v-53.6h188.3V262H2909.5z M3053.4,496.1c-31,0-55-8.1-71.9-24.4c-16.9-16.2-25.4-40.3-25.4-72V143.1h66.1V398c0,13.6,3.4,24.1,10.3,31.6 c6.9,7.5,16.5,11.2,28.8,11.2c14.8,0,27.2-3.9,37-11.8l18.5,47.7c-7.8,6.4-17.3,11.2-28.6,14.5 C3077,494.5,3065.3,496.1,3053.4,496.1z"
|
||||
/>
|
||||
<!-- foreign -->
|
||||
<path
|
||||
v-else
|
||||
fill="currentColor"
|
||||
fill-rule="nonzero"
|
||||
d="M657.4,254.8v-53.6h188.3v53.6H657.4z M704,485.1V183c0-30,8.7-54,26.2-72c17.5-18,42.4-27,74.9-27 c11.6,0,22.7,1.3,33.1,3.7c10.4,2.5,19.1,6.4,26.2,11.8l-18,50.3c-4.9-3.9-10.6-6.9-16.9-8.8c-6.3-2-12.9-2.9-19.6-2.9 c-13.8,0-24.2,3.8-31.2,11.5c-7.1,7.7-10.6,19.2-10.6,34.5v32.1l2.1,30v238.9H704z M1012.9,488.9c-28.9,0-54.7-6.3-77.2-19 c-22.6-12.7-40.4-30.1-53.4-52.2c-13.1-22.1-19.6-47.3-19.6-75.5c0-28.6,6.5-53.8,19.6-75.8c13-22,30.9-39.2,53.4-51.7 c22.6-12.5,48.3-18.7,77.2-18.7c29.3,0,55.3,6.3,78,18.7c22.7,12.5,40.6,29.6,53.4,51.4c12.9,21.8,19.3,47.1,19.3,76.1 c0,28.2-6.4,53.4-19.3,75.5c-12.9,22.1-30.7,39.6-53.4,52.2C1068.2,482.5,1042.2,488.9,1012.9,488.9z M1012.9,431.6 c16.2,0,30.7-3.6,43.4-10.7c12.7-7.1,22.7-17.5,29.9-31.1c7.2-13.6,10.8-29.5,10.8-47.7c0-18.6-3.6-34.5-10.8-47.9 c-7.2-13.4-17.2-23.6-29.9-30.8c-12.7-7.1-27-10.7-42.9-10.7c-16.2,0-30.6,3.6-43.1,10.7c-12.5,7.1-22.5,17.4-29.9,30.8 s-11.1,29.4-11.1,47.9c0,18.2,3.7,34.1,11.1,47.7c7.4,13.6,17.4,23.9,29.9,31.1C982.9,428,997.1,431.6,1012.9,431.6z M1227.2,485.1 v-286h63v78.7l-7.4-23c8.5-19.3,21.8-33.9,39.9-43.9c18.2-10,40.8-15,68-15v63.7c-2.8-0.7-5.5-1.2-7.9-1.3c-2.5-0.2-4.9-0.3-7.4-0.3 c-25,0-45,7.4-59.8,22.2c-14.8,14.8-22.2,36.9-22.2,66.1v138.7H1227.2z M1576.9,488.9c-31.4,0-58.8-6.3-82.3-19 c-23.5-12.7-41.6-30.1-54.5-52.2c-12.9-22.1-19.3-47.3-19.3-75.5c0-28.6,6.3-53.8,18.8-75.8c12.5-22,29.8-39.2,51.8-51.7 c22-12.5,47.2-18.7,75.4-18.7c27.5,0,52,6.1,73.5,18.2c21.5,12.1,38.4,29.3,50.8,51.4c12.3,22.1,18.5,48.2,18.5,78.2 c0,2.9-0.1,6.1-0.3,9.6c-0.2,3.6-0.4,7-0.8,10.2h-235.4v-44.5h200.5l-25.9,13.9c0.3-16.4-3-30.9-10.1-43.4 c-7.1-12.5-16.7-22.3-28.8-29.5c-12.2-7.1-26.2-10.7-42.1-10.7c-16.2,0-30.4,3.6-42.6,10.7c-12.2,7.1-21.7,17.1-28.6,29.7 c-6.9,12.7-10.3,27.6-10.3,44.7v10.7c0,17.1,3.9,32.3,11.6,45.5c7.8,13.2,18.7,23.4,32.8,30.5c14.1,7.1,30.3,10.7,48.7,10.7 c15.9,0,30.2-2.5,42.9-7.5c12.7-5,24-12.9,33.9-23.6l35.4,41.2c-12.7,15-28.7,26.5-47.9,34.5 C1623.5,484.9,1601.6,488.9,1576.9,488.9z M1806.5,152c-12.3,0-22.5-3.9-30.4-11.8c-7.9-7.8-11.9-17.3-11.9-28.4 c0-11.4,4-21,11.9-28.7c7.9-7.7,18.1-11.5,30.4-11.5c12.3,0,22.5,3.7,30.4,11c7.9,7.3,11.9,16.5,11.9,27.6 c0,11.8-3.9,21.7-11.6,29.7C1829.4,148,1819.2,152,1806.5,152z M1773.1,485.1v-286h66.1v286H1773.1z M2045.6,472.3 c-26.8,0-51-5.8-72.7-17.4c-21.7-11.6-38.8-27.8-51.3-48.5c-12.5-20.7-18.8-45-18.8-72.8c0-27.5,6.3-51.6,18.8-72.3 c12.5-20.7,29.6-36.8,51.3-48.2c21.7-11.4,45.9-17.1,72.7-17.1c24,0,45.5,4.8,64.5,14.5s34.3,24.6,45.8,45 c11.5,20.4,17.2,46.4,17.2,78.2c0,31.8-5.7,57.9-17.2,78.5c-11.5,20.5-26.7,35.7-45.8,45.5C2091.1,467.4,2069.6,472.3,2045.6,472.3z M2054,592.8c-26.1,0-51.6-3.5-76.4-10.4c-24.9-7-45.2-17.1-61.1-30.3l29.6-50.3c12.3,10.4,27.9,18.7,46.8,24.9 c18.9,6.2,38,9.4,57.4,9.4c31,0,53.8-7.2,68.2-21.7c14.5-14.5,21.7-36.2,21.7-65.1v-50.9l5.3-64.8l-2.1-64.8v-69.6h63v242.6 c0,51.8-13.1,89.9-39.1,114.3C2141.2,580.5,2103.4,592.8,2054,592.8z M2055.6,415.5c16.6,0,31.4-3.5,44.4-10.4 c13-7,23.2-16.6,30.4-28.9c7.2-12.3,10.8-26.5,10.8-42.6c0-16.1-3.6-30.3-10.8-42.6c-7.2-12.3-17.4-21.8-30.4-28.4 c-13.1-6.6-27.9-9.9-44.4-9.9c-16.6,0-31.5,3.3-44.7,9.9c-13.2,6.6-23.5,16.1-30.7,28.4c-7.2,12.3-10.8,26.5-10.8,42.6 c0,16.1,3.6,30.3,10.8,42.6c7.2,12.3,17.5,22,30.7,28.9S2039.1,415.5,2055.6,415.5z M2453.5,195.9c22.6,0,42.8,4.5,60.6,13.4 c17.8,8.9,31.8,22.6,42.1,41c10.2,18.4,15.3,42,15.3,71v163.9h-66.1V329.8c0-25.3-5.9-44.3-17.7-56.8 c-11.8-12.5-28.3-18.7-49.5-18.7c-15.5,0-29.3,3.2-41.3,9.6c-12,6.4-21.3,16-27.8,28.7c-6.5,12.7-9.8,28.7-9.8,47.9v144.6h-66.1 v-286h63v77.1l-11.1-23.6c9.9-18.2,24.2-32.2,43.1-42C2407,200.8,2428.8,195.9,2453.5,195.9z"
|
||||
/>
|
||||
<g fill="var(--color-brand)">
|
||||
<g clip-path="url(#clip0_2001_62)" shape-rendering="crispEdges">
|
||||
<circle cx="121" cy="121" r="121" fill="url(#paint0_radial_2001_62)" />
|
||||
<circle cx="121" cy="121" r="114" fill="url(#paint1_radial_2001_62)" />
|
||||
<circle cx="121" cy="121" r="114" fill="url(#paint2_linear_2001_62)" fill-opacity="0.7" />
|
||||
<g filter="url(#filter0_ddii_2001_62)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M205.988 143.917C209.14 132.173 209.827 119.904 208.008 107.882C206.188 95.859 201.9 84.343 195.413 74.0581C188.927 63.7731 180.382 54.9417 170.317 48.1194C160.252 41.2971 148.883 36.6314 136.927 34.4159C124.971 32.2005 112.686 32.4831 100.845 35.246C89.0031 38.009 77.8615 43.1924 68.1205 50.4704C58.3796 57.7484 50.2501 66.9633 44.2434 77.5357C38.2367 88.108 34.4828 99.809 33.2178 111.903H48.1256C50.1683 95.7491 57.551 80.7416 69.1007 69.2649C80.6503 57.7882 95.7043 50.5007 111.87 48.5604C128.037 46.6201 144.388 50.1383 158.325 58.5561C172.262 66.9738 182.987 79.808 188.794 95.0194L174.308 98.8955C171.069 91.1086 166.166 84.1246 159.943 78.4332C153.719 72.7418 146.326 68.4808 138.281 65.9491L135.61 81.0185C143.504 83.9164 150.365 89.0847 155.328 95.8729C160.292 102.661 163.137 110.766 163.505 119.167C163.872 127.568 161.747 135.891 157.396 143.087C153.044 150.283 146.662 156.031 139.051 159.608L143.014 174.391C154.759 169.528 164.572 160.928 170.935 149.923C177.297 138.918 179.854 126.122 178.209 113.518L192.645 109.666C194.169 119.297 193.721 129.136 191.328 138.588L205.988 143.917Z"
|
||||
fill="#1BD96A"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M205.988 143.917C209.14 132.173 209.827 119.904 208.008 107.882C206.188 95.859 201.9 84.343 195.413 74.0581C188.927 63.7731 180.382 54.9417 170.317 48.1194C160.252 41.2971 148.883 36.6314 136.927 34.4159C124.971 32.2005 112.686 32.4831 100.845 35.246C89.0031 38.009 77.8615 43.1924 68.1205 50.4704C58.3796 57.7484 50.2501 66.9633 44.2434 77.5357C38.2367 88.108 34.4828 99.809 33.2178 111.903H48.1256C50.1683 95.7491 57.551 80.7416 69.1007 69.2649C80.6503 57.7882 95.7043 50.5007 111.87 48.5604C128.037 46.6201 144.388 50.1383 158.325 58.5561C172.262 66.9738 182.987 79.808 188.794 95.0194L174.308 98.8955C171.069 91.1086 166.166 84.1246 159.943 78.4332C153.719 72.7418 146.326 68.4808 138.281 65.9491L135.61 81.0185C143.504 83.9164 150.365 89.0847 155.328 95.8729C160.292 102.661 163.137 110.766 163.505 119.167C163.872 127.568 161.747 135.891 157.396 143.087C153.044 150.283 146.662 156.031 139.051 159.608L143.014 174.391C154.759 169.528 164.572 160.928 170.935 149.923C177.297 138.918 179.854 126.122 178.209 113.518L192.645 109.666C194.169 119.297 193.721 129.136 191.328 138.588L205.988 143.917Z"
|
||||
fill="url(#paint3_radial_2001_62)"
|
||||
fill-opacity="0.3"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M205.988 143.917C209.14 132.173 209.827 119.904 208.008 107.882C206.188 95.859 201.9 84.343 195.413 74.0581C188.927 63.7731 180.382 54.9417 170.317 48.1194C160.252 41.2971 148.883 36.6314 136.927 34.4159C124.971 32.2005 112.686 32.4831 100.845 35.246C89.0031 38.009 77.8615 43.1924 68.1205 50.4704C58.3796 57.7484 50.2501 66.9633 44.2434 77.5357C38.2367 88.108 34.4828 99.809 33.2178 111.903H48.1256C50.1683 95.7491 57.551 80.7416 69.1007 69.2649C80.6503 57.7882 95.7043 50.5007 111.87 48.5604C128.037 46.6201 144.388 50.1383 158.325 58.5561C172.262 66.9738 182.987 79.808 188.794 95.0194L174.308 98.8955C171.069 91.1086 166.166 84.1246 159.943 78.4332C153.719 72.7418 146.326 68.4808 138.281 65.9491L135.61 81.0185C143.504 83.9164 150.365 89.0847 155.328 95.8729C160.292 102.661 163.137 110.766 163.505 119.167C163.872 127.568 161.747 135.891 157.396 143.087C153.044 150.283 146.662 156.031 139.051 159.608L143.014 174.391C154.759 169.528 164.572 160.928 170.935 149.923C177.297 138.918 179.854 126.122 178.209 113.518L192.645 109.666C194.169 119.297 193.721 129.136 191.328 138.588L205.988 143.917Z"
|
||||
fill="url(#paint4_radial_2001_62)"
|
||||
/>
|
||||
<path
|
||||
d="M143.685 206.046C131.155 209.41 118.037 209.965 105.268 207.673C92.4982 205.38 80.3923 200.297 69.8148 192.785C59.2373 185.272 50.449 175.517 44.0777 164.216C37.7064 152.914 33.9092 140.345 32.9573 127.407H47.8651C48.253 131.566 48.9927 135.684 50.0765 139.718C51.2072 143.949 52.7165 148.069 54.5861 152.03L67.8541 144.066C63.9723 135.074 62.4364 125.243 63.3904 115.495C64.3444 105.748 67.757 96.4012 73.3086 88.3321C78.8602 80.2629 86.3691 73.7348 95.132 69.3594C103.895 64.984 113.625 62.9044 123.411 63.3154L120.715 78.3847C114.187 78.4145 107.754 79.94 101.908 82.844C96.0617 85.7481 90.9591 89.9535 86.9919 95.1371C83.0247 100.321 80.2984 106.345 79.0224 112.746C77.7464 119.148 77.9547 125.757 79.6313 132.065C80.0164 133.469 80.4264 134.861 80.9606 136.103L97.9307 125.928L92.8496 112.412L108.876 95.9512L129.138 91.5906L134.964 98.8334L125.622 108.287L117.485 110.847L111.658 116.835L114.516 124.761C114.516 124.761 120.28 130.898 120.293 130.898L128.455 128.736L134.256 122.363L146.928 118.338L150.655 126.835L137.635 142.849L115.721 149.769L105.882 138.824L88.75 149.123C93.1599 154.142 98.6841 158.058 104.88 160.559C111.076 163.059 117.771 164.074 124.429 163.521L128.392 178.342C118.504 179.633 108.449 178.348 99.2029 174.611C89.9568 170.874 81.8329 164.811 75.6186 157.011L62.4003 164.937C69.9496 174.848 79.9079 182.664 91.3288 187.642C102.75 192.62 115.254 194.594 127.653 193.378C140.052 192.162 151.934 187.795 162.17 180.693C172.406 173.591 180.656 163.989 186.136 152.8L200.795 158.129C195.368 169.812 187.439 180.16 177.57 188.44C167.702 196.72 156.134 202.731 143.685 206.046Z"
|
||||
fill="#1BD96A"
|
||||
/>
|
||||
<path
|
||||
d="M143.685 206.046C131.155 209.41 118.037 209.965 105.268 207.673C92.4982 205.38 80.3923 200.297 69.8148 192.785C59.2373 185.272 50.449 175.517 44.0777 164.216C37.7064 152.914 33.9092 140.345 32.9573 127.407H47.8651C48.253 131.566 48.9927 135.684 50.0765 139.718C51.2072 143.949 52.7165 148.069 54.5861 152.03L67.8541 144.066C63.9723 135.074 62.4364 125.243 63.3904 115.495C64.3444 105.748 67.757 96.4012 73.3086 88.3321C78.8602 80.2629 86.3691 73.7348 95.132 69.3594C103.895 64.984 113.625 62.9044 123.411 63.3154L120.715 78.3847C114.187 78.4145 107.754 79.94 101.908 82.844C96.0617 85.7481 90.9591 89.9535 86.9919 95.1371C83.0247 100.321 80.2984 106.345 79.0224 112.746C77.7464 119.148 77.9547 125.757 79.6313 132.065C80.0164 133.469 80.4264 134.861 80.9606 136.103L97.9307 125.928L92.8496 112.412L108.876 95.9512L129.138 91.5906L134.964 98.8334L125.622 108.287L117.485 110.847L111.658 116.835L114.516 124.761C114.516 124.761 120.28 130.898 120.293 130.898L128.455 128.736L134.256 122.363L146.928 118.338L150.655 126.835L137.635 142.849L115.721 149.769L105.882 138.824L88.75 149.123C93.1599 154.142 98.6841 158.058 104.88 160.559C111.076 163.059 117.771 164.074 124.429 163.521L128.392 178.342C118.504 179.633 108.449 178.348 99.2029 174.611C89.9568 170.874 81.8329 164.811 75.6186 157.011L62.4003 164.937C69.9496 174.848 79.9079 182.664 91.3288 187.642C102.75 192.62 115.254 194.594 127.653 193.378C140.052 192.162 151.934 187.795 162.17 180.693C172.406 173.591 180.656 163.989 186.136 152.8L200.795 158.129C195.368 169.812 187.439 180.16 177.57 188.44C167.702 196.72 156.134 202.731 143.685 206.046Z"
|
||||
fill="url(#paint5_radial_2001_62)"
|
||||
fill-opacity="0.3"
|
||||
/>
|
||||
<path
|
||||
d="M143.685 206.046C131.155 209.41 118.037 209.965 105.268 207.673C92.4982 205.38 80.3923 200.297 69.8148 192.785C59.2373 185.272 50.449 175.517 44.0777 164.216C37.7064 152.914 33.9092 140.345 32.9573 127.407H47.8651C48.253 131.566 48.9927 135.684 50.0765 139.718C51.2072 143.949 52.7165 148.069 54.5861 152.03L67.8541 144.066C63.9723 135.074 62.4364 125.243 63.3904 115.495C64.3444 105.748 67.757 96.4012 73.3086 88.3321C78.8602 80.2629 86.3691 73.7348 95.132 69.3594C103.895 64.984 113.625 62.9044 123.411 63.3154L120.715 78.3847C114.187 78.4145 107.754 79.94 101.908 82.844C96.0617 85.7481 90.9591 89.9535 86.9919 95.1371C83.0247 100.321 80.2984 106.345 79.0224 112.746C77.7464 119.148 77.9547 125.757 79.6313 132.065C80.0164 133.469 80.4264 134.861 80.9606 136.103L97.9307 125.928L92.8496 112.412L108.876 95.9512L129.138 91.5906L134.964 98.8334L125.622 108.287L117.485 110.847L111.658 116.835L114.516 124.761C114.516 124.761 120.28 130.898 120.293 130.898L128.455 128.736L134.256 122.363L146.928 118.338L150.655 126.835L137.635 142.849L115.721 149.769L105.882 138.824L88.75 149.123C93.1599 154.142 98.6841 158.058 104.88 160.559C111.076 163.059 117.771 164.074 124.429 163.521L128.392 178.342C118.504 179.633 108.449 178.348 99.2029 174.611C89.9568 170.874 81.8329 164.811 75.6186 157.011L62.4003 164.937C69.9496 174.848 79.9079 182.664 91.3288 187.642C102.75 192.62 115.254 194.594 127.653 193.378C140.052 192.162 151.934 187.795 162.17 180.693C172.406 173.591 180.656 163.989 186.136 152.8L200.795 158.129C195.368 169.812 187.439 180.16 177.57 188.44C167.702 196.72 156.134 202.731 143.685 206.046Z"
|
||||
fill="url(#paint6_radial_2001_62)"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="m29 424.4 188.2-112.95-17.15-45.48 53.75-55.21 67.93-14.64 19.67 24.21-31.32 31.72-27.3 8.6-19.52 20.05 9.56 26.6 19.4 20.6 27.36-7.28 19.47-21.38 42.51-13.47 12.67 28.5-43.87 53.78-73.5 23.27-32.97-36.7L55.06 467.94C46.1 456.41 35.67 440.08 29 424.4Zm543.03-230.25-149.5 40.32c8.24 21.92 10.95 34.8 13.23 49l149.23-40.26c-2.38-15.94-6.65-32.17-12.96-49.06Z"
|
||||
d="M919.408 57.084V112.035H919.74C926.27 101.299 935.567 95.9316 947.631 95.9316C966.778 95.9316 976.352 107.47 976.352 130.546V182.924H962.738V133.949C962.738 116.241 956.153 107.387 942.982 107.387C936.342 107.387 930.753 109.96 926.215 115.106C921.677 120.198 919.408 126.755 919.408 134.779V182.924H905.795V57.084H919.408Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M51.28 316.13c10.59 125 115.54 223.3 243.27 223.3 96.51 0 180.02-56.12 219.63-137.46l48.61 16.83c-46.78 101.34-149.35 171.75-268.24 171.75C138.6 590.55 10.71 469.38 0 316.13h51.28ZM.78 265.24C15.86 116.36 141.73 0 294.56 0c162.97 0 295.28 132.31 295.28 295.28 0 26.14-3.4 51.49-9.8 75.63l-48.48-16.78a244.28 244.28 0 0 0 7.15-58.85c0-134.75-109.4-244.15-244.15-244.15-124.58 0-227.49 93.5-242.32 214.11H.8Z"
|
||||
class="ring--large ring"
|
||||
d="M852.587 97.9238V77.1719L866.2 72.7725V97.9238H887.616V109.545H866.2V157.44C866.2 163.14 867.169 167.208 869.105 169.643C871.042 172.077 874.252 173.295 878.734 173.295C882.165 173.295 885.126 172.354 887.616 170.473V182.094C884.407 183.865 880.173 184.75 874.916 184.75C860.03 184.75 852.587 176.449 852.587 159.848V109.545H837.978V97.9238H852.587Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M293.77 153.17c-78.49.07-142.2 63.83-142.2 142.34 0 78.56 63.79 142.34 142.35 142.34 3.98 0 7.93-.16 11.83-.49l14.22 49.76a194.65 194.65 0 0 1-26.05 1.74c-106.72 0-193.36-86.64-193.36-193.35 0-106.72 86.64-193.35 193.36-193.35 2.64 0 5.28.05 7.9.16l-8.05 50.85Zm58.2-42.13c78.39 24.67 135.3 97.98 135.3 184.47 0 80.07-48.77 148.83-118.2 178.18l-14.17-49.55c48.08-22.85 81.36-71.89 81.36-128.63 0-60.99-38.44-113.07-92.39-133.32l8.1-51.15Z"
|
||||
class="ring--small ring"
|
||||
d="M765.512 97.9238V112.035H765.844C772.263 101.299 781.56 95.9316 793.734 95.9316C803.031 95.9316 810.142 98.9476 815.067 104.979C819.993 110.956 822.455 119.617 822.455 130.961V182.924H808.842V134.447C808.842 116.407 802.257 107.387 789.086 107.387C782.279 107.387 776.635 109.96 772.152 115.106C767.725 120.198 765.512 126.645 765.512 134.447V182.924H751.898V97.9238H765.512Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M710.727 97.9238H724.34V182.924H710.727V97.9238ZM711.474 61.1514C713.189 59.4912 715.264 58.6611 717.699 58.6611C720.189 58.6611 722.292 59.5189 724.008 61.2344C725.779 62.8945 726.664 64.9974 726.664 67.543C726.664 69.9779 725.779 72.0531 724.008 73.7686C722.292 75.484 720.189 76.3418 717.699 76.3418C715.264 76.3418 713.189 75.5117 711.474 73.8516C709.758 72.1914 708.9 70.0885 708.9 67.543C708.9 64.9974 709.758 62.8669 711.474 61.1514Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M651.625 97.9238H665.238V115.438H665.57C667.507 109.462 670.468 104.813 674.452 101.493C678.437 98.1175 682.891 96.4297 687.816 96.4297C691.358 96.4297 694.07 96.8171 695.951 97.5918V111.703C693.572 109.877 690.141 108.964 685.658 108.964C679.848 108.964 674.978 111.703 671.049 117.182C667.175 122.66 665.238 130.131 665.238 139.594V182.924H651.625V97.9238Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M556.332 108.632C563.415 100.165 572.851 95.9316 584.638 95.9316C596.314 95.9316 604.809 100.525 610.121 109.711H610.453V57.084H624.066V182.924H610.453V168.48H610.121C603.812 179.438 594.073 184.916 580.902 184.916C570.222 184.916 561.672 181.125 555.253 173.544C548.889 165.907 545.707 155.531 545.707 142.416C545.707 128.36 549.249 117.099 556.332 108.632ZM559.652 141.752C559.652 151.547 561.921 159.294 566.459 164.994C571.052 170.639 577.195 173.461 584.887 173.461C592.468 173.461 598.611 170.722 603.314 165.243C608.074 159.765 610.453 152.847 610.453 144.491V131.957C610.453 125.095 608.184 119.285 603.646 114.525C599.109 109.766 593.354 107.387 586.381 107.387C578.08 107.387 571.55 110.43 566.791 116.518C562.032 122.605 559.652 131.016 559.652 141.752Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M457.802 107.968C465.604 99.9437 476.146 95.9316 489.428 95.9316C502.1 95.9316 511.978 99.833 519.062 107.636C526.2 115.438 529.77 126.257 529.77 140.092C529.77 153.65 525.924 164.524 518.231 172.714C510.595 180.849 500.329 184.916 487.436 184.916C474.874 184.916 464.83 180.959 457.304 173.046C449.833 165.077 446.098 154.535 446.098 141.42C446.098 127.143 449.999 115.992 457.802 107.968ZM467.763 164.745C472.909 170.556 479.799 173.461 488.432 173.461C497.23 173.461 503.982 170.611 508.686 164.911C513.445 159.211 515.824 151.104 515.824 140.59C515.824 129.965 513.445 121.775 508.686 116.02C503.982 110.264 497.23 107.387 488.432 107.387C479.688 107.387 472.771 110.375 467.68 116.352C462.589 122.273 460.043 130.463 460.043 140.922C460.043 150.993 462.616 158.935 467.763 164.745Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M301 182.924V63.8906H319.428L355.951 146.898C358.773 153.262 360.6 158.021 361.43 161.176H361.928C364.307 154.646 366.216 149.776 367.655 146.566L404.926 63.8906H422.357V182.924H408.495V103.07C408.495 96.7617 408.882 89.042 409.657 79.9111H409.325C407.997 85.279 406.807 89.125 405.756 91.4492L365.082 182.924H358.275L317.685 92.1133C316.522 89.457 315.333 85.3896 314.115 79.9111H313.783C314.226 84.6702 314.447 92.4453 314.447 103.236V182.924H301Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_ddii_2001_62"
|
||||
x="10.5573"
|
||||
y="16.8408"
|
||||
width="238.843"
|
||||
height="241.619"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dx="9" dy="18" />
|
||||
<feGaussianBlur stdDeviation="15.7" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" />
|
||||
<feBlend mode="plus-darker" in2="BackgroundImageFix" result="effect1_dropShadow_2001_62" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dx="2" dy="4" />
|
||||
<feGaussianBlur stdDeviation="10.05" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.59 0" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="effect1_dropShadow_2001_62"
|
||||
result="effect2_dropShadow_2001_62"
|
||||
/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_2001_62" result="shape" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dx="1" dy="-1" />
|
||||
<feGaussianBlur stdDeviation="0.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0" />
|
||||
<feBlend mode="normal" in2="shape" result="effect3_innerShadow_2001_62" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dx="-1" dy="1" />
|
||||
<feGaussianBlur stdDeviation="0.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="effect3_innerShadow_2001_62"
|
||||
result="effect4_innerShadow_2001_62"
|
||||
/>
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_2001_62"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(121 212.5) rotate(-90) scale(151)"
|
||||
>
|
||||
<stop stop-color="#46EAB1" />
|
||||
<stop offset="1" stop-color="#164232" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint1_radial_2001_62"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(121 207.207) rotate(-90) scale(142.264)"
|
||||
>
|
||||
<stop stop-color="#6CF8C7" />
|
||||
<stop offset="0.54" stop-color="#34BB93" />
|
||||
<stop offset="1" stop-color="#2A694D" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_2001_62"
|
||||
x1="121"
|
||||
y1="-16"
|
||||
x2="121"
|
||||
y2="131"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="currentColor" stop-opacity="0.55" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint3_radial_2001_62"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(-299.432 132.639 -67.2068 -98.1582 116.946 87.7474)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#94FFD4" />
|
||||
<stop offset="1" stop-color="#040C1B" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint4_radial_2001_62"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(120.324 -47.4604 58.9797 96.7404 126.919 101.805)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#95F1BB" />
|
||||
<stop offset="1" stop-color="#3BCF79" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint5_radial_2001_62"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(-285.899 136.712 -64.1693 -101.173 112.901 119.754)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#94FFD4" />
|
||||
<stop offset="1" stop-color="#040C1B" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint6_radial_2001_62"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(114.886 -48.9181 56.314 99.7116 122.423 134.243)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#95F1BB" />
|
||||
<stop offset="1" stop-color="#3BCF79" />
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_2001_62">
|
||||
<rect width="976" height="242" fill="currentColor" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const loading = useLoading()
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const api = computed(() => {
|
||||
if (flags.value.demoMode) return 'prod'
|
||||
|
||||
const apiUrl = config.public.apiBaseUrl
|
||||
if (apiUrl.startsWith('https://api.modrinth.com')) {
|
||||
return 'prod'
|
||||
} else if (apiUrl.startsWith('https://staging-api.modrinth.com')) {
|
||||
return 'staging'
|
||||
} else if (apiUrl.startsWith('localhost') || apiUrl.startsWith('127.0.0.1')) {
|
||||
return 'localhost'
|
||||
}
|
||||
return 'foreign'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.animate {
|
||||
.ring {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
animation-fill-mode: forwards;
|
||||
transition: transform 2s ease-in-out;
|
||||
&--large {
|
||||
animation: spin 1s ease-in-out infinite forwards;
|
||||
}
|
||||
&--small {
|
||||
animation: spin 2s ease-in-out infinite reverse;
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
<template>
|
||||
<div class="wrapper relative mb-3 flex w-full justify-center rounded-2xl">
|
||||
<div class="wrapper relative mb-3 mt-3 flex w-full justify-center">
|
||||
<AutoLink
|
||||
:to="currentAd.link"
|
||||
:aria-label="currentAd.description"
|
||||
class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 rounded-[inherit] bg-bg-raised"
|
||||
class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 bg-bg-raised"
|
||||
>
|
||||
<img
|
||||
:src="currentAd.light"
|
||||
:src="currentAd.src"
|
||||
aria-hidden="true"
|
||||
:alt="currentAd.description"
|
||||
class="light-image hidden rounded-[inherit]"
|
||||
/>
|
||||
<img
|
||||
:src="currentAd.dark"
|
||||
aria-hidden="true"
|
||||
:alt="currentAd.description"
|
||||
class="dark-image rounded-[inherit]"
|
||||
class="rounded-[inherit]"
|
||||
/>
|
||||
</AutoLink>
|
||||
<div
|
||||
class="absolute top-0 flex items-center justify-center overflow-hidden rounded-2xl bg-bg-raised"
|
||||
>
|
||||
<div class="absolute top-0 flex items-center justify-center overflow-hidden bg-bg-raised">
|
||||
<div id="modrinth-rail-1" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,14 +54,12 @@ useHead({
|
||||
|
||||
const AD_PRESETS = {
|
||||
medal: {
|
||||
light: 'https://cdn-raw.modrinth.com/modrinth-hosting-medal-light.webp',
|
||||
dark: 'https://cdn-raw.modrinth.com/modrinth-hosting-medal-dark.webp',
|
||||
src: 'https://cdn-raw.modrinth.com/modrinth-hosting-medal-light.webp',
|
||||
description: 'Host your next server with Modrinth Hosting',
|
||||
link: '/hosting?plan&ref=medal',
|
||||
},
|
||||
'modrinth-hosting': {
|
||||
light: 'https://cdn-raw.modrinth.com/modrinth-hosting-light.webp',
|
||||
dark: 'https://cdn-raw.modrinth.com/modrinth-hosting-dark.webp',
|
||||
src: 'https://cdn-raw.modrinth.com/modrinth-hosting-light.webp',
|
||||
description: 'Host your next server with Modrinth Hosting',
|
||||
link: '/hosting',
|
||||
},
|
||||
@@ -110,7 +100,6 @@ iframe[id^='google_ads_iframe'] {
|
||||
|
||||
#qc-cmp2-ui {
|
||||
background: var(--color-raised-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
@@ -123,7 +112,6 @@ iframe[id^='google_ads_iframe'] {
|
||||
#qc-cmp2-ui button[mode='secondary'] {
|
||||
background: var(--color-brand);
|
||||
color: var(--color-accent-contrast);
|
||||
border-radius: var(--radius-lg);
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -158,19 +146,5 @@ iframe[id^='google_ads_iframe'] {
|
||||
}
|
||||
|
||||
.wrapper > * {
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.light,
|
||||
.light-mode {
|
||||
.dark-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.light-image {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
>.
|
||||
</Admonition>
|
||||
<div
|
||||
class="grid grid-cols-[1fr_auto_1fr] items-center justify-center gap-6 rounded-2xl bg-surface-2 p-4"
|
||||
class="grid grid-cols-[1fr_auto_1fr] items-center justify-center gap-6 rounded-[4px] bg-surface-2 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar :src="currentOwner.avatar_url" :alt="currentOwner.username" size="xs" circle />
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
>.
|
||||
</Admonition>
|
||||
<div
|
||||
class="grid grid-cols-[1fr_auto_1fr] items-center justify-center gap-6 rounded-2xl bg-surface-2 p-4"
|
||||
class="grid grid-cols-[1fr_auto_1fr] items-center justify-center gap-6 rounded-[4px] bg-surface-2 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar :src="currentOwner.avatar_url" :alt="currentOwner.username" size="xs" circle />
|
||||
|
||||
@@ -42,7 +42,7 @@ defineExpose({
|
||||
id="h-captcha"
|
||||
class="h-captcha"
|
||||
data-sitekey="4a7a2c80-68f2-4190-9d52-131c76e0c14e"
|
||||
:data-theme="$theme.active === 'light' ? 'light' : 'dark'"
|
||||
data-theme="light"
|
||||
data-callback="hCaptchaUpdateToken"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
@@ -21,7 +21,7 @@ const ariaLabelByType = computed(() => `Banner with ${props.messageType} message
|
||||
min-height: var(--font-size-2xl);
|
||||
|
||||
background: var(--color-raised-bg);
|
||||
border-radius: var(--size-rounded-card);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: -2px;
|
||||
@@ -41,17 +41,20 @@ const ariaLabelByType = computed(() => `Banner with ${props.messageType} message
|
||||
}
|
||||
|
||||
.message-banner__content {
|
||||
padding: var(--spacing-card-md) var(--spacing-card-lg);
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.message-banner__content_warning {
|
||||
border-left: 0.5rem solid var(--color-warning-banner-side);
|
||||
border: 1px solid var(--color-warning-banner-side);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-warning-banner-bg);
|
||||
color: var(--color-warning-banner-text);
|
||||
}
|
||||
|
||||
.message-banner__content_information {
|
||||
border-left: 0.5rem solid var(--color-info-banner-side);
|
||||
border: 1px solid var(--color-info-banner-side);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-info-banner-bg);
|
||||
color: var(--color-info-banner-text);
|
||||
}
|
||||
|
||||
@@ -39,32 +39,38 @@ const socialLinks: {
|
||||
href: string
|
||||
icon: Component
|
||||
rel?: string
|
||||
brandColor: string
|
||||
}[] = [
|
||||
{
|
||||
label: defineMessage({ id: 'layout.footer.social.discord', defaultMessage: 'Discord' }),
|
||||
href: 'https://discord.modrinth.com',
|
||||
icon: DiscordIcon,
|
||||
brandColor: '#7089DA',
|
||||
},
|
||||
{
|
||||
label: defineMessage({ id: 'layout.footer.social.twitter', defaultMessage: 'Twitter' }),
|
||||
href: 'https://twitter.com/modrinth',
|
||||
icon: TwitterIcon,
|
||||
brandColor: '#16D4FA',
|
||||
},
|
||||
{
|
||||
label: defineMessage({ id: 'layout.footer.social.bluesky', defaultMessage: 'Bluesky' }),
|
||||
href: 'https://bsky.app/profile/modrinth.com',
|
||||
icon: BlueskyIcon,
|
||||
brandColor: '#0085FF',
|
||||
},
|
||||
{
|
||||
label: defineMessage({ id: 'layout.footer.social.mastodon', defaultMessage: 'Mastodon' }),
|
||||
href: 'https://floss.social/@modrinth',
|
||||
icon: MastodonIcon,
|
||||
rel: 'me',
|
||||
},
|
||||
{
|
||||
label: defineMessage({ id: 'layout.footer.social.x', defaultMessage: 'X' }),
|
||||
href: 'https://x.com/modrinth',
|
||||
icon: TwitterIcon,
|
||||
brandColor: '#6364FF',
|
||||
},
|
||||
{
|
||||
label: defineMessage({ id: 'layout.footer.social.github', defaultMessage: 'GitHub' }),
|
||||
href: 'https://github.com/modrinth',
|
||||
icon: GithubIcon,
|
||||
brandColor: '#5a5a5a',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -197,8 +203,6 @@ const footerLinks: {
|
||||
|
||||
const developerModeCounter = ref(0)
|
||||
|
||||
const state = useGeneratedState()
|
||||
|
||||
function developerModeIncrement() {
|
||||
developerModeCounter.value++
|
||||
if (developerModeCounter.value >= 5) {
|
||||
@@ -240,12 +244,12 @@ function developerModeIncrement() {
|
||||
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
|
||||
@click="developerModeIncrement()"
|
||||
/>
|
||||
<div class="flex flex-wrap justify-center gap-px sm:-mx-2">
|
||||
<div class="flex flex-wrap justify-center gap-1">
|
||||
<ButtonStyled
|
||||
v-for="(social, index) in socialLinks"
|
||||
:key="`footer-social-${index}`"
|
||||
circular
|
||||
type="transparent"
|
||||
:custom-color="social.brandColor"
|
||||
>
|
||||
<a
|
||||
v-tooltip="formatMessage(social.label)"
|
||||
@@ -257,6 +261,9 @@ function developerModeIncrement() {
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<a href="https://www.firefox.com/" target="_blank"
|
||||
><img src="/firefox.gif" alt="Get FireFox!!"
|
||||
/></a>
|
||||
<div class="mt-auto flex flex-wrap justify-center gap-3 md:flex-col">
|
||||
<p class="m-0">
|
||||
<IntlFormatted :message-id="messages.openSource">
|
||||
@@ -272,7 +279,7 @@ function developerModeIncrement() {
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<p class="m-0">© {{ state.buildYear ?? '2025' }} Rinth, Inc.</p>
|
||||
<p class="m-0">© 2008 Rinth, Inc.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:contents">
|
||||
@@ -327,6 +334,11 @@ function developerModeIncrement() {
|
||||
<div class="flex justify-center text-center text-xs font-medium text-secondary opacity-50">
|
||||
{{ formatMessage(messages.legalDisclaimer) }}
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Some icons are from <a href="https://www.gnome.org" target="_blank">GNOME Project</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<nav :aria-label="ariaLabel" class="w-full">
|
||||
<ul
|
||||
class="card-shadow m-0 flex list-none flex-col items-start gap-1.5 rounded-2xl bg-bg-raised p-4"
|
||||
class="m-0 flex list-none flex-col items-start overflow-hidden rounded-[4px] border border-solid border-black bg-white p-0"
|
||||
>
|
||||
<slot v-if="hasSlotContent" />
|
||||
|
||||
@@ -9,54 +9,27 @@
|
||||
<li v-for="(item, idx) in filteredItems" :key="getKey(item, idx)" class="contents">
|
||||
<hr v-if="isSeparator(item)" class="my-1 w-full border-t border-solid" />
|
||||
|
||||
<div
|
||||
v-else-if="isHeading(item)"
|
||||
class="px-4 pb-1 pt-2 text-xs font-bold uppercase tracking-wide text-secondary"
|
||||
>
|
||||
<div v-else-if="isHeading(item)" class="px-3 pb-1 pt-2 text-sm font-bold tracking-wide">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
|
||||
<NuxtLink
|
||||
v-else-if="item.link ?? item.to"
|
||||
:to="(item.link ?? item.to) as string"
|
||||
class="nav-item inline-flex w-full cursor-pointer items-center gap-2 rounded-xl border-none bg-transparent px-4 py-2.5 text-left text-base font-semibold leading-tight text-button-text transition-all hover:bg-button-bg hover:text-contrast active:scale-[0.97]"
|
||||
class="nav-item inline-flex w-full cursor-pointer items-center gap-2 border border-solid border-transparent bg-transparent px-3 py-1 text-left text-base leading-tight text-button-text hover:bg-[#dddddd] hover:text-contrast"
|
||||
:class="{ 'is-active': isActive(item as NavStackLinkItem) }"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
v-if="item.icon"
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5 shrink-0"
|
||||
/>
|
||||
<span class="text-contrast">{{ item.label }}</span>
|
||||
<span
|
||||
v-if="item.badge != null"
|
||||
class="rounded-full bg-brand-highlight px-2 text-sm font-bold text-brand"
|
||||
>
|
||||
{{ String(item.badge) }}
|
||||
</span>
|
||||
<span v-if="item.chevron" class="ml-auto"><ChevronRightIcon /></span>
|
||||
</NuxtLink>
|
||||
|
||||
<button
|
||||
v-else-if="item.action"
|
||||
class="nav-item inline-flex w-full cursor-pointer items-center gap-2 text-nowrap rounded-xl border-none bg-transparent px-4 py-2.5 text-left text-base font-semibold leading-tight text-button-text transition-all hover:bg-button-bg hover:text-contrast active:scale-[0.97]"
|
||||
class="nav-item inline-flex w-full cursor-pointer items-center gap-2 text-nowrap border-none bg-transparent px-4 py-2.5 text-left text-base leading-tight text-button-text hover:bg-[#dddddd] hover:text-contrast"
|
||||
:class="{ 'danger-button': item.danger }"
|
||||
@click="item.action"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
v-if="item.icon"
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5 shrink-0"
|
||||
/>
|
||||
<span class="text-contrast">{{ item.label }}</span>
|
||||
<span
|
||||
v-if="item.badge != null"
|
||||
class="rounded-full bg-brand-highlight px-2 text-sm font-bold text-brand"
|
||||
>
|
||||
{{ String(item.badge) }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<span v-else>You frog. 🐸</span>
|
||||
@@ -145,11 +118,18 @@ li {
|
||||
}
|
||||
.router-link-exact-active.nav-item,
|
||||
.nav-item.is-active {
|
||||
background: var(--color-button-bg-selected);
|
||||
color: var(--color-button-text-selected);
|
||||
background: #e9eff7;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
border: 1px solid #aecff7;
|
||||
border-inline: 0;
|
||||
}
|
||||
.router-link-exact-active.nav-item .text-contrast,
|
||||
.nav-item.is-active .text-contrast {
|
||||
color: var(--color-button-text-selected);
|
||||
color: black;
|
||||
}
|
||||
.router-link-exact-active.nav-item svg.text-contrast,
|
||||
.nav-item.is-active svg.text-contrast {
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,128 +1,44 @@
|
||||
<template>
|
||||
<nav
|
||||
ref="scrollContainer"
|
||||
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
>
|
||||
<button
|
||||
v-for="(option, index) in options"
|
||||
:key="`option-group-${index}`"
|
||||
ref="optionButtons"
|
||||
class="button-animation z-[1] flex flex-row items-center gap-2 rounded-full bg-transparent px-4 py-2 font-semibold"
|
||||
:class="{
|
||||
'text-button-textSelected': modelValue === option,
|
||||
'text-primary': modelValue !== option,
|
||||
}"
|
||||
@click="setOption(option)"
|
||||
>
|
||||
<div class="option-group" role="radiogroup" :aria-label="ariaLabel">
|
||||
<label v-for="(option, index) in options" :key="`option-group-${index}`">
|
||||
<input
|
||||
v-model="modelValue"
|
||||
class="option-group__input"
|
||||
type="radio"
|
||||
:name="radioName"
|
||||
:value="option"
|
||||
/>
|
||||
<slot :option="option" :selected="modelValue === option" />
|
||||
</button>
|
||||
<div
|
||||
class="navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full bg-button-bgSelected p-1"
|
||||
:style="{
|
||||
left: sliderLeftPx,
|
||||
top: sliderTopPx,
|
||||
right: sliderRightPx,
|
||||
bottom: sliderBottomPx,
|
||||
opacity: initialized ? 1 : 0,
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</nav>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, useId } from 'vue'
|
||||
|
||||
const modelValue = defineModel<T>({ required: true })
|
||||
|
||||
const props = defineProps<{
|
||||
options: T[]
|
||||
name?: string
|
||||
ariaLabel?: string
|
||||
}>()
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const sliderLeft = ref(4)
|
||||
const sliderTop = ref(4)
|
||||
const sliderRight = ref(4)
|
||||
const sliderBottom = ref(4)
|
||||
|
||||
const sliderLeftPx = computed(() => `${sliderLeft.value}px`)
|
||||
const sliderTopPx = computed(() => `${sliderTop.value}px`)
|
||||
const sliderRightPx = computed(() => `${sliderRight.value}px`)
|
||||
const sliderBottomPx = computed(() => `${sliderBottom.value}px`)
|
||||
|
||||
const optionButtons = ref()
|
||||
|
||||
const initialized = ref(false)
|
||||
|
||||
function setOption(option: T) {
|
||||
modelValue.value = option
|
||||
}
|
||||
|
||||
watch(modelValue, () => {
|
||||
startAnimation(props.options.indexOf(modelValue.value))
|
||||
})
|
||||
|
||||
function startAnimation(index: number) {
|
||||
const el = optionButtons.value[index]
|
||||
|
||||
if (!el || !el.offsetParent) return
|
||||
|
||||
const newValues = {
|
||||
left: el.offsetLeft,
|
||||
top: el.offsetTop,
|
||||
right: el.offsetParent.offsetWidth - el.offsetLeft - el.offsetWidth,
|
||||
bottom: el.offsetParent.offsetHeight - el.offsetTop - el.offsetHeight,
|
||||
}
|
||||
|
||||
if (sliderLeft.value === 4 && sliderRight.value === 4) {
|
||||
sliderLeft.value = newValues.left
|
||||
sliderRight.value = newValues.right
|
||||
sliderTop.value = newValues.top
|
||||
sliderBottom.value = newValues.bottom
|
||||
} else {
|
||||
const delay = 200
|
||||
|
||||
if (newValues.left < sliderLeft.value) {
|
||||
sliderLeft.value = newValues.left
|
||||
setTimeout(() => {
|
||||
sliderRight.value = newValues.right
|
||||
}, delay)
|
||||
} else {
|
||||
sliderRight.value = newValues.right
|
||||
setTimeout(() => {
|
||||
sliderLeft.value = newValues.left
|
||||
}, delay)
|
||||
}
|
||||
|
||||
if (newValues.top < sliderTop.value) {
|
||||
sliderTop.value = newValues.top
|
||||
setTimeout(() => {
|
||||
sliderBottom.value = newValues.bottom
|
||||
}, delay)
|
||||
} else {
|
||||
sliderBottom.value = newValues.bottom
|
||||
setTimeout(() => {
|
||||
sliderTop.value = newValues.top
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
initialized.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startAnimation(props.options.indexOf(modelValue.value))
|
||||
})
|
||||
const autoName = useId()
|
||||
const radioName = computed(() => props.name ?? autoName)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navtabs-transition {
|
||||
transition:
|
||||
all 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
|
||||
.option-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: var(--shadow-card);
|
||||
.option-group label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
<div
|
||||
:style="{
|
||||
'--color-brand': isUsingProjectColors
|
||||
? intToRgba(project.color, project.id, theme.active ?? undefined)
|
||||
? intToRgba(project.color, project.id, 'light')
|
||||
: getDefaultColor(project.id),
|
||||
}"
|
||||
class="legend__item__color"
|
||||
@@ -339,7 +339,6 @@ const { formatCompactNumber } = useCompactNumber()
|
||||
const formatMoney = useFormatMoney()
|
||||
|
||||
const router = useNativeRouter()
|
||||
const theme = useTheme()
|
||||
|
||||
debug('setup start', { server: import.meta.server, client: import.meta.client })
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="flex w-full max-w-full flex-col gap-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">Add dependency</span>
|
||||
<div class="flex flex-col gap-3 rounded-2xl border border-solid border-surface-5 p-4">
|
||||
<div class="flex flex-col gap-3 rounded-[4px] border border-solid border-surface-5 p-4">
|
||||
<div class="grid gap-2.5">
|
||||
<span class="font-semibold text-contrast">Project</span>
|
||||
<DependencySelect v-model="newDependencyProjectId" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-row gap-2 md:gap-3">
|
||||
<div
|
||||
class="flex h-10 min-h-10 w-10 min-w-10 items-center justify-center rounded-full border-[1px] border-solid border-button-bg bg-bg-raised !p-0 shadow-md md:h-12 md:min-h-12 md:w-12 md:min-w-12"
|
||||
class="flex h-10 min-h-10 w-10 min-w-10 items-center justify-center rounded-full border-[1px] border-solid border-button-bg bg-bg-raised !p-0 md:h-12 md:min-h-12 md:w-12 md:min-w-12"
|
||||
>
|
||||
<img
|
||||
v-if="methodIconUrl"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
leave-from-class="opacity-100 max-h-40"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div v-if="amount > 0" class="flex flex-col gap-2.5 rounded-[20px] bg-surface-2 p-4">
|
||||
<div v-if="amount > 0" class="flex flex-col gap-2.5 rounded-[4px] bg-surface-2 p-4">
|
||||
<template v-if="isGiftCard && shouldShowExchangeRate">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownGiftCardValue) }}</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex w-full flex-col gap-3">
|
||||
<div class="span-4 flex w-full flex-col gap-2.5 rounded-2xl bg-surface-2 p-4">
|
||||
<div class="span-4 flex w-full flex-col gap-2.5 rounded-[4px] bg-surface-2 p-4">
|
||||
<div
|
||||
class="flex w-full flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-0"
|
||||
>
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
}}</span>
|
||||
</label>
|
||||
|
||||
<div class="flex flex-col gap-2 rounded-2xl bg-surface-2 px-4 py-2.5">
|
||||
<div class="flex flex-col gap-2 rounded-[4px] bg-surface-2 px-4 py-2.5">
|
||||
<span>{{ paypalEmail }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-4 rounded-2xl border-[1px] border-solid border-blue bg-highlight-blue p-4"
|
||||
class="flex flex-col gap-4 rounded-[4px] border-[1px] border-solid border-blue bg-highlight-blue p-4"
|
||||
>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-col text-contrast">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="shadow-card rounded-2xl border border-surface-5 bg-surface-3 p-4">
|
||||
<div class="shadow-card rounded-[4px] border border-surface-5 bg-surface-3 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar
|
||||
:src="queueEntry.project.icon_url"
|
||||
size="4rem"
|
||||
class="rounded-2xl border border-surface-5 bg-surface-4 !shadow-none"
|
||||
class="rounded-[4px] border border-surface-5 bg-surface-4 !shadow-none"
|
||||
/>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="overflow-hidden rounded-2xl">
|
||||
<div class="overflow-hidden rounded-[4px]">
|
||||
<div class="bg-bg-raised p-4">
|
||||
<div
|
||||
class="flex w-full flex-col items-start justify-between gap-3 sm:flex-row sm:items-center sm:gap-0"
|
||||
@@ -65,7 +65,7 @@
|
||||
size="4rem"
|
||||
:class="[
|
||||
'flex-shrink-0 border border-surface-5 bg-surface-4 !shadow-none',
|
||||
report.item_type !== 'user' && 'rounded-2xl',
|
||||
report.item_type !== 'user' && 'rounded-[4px]',
|
||||
]"
|
||||
/>
|
||||
|
||||
|
||||
@@ -904,13 +904,13 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="shadow-card overflow-hidden rounded-2xl border border-surface-5 bg-surface-3">
|
||||
<div class="shadow-card overflow-hidden rounded-[4px] border border-surface-5 bg-surface-3">
|
||||
<div class="flex flex-col gap-4 bg-surface-3 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar
|
||||
:src="item.project.icon_url"
|
||||
class="rounded-2xl border border-surface-5 bg-surface-4 !shadow-none"
|
||||
class="rounded-[4px] border border-surface-5 bg-surface-4 !shadow-none"
|
||||
size="4rem"
|
||||
/>
|
||||
|
||||
@@ -1071,7 +1071,7 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
class="flex items-center justify-between border-0 border-x border-b border-solid border-surface-3 bg-surface-2 px-4 py-3"
|
||||
:class="{
|
||||
'rounded-bl-2xl rounded-br-2xl': idx === allFiles.length - 1,
|
||||
'bg-[#E8E8E8] dark:bg-[#1A1C20]': idx % 2 === 1,
|
||||
'bg-[#E8E8E8]': idx % 2 === 1,
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<KeybindsModal ref="keybindsModal" />
|
||||
<div
|
||||
tabindex="0"
|
||||
class="moderation-checklist flex w-[600px] max-w-full flex-col rounded-2xl border-[1px] border-solid border-orange bg-bg-raised p-4 transition-all delay-200 duration-200 ease-in-out"
|
||||
class="moderation-checklist flex w-[600px] max-w-full flex-col rounded-[4px] border-[1px] border-solid border-orange bg-bg-raised p-4 transition-all delay-200 duration-200 ease-in-out"
|
||||
:class="{ '!w-fit': collapsed, locked: lockStatus?.locked && !lockStatus?.isOwnLock }"
|
||||
>
|
||||
<div class="flex grow-0 items-center gap-2">
|
||||
|
||||
@@ -1,39 +1,25 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-[1280px] p-4 !py-8 sm:py-32">
|
||||
<div class="my-8 flex items-center justify-between">
|
||||
<h2 class="m-0 mx-auto text-3xl font-extrabold sm:text-4xl">
|
||||
{{ formatMessage(messages.latestNews) }}
|
||||
</h2>
|
||||
<div class="mx-auto max-w-[1280px] border border-solid border-[#c3c3c3] bg-[#EFEFEF] p-4">
|
||||
<div>
|
||||
<h2 class="m-0 mb-2 text-lg font-bold">News feed</h2>
|
||||
</div>
|
||||
|
||||
<div v-if="latestArticles" class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
|
||||
<div
|
||||
v-for="(article, index) in latestArticles"
|
||||
:key="article.slug"
|
||||
:class="{ 'max-xl:hidden': index === 2 }"
|
||||
>
|
||||
<NewsArticleCard :article="article" />
|
||||
<div v-if="latestArticles" class="flex flex-col gap-2">
|
||||
<div v-for="article in latestArticles" :key="article.slug">
|
||||
<NewsArticleListItem :article="article" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-2 my-8 flex w-full items-center justify-center">
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<nuxt-link to="/news">
|
||||
<NewspaperIcon />
|
||||
{{ formatMessage(messages.viewAll) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<div class="mt-4">
|
||||
<nuxt-link to="/news" class="text-link"> Read more articles ►</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NewspaperIcon } from '@modrinth/assets'
|
||||
import { articles as rawArticles } from '@modrinth/blog'
|
||||
import { ButtonStyled, defineMessages, NewsArticleCard, useVIntl } from '@modrinth/ui'
|
||||
import { NewsArticleListItem } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const articles = ref(
|
||||
rawArticles
|
||||
.map((article) => ({
|
||||
@@ -51,16 +37,5 @@ const articles = ref(
|
||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()),
|
||||
)
|
||||
|
||||
const messages = defineMessages({
|
||||
latestNews: {
|
||||
id: 'ui.latest-news-row.latest-news',
|
||||
defaultMessage: 'Latest news from Modrinth',
|
||||
},
|
||||
viewAll: {
|
||||
id: 'ui.latest-news-row.view-all',
|
||||
defaultMessage: 'View all news',
|
||||
},
|
||||
})
|
||||
|
||||
const latestArticles = computed(() => articles.value.slice(0, 3))
|
||||
const latestArticles = computed(() => articles.value.slice(0, 10))
|
||||
</script>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<div
|
||||
v-if="content"
|
||||
class="flex justify-between gap-4 rounded-2xl border border-solid border-surface-5 p-4"
|
||||
class="flex justify-between gap-4 rounded-[4px] border border-solid border-surface-5 p-4"
|
||||
>
|
||||
<!-- kind = vanilla -->
|
||||
<div
|
||||
@@ -95,7 +95,7 @@
|
||||
>
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<div class="font-medium text-secondary">Required modpack</div>
|
||||
<div class="w-fullitems-center flex gap-3 rounded-2xl bg-surface-1 p-3">
|
||||
<div class="w-fullitems-center flex gap-3 rounded-[4px] bg-surface-1 p-3">
|
||||
<Avatar
|
||||
v-if="!usingCustomMrpack"
|
||||
:src="modpackProject.icon_url"
|
||||
@@ -130,7 +130,7 @@
|
||||
v-if="modpackOrg?.icon_url"
|
||||
:src="modpackOrg.icon_url"
|
||||
size="24px"
|
||||
class="rounded-2xl"
|
||||
class="rounded-[4px]"
|
||||
/>
|
||||
{{ modpackOrg.name }}
|
||||
</NuxtLink>
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
@click="selectType(option.value)"
|
||||
>
|
||||
<div
|
||||
class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl border border-solid border-surface-5"
|
||||
class="flex h-14 w-14 shrink-0 items-center justify-center rounded-[4px] border border-solid border-surface-5"
|
||||
>
|
||||
<component :is="option.icon" class="h-9 w-9 text-secondary" />
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="font-semibold text-contrast">Select modpack</div>
|
||||
|
||||
<div class="flex flex-col gap-6 rounded-2xl border border-solid border-surface-5 p-4">
|
||||
<div class="flex flex-col gap-6 rounded-[4px] border border-solid border-surface-5 p-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-semibold text-contrast">Project</label>
|
||||
<ProjectCombobox
|
||||
@@ -32,7 +32,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedVersion" class="flex flex-col gap-4 rounded-2xl bg-surface-2 p-4">
|
||||
<div v-if="selectedVersion" class="flex flex-col gap-4 rounded-[4px] bg-surface-2 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-secondary">Game version</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2">
|
||||
<div class="flex w-full flex-col gap-1 rounded-[4px] bg-table-alternateRow p-2">
|
||||
<div
|
||||
v-for="loader in vanillaLoaders"
|
||||
:key="loader.name"
|
||||
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
||||
class="group relative flex items-center justify-between rounded-[4px] p-2 pr-2.5 hover:bg-bg"
|
||||
>
|
||||
<LoaderSelectorCard
|
||||
:loader="loader"
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<h2 class="mb-2 px-2 text-lg font-bold text-contrast">Mod loaders</h2>
|
||||
<div class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2">
|
||||
<div class="flex w-full flex-col gap-1 rounded-[4px] bg-table-alternateRow p-2">
|
||||
<div
|
||||
v-for="loader in modLoaders"
|
||||
:key="loader.name"
|
||||
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
||||
class="group relative flex items-center justify-between rounded-[4px] p-2 pr-2.5 hover:bg-bg"
|
||||
>
|
||||
<LoaderSelectorCard
|
||||
:loader="loader"
|
||||
@@ -38,11 +38,11 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<h2 class="mb-2 px-2 text-lg font-bold text-contrast">Plugin loaders</h2>
|
||||
<div class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2">
|
||||
<div class="flex w-full flex-col gap-1 rounded-[4px] bg-table-alternateRow p-2">
|
||||
<div
|
||||
v-for="loader in pluginLoaders"
|
||||
:key="loader.name"
|
||||
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
||||
class="group relative flex items-center justify-between rounded-[4px] p-2 pr-2.5 hover:bg-bg"
|
||||
>
|
||||
<LoaderSelectorCard
|
||||
:loader="loader"
|
||||
|
||||
@@ -1156,18 +1156,6 @@ onUnmounted(() => {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
html.light-mode .console {
|
||||
--console-bg: var(--color-bg);
|
||||
}
|
||||
|
||||
html.dark-mode .console {
|
||||
--console-bg: black;
|
||||
}
|
||||
|
||||
html.oled-mode .console {
|
||||
--console-bg: black;
|
||||
}
|
||||
|
||||
.console {
|
||||
background: var(--console-bg);
|
||||
}
|
||||
@@ -1221,14 +1209,6 @@ html.oled-mode .console {
|
||||
);
|
||||
}
|
||||
|
||||
html.dark-mode .progressive-gradient {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
color-mix(in srgb, black, transparent var(--transparency)) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.scroll-to-bottom-enter-active,
|
||||
.scroll-to-bottom-leave-active {
|
||||
transition:
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
class="w-full max-w-full"
|
||||
/>
|
||||
|
||||
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||
<div class="flex w-full flex-col gap-2 rounded-[4px] bg-table-alternateRow p-4">
|
||||
<div class="flex w-full flex-row items-center justify-between">
|
||||
<label class="w-full text-lg font-bold text-contrast" for="modpack-hard-reset">
|
||||
Erase all data
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div v-if="!isSecondPhase" class="flex flex-col gap-4">
|
||||
<div class="mx-auto flex flex-row items-center gap-4">
|
||||
<div
|
||||
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-button-bg shadow-sm"
|
||||
class="grid size-16 place-content-center rounded-[4px] border-[2px] border-solid border-button-border bg-button-bg shadow-sm"
|
||||
>
|
||||
<LoaderIcon class="size-10" :loader="selectedLoader" />
|
||||
</div>
|
||||
@@ -46,13 +46,13 @@
|
||||
<path d="M9 9h3V5l7 7-7 7v-4H9V9z" />
|
||||
</svg>
|
||||
<div
|
||||
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-table-alternateRow shadow-sm"
|
||||
class="grid size-16 place-content-center rounded-[4px] border-[2px] border-solid border-button-border bg-table-alternateRow shadow-sm"
|
||||
>
|
||||
<ServerIcon class="size-10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||
<div class="flex w-full flex-col gap-2 rounded-[4px] bg-table-alternateRow p-4">
|
||||
<div class="text-lg font-bold text-contrast">Minecraft version</div>
|
||||
<Combobox
|
||||
v-model="selectedMCVersion"
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
<div
|
||||
v-if="selectedLoader.toLowerCase() !== 'vanilla'"
|
||||
class="flex w-full flex-col gap-2 rounded-2xl p-4"
|
||||
class="flex w-full flex-col gap-2 rounded-[4px] p-4"
|
||||
:class="{
|
||||
'bg-table-alternateRow':
|
||||
!selectedMCVersion || isLoading || selectedLoaderVersions.length > 0,
|
||||
@@ -137,7 +137,7 @@
|
||||
|
||||
<div
|
||||
v-if="!initialSetup"
|
||||
class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4"
|
||||
class="flex w-full flex-col gap-2 rounded-[4px] bg-table-alternateRow p-4"
|
||||
>
|
||||
<div class="flex w-full flex-row items-center justify-between">
|
||||
<label class="w-full text-lg font-bold text-contrast" for="hard-reset">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
data-pyro-save-banner
|
||||
class="fixed bottom-16 left-0 right-0 z-[10] mx-auto h-fit w-full max-w-4xl transition-all duration-300 sm:bottom-8"
|
||||
>
|
||||
<div class="mx-2 rounded-2xl border-2 border-solid border-button-border bg-bg-raised p-4">
|
||||
<div class="mx-2 rounded-[4px] border-2 border-solid border-button-border bg-bg-raised p-4">
|
||||
<div class="flex flex-col items-center justify-between gap-2 md:flex-row">
|
||||
<span class="font-bold text-contrast">Careful, you have unsaved changes!</span>
|
||||
<div class="flex gap-2">
|
||||
|
||||
@@ -1,26 +1,11 @@
|
||||
<template>
|
||||
<div class="static w-full grid-cols-1 md:relative md:flex">
|
||||
<div class="static h-full flex-col pb-4 md:flex md:pb-0 md:pr-4">
|
||||
<div class="z-10 flex select-none flex-col gap-2 rounded-2xl bg-bg-raised p-4 md:w-[16rem]">
|
||||
<div
|
||||
v-for="link in navLinks.filter((x) => x.shown === undefined || x.shown)"
|
||||
:key="link.label"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="link.href"
|
||||
class="flex items-center gap-2 rounded-xl p-2 hover:bg-button-bg"
|
||||
:class="{ 'bg-button-bg text-contrast': route.path === link.href }"
|
||||
>
|
||||
<div class="flex items-center gap-2 font-bold">
|
||||
<component :is="link.icon" class="size-6" />
|
||||
{{ link.label }}
|
||||
</div>
|
||||
|
||||
<div class="flex-grow" />
|
||||
<RightArrowIcon v-if="link.external" class="size-4" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<NavStack
|
||||
class="z-10 select-none md:w-[16rem]"
|
||||
aria-label="Server options"
|
||||
:items="navStackItems"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="h-full w-full">
|
||||
@@ -30,12 +15,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RightArrowIcon } from '@modrinth/assets'
|
||||
import type { Component } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
|
||||
import NavStack, { type NavStackEntry } from '~/components/ui/NavStack.vue'
|
||||
|
||||
const emit = defineEmits(['reinstall'])
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
navLinks: {
|
||||
label: string
|
||||
href: string
|
||||
@@ -46,6 +34,16 @@ defineProps<{
|
||||
route: RouteLocationNormalized
|
||||
}>()
|
||||
|
||||
const navStackItems = computed<NavStackEntry[]>(() =>
|
||||
props.navLinks.map((link) => ({
|
||||
label: link.label,
|
||||
link: link.href,
|
||||
icon: link.icon,
|
||||
chevron: link.external,
|
||||
shown: link.shown,
|
||||
})),
|
||||
)
|
||||
|
||||
const onReinstall = (...args: any[]) => {
|
||||
emit('reinstall', ...args)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div
|
||||
v-for="(metric, index) in metrics"
|
||||
:key="index"
|
||||
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8"
|
||||
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-[4px] bg-bg-raised p-8"
|
||||
>
|
||||
<div class="relative z-10 -ml-3 w-fit rounded-xl px-3 py-1">
|
||||
<div class="relative z-10">
|
||||
@@ -54,7 +54,7 @@
|
||||
</div>
|
||||
<nuxt-link
|
||||
:to="loading ? undefined : `/hosting/manage/${serverId}/files`"
|
||||
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8"
|
||||
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-[4px] bg-bg-raised p-8"
|
||||
:class="loading ? '' : 'transition-transform duration-100 hover:scale-105 active:scale-100'"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
v-if="isOpen"
|
||||
ref="menuRef"
|
||||
data-pyro-telepopover-root
|
||||
class="experimental-styles-within fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-hidden rounded-2xl border-[1px] border-solid border-surface-5 bg-bg-raised p-2 shadow-lg"
|
||||
class="experimental-styles-within fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-hidden rounded-[4px] border-[1px] border-solid border-surface-5 bg-bg-raised p-2 shadow-lg"
|
||||
:style="menuStyle"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="medal-promotion relative flex w-full flex-row items-center justify-between rounded-2xl p-4 shadow-xl"
|
||||
class="medal-promotion relative flex w-full flex-row items-center justify-between rounded-[4px] p-4 shadow-xl"
|
||||
>
|
||||
<MedalBackgroundImage />
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ const billingMonths = computed(() => {
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
class="flex w-full flex-col justify-between gap-4 rounded-2xl bg-bg p-8 text-left"
|
||||
class="flex w-full flex-col justify-between gap-4 rounded-[4px] bg-bg p-8 text-left"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row flex-wrap items-center gap-3">
|
||||
|
||||
@@ -28,7 +28,7 @@ defineProps<{
|
||||
}>()
|
||||
</script>
|
||||
<template>
|
||||
<div class="col-span-full grid grid-cols-subgrid gap-4 rounded-2xl bg-bg-raised p-4">
|
||||
<div class="col-span-full grid grid-cols-subgrid gap-4 rounded-[4px] bg-bg-raised p-4">
|
||||
<div class="col-span-full grid grid-cols-subgrid items-center gap-4">
|
||||
<div>
|
||||
<CopyCode :text="`${notice.id}`" />
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
Reject
|
||||
</button>
|
||||
<OverflowMenu
|
||||
class="btn btn-danger btn-dropdown-animation icon-only"
|
||||
class="btn btn-danger icon-only"
|
||||
:options="
|
||||
replyBody
|
||||
? [
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { Cosmetics } from '~/plugins/cosmetics.ts'
|
||||
|
||||
export function useTheme() {
|
||||
return useNuxtApp().$theme
|
||||
}
|
||||
|
||||
export function useCosmetics() {
|
||||
return useNuxtApp().$cosmetics as Ref<Cosmetics>
|
||||
}
|
||||
|
||||
@@ -54,255 +54,39 @@
|
||||
:api-url="config.public.apiBaseUrl"
|
||||
/>
|
||||
<header
|
||||
class="experimental-styles-within desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
|
||||
class="experimental-styles-within desktop-only relative z-[5] mx-auto flex w-[1200px] max-w-[1200px] items-center pr-4"
|
||||
>
|
||||
<div>
|
||||
<NuxtLink
|
||||
to="/"
|
||||
:aria-label="formatMessage(messages.modrinthHomePage)"
|
||||
class="group hover:brightness-[--hover-brightness] focus-visible:brightness-[--hover-brightness]"
|
||||
>
|
||||
<TextLogo
|
||||
aria-hidden="true"
|
||||
class="h-7 w-auto text-contrast transition-transform group-active:scale-[0.98]"
|
||||
/>
|
||||
<div class="px-4 py-2">
|
||||
<NuxtLink to="/" :aria-label="formatMessage(messages.modrinthHomePage)" class="flex h-12">
|
||||
<img src="/modrinth-vista-dark.png" alt="Modrinth logo" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div
|
||||
class="col-span-2 row-start-2 flex justify-center lg:col-span-1 lg:row-start-auto"
|
||||
:class="{ 'gap-4': !flags.projectTypesPrimaryNav }"
|
||||
<NuxtLink
|
||||
to="/discover/mods"
|
||||
class="px-2 py-2"
|
||||
:class="route.name.startsWith('discover-') ? 'text-black hover:underline' : 'text-link'"
|
||||
>
|
||||
<template v-if="flags.projectTypesPrimaryNav">
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="route.name === 'discover-mods' || route.path.startsWith('/mod/')"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-mods' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/mods">
|
||||
<BoxIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.mod) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="
|
||||
route.name === 'discover-resourcepacks' || route.path.startsWith('/resourcepack/')
|
||||
"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-resourcepacks' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/resourcepacks">
|
||||
<PaintbrushIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.resourcepack) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="
|
||||
route.name === 'discover-datapacks' || route.path.startsWith('/datapack/')
|
||||
"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-datapacks' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/datapacks">
|
||||
<BracesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.datapack) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="route.name === 'discover-modpacks' || route.path.startsWith('/modpack/')"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-modpacks' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/modpacks">
|
||||
<PackageOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.modpack) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="route.name === 'discover-shaders' || route.path.startsWith('/shader/')"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-shaders' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/shaders">
|
||||
<GlassesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.shader) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="route.name === 'discover-plugins' || route.path.startsWith('/plugin/')"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-plugins' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/plugins">
|
||||
<PlugIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.plugin) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="route.name === 'discover-servers' || route.path.startsWith('/server/')"
|
||||
:highlighted-style="
|
||||
route.name === 'discover-servers' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/discover/servers">
|
||||
<ServerIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.server) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="isDiscovering || isDiscoveringSubpage"
|
||||
:highlighted-style="isDiscoveringSubpage ? 'main-nav-secondary' : 'main-nav-primary'"
|
||||
>
|
||||
<TeleportOverflowMenu
|
||||
:options="[
|
||||
{
|
||||
id: 'mods',
|
||||
action: '/discover/mods',
|
||||
},
|
||||
{
|
||||
id: 'resourcepacks',
|
||||
action: '/discover/resourcepacks',
|
||||
},
|
||||
{
|
||||
id: 'datapacks',
|
||||
action: '/discover/datapacks',
|
||||
},
|
||||
{
|
||||
id: 'shaders',
|
||||
action: '/discover/shaders',
|
||||
},
|
||||
{
|
||||
id: 'modpacks',
|
||||
action: '/discover/modpacks',
|
||||
},
|
||||
{
|
||||
id: 'plugins',
|
||||
action: '/discover/plugins',
|
||||
},
|
||||
{
|
||||
id: 'servers',
|
||||
action: '/discover/servers',
|
||||
},
|
||||
]"
|
||||
hoverable
|
||||
>
|
||||
<BoxIcon
|
||||
v-if="route.name === 'discover-mods' || route.path.startsWith('/mod/')"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<PaintbrushIcon
|
||||
v-else-if="
|
||||
route.name === 'discover-resourcepacks' || route.path.startsWith('/resourcepack/')
|
||||
"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<BracesIcon
|
||||
v-else-if="
|
||||
route.name === 'discover-datapacks' || route.path.startsWith('/datapack/')
|
||||
"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<PackageOpenIcon
|
||||
v-else-if="route.name === 'discover-modpacks' || route.path.startsWith('/modpack/')"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<GlassesIcon
|
||||
v-else-if="route.name === 'discover-shaders' || route.path.startsWith('/shader/')"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<PlugIcon
|
||||
v-else-if="route.name === 'discover-plugins' || route.path.startsWith('/plugin/')"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ServerIcon
|
||||
v-else-if="route.name === 'discover-servers' || route.path.startsWith('/server/')"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CompassIcon v-else aria-hidden="true" />
|
||||
<span class="hidden md:contents">{{
|
||||
formatMessage(navMenuMessages.discoverContent)
|
||||
}}</span>
|
||||
<span class="contents md:hidden">{{ formatMessage(navMenuMessages.discover) }}</span>
|
||||
<DropdownIcon aria-hidden="true" class="h-5 w-5" />
|
||||
|
||||
<template #mods>
|
||||
<BoxIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.mod) }}
|
||||
</template>
|
||||
<template #resourcepacks>
|
||||
<PaintbrushIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.resourcepack) }}
|
||||
</template>
|
||||
<template #datapacks>
|
||||
<BracesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.datapack) }}
|
||||
</template>
|
||||
<template #plugins>
|
||||
<PlugIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.plugin) }}
|
||||
</template>
|
||||
<template #shaders>
|
||||
<GlassesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.shader) }}
|
||||
</template>
|
||||
<template #modpacks>
|
||||
<PackageOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.modpack) }}
|
||||
</template>
|
||||
<template #servers>
|
||||
<ServerIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.server) }}
|
||||
</template>
|
||||
</TeleportOverflowMenu>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
type="transparent"
|
||||
:highlighted="
|
||||
route.name?.startsWith('hosting') ||
|
||||
(route.name?.startsWith('discover-') && !!route.query.sid)
|
||||
"
|
||||
:highlighted-style="
|
||||
route.name === 'hosting' ? 'main-nav-primary' : 'main-nav-secondary'
|
||||
"
|
||||
>
|
||||
<nuxt-link to="/hosting">
|
||||
<ServerIcon aria-hidden="true" />
|
||||
{{ formatMessage(navMenuMessages.hostAServer) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled type="transparent" :highlighted="route.name === 'app'">
|
||||
<nuxt-link to="/app">
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
<span class="hidden md:contents">{{
|
||||
formatMessage(navMenuMessages.getModrinthApp)
|
||||
}}</span>
|
||||
<span class="contents md:hidden">{{
|
||||
formatMessage(navMenuMessages.modrinthApp)
|
||||
}}</span>
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
Browse content
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/hosting"
|
||||
class="px-2 py-2"
|
||||
:class="route.name.startsWith('hosting') ? 'text-black hover:underline' : 'text-link'"
|
||||
>
|
||||
Host a server
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/app"
|
||||
class="px-2 py-2"
|
||||
:class="route.name.startsWith('app') ? 'text-black hover:underline' : 'text-link'"
|
||||
>
|
||||
Download application
|
||||
</NuxtLink>
|
||||
<div class="ml-auto flex items-center gap-1">
|
||||
<ButtonStyled type="transparent">
|
||||
<OverflowMenu
|
||||
v-if="auth.user && isStaff(auth.user)"
|
||||
class="btn-dropdown-animation flex items-center gap-1 rounded-xl bg-transparent px-2 py-1"
|
||||
class="flex items-center gap-1 rounded-xl bg-transparent px-2 py-1"
|
||||
position="bottom"
|
||||
direction="left"
|
||||
:dropdown-id="`${basePopoutId}-staff`"
|
||||
@@ -367,7 +151,7 @@
|
||||
]"
|
||||
>
|
||||
<ModrinthIcon aria-hidden="true" />
|
||||
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
|
||||
▼
|
||||
<template #review-projects>
|
||||
<ScaleIcon aria-hidden="true" /> {{ formatMessage(messages.reviewProjects) }}
|
||||
</template>
|
||||
@@ -400,7 +184,7 @@
|
||||
<ButtonStyled type="transparent">
|
||||
<OverflowMenu
|
||||
v-if="auth.user"
|
||||
class="btn-dropdown-animation flex items-center gap-1 rounded-xl bg-transparent px-2 py-1"
|
||||
class="flex items-center gap-1 rounded-xl bg-transparent px-2 py-1"
|
||||
position="bottom"
|
||||
direction="left"
|
||||
:dropdown-id="`${basePopoutId}-create`"
|
||||
@@ -408,11 +192,11 @@
|
||||
:options="[
|
||||
{
|
||||
id: 'new-project',
|
||||
action: (event) => $refs.modal_creation.show(event),
|
||||
action: (event) => openProjectCreateModal(event),
|
||||
},
|
||||
{
|
||||
id: 'new-server-project',
|
||||
action: (event) => $refs.modal_creation.show(event, { type: 'server' }),
|
||||
action: (event) => openProjectCreateModal(event, { type: 'server' }),
|
||||
},
|
||||
{
|
||||
id: 'new-collection',
|
||||
@@ -435,7 +219,7 @@
|
||||
</template>
|
||||
<!-- <template #import-project> <BoxImportIcon /> Import project </template>-->
|
||||
<template #new-collection>
|
||||
<CollectionIcon aria-hidden="true" /> {{ formatMessage(messages.newCollection) }}
|
||||
<LibraryIcon aria-hidden="true" /> {{ formatMessage(messages.newCollection) }}
|
||||
</template>
|
||||
<template #new-organization>
|
||||
<OrganizationIcon aria-hidden="true" /> {{ formatMessage(messages.newOrganization) }}
|
||||
@@ -445,7 +229,7 @@
|
||||
<OverflowMenu
|
||||
v-if="auth.user"
|
||||
:dropdown-id="`${basePopoutId}-user`"
|
||||
class="btn-dropdown-animation flex items-center gap-1 rounded-xl bg-transparent px-2 py-1"
|
||||
class="flex items-center gap-1 rounded-xl bg-transparent px-2 py-1"
|
||||
:options="userMenuOptions"
|
||||
>
|
||||
<Avatar :src="auth.user.avatar_url" aria-hidden="true" circle />
|
||||
@@ -483,17 +267,20 @@
|
||||
<OrganizationIcon aria-hidden="true" /> {{ formatMessage(messages.organizations) }}
|
||||
</template>
|
||||
<template #affiliate-links>
|
||||
<AffiliateIcon aria-hidden="true" />
|
||||
<svg></svg>
|
||||
{{ formatMessage(commonMessages.affiliateLinksButton) }}
|
||||
</template>
|
||||
<template #revenue>
|
||||
<CurrencyIcon aria-hidden="true" /> {{ formatMessage(messages.revenue) }}
|
||||
<svg></svg>
|
||||
{{ formatMessage(messages.revenue) }}
|
||||
</template>
|
||||
<template #analytics>
|
||||
<ChartIcon aria-hidden="true" /> {{ formatMessage(commonMessages.analyticsButton) }}
|
||||
<svg></svg>
|
||||
{{ formatMessage(commonMessages.analyticsButton) }}
|
||||
</template>
|
||||
<template #moderation>
|
||||
<ScaleIcon aria-hidden="true" /> {{ formatMessage(commonMessages.moderationLabel) }}
|
||||
<svg></svg>
|
||||
{{ formatMessage(commonMessages.moderationLabel) }}
|
||||
</template>
|
||||
<template #sign-out>
|
||||
<LogOutIcon aria-hidden="true" /> {{ formatMessage(commonMessages.signOutButton) }}
|
||||
@@ -506,180 +293,9 @@
|
||||
{{ formatMessage(commonMessages.signInButton) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<nuxt-link :v-tooltip="formatMessage(commonMessages.settingsLabel)" to="/settings">
|
||||
<SettingsIcon :aria-label="formatMessage(commonMessages.settingsLabel)" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
<header class="mobile-navigation mobile-only">
|
||||
<div
|
||||
class="nav-menu nav-menu-browse"
|
||||
:class="{ expanded: isBrowseMenuOpen }"
|
||||
@focusin="isBrowseMenuOpen = true"
|
||||
@focusout="isBrowseMenuOpen = false"
|
||||
>
|
||||
<div class="links cascade-links">
|
||||
<NuxtLink
|
||||
v-for="navRoute in navRoutes"
|
||||
:key="navRoute.href"
|
||||
:to="navRoute.href"
|
||||
class="iconified-button"
|
||||
>
|
||||
{{ navRoute.label }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="nav-menu nav-menu-mobile"
|
||||
:class="{ expanded: isMobileMenuOpen }"
|
||||
@focusin="isMobileMenuOpen = true"
|
||||
@focusout="isMobileMenuOpen = false"
|
||||
>
|
||||
<div class="account-container">
|
||||
<NuxtLink
|
||||
v-if="auth.user"
|
||||
:to="`/user/${auth.user.username}`"
|
||||
class="iconified-button account-button"
|
||||
>
|
||||
<Avatar
|
||||
:src="auth.user.avatar_url"
|
||||
class="user-icon"
|
||||
:alt="formatMessage(messages.yourAvatarAlt)"
|
||||
aria-hidden="true"
|
||||
circle
|
||||
/>
|
||||
<div class="account-text">
|
||||
<div>@{{ auth.user.username }}</div>
|
||||
<div>{{ formatMessage(commonMessages.visitYourProfile) }}</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<nuxt-link v-else class="iconified-button brand-button" to="/auth/sign-in">
|
||||
<LogInIcon aria-hidden="true" /> {{ formatMessage(commonMessages.signInButton) }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<div class="links">
|
||||
<template v-if="auth.user">
|
||||
<button class="iconified-button danger-button" @click="logoutUser()">
|
||||
<LogOutIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.signOutButton) }}
|
||||
</button>
|
||||
<button class="iconified-button" @click="$refs.modal_creation.show()">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.createAProjectButton) }}
|
||||
</button>
|
||||
<NuxtLink class="iconified-button" to="/dashboard/collections">
|
||||
<LibraryIcon class="icon" />
|
||||
{{ formatMessage(commonMessages.collectionsLabel) }}
|
||||
</NuxtLink>
|
||||
<NuxtLink class="iconified-button" to="/hosting/manage">
|
||||
<ServerIcon class="icon" />
|
||||
{{ formatMessage(commonMessages.serversLabel) }}
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="auth.user.role === 'moderator' || auth.user.role === 'admin'"
|
||||
class="iconified-button"
|
||||
to="/moderation"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.moderationLabel) }}
|
||||
</NuxtLink>
|
||||
<NuxtLink v-if="flags.developerMode" class="iconified-button" to="/settings/flags">
|
||||
<ToggleRightIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonSettingsMessages.featureFlags) }}
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<NuxtLink class="iconified-button" to="/settings">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.settingsLabel) }}
|
||||
</NuxtLink>
|
||||
<button class="iconified-button" @click="changeTheme">
|
||||
<MoonIcon v-if="$theme.active === 'light'" class="icon" />
|
||||
<SunIcon v-else class="icon" />
|
||||
<span class="dropdown-item__text">
|
||||
{{ formatMessage(messages.changeTheme) }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-navbar" :class="{ expanded: isBrowseMenuOpen || isMobileMenuOpen }">
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="tab button-animation"
|
||||
:title="formatMessage(navMenuMessages.home)"
|
||||
:aria-label="formatMessage(navMenuMessages.home)"
|
||||
>
|
||||
<HomeIcon aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
<button
|
||||
class="tab button-animation"
|
||||
:class="{ 'router-link-exact-active': isBrowseMenuOpen }"
|
||||
:title="formatMessage(navMenuMessages.search)"
|
||||
:aria-label="formatMessage(navMenuMessages.search)"
|
||||
@click="toggleBrowseMenu()"
|
||||
>
|
||||
<template v-if="auth.user">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<SearchIcon aria-hidden="true" class="smaller" />
|
||||
{{ formatMessage(navMenuMessages.search) }}
|
||||
</template>
|
||||
</button>
|
||||
<template v-if="auth.user">
|
||||
<NuxtLink
|
||||
to="/dashboard/notifications"
|
||||
class="tab button-animation"
|
||||
:aria-label="formatMessage(commonMessages.notificationsLabel)"
|
||||
:class="{
|
||||
'no-active': isMobileMenuOpen || isBrowseMenuOpen,
|
||||
}"
|
||||
:title="formatMessage(commonMessages.notificationsLabel)"
|
||||
@click="
|
||||
() => {
|
||||
isMobileMenuOpen = false
|
||||
isBrowseMenuOpen = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<BellIcon aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/dashboard"
|
||||
class="tab button-animation"
|
||||
:aria-label="formatMessage(commonMessages.dashboardLabel)"
|
||||
:title="formatMessage(commonMessages.dashboardLabel)"
|
||||
>
|
||||
<ChartIcon aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<button
|
||||
class="tab button-animation"
|
||||
:title="formatMessage(messages.toggleMenu)"
|
||||
:aria-label="
|
||||
isMobileMenuOpen ? formatMessage(messages.closeMenu) : formatMessage(messages.openMenu)
|
||||
"
|
||||
@click="toggleMobileMenu()"
|
||||
>
|
||||
<template v-if="!auth.user">
|
||||
<HamburgerIcon v-if="!isMobileMenuOpen" aria-hidden="true" />
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<Avatar
|
||||
:src="auth.user.avatar_url"
|
||||
class="user-icon"
|
||||
:class="{ expanded: isMobileMenuOpen }"
|
||||
:alt="formatMessage(messages.yourAvatarAlt)"
|
||||
aria-hidden="true"
|
||||
circle
|
||||
/>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main class="min-h-[calc(100vh-4.5rem-310.59px)]">
|
||||
<ProjectCreateModal v-if="auth.user" ref="modal_creation" />
|
||||
<CollectionCreateModal ref="modal_collection_creation" />
|
||||
@@ -696,46 +312,29 @@ import {
|
||||
ArrowBigUpDashIcon,
|
||||
BellIcon,
|
||||
BoxIcon,
|
||||
BracesIcon,
|
||||
ChartIcon,
|
||||
CollectionIcon,
|
||||
CompassIcon,
|
||||
CurrencyIcon,
|
||||
DownloadIcon,
|
||||
DropdownIcon,
|
||||
FileIcon,
|
||||
GlassesIcon,
|
||||
HamburgerIcon,
|
||||
HomeIcon,
|
||||
IssuesIcon,
|
||||
LibraryIcon,
|
||||
LogInIcon,
|
||||
LogOutIcon,
|
||||
ModrinthIcon,
|
||||
MoonIcon,
|
||||
OrganizationIcon,
|
||||
PackageOpenIcon,
|
||||
PaintbrushIcon,
|
||||
PlugIcon,
|
||||
PlusIcon,
|
||||
ReportIcon,
|
||||
ScaleIcon,
|
||||
SearchIcon,
|
||||
ServerIcon,
|
||||
SettingsIcon,
|
||||
ShieldAlertIcon,
|
||||
SunIcon,
|
||||
ToggleRightIcon,
|
||||
TransferIcon,
|
||||
UserIcon,
|
||||
UserSearchIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
commonProjectTypeCategoryMessages,
|
||||
commonSettingsMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
@@ -744,9 +343,9 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { provide, useTemplateRef } from 'vue'
|
||||
|
||||
import { getTaxThreshold } from '@/providers/creator-withdraw.ts'
|
||||
import TextLogo from '~/components/brand/TextLogo.vue'
|
||||
import BatchCreditModal from '~/components/ui/admin/BatchCreditModal.vue'
|
||||
import GeneratedStateErrorsBanner from '~/components/ui/banner/GeneratedStateErrorsBanner.vue'
|
||||
import PreviewBanner from '~/components/ui/banner/PreviewBanner.vue'
|
||||
@@ -760,9 +359,7 @@ import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.
|
||||
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
||||
import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
|
||||
import ModrinthFooter from '~/components/ui/ModrinthFooter.vue'
|
||||
import TeleportOverflowMenu from '~/components/ui/servers/TeleportOverflowMenu.vue'
|
||||
import { errors as generatedStateErrors } from '~/generated/state.json'
|
||||
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
||||
|
||||
const generatedState = useGeneratedState()
|
||||
|
||||
@@ -809,36 +406,13 @@ const showTinMismatchBanner = computed(() => {
|
||||
|
||||
const basePopoutId = useId()
|
||||
|
||||
const navMenuMessages = defineMessages({
|
||||
home: {
|
||||
id: 'layout.nav.home',
|
||||
defaultMessage: 'Home',
|
||||
},
|
||||
search: {
|
||||
id: 'layout.nav.search',
|
||||
defaultMessage: 'Search',
|
||||
},
|
||||
discoverContent: {
|
||||
id: 'layout.nav.discover-content',
|
||||
defaultMessage: 'Discover content',
|
||||
},
|
||||
discover: {
|
||||
id: 'layout.nav.discover',
|
||||
defaultMessage: 'Discover',
|
||||
},
|
||||
hostAServer: {
|
||||
id: 'layout.nav.host-a-server',
|
||||
defaultMessage: 'Host a server',
|
||||
},
|
||||
getModrinthApp: {
|
||||
id: 'layout.nav.get-modrinth-app',
|
||||
defaultMessage: 'Get Modrinth App',
|
||||
},
|
||||
modrinthApp: {
|
||||
id: 'layout.nav.modrinth-app',
|
||||
defaultMessage: 'Modrinth App',
|
||||
},
|
||||
})
|
||||
const modalCreationRef = useTemplateRef('modal_creation')
|
||||
|
||||
function openProjectCreateModal(event, options) {
|
||||
modalCreationRef.value?.show(event, options)
|
||||
}
|
||||
|
||||
provide('openProjectCreateModal', openProjectCreateModal)
|
||||
|
||||
const messages = defineMessages({
|
||||
toggleMenu: {
|
||||
@@ -849,10 +423,6 @@ const messages = defineMessages({
|
||||
id: 'layout.avatar.alt',
|
||||
defaultMessage: 'Your avatar',
|
||||
},
|
||||
changeTheme: {
|
||||
id: 'layout.action.change-theme',
|
||||
defaultMessage: 'Change theme',
|
||||
},
|
||||
modrinthHomePage: {
|
||||
id: 'layout.nav.modrinth-home-page',
|
||||
defaultMessage: 'Modrinth home page',
|
||||
@@ -970,7 +540,7 @@ useSeoMeta({
|
||||
}),
|
||||
publisher: 'Modrinth',
|
||||
themeColor: '#1bd96a',
|
||||
colorScheme: 'dark light',
|
||||
colorScheme: 'light',
|
||||
|
||||
// OpenGraph
|
||||
ogTitle: 'Modrinth',
|
||||
@@ -991,37 +561,6 @@ useSeoMeta({
|
||||
|
||||
const isMobileMenuOpen = ref(false)
|
||||
const isBrowseMenuOpen = ref(false)
|
||||
const navRoutes = computed(() => [
|
||||
{
|
||||
id: 'mods',
|
||||
label: formatMessage(getProjectTypeMessage('mod', true)),
|
||||
href: '/discover/mods',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('plugin', true)),
|
||||
href: '/discover/plugins',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('datapack', true)),
|
||||
href: '/discover/datapacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('shader', true)),
|
||||
href: '/discover/shaders',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('resourcepack', true)),
|
||||
href: '/discover/resourcepacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('modpack', true)),
|
||||
href: '/discover/modpacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('server', true)),
|
||||
href: '/discover/servers',
|
||||
},
|
||||
])
|
||||
|
||||
const userMenuOptions = computed(() => {
|
||||
const user = auth.value.user
|
||||
@@ -1112,14 +651,6 @@ const userMenuOptions = computed(() => {
|
||||
return options
|
||||
})
|
||||
|
||||
const isDiscovering = computed(
|
||||
() => route.name && route.name.startsWith('discover-') && !route.query.sid,
|
||||
)
|
||||
|
||||
const isDiscoveringSubpage = computed(
|
||||
() => route.name && route.name.startsWith('type-id') && !route.query.sid,
|
||||
)
|
||||
|
||||
const isRussia = computed(() => country.value === 'ru')
|
||||
|
||||
const rCount = ref(0)
|
||||
@@ -1217,30 +748,44 @@ function runAnalytics() {
|
||||
console.error(`Sending analytics failed (CORS error? If so, ignore)`, e)
|
||||
}
|
||||
}
|
||||
function toggleMobileMenu() {
|
||||
isMobileMenuOpen.value = !isMobileMenuOpen.value
|
||||
if (isMobileMenuOpen.value) {
|
||||
isBrowseMenuOpen.value = false
|
||||
}
|
||||
}
|
||||
function toggleBrowseMenu() {
|
||||
isBrowseMenuOpen.value = !isBrowseMenuOpen.value
|
||||
|
||||
if (isBrowseMenuOpen.value) {
|
||||
isMobileMenuOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const { cycle: changeTheme } = useTheme()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~/assets/styles/global.scss';
|
||||
// @import '@modrinth/assets';
|
||||
|
||||
body {
|
||||
background: #c4e8c9;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.header-background {
|
||||
background-color: var(--color-brand);
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
var(--color-green-300),
|
||||
var(--color-green-500),
|
||||
var(--color-green-600)
|
||||
);
|
||||
border-radius: 8px 8px 0 0;
|
||||
border: 1px solid var(--color-green-500);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.selected-header-item {
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.15));
|
||||
}
|
||||
|
||||
.layout {
|
||||
background: white;
|
||||
background-image: linear-gradient(to bottom, white, #ececec);
|
||||
max-width: 1200px;
|
||||
width: 1200px;
|
||||
min-wdith: 1200px;
|
||||
margin-inline: auto;
|
||||
min-height: 100vh;
|
||||
display: block;
|
||||
border-radius: 8px 8px 0 0;
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
min-height: calc(100vh - var(--spacing-card-bg));
|
||||
@@ -1271,218 +816,6 @@ const { cycle: changeTheme } = useTheme()
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-navigation {
|
||||
display: none;
|
||||
|
||||
.nav-menu {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: calc(var(--size-mobile-navbar-height) - var(--size-rounded-card));
|
||||
padding-bottom: var(--size-rounded-card);
|
||||
left: 0;
|
||||
background-color: var(--color-raised-bg);
|
||||
z-index: 11; // 20 = modals, 10 = svg icons
|
||||
transform: translateY(calc(100% + env(safe-area-inset-bottom)));
|
||||
transition: transform 0.4s cubic-bezier(0.54, 0.84, 0.42, 1);
|
||||
border-radius: var(--size-rounded-card) var(--size-rounded-card) 0 0;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0);
|
||||
|
||||
.links,
|
||||
.account-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
grid-gap: 1rem;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
|
||||
.iconified-button {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
padding: 0.75rem;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.cascade-links {
|
||||
@media screen and (min-width: 354px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 674px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
&-browse {
|
||||
&.expanded {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&-mobile {
|
||||
.account-container {
|
||||
padding-bottom: 0;
|
||||
|
||||
.account-button {
|
||||
padding: var(--spacing-card-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.user-icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
.account-text {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-navbar {
|
||||
display: flex;
|
||||
height: calc(var(--size-mobile-navbar-height) + env(safe-area-inset-bottom));
|
||||
border-radius: var(--size-rounded-card) var(--size-rounded-card) 0 0;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.3);
|
||||
z-index: 11; // 20 = modals, 10 = svg icons
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: border-radius 0.3s ease-out;
|
||||
border-top: 2px solid rgba(0, 0, 0, 0);
|
||||
box-sizing: border-box;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background-color: var(--color-raised-bg);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
background: none;
|
||||
display: flex;
|
||||
flex-basis: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 0.25rem;
|
||||
font-weight: bold;
|
||||
padding: 0;
|
||||
transition: color ease-in-out 0.15s;
|
||||
color: var(--color-text-inactive);
|
||||
text-align: center;
|
||||
|
||||
&.browse {
|
||||
svg {
|
||||
transform: rotate(180deg);
|
||||
transition: transform ease-in-out 0.3s;
|
||||
|
||||
&.closed {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble {
|
||||
&::after {
|
||||
background-color: var(--color-brand);
|
||||
border-radius: var(--size-rounded-max);
|
||||
content: '';
|
||||
height: 0.5rem;
|
||||
position: absolute;
|
||||
left: 1.5rem;
|
||||
top: 0;
|
||||
width: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
|
||||
&.smaller {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
transition: border ease-in-out 0.15s;
|
||||
border: 0 solid var(--color-brand);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.expanded {
|
||||
border: 2px solid var(--color-brand);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
&.router-link-exact-active:not(&.no-active) {
|
||||
svg {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (any-hover: none) and (max-width: 640px) {
|
||||
.desktop-only {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (any-hover: none) and (max-width: 640px) {
|
||||
.mobile-navigation {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.over-the-top-random-animation {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
|
||||
@@ -8,204 +8,6 @@
|
||||
"admin.billing.error.not-found": {
|
||||
"message": "User not found"
|
||||
},
|
||||
"app-marketing.download.description": {
|
||||
"message": "Our desktop app is available across all platforms, choose your desired version."
|
||||
},
|
||||
"app-marketing.download.download-appimage": {
|
||||
"message": "Download the AppImage"
|
||||
},
|
||||
"app-marketing.download.download-beta": {
|
||||
"message": "Download the beta"
|
||||
},
|
||||
"app-marketing.download.download-deb": {
|
||||
"message": "Download the DEB"
|
||||
},
|
||||
"app-marketing.download.download-rpm": {
|
||||
"message": "Download the RPM"
|
||||
},
|
||||
"app-marketing.download.flathub": {
|
||||
"message": "Get it on Flathub"
|
||||
},
|
||||
"app-marketing.download.linux": {
|
||||
"message": "Linux"
|
||||
},
|
||||
"app-marketing.download.linux-disclaimer": {
|
||||
"message": "The Linux versions of Modrinth App are <issues-link>known to have issues</issues-link> on certain systems and configurations. If Modrinth App is unstable on your system, we encourage you to try other apps like <prism-link>Prism Launcher</prism-link> to easily install Modrinth content."
|
||||
},
|
||||
"app-marketing.download.mac": {
|
||||
"message": "Mac"
|
||||
},
|
||||
"app-marketing.download.options-title": {
|
||||
"message": "Download options"
|
||||
},
|
||||
"app-marketing.download.terms": {
|
||||
"message": "By downloading Modrinth App, you agree to our <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
|
||||
},
|
||||
"app-marketing.download.title": {
|
||||
"message": "Download Modrinth App (Beta)"
|
||||
},
|
||||
"app-marketing.download.windows": {
|
||||
"message": "Windows"
|
||||
},
|
||||
"app-marketing.features.follow.description": {
|
||||
"message": "Save content you love and receive updates with one click."
|
||||
},
|
||||
"app-marketing.features.follow.title": {
|
||||
"message": "Follow projects"
|
||||
},
|
||||
"app-marketing.features.importing.description": {
|
||||
"message": "Import all your favorite profiles from the launcher you were using before, and get started with Modrinth App in seconds!"
|
||||
},
|
||||
"app-marketing.features.importing.gdlauncher-alt": {
|
||||
"message": "GDLauncher"
|
||||
},
|
||||
"app-marketing.features.importing.multimc-alt": {
|
||||
"message": "MultiMC"
|
||||
},
|
||||
"app-marketing.features.importing.title": {
|
||||
"message": "Profile importing"
|
||||
},
|
||||
"app-marketing.features.mod-management.actions": {
|
||||
"message": "Actions"
|
||||
},
|
||||
"app-marketing.features.mod-management.byAuthor": {
|
||||
"message": "by {author}"
|
||||
},
|
||||
"app-marketing.features.mod-management.description": {
|
||||
"message": "Modrinth makes it easy to manage all your mods in one place. You can install, uninstall, and update mods with a single click."
|
||||
},
|
||||
"app-marketing.features.mod-management.installed-mods": {
|
||||
"message": "Installed mods"
|
||||
},
|
||||
"app-marketing.features.mod-management.name": {
|
||||
"message": "Name"
|
||||
},
|
||||
"app-marketing.features.mod-management.search-mods": {
|
||||
"message": "Search mods"
|
||||
},
|
||||
"app-marketing.features.mod-management.title": {
|
||||
"message": "Mod management"
|
||||
},
|
||||
"app-marketing.features.mod-management.version": {
|
||||
"message": "Version"
|
||||
},
|
||||
"app-marketing.features.offline.description": {
|
||||
"message": "Play your mods, whether you are connected to the internet, or not."
|
||||
},
|
||||
"app-marketing.features.offline.title": {
|
||||
"message": "Offline mode"
|
||||
},
|
||||
"app-marketing.features.open-source.description": {
|
||||
"message": "Modrinth's launcher is fully open source. You can view the source code on our <github-link>GitHub</github-link>!"
|
||||
},
|
||||
"app-marketing.features.open-source.title": {
|
||||
"message": "Open source"
|
||||
},
|
||||
"app-marketing.features.performance.activity-monitor": {
|
||||
"message": "Activity monitor"
|
||||
},
|
||||
"app-marketing.features.performance.cpu-percent": {
|
||||
"message": "% CPU"
|
||||
},
|
||||
"app-marketing.features.performance.description": {
|
||||
"message": "Modrinth App performs better than many of the leading mod managers, using just 150 MB of RAM!"
|
||||
},
|
||||
"app-marketing.features.performance.discord": {
|
||||
"message": "Discord"
|
||||
},
|
||||
"app-marketing.features.performance.good-performance": {
|
||||
"message": "Good performance"
|
||||
},
|
||||
"app-marketing.features.performance.google-chrome": {
|
||||
"message": "Google Chrome"
|
||||
},
|
||||
"app-marketing.features.performance.infinite-mb": {
|
||||
"message": "∞ MB"
|
||||
},
|
||||
"app-marketing.features.performance.infinite-times-infinite-mb": {
|
||||
"message": "∞ * ∞ MB"
|
||||
},
|
||||
"app-marketing.features.performance.less-than-150mb": {
|
||||
"message": "< 150 MB"
|
||||
},
|
||||
"app-marketing.features.performance.modrinth-app": {
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"app-marketing.features.performance.one-billion-percent": {
|
||||
"message": "1 billion %"
|
||||
},
|
||||
"app-marketing.features.performance.process-name": {
|
||||
"message": "Process name"
|
||||
},
|
||||
"app-marketing.features.performance.ram": {
|
||||
"message": "RAM"
|
||||
},
|
||||
"app-marketing.features.performance.small": {
|
||||
"message": "Small"
|
||||
},
|
||||
"app-marketing.features.performance.title": {
|
||||
"message": "Performant"
|
||||
},
|
||||
"app-marketing.features.play.description": {
|
||||
"message": "Use Modrinth App to download and play with your favorite mods and modpacks."
|
||||
},
|
||||
"app-marketing.features.play.title": {
|
||||
"message": "Play with your favorite mods"
|
||||
},
|
||||
"app-marketing.features.sharing.description": {
|
||||
"message": "Build, share, and play modpacks with any of the thousands of mods and modpacks hosted here on Modrinth."
|
||||
},
|
||||
"app-marketing.features.sharing.modpack": {
|
||||
"message": "Modpack"
|
||||
},
|
||||
"app-marketing.features.sharing.share-button": {
|
||||
"message": "Share"
|
||||
},
|
||||
"app-marketing.features.sharing.title": {
|
||||
"message": "Share modpacks"
|
||||
},
|
||||
"app-marketing.features.unlike-any-launcher": {
|
||||
"message": "Unlike any launcher"
|
||||
},
|
||||
"app-marketing.features.website.description": {
|
||||
"message": "Modrinth App is fully integrated with the website, so you can access all your favorite projects from the app!"
|
||||
},
|
||||
"app-marketing.features.website.title": {
|
||||
"message": "Website integration"
|
||||
},
|
||||
"app-marketing.features.youve-used-before": {
|
||||
"message": "you've used before"
|
||||
},
|
||||
"app-marketing.hero.app-screenshot-alt": {
|
||||
"message": "Screenshot of Modrinth App with a Cobblemon instance opened to the 'Content' page."
|
||||
},
|
||||
"app-marketing.hero.description": {
|
||||
"message": "Modrinth App is a unique, open source launcher that allows you to play your favorite mods, and keep them up to date, all in one neat little package."
|
||||
},
|
||||
"app-marketing.hero.download-button": {
|
||||
"message": "Download Modrinth App"
|
||||
},
|
||||
"app-marketing.hero.download-modrinth-app": {
|
||||
"message": "Download Modrinth App"
|
||||
},
|
||||
"app-marketing.hero.download-modrinth-app-for-os": {
|
||||
"message": "Download Modrinth App for {os}"
|
||||
},
|
||||
"app-marketing.hero.minecraft-screenshot-alt": {
|
||||
"message": "Screenshot of the Cobblemon instance's main menu screen."
|
||||
},
|
||||
"app-marketing.hero.more-download-options": {
|
||||
"message": "More Download Options"
|
||||
},
|
||||
"app-marketing.hide-other-packages": {
|
||||
"message": "Hide other packages"
|
||||
},
|
||||
"app-marketing.not-recommended": {
|
||||
"message": "We don't recommend you use these unless you know what you're doing."
|
||||
},
|
||||
"app-marketing.show-other-packages": {
|
||||
"message": "Show other packages"
|
||||
},
|
||||
"auth.authorize.action.authorize": {
|
||||
"message": "Authorize"
|
||||
},
|
||||
@@ -1100,153 +902,6 @@
|
||||
"frog.title": {
|
||||
"message": "Frog"
|
||||
},
|
||||
"hosting-marketing.available-locations": {
|
||||
"message": "Available in North America, Europe, and Southeast Asia for wide coverage."
|
||||
},
|
||||
"hosting-marketing.billing.monthly": {
|
||||
"message": "Pay monthly"
|
||||
},
|
||||
"hosting-marketing.billing.quarterly": {
|
||||
"message": "Pay quarterly"
|
||||
},
|
||||
"hosting-marketing.billing.save-with-quarterly": {
|
||||
"message": "Save 16% with quarterly billing!"
|
||||
},
|
||||
"hosting-marketing.billing.starting-at": {
|
||||
"message": "Starting at {price} / month"
|
||||
},
|
||||
"hosting-marketing.billing.yearly": {
|
||||
"message": "Pay yearly"
|
||||
},
|
||||
"hosting-marketing.faq.burst-threads": {
|
||||
"message": "How do CPU burst threads work?"
|
||||
},
|
||||
"hosting-marketing.faq.burst-threads.answer": {
|
||||
"message": "When your server is under heavy load, we temporarily give it access to additional CPU threads to help mitigate lag spikes and instability. This helps prevent the TPS from going below 20, ensuring the smoothest experience possible. Since those extra CPU threads are only shortly available during high load periods, they might not show up in Spark reports or other profiling tools."
|
||||
},
|
||||
"hosting-marketing.faq.cpu-kind": {
|
||||
"message": "What kind of CPUs do Modrinth Hosting servers run on?"
|
||||
},
|
||||
"hosting-marketing.faq.cpu-kind.answer": {
|
||||
"message": "Modrinth Hosting servers are powered by AMD Ryzen 7900 and 7950X3D equivalent CPUs at 5+ GHz, paired with DDR5 memory."
|
||||
},
|
||||
"hosting-marketing.faq.currency": {
|
||||
"message": "What currency are the prices in?"
|
||||
},
|
||||
"hosting-marketing.faq.currency.answer": {
|
||||
"message": "All prices are listed in United States Dollars (USD)."
|
||||
},
|
||||
"hosting-marketing.faq.ddos-protection": {
|
||||
"message": "Do Modrinth Hosting servers have DDoS protection?"
|
||||
},
|
||||
"hosting-marketing.faq.ddos-protection.answer": {
|
||||
"message": "Yes. All Modrinth Hosting servers come with DDoS protection, with up to 17 Tbps capacity in some locations."
|
||||
},
|
||||
"hosting-marketing.faq.heading": {
|
||||
"message": "Frequently Asked Questions"
|
||||
},
|
||||
"hosting-marketing.faq.how-fast": {
|
||||
"message": "How fast are Modrinth Hosting servers?"
|
||||
},
|
||||
"hosting-marketing.faq.how-fast.answer.one": {
|
||||
"message": "Modrinth Hosting servers are hosted on very modern high-performance hardware, but it's tough to say how exactly that will translate into how fast your server will run because there are so many factors that affect it, such as the mods, data packs, or plugins you're running on your server, and even user behavior."
|
||||
},
|
||||
"hosting-marketing.faq.how-fast.answer.two": {
|
||||
"message": "Most performance issues that arise tend to be the fault of an unoptimized modpack, mod, data pack, or plugin that causes the server to lag. Since our servers are very high-end, you shouldn't run into much trouble as long as you pick an appropriate plan for the content you're running on the server."
|
||||
},
|
||||
"hosting-marketing.faq.increase-storage": {
|
||||
"message": "Can I increase the storage on my server?"
|
||||
},
|
||||
"hosting-marketing.faq.increase-storage.answer": {
|
||||
"message": "Yes, storage can be increased on your server at no additional cost. If you need more storage, reach out to Modrinth Support."
|
||||
},
|
||||
"hosting-marketing.faq.location": {
|
||||
"message": "Where are Modrinth Hosting servers located? Can I choose a region?"
|
||||
},
|
||||
"hosting-marketing.faq.location.answer": {
|
||||
"message": "We have servers available in North America, Europe, and Southeast Asia at the moment that you can choose upon purchase. More regions to come in the future! If you'd like to switch your region, please contact support."
|
||||
},
|
||||
"hosting-marketing.faq.versions-loaders": {
|
||||
"message": "What Minecraft versions and loaders can be used?"
|
||||
},
|
||||
"hosting-marketing.faq.versions-loaders.answer.one": {
|
||||
"message": "Modrinth Hosting servers can run any version of Minecraft: Java Edition going all the way back to version 1.2.5, including snapshot versions."
|
||||
},
|
||||
"hosting-marketing.faq.versions-loaders.answer.two": {
|
||||
"message": "We also support a wide range of mod and plugin loaders, including Fabric, Quilt, Forge, and NeoForge for mods, as well as Paper and Purpur for plugins. Availability depends on whether the mod or plugin loader supports the selected Minecraft version."
|
||||
},
|
||||
"hosting-marketing.get-started": {
|
||||
"message": "Get started"
|
||||
},
|
||||
"hosting-marketing.hero.button.manage-your-servers": {
|
||||
"message": "Manage your servers"
|
||||
},
|
||||
"hosting-marketing.hero.button.start-a-new-server": {
|
||||
"message": "Start a new server"
|
||||
},
|
||||
"hosting-marketing.hero.button.start-your-server": {
|
||||
"message": "Start your server"
|
||||
},
|
||||
"hosting-marketing.hero.host-with-modrinth": {
|
||||
"message": "Host your next server with Modrinth Hosting"
|
||||
},
|
||||
"hosting-marketing.hero.hosting-description": {
|
||||
"message": "Modrinth Hosting is the easiest way to host your own Minecraft: Java Edition server. Seamlessly install and play your favorite mods and modpacks, all within the Modrinth platform."
|
||||
},
|
||||
"hosting-marketing.included.advanced-networking": {
|
||||
"message": "Advanced networking management"
|
||||
},
|
||||
"hosting-marketing.included.advanced-networking.description": {
|
||||
"message": "Add your own domain to your server, reserve up to 15 ports for mods that require them, and more."
|
||||
},
|
||||
"hosting-marketing.included.backups-included": {
|
||||
"message": "Backups included"
|
||||
},
|
||||
"hosting-marketing.included.backups-included.description": {
|
||||
"message": "Every server comes with 15 backups stored securely off-site."
|
||||
},
|
||||
"hosting-marketing.included.custom-url": {
|
||||
"message": "Custom URL"
|
||||
},
|
||||
"hosting-marketing.included.custom-url.description": {
|
||||
"message": "Share your server with a custom <contrast>modrinth.gg</contrast> URL."
|
||||
},
|
||||
"hosting-marketing.included.description": {
|
||||
"message": "Included with every server is a suite of features designed to provide a hosting experience that only Modrinth can offer."
|
||||
},
|
||||
"hosting-marketing.included.file-manager": {
|
||||
"message": "Easy to use file manager"
|
||||
},
|
||||
"hosting-marketing.included.file-manager.description": {
|
||||
"message": "Search, manage, edit, and upload files directly to your server with ease."
|
||||
},
|
||||
"hosting-marketing.included.heading": {
|
||||
"message": "Comes with all the features you need."
|
||||
},
|
||||
"hosting-marketing.included.help": {
|
||||
"message": "Help when you need it"
|
||||
},
|
||||
"hosting-marketing.included.help.description": {
|
||||
"message": "Reach out to the Modrinth team for help with your server at any time."
|
||||
},
|
||||
"hosting-marketing.included.powerful-console": {
|
||||
"message": "A powerful console, server properties manager, and more"
|
||||
},
|
||||
"hosting-marketing.included.powerful-console.description": {
|
||||
"message": "Modrinth Hosting comes with powerful tools to manage your server."
|
||||
},
|
||||
"hosting-marketing.included.sftp-access": {
|
||||
"message": "SFTP access"
|
||||
},
|
||||
"hosting-marketing.included.sftp-access.description": {
|
||||
"message": "Access your server's files directly with SFTP built into Modrinth Hosting."
|
||||
},
|
||||
"hosting-marketing.included.with-your-server": {
|
||||
"message": "Included with your server"
|
||||
},
|
||||
"hosting-marketing.know-what-you-need": {
|
||||
"message": "Know exactly what you need?"
|
||||
},
|
||||
"hosting-marketing.medal.info": {
|
||||
"message": "Try a free <orange>3 GB server</orange> for 5 days powered by <orange>Medal</orange>"
|
||||
},
|
||||
@@ -1256,51 +911,6 @@
|
||||
"hosting-marketing.medal.text-secondary": {
|
||||
"message": "Limited-time offer. No credit card required. Available for US servers."
|
||||
},
|
||||
"hosting-marketing.pick-customized-plan": {
|
||||
"message": "Pick a customized plan with just the specs you need."
|
||||
},
|
||||
"hosting-marketing.server-for-everyone": {
|
||||
"message": "There's a server for everyone"
|
||||
},
|
||||
"hosting-marketing.why.all-on-modrinth": {
|
||||
"message": "Manage it all on Modrinth"
|
||||
},
|
||||
"hosting-marketing.why.all-on-modrinth.description": {
|
||||
"message": "Your server, mods, players, and more are all on Modrinth. No need to switch between platforms."
|
||||
},
|
||||
"hosting-marketing.why.consistently-fast": {
|
||||
"message": "Consistently fast"
|
||||
},
|
||||
"hosting-marketing.why.consistently-fast.description": {
|
||||
"message": "Our infrastructure is never overloaded, meaning each server hosted with Modrinth always runs at its full performance."
|
||||
},
|
||||
"hosting-marketing.why.description": {
|
||||
"message": "Choose from the thousands of modpacks on Modrinth or create your own. Invite your friends when you're ready to play."
|
||||
},
|
||||
"hosting-marketing.why.heading": {
|
||||
"message": "Find a modpack. Now it's a server."
|
||||
},
|
||||
"hosting-marketing.why.modern-reliable-hosting": {
|
||||
"message": "Experience modern, reliable hosting"
|
||||
},
|
||||
"hosting-marketing.why.modern-reliable-hosting.description": {
|
||||
"message": "Modrinth Hosting servers are hosted on <contrast>high-performance AMD CPUs with DDR5 RAM</contrast>, running on custom-built software to ensure your server performs smoothly."
|
||||
},
|
||||
"hosting-marketing.why.where-mods-are": {
|
||||
"message": "Play where your mods are"
|
||||
},
|
||||
"hosting-marketing.why.where-mods-are.description": {
|
||||
"message": "Modrinth Hosting seamlessly integrates the mod and modpack installation process into your server."
|
||||
},
|
||||
"hosting-marketing.why.why-modrinth-hosting": {
|
||||
"message": "Why Modrinth Hosting?"
|
||||
},
|
||||
"hosting-marketing.why.your-favorite-mods": {
|
||||
"message": "All your favorite mods"
|
||||
},
|
||||
"hosting-marketing.why.your-favorite-mods.description": {
|
||||
"message": "Choose between Vanilla, Fabric, Forge, Quilt and NeoForge. If it's on Modrinth, it can run on your server."
|
||||
},
|
||||
"hosting.loader.failed-to-change-version": {
|
||||
"message": "Failed to change modpack version"
|
||||
},
|
||||
@@ -1361,141 +971,6 @@
|
||||
"hosting.plan.select-plan": {
|
||||
"message": "Select plan"
|
||||
},
|
||||
"landing.button.discover-mods": {
|
||||
"message": "Discover mods"
|
||||
},
|
||||
"landing.button.go-to-dashboard": {
|
||||
"message": "Go to dashboard"
|
||||
},
|
||||
"landing.creator.feature.constantly-evolving.description": {
|
||||
"message": "Get the best modding experience possible with constant updates from the Modrinth team"
|
||||
},
|
||||
"landing.creator.feature.constantly-evolving.title": {
|
||||
"message": "Constantly Evolving"
|
||||
},
|
||||
"landing.creator.feature.data-statistics.description": {
|
||||
"message": "Get detailed reports on page views, download counts, and revenue"
|
||||
},
|
||||
"landing.creator.feature.data-statistics.title": {
|
||||
"message": "Data and Statistics"
|
||||
},
|
||||
"landing.creator.feature.discovery.description": {
|
||||
"message": "Get your project discovered by thousands of users through search, our home page, Discord server, and more ways to come in the future!"
|
||||
},
|
||||
"landing.creator.feature.discovery.title": {
|
||||
"message": "Discovery"
|
||||
},
|
||||
"landing.creator.feature.diverse-ecosystem.description": {
|
||||
"message": "Integrate with your build tools through Minotaur for automatic uploads right when you release a new version"
|
||||
},
|
||||
"landing.creator.feature.diverse-ecosystem.title": {
|
||||
"message": "Diverse Ecosystem"
|
||||
},
|
||||
"landing.creator.feature.monetization.description": {
|
||||
"message": "Get paid ad revenue from your project pages and withdraw your funds at any time"
|
||||
},
|
||||
"landing.creator.feature.monetization.title": {
|
||||
"message": "Monetization"
|
||||
},
|
||||
"landing.creator.feature.team-management.description": {
|
||||
"message": "Invite your teammates and manage roles and permissions with ease"
|
||||
},
|
||||
"landing.creator.feature.team-management.title": {
|
||||
"message": "Team Management"
|
||||
},
|
||||
"landing.error.failedToLoadRandomProjects": {
|
||||
"message": "Failed to load random projects :("
|
||||
},
|
||||
"landing.feature.follow.description": {
|
||||
"message": "Get notified every time your favorite projects update and stay in the loop."
|
||||
},
|
||||
"landing.feature.follow.heading": {
|
||||
"message": "Follow projects you love"
|
||||
},
|
||||
"landing.feature.launcher.description": {
|
||||
"message": "Modrinth's open-source API lets launchers add deep integration with Modrinth. You can use Modrinth through <link>our own app</link> and some of the most popular launchers like ATLauncher, MultiMC, and Prism Launcher."
|
||||
},
|
||||
"landing.feature.launcher.heading": {
|
||||
"message": "Play with your favorite launcher"
|
||||
},
|
||||
"landing.feature.search.description": {
|
||||
"message": "Modrinth's lightning-fast search and powerful filters let you find what you want as you type."
|
||||
},
|
||||
"landing.feature.search.heading": {
|
||||
"message": "Find what you want, quickly and easily"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft": {
|
||||
"message": "The place for Minecraft {content}"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.data-packs": {
|
||||
"message": "data packs"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.modpacks": {
|
||||
"message": "modpacks"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.mods": {
|
||||
"message": "mods"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.plugins": {
|
||||
"message": "plugins"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.resource-packs": {
|
||||
"message": "resource packs"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.servers": {
|
||||
"message": "servers"
|
||||
},
|
||||
"landing.heading.the-place-for-minecraft.shaders": {
|
||||
"message": "shaders"
|
||||
},
|
||||
"landing.launcher.atlauncher-label": {
|
||||
"message": "ATLauncher"
|
||||
},
|
||||
"landing.launcher.graphic-alt": {
|
||||
"message": "A simplified representation of a Minecraft window, with the Mojang Studios logo in Modrinth green."
|
||||
},
|
||||
"landing.launcher.modrinth-app-label": {
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"landing.launcher.prism-launcher-label": {
|
||||
"message": "Prism Launcher"
|
||||
},
|
||||
"landing.notifications.has-been-updated": {
|
||||
"message": "{title} has been updated!"
|
||||
},
|
||||
"landing.notifications.heading": {
|
||||
"message": "Notifications"
|
||||
},
|
||||
"landing.notifications.received-time": {
|
||||
"message": "Received {time}"
|
||||
},
|
||||
"landing.notifications.version-released": {
|
||||
"message": "Version {version} has been released for {gameVersion}"
|
||||
},
|
||||
"landing.section.for-creators.description": {
|
||||
"message": "Give an online home to your creations and reach a massive audience of dedicated players."
|
||||
},
|
||||
"landing.section.for-creators.label": {
|
||||
"message": "For Creators"
|
||||
},
|
||||
"landing.section.for-creators.tagline": {
|
||||
"message": "Share your content with the world"
|
||||
},
|
||||
"landing.section.for-players.description": {
|
||||
"message": "From magical biomes to cursed dungeons, you can be sure to find content to bring your gameplay to the next level."
|
||||
},
|
||||
"landing.section.for-players.label": {
|
||||
"message": "For Players"
|
||||
},
|
||||
"landing.section.for-players.tagline": {
|
||||
"message": "Discover over {count, number} creations"
|
||||
},
|
||||
"landing.subheading": {
|
||||
"message": "Discover, play, and share Minecraft content through our open-source platform built for the community."
|
||||
},
|
||||
"layout.action.change-theme": {
|
||||
"message": "Change theme"
|
||||
},
|
||||
"layout.action.create-new": {
|
||||
"message": "Create new..."
|
||||
},
|
||||
@@ -1682,8 +1157,8 @@
|
||||
"layout.footer.social.mastodon": {
|
||||
"message": "Mastodon"
|
||||
},
|
||||
"layout.footer.social.x": {
|
||||
"message": "X"
|
||||
"layout.footer.social.twitter": {
|
||||
"message": "Twitter"
|
||||
},
|
||||
"layout.menu-toggle.action": {
|
||||
"message": "Toggle menu"
|
||||
@@ -1703,24 +1178,6 @@
|
||||
"layout.nav.active-reports": {
|
||||
"message": "Active reports"
|
||||
},
|
||||
"layout.nav.discover": {
|
||||
"message": "Discover"
|
||||
},
|
||||
"layout.nav.discover-content": {
|
||||
"message": "Discover content"
|
||||
},
|
||||
"layout.nav.get-modrinth-app": {
|
||||
"message": "Get Modrinth App"
|
||||
},
|
||||
"layout.nav.home": {
|
||||
"message": "Home"
|
||||
},
|
||||
"layout.nav.host-a-server": {
|
||||
"message": "Host a server"
|
||||
},
|
||||
"layout.nav.modrinth-app": {
|
||||
"message": "Modrinth App"
|
||||
},
|
||||
"layout.nav.modrinth-home-page": {
|
||||
"message": "Modrinth home page"
|
||||
},
|
||||
@@ -1742,9 +1199,6 @@
|
||||
"layout.nav.saved-projects": {
|
||||
"message": "Saved projects"
|
||||
},
|
||||
"layout.nav.search": {
|
||||
"message": "Search"
|
||||
},
|
||||
"layout.nav.upgrade-to-modrinth-plus": {
|
||||
"message": "Upgrade to Modrinth+"
|
||||
},
|
||||
@@ -3210,7 +2664,7 @@
|
||||
"message": "Modrinth Hosting"
|
||||
},
|
||||
"settings.billing.charges.title": {
|
||||
"message": "Past charges"
|
||||
"message": "Charge history"
|
||||
},
|
||||
"settings.billing.expires": {
|
||||
"message": "Expires {date}"
|
||||
@@ -3416,48 +2870,6 @@
|
||||
"settings.display.notification.developer-mode-deactivated.title": {
|
||||
"message": "Developer mode deactivated"
|
||||
},
|
||||
"settings.display.project-list-layouts.datapack": {
|
||||
"message": "Data Packs page"
|
||||
},
|
||||
"settings.display.project-list-layouts.description": {
|
||||
"message": "Select your preferred layout for each page that displays project lists on this device."
|
||||
},
|
||||
"settings.display.project-list-layouts.mod": {
|
||||
"message": "Mods page"
|
||||
},
|
||||
"settings.display.project-list-layouts.mode.gallery": {
|
||||
"message": "Gallery"
|
||||
},
|
||||
"settings.display.project-list-layouts.mode.grid": {
|
||||
"message": "Grid"
|
||||
},
|
||||
"settings.display.project-list-layouts.mode.rows": {
|
||||
"message": "Rows"
|
||||
},
|
||||
"settings.display.project-list-layouts.modpack": {
|
||||
"message": "Modpacks page"
|
||||
},
|
||||
"settings.display.project-list-layouts.plugin": {
|
||||
"message": "Plugins page"
|
||||
},
|
||||
"settings.display.project-list-layouts.resourcepack": {
|
||||
"message": "Resource Packs page"
|
||||
},
|
||||
"settings.display.project-list-layouts.server": {
|
||||
"message": "Servers page"
|
||||
},
|
||||
"settings.display.project-list-layouts.shader": {
|
||||
"message": "Shaders page"
|
||||
},
|
||||
"settings.display.project-list-layouts.title": {
|
||||
"message": "Project list layouts"
|
||||
},
|
||||
"settings.display.project-list-layouts.user": {
|
||||
"message": "User profile pages"
|
||||
},
|
||||
"settings.display.project-list.layouts.collection": {
|
||||
"message": "Collection"
|
||||
},
|
||||
"settings.display.sidebar.advanced-rendering.description": {
|
||||
"message": "Enables advanced rendering such as blur effects that may cause performance issues without hardware-accelerated rendering."
|
||||
},
|
||||
@@ -3488,12 +2900,6 @@
|
||||
"settings.display.sidebar.right-aligned-filters-sidebar.title": {
|
||||
"message": "Right-aligned filters sidebar on search pages"
|
||||
},
|
||||
"settings.display.theme.description": {
|
||||
"message": "Select your preferred color theme for Modrinth on this device."
|
||||
},
|
||||
"settings.display.theme.title": {
|
||||
"message": "Color theme"
|
||||
},
|
||||
"settings.head-title": {
|
||||
"message": "Display settings"
|
||||
},
|
||||
@@ -3599,12 +3005,6 @@
|
||||
"settings.sidebar.label.display": {
|
||||
"message": "Display"
|
||||
},
|
||||
"ui.latest-news-row.latest-news": {
|
||||
"message": "Latest news from Modrinth"
|
||||
},
|
||||
"ui.latest-news-row.view-all": {
|
||||
"message": "View all news"
|
||||
},
|
||||
"ui.newsletter-button.subscribe": {
|
||||
"message": "Subscribe"
|
||||
},
|
||||
|
||||
@@ -45,15 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="experimental-styles-within">
|
||||
<NewModal ref="settingsModal">
|
||||
<template #title>
|
||||
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" />
|
||||
<span class="text-lg font-extrabold text-contrast">
|
||||
{{ formatMessage(messages.settingsTitle) }}
|
||||
</span>
|
||||
</template>
|
||||
</NewModal>
|
||||
<div v-else>
|
||||
<NewModal
|
||||
ref="modalLicense"
|
||||
:header="project.license.name ? project.license.name : formatMessage(messages.licenseTitle)"
|
||||
@@ -74,26 +66,6 @@
|
||||
/>
|
||||
</NewModal>
|
||||
<OpenInAppModal ref="openInAppModal" />
|
||||
<div
|
||||
class="over-the-top-download-animation"
|
||||
:class="{ 'animation-hidden': !overTheTopDownloadAnimation }"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="animation-ring-3 flex items-center justify-center rounded-full border-4 border-solid border-brand bg-brand-highlight opacity-40"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="animation-ring-2 flex items-center justify-center rounded-full border-4 border-solid border-brand bg-brand-highlight opacity-60"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="animation-ring-1 flex items-center justify-center rounded-full border-4 border-solid border-brand bg-brand-highlight"
|
||||
>
|
||||
<DownloadIcon class="h-20 w-20 text-contrast" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NewModal
|
||||
ref="downloadModal"
|
||||
:on-show="
|
||||
@@ -147,11 +119,11 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 px-4">
|
||||
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
|
||||
<div class="flex h-[2px] w-full rounded-[4px] bg-button-bg"></div>
|
||||
<span class="flex-shrink-0 text-sm font-semibold text-secondary">
|
||||
{{ formatMessage(commonMessages.orLabel) }}
|
||||
</span>
|
||||
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
|
||||
<div class="flex h-[2px] w-full rounded-[4px] bg-button-bg"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -446,362 +418,147 @@
|
||||
:member="!!currentMember"
|
||||
>
|
||||
<template #actions>
|
||||
<ButtonStyled
|
||||
v-if="auth.user && currentMember"
|
||||
size="large"
|
||||
color="brand"
|
||||
class="lg:!hidden"
|
||||
circular
|
||||
>
|
||||
<nuxt-link
|
||||
v-tooltip="'Edit project'"
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||
class="!font-bold"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-if="auth.user && currentMember"
|
||||
size="large"
|
||||
color="brand"
|
||||
class="max-lg:!hidden"
|
||||
>
|
||||
<nuxt-link
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||
class="!font-bold"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
Edit project
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
|
||||
<div class="hidden sm:contents">
|
||||
<ButtonStyled
|
||||
v-if="!isServerProject"
|
||||
size="large"
|
||||
:color="
|
||||
(auth.user && currentMember) || route.name === 'type-id-version-version'
|
||||
? `standard`
|
||||
: `brand`
|
||||
"
|
||||
:circular="!!auth.user && !!currentMember"
|
||||
>
|
||||
<button
|
||||
v-tooltip="
|
||||
auth.user && currentMember ? formatMessage(commonMessages.downloadButton) : ''
|
||||
"
|
||||
@click="(event) => downloadModal.show(event)"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
{{
|
||||
auth.user && currentMember ? '' : formatMessage(commonMessages.downloadButton)
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else
|
||||
size="large"
|
||||
:color="
|
||||
(auth.user && currentMember) || route.name === 'type-id-version-version'
|
||||
? `standard`
|
||||
: `brand`
|
||||
"
|
||||
:circular="!!auth.user && !!currentMember"
|
||||
>
|
||||
<button
|
||||
v-tooltip="auth.user && currentMember && !openInAppModal?.open ? 'Play' : ''"
|
||||
@click="handlePlayServerProject"
|
||||
>
|
||||
<PlayIcon aria-hidden="true" />
|
||||
{{ auth.user && currentMember ? '' : 'Play' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<div class="contents sm:hidden">
|
||||
<ButtonStyled
|
||||
v-if="!isServerProject"
|
||||
size="large"
|
||||
circular
|
||||
:color="
|
||||
route.name === 'type-id-version-version' || (auth.user && currentMember)
|
||||
? `standard`
|
||||
: `brand`
|
||||
"
|
||||
>
|
||||
<button
|
||||
:aria-label="formatMessage(commonMessages.downloadButton)"
|
||||
class="flex sm:hidden"
|
||||
@click="(event) => downloadModal.show(event)"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else
|
||||
size="large"
|
||||
circular
|
||||
:color="
|
||||
route.name === 'type-id-version-version' || (auth.user && currentMember)
|
||||
? `standard`
|
||||
: `brand`
|
||||
"
|
||||
>
|
||||
<button aria-label="Play" class="flex sm:hidden" @click="handlePlayServerProject">
|
||||
<PlayIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<Tooltip
|
||||
v-if="canCreateServerFrom && flags.showProjectPageQuickServerButton"
|
||||
theme="dismissable-prompt"
|
||||
:triggers="[]"
|
||||
:shown="flags.showProjectPageCreateServersTooltip"
|
||||
:auto-hide="false"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<ButtonStyled size="large" circular>
|
||||
<nuxt-link
|
||||
v-tooltip="formatMessage(messages.createServerTooltip)"
|
||||
:to="`/hosting?project=${project.id}#plan`"
|
||||
@click="
|
||||
() => {
|
||||
flags.showProjectPageCreateServersTooltip = false
|
||||
saveFeatureFlags()
|
||||
}
|
||||
"
|
||||
>
|
||||
<ServerPlusIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<template #popper>
|
||||
<div class="experimental-styles-within grid grid-cols-[min-content] gap-1">
|
||||
<div class="flex min-w-60 items-center justify-between gap-4">
|
||||
<h3
|
||||
class="m-0 flex items-center gap-2 whitespace-nowrap text-base font-bold text-contrast"
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<div class="flex gap-1">
|
||||
<div class="hidden sm:contents">
|
||||
<ButtonStyled
|
||||
v-if="!isServerProject"
|
||||
size="large"
|
||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||
>
|
||||
<button @click="(event) => downloadModal.show(event)">Download</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-else
|
||||
size="large"
|
||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||
>
|
||||
<button
|
||||
v-tooltip="
|
||||
auth.user && currentMember && !openInAppModal?.open ? 'Play' : ''
|
||||
"
|
||||
@click="handlePlayServerProject"
|
||||
>
|
||||
{{ formatMessage(messages.serversPromoTitle) }}
|
||||
<TagItem
|
||||
:style="{
|
||||
'--_color': 'var(--color-brand)',
|
||||
'--_bg-color': 'var(--color-brand-highlight)',
|
||||
}"
|
||||
>{{ formatMessage(commonMessages.newBadge) }}</TagItem
|
||||
>
|
||||
</h3>
|
||||
<ButtonStyled size="small" circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.dontShowAgain)"
|
||||
@click="
|
||||
() => {
|
||||
flags.showProjectPageCreateServersTooltip = false
|
||||
saveFeatureFlags()
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<p class="m-0 text-wrap text-sm font-medium leading-tight text-secondary">
|
||||
{{ formatMessage(messages.serversPromoDescription) }}
|
||||
</p>
|
||||
|
||||
<p class="m-0 text-wrap text-sm font-bold text-primary">
|
||||
<IntlFormatted
|
||||
:message-id="messages.serversPromoPricing"
|
||||
:values="{
|
||||
price: formatPrice(500, 'USD', true),
|
||||
}"
|
||||
>
|
||||
<template #small="{ children }">
|
||||
<span class="text-xs">
|
||||
<component :is="() => children" />
|
||||
</span>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<PlayIcon aria-hidden="true" />
|
||||
Play
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
<ButtonStyled size="large" circular>
|
||||
<ClientOnly>
|
||||
<button
|
||||
v-if="auth.user"
|
||||
v-tooltip="
|
||||
following
|
||||
? formatMessage(commonMessages.unfollowButton)
|
||||
: formatMessage(commonMessages.followButton)
|
||||
"
|
||||
:aria-label="
|
||||
following
|
||||
? formatMessage(commonMessages.unfollowButton)
|
||||
: formatMessage(commonMessages.followButton)
|
||||
"
|
||||
@click="userFollowProject(project)"
|
||||
>
|
||||
<HeartIcon :fill="following ? 'currentColor' : 'none'" aria-hidden="true" />
|
||||
</button>
|
||||
<nuxt-link
|
||||
v-else
|
||||
v-tooltip="formatMessage(commonMessages.followButton)"
|
||||
to="/auth/sign-in"
|
||||
:aria-label="formatMessage(commonMessages.followButton)"
|
||||
>
|
||||
<HeartIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
<template #fallback>
|
||||
<nuxt-link
|
||||
v-tooltip="formatMessage(commonMessages.followButton)"
|
||||
to="/auth/sign-in"
|
||||
:aria-label="formatMessage(commonMessages.followButton)"
|
||||
>
|
||||
<HeartIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled size="large" circular>
|
||||
<PopoutMenu
|
||||
v-if="auth.user"
|
||||
:tooltip="
|
||||
collections.some((x) => x.projects.includes(project.id))
|
||||
? formatMessage(commonMessages.savedLabel)
|
||||
: formatMessage(commonMessages.saveButton)
|
||||
"
|
||||
from="top-right"
|
||||
:aria-label="formatMessage(commonMessages.saveButton)"
|
||||
:dropdown-id="`${baseId}-save`"
|
||||
>
|
||||
<BookmarkIcon
|
||||
aria-hidden="true"
|
||||
:fill="
|
||||
collections.some((x) => x.projects.includes(project.id))
|
||||
? 'currentColor'
|
||||
: 'none'
|
||||
"
|
||||
/>
|
||||
<template #menu>
|
||||
<StyledInput
|
||||
v-model="displayCollectionsSearch"
|
||||
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
|
||||
wrapper-class="menu-search"
|
||||
/>
|
||||
<div v-if="collections.length > 0" class="collections-list text-primary">
|
||||
<Checkbox
|
||||
v-for="option in collections
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name))"
|
||||
:key="option.id"
|
||||
:model-value="option.projects.includes(project.id)"
|
||||
class="popout-checkbox"
|
||||
@update:model-value="() => onUserCollectProject(option, project.id)"
|
||||
|
||||
<ButtonStyled size="large" circular>
|
||||
<ClientOnly>
|
||||
<button
|
||||
v-if="auth.user"
|
||||
v-tooltip="
|
||||
following
|
||||
? formatMessage(commonMessages.unfollowButton)
|
||||
: formatMessage(commonMessages.followButton)
|
||||
"
|
||||
:aria-label="
|
||||
following
|
||||
? formatMessage(commonMessages.unfollowButton)
|
||||
: formatMessage(commonMessages.followButton)
|
||||
"
|
||||
@click="userFollowProject(project)"
|
||||
>
|
||||
{{ option.name }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
<div v-else class="menu-text">
|
||||
<p class="popout-text">{{ formatMessage(messages.noCollectionsFound) }}</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn collection-button"
|
||||
@click="(event) => $refs.modal_collection.show(event)"
|
||||
<HeartIcon :fill="following ? 'currentColor' : 'none'" aria-hidden="true" />
|
||||
</button>
|
||||
<nuxt-link
|
||||
v-else
|
||||
v-tooltip="formatMessage(commonMessages.followButton)"
|
||||
to="/auth/sign-in"
|
||||
:aria-label="formatMessage(commonMessages.followButton)"
|
||||
>
|
||||
<HeartIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
<template #fallback>
|
||||
<nuxt-link
|
||||
v-tooltip="formatMessage(commonMessages.followButton)"
|
||||
to="/auth/sign-in"
|
||||
:aria-label="formatMessage(commonMessages.followButton)"
|
||||
>
|
||||
<HeartIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled size="large" circular>
|
||||
<PopoutMenu
|
||||
v-if="auth.user"
|
||||
:tooltip="
|
||||
collections.some((x) => x.projects.includes(project.id))
|
||||
? formatMessage(commonMessages.savedLabel)
|
||||
: formatMessage(commonMessages.saveButton)
|
||||
"
|
||||
from="top-right"
|
||||
:aria-label="formatMessage(commonMessages.saveButton)"
|
||||
:dropdown-id="`${baseId}-save`"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createNewCollection) }}
|
||||
</button>
|
||||
</template>
|
||||
</PopoutMenu>
|
||||
<nuxt-link v-else v-tooltip="'Save'" to="/auth/sign-in" aria-label="Save">
|
||||
<BookmarkIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<BookmarkIcon
|
||||
aria-hidden="true"
|
||||
:fill="
|
||||
collections.some((x) => x.projects.includes(project.id))
|
||||
? 'currentColor'
|
||||
: 'none'
|
||||
"
|
||||
/>
|
||||
<template #menu>
|
||||
<StyledInput
|
||||
v-model="displayCollectionsSearch"
|
||||
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
|
||||
wrapper-class="menu-search"
|
||||
/>
|
||||
<div v-if="collections.length > 0" class="collections-list text-primary">
|
||||
<Checkbox
|
||||
v-for="option in collections
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name))"
|
||||
:key="option.id"
|
||||
:model-value="option.projects.includes(project.id)"
|
||||
class="popout-checkbox"
|
||||
@update:model-value="() => onUserCollectProject(option, project.id)"
|
||||
>
|
||||
{{ option.name }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
<ButtonStyled size="large" circular type="transparent">
|
||||
<OverflowMenu
|
||||
:tooltip="formatMessage(commonMessages.moreOptionsButton)"
|
||||
:options="[
|
||||
{
|
||||
id: 'analytics',
|
||||
link: `/${project.project_type}/${project.slug ? project.slug : project.id}/settings/analytics`,
|
||||
hoverOnly: true,
|
||||
shown: auth.user && !!currentMember,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
shown: auth.user && !!currentMember,
|
||||
},
|
||||
{
|
||||
id: 'moderation-checklist',
|
||||
action: () => {
|
||||
moderationStore.setSingleProject(project.id)
|
||||
showModerationChecklist = true
|
||||
},
|
||||
color: 'orange',
|
||||
hoverOnly: true,
|
||||
shown:
|
||||
auth.user &&
|
||||
tags.staffRoles.includes(auth.user.role) &&
|
||||
!showModerationChecklist,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
shown:
|
||||
auth.user &&
|
||||
tags.staffRoles.includes(auth.user.role) &&
|
||||
!showModerationChecklist,
|
||||
},
|
||||
{
|
||||
id: 'tech-review',
|
||||
link: `/moderation/technical-review/${project.id}`,
|
||||
color: 'orange',
|
||||
hoverOnly: true,
|
||||
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
action: () =>
|
||||
auth.user ? reportProject(project.id) : navigateTo('/auth/sign-in'),
|
||||
color: 'red',
|
||||
hoverOnly: true,
|
||||
shown: !isMember,
|
||||
},
|
||||
{ id: 'copy-id', action: () => copyId() },
|
||||
{ id: 'copy-permalink', action: () => copyPermalink() },
|
||||
]"
|
||||
:aria-label="formatMessage(commonMessages.moreOptionsButton)"
|
||||
:dropdown-id="`${baseId}-more-options`"
|
||||
<div v-else class="menu-text">
|
||||
<p class="popout-text">
|
||||
{{ formatMessage(messages.noCollectionsFound) }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn collection-button"
|
||||
@click="(event) => $refs.modal_collection.show(event)"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createNewCollection) }}
|
||||
</button>
|
||||
</template>
|
||||
</PopoutMenu>
|
||||
<nuxt-link v-else v-tooltip="'Save'" to="/auth/sign-in" aria-label="Save">
|
||||
<BookmarkIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
|
||||
<nuxt-link
|
||||
v-if="currentMember"
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||
class="text-link"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
<template #analytics>
|
||||
<ChartIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.analyticsButton) }}
|
||||
</template>
|
||||
<template #moderation-checklist>
|
||||
<ScaleIcon aria-hidden="true" /> {{ formatMessage(messages.reviewProject) }}
|
||||
</template>
|
||||
<template #tech-review> <ScanEyeIcon aria-hidden="true" /> Tech review </template>
|
||||
<template #report>
|
||||
<ReportIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.reportButton) }}
|
||||
</template>
|
||||
<template #copy-id>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.copyIdButton) }}
|
||||
</template>
|
||||
<template #copy-permalink>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.copyPermalinkButton) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
Edit project ►
|
||||
</nuxt-link>
|
||||
<button
|
||||
v-if="!isMember"
|
||||
class="w-fit bg-transparent p-0 text-link"
|
||||
@click="
|
||||
() => (auth.user ? reportProject(project.id) : navigateTo('/auth/sign-in'))
|
||||
"
|
||||
>
|
||||
Report project
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</ProjectHeader>
|
||||
<ProjectMemberHeader
|
||||
@@ -871,32 +628,29 @@
|
||||
:supported-versions="serverSupportedVersions"
|
||||
:loaders="serverModpackLoaders"
|
||||
:status-online="projectV3?.minecraft_java_server?.ping?.data != null"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class=""
|
||||
/>
|
||||
<ProjectSidebarCompatibility
|
||||
v-if="projectV3Loaded && !isServerProject"
|
||||
:project="project"
|
||||
:tags="tags"
|
||||
:project-v3="projectV3"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class=""
|
||||
/>
|
||||
<AdPlaceholder v-if="!auth.user && tags.approvedStatuses.includes(project.status)" />
|
||||
<ProjectSidebarLinks
|
||||
:project="project"
|
||||
:project-v3="projectV3"
|
||||
:link-target="$external()"
|
||||
class="card flex-card experimental-styles-within"
|
||||
/>
|
||||
<ProjectSidebarTags
|
||||
:project="project"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class=""
|
||||
/>
|
||||
<ProjectSidebarTags :project="project" class="" />
|
||||
<ProjectSidebarCreators
|
||||
:organization="organization"
|
||||
:members="members"
|
||||
:org-link="(slug) => `/organization/${slug}`"
|
||||
:user-link="(username) => `/user/${username}`"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class=""
|
||||
/>
|
||||
<!-- TODO: Finish license modal and enable -->
|
||||
<ProjectSidebarDetails
|
||||
@@ -905,14 +659,13 @@
|
||||
:has-versions="versions.length > 0"
|
||||
:link-target="$external()"
|
||||
:show-followers="isServerProject"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class=""
|
||||
/>
|
||||
<div class="card flex-card experimental-styles-within">
|
||||
<h2>{{ formatMessage(detailsMessages.title) }}</h2>
|
||||
<div class="mt-4 flex flex-col gap-1">
|
||||
<h2 class="m-0 text-base text-primary">{{ formatMessage(detailsMessages.title) }}</h2>
|
||||
|
||||
<div class="details-list">
|
||||
<div v-if="projectV3Loaded && !isServerProject" class="details-list__item">
|
||||
<BookTextIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{ formatMessage(messages.licensedLabel) }}
|
||||
<a
|
||||
@@ -923,7 +676,6 @@
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
{{ licenseIdDisplay }}
|
||||
<ExternalIcon aria-hidden="true" class="external-icon ml-1 mt-[-1px] inline" />
|
||||
</a>
|
||||
<span
|
||||
v-else-if="
|
||||
@@ -940,7 +692,6 @@
|
||||
</div>
|
||||
|
||||
<div v-if="isServerProject" class="details-list__item">
|
||||
<HeartIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
capitalizeString(
|
||||
@@ -956,7 +707,6 @@
|
||||
v-tooltip="formatDateTime(project.approved)"
|
||||
class="details-list__item"
|
||||
>
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
capitalizeString(
|
||||
@@ -969,7 +719,6 @@
|
||||
</div>
|
||||
|
||||
<div v-else v-tooltip="formatDateTime(project.published)" class="details-list__item">
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
capitalizeString(formatMessage(detailsMessages.created, { date: createdDate }))
|
||||
@@ -982,7 +731,6 @@
|
||||
v-tooltip="formatDateTime(project.queued)"
|
||||
class="details-list__item"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
capitalizeString(
|
||||
@@ -999,7 +747,6 @@
|
||||
v-tooltip="formatDateTime(project.updated)"
|
||||
class="details-list__item"
|
||||
>
|
||||
<VersionIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
capitalizeString(formatMessage(detailsMessages.updated, { date: updatedDate }))
|
||||
@@ -1011,8 +758,10 @@
|
||||
</div>
|
||||
|
||||
<div class="normal-page__content">
|
||||
<div class="overflow-x-auto"><NavTabs :links="navLinks" replace class="mb-4" /></div>
|
||||
<NuxtPage @on-download="triggerDownloadAnimation" @delete-version="deleteVersion" />
|
||||
<div class="overflow-x-auto"><NavTabs :links="navLinks" replace /></div>
|
||||
<div class="mx-4 rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3">
|
||||
<NuxtPage @on-download="triggerDownloadAnimation" @delete-version="deleteVersion" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1039,31 +788,19 @@
|
||||
<script setup>
|
||||
import {
|
||||
BookmarkIcon,
|
||||
BookTextIcon,
|
||||
CalendarIcon,
|
||||
ChartIcon,
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
ClipboardCopyIcon,
|
||||
DownloadIcon,
|
||||
ExternalIcon,
|
||||
GameIcon,
|
||||
HeartIcon,
|
||||
InfoIcon,
|
||||
ListIcon,
|
||||
ModrinthIcon,
|
||||
MoreVerticalIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
ReportIcon,
|
||||
ScaleIcon,
|
||||
ScanEyeIcon,
|
||||
SearchIcon,
|
||||
ServerPlusIcon,
|
||||
SettingsIcon,
|
||||
VersionIcon,
|
||||
WrenchIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
@@ -1075,11 +812,9 @@ import {
|
||||
getTagMessage,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
NavTabs,
|
||||
NewModal,
|
||||
OpenInAppModal,
|
||||
OverflowMenu,
|
||||
PopoutMenu,
|
||||
ProjectBackgroundGradient,
|
||||
ProjectEnvironmentModal,
|
||||
@@ -1094,10 +829,8 @@ import {
|
||||
ScrollablePanel,
|
||||
ServersPromo,
|
||||
StyledInput,
|
||||
TagItem,
|
||||
useDebugLogger,
|
||||
useFormatDateTime,
|
||||
useFormatPrice,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
@@ -1106,7 +839,6 @@ import { capitalizeString, formatProjectType, renderString } from '@modrinth/uti
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import dayjs from 'dayjs'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import { nextTick, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import { navigateTo } from '#app'
|
||||
@@ -1121,7 +853,6 @@ import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
|
||||
import { versionQueryOptions } from '~/composables/queries/version'
|
||||
import { userCollectProject, userFollowProject } from '~/composables/user.js'
|
||||
import { useModerationStore } from '~/store/moderation.ts'
|
||||
import { reportProject } from '~/utils/report-helpers.ts'
|
||||
|
||||
definePageMeta({
|
||||
@@ -1130,8 +861,6 @@ definePageMeta({
|
||||
|
||||
const data = useNuxtApp()
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
const moderationStore = useModerationStore()
|
||||
const notifications = injectNotificationManager()
|
||||
const { addNotification } = notifications
|
||||
|
||||
@@ -1143,7 +872,6 @@ const flags = useFeatureFlags()
|
||||
const cosmetics = useCosmetics()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
@@ -1151,7 +879,6 @@ const formatDateTime = useFormatDateTime({
|
||||
|
||||
const debug = useDebugLogger('DownloadModal')
|
||||
|
||||
const settingsModal = ref()
|
||||
const downloadModal = ref()
|
||||
const openInAppModal = ref()
|
||||
const overTheTopDownloadAnimation = ref()
|
||||
@@ -2287,11 +2014,6 @@ const description = computed(() =>
|
||||
: '',
|
||||
)
|
||||
|
||||
const canCreateServerFrom = computed(() => {
|
||||
if (!project.value) return false
|
||||
return project.value.project_type === 'modpack' && project.value.server_side !== 'unsupported'
|
||||
})
|
||||
|
||||
if (!route.name.startsWith('type-id-settings')) {
|
||||
useSeoMeta({
|
||||
title: () => title.value,
|
||||
@@ -2479,14 +2201,6 @@ async function deleteGalleryItem(imageUrl) {
|
||||
})
|
||||
}
|
||||
|
||||
async function copyId() {
|
||||
await navigator.clipboard.writeText(project.value.id)
|
||||
}
|
||||
|
||||
async function copyPermalink() {
|
||||
await navigator.clipboard.writeText(`${config.public.siteUrl}/project/${project.value.id}`)
|
||||
}
|
||||
|
||||
const collapsedChecklist = ref(false)
|
||||
|
||||
const showModerationChecklist = useLocalStorage(
|
||||
@@ -2684,7 +2398,7 @@ provideProjectPageContext({
|
||||
}
|
||||
|
||||
:deep(.accordion-with-bg) {
|
||||
@apply rounded-2xl bg-bg p-2;
|
||||
@apply rounded-[4px] bg-bg p-2;
|
||||
--scrollable-pane-bg: var(--color-bg);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +55,9 @@
|
||||
</div>
|
||||
<a
|
||||
:href="version.primaryFile?.url"
|
||||
class="iconified-button download"
|
||||
class="text-link"
|
||||
:title="`Download ${version.name}`"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
@@ -85,7 +84,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import { SpinnerIcon } from '@modrinth/assets'
|
||||
import {
|
||||
injectModrinthClient,
|
||||
injectProjectPageContext,
|
||||
@@ -287,6 +286,10 @@ function updateQuery(newQueries) {
|
||||
|
||||
.markdown-body {
|
||||
margin: 0.5rem 0.5rem 0 0;
|
||||
|
||||
ul {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.version-header {
|
||||
@@ -309,7 +312,7 @@ function updateQuery(newQueries) {
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-lg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h2,
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
class="expanded-image-modal"
|
||||
@click="expandedGalleryItem = null"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="content relative">
|
||||
<img
|
||||
class="image"
|
||||
:class="{ 'zoomed-in': zoomedIn }"
|
||||
@@ -144,7 +144,13 @@
|
||||
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
||||
@click.stop
|
||||
/>
|
||||
|
||||
<div class="absolute right-1 top-1">
|
||||
<ButtonStyled circular>
|
||||
<button @click="expandedGalleryItem = null">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="floating" @click.stop>
|
||||
<div class="text">
|
||||
<h2 v-if="expandedGalleryItem.title">
|
||||
@@ -154,59 +160,25 @@
|
||||
{{ expandedGalleryItem.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<button class="close circle-button" @click="expandedGalleryItem = null">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
<a
|
||||
class="open circle-button"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem?.raw_url
|
||||
? expandedGalleryItem?.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
<button class="circle-button" @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="previous circle-button"
|
||||
@click="previousImage()"
|
||||
>
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="next circle-button"
|
||||
@click="nextImage()"
|
||||
>
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentMember && filteredGallery.length" class="card header-buttons">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
:accept="acceptFileTypes"
|
||||
prompt="Upload an image"
|
||||
aria-label="Upload an image"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
|
||||
@change="handleFiles"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<span class="indicator">
|
||||
<ButtonStyled color="brand">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
:accept="acceptFileTypes"
|
||||
prompt="Upload an image"
|
||||
aria-label="Upload an image"
|
||||
class="button-like"
|
||||
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
|
||||
@change="handleFiles"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<span class="flex items-center gap-1">
|
||||
<InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page
|
||||
</span>
|
||||
<DropArea
|
||||
@@ -233,14 +205,10 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-bottom">
|
||||
<div class="gallery-created">
|
||||
<CalendarIcon aria-hidden="true" aria-label="Date created" />
|
||||
{{ formatDate(item.created) }}
|
||||
</div>
|
||||
<div v-if="currentMember" class="gallery-buttons input-group">
|
||||
<div class="mt-1">
|
||||
<div v-if="currentMember" class="flex gap-3">
|
||||
<button
|
||||
class="iconified-button"
|
||||
class="m-0 w-fit bg-transparent p-0 text-link"
|
||||
@click="
|
||||
() => {
|
||||
resetEdit()
|
||||
@@ -253,11 +221,10 @@
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
class="iconified-button"
|
||||
class="m-0 w-fit bg-transparent p-0 text-link"
|
||||
@click="
|
||||
() => {
|
||||
deleteIndex = index
|
||||
@@ -265,8 +232,7 @@
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -286,42 +252,28 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
CalendarIcon,
|
||||
ContractIcon,
|
||||
EditIcon,
|
||||
ExpandIcon,
|
||||
ExternalIcon,
|
||||
ImageIcon,
|
||||
InfoIcon,
|
||||
LeftArrowIcon,
|
||||
PlusIcon,
|
||||
RightArrowIcon,
|
||||
SaveIcon,
|
||||
StarIcon,
|
||||
TransferIcon,
|
||||
TrashIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
DropArea,
|
||||
FileInput,
|
||||
injectProjectPageContext,
|
||||
NewModal as Modal,
|
||||
StyledInput,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import { isPermission } from '~/utils/permissions.ts'
|
||||
|
||||
const formatDate = useFormatDateTime({
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
|
||||
// Single DI injection
|
||||
const {
|
||||
projectV2: project,
|
||||
@@ -531,7 +483,7 @@ async function deleteGalleryImage() {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000000;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -585,7 +537,8 @@ async function deleteGalleryImage() {
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: calc(100vw - 2 * var(--spacing-card-lg));
|
||||
max-height: calc(100vh - 2 * var(--spacing-card-lg));
|
||||
border-radius: var(--size-rounded-card);
|
||||
border-radius: 0;
|
||||
border: 2px solid black;
|
||||
|
||||
&.zoomed-in {
|
||||
object-fit: cover;
|
||||
@@ -607,17 +560,6 @@ async function deleteGalleryImage() {
|
||||
opacity: 1;
|
||||
padding: 2rem 2rem 0 2rem;
|
||||
|
||||
&:not(&:hover) {
|
||||
opacity: 0.4;
|
||||
.text {
|
||||
transform: translateY(2.5rem) scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
.controls {
|
||||
transform: translateY(0.25rem) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -625,19 +567,19 @@ async function deleteGalleryImage() {
|
||||
transition:
|
||||
opacity 0.25s ease-in-out,
|
||||
transform 0.25s ease-in-out;
|
||||
text-shadow: 1px 1px 10px #000000d4;
|
||||
text-shadow: 2px 2px 1px #000000d4;
|
||||
margin-bottom: 0.25rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
h2 {
|
||||
color: var(--dark-color-text-dark);
|
||||
color: #ffffff;
|
||||
font-size: 1.25rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--dark-color-text);
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -681,7 +623,7 @@ async function deleteGalleryImage() {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-radius: var(--size-rounded-card) var(--size-rounded-card) 0 0;
|
||||
border-radius: 4px;
|
||||
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
@@ -689,16 +631,18 @@ async function deleteGalleryImage() {
|
||||
|
||||
.gallery-body {
|
||||
width: calc(100% - 2 * var(--spacing-card-md));
|
||||
padding: var(--spacing-card-sm) var(--spacing-card-md);
|
||||
padding-top: 0.5rem;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
.gallery-info {
|
||||
h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<section class="normal-page__content">
|
||||
<div v-if="projectV2.body" class="card">
|
||||
<div v-if="projectV2.body">
|
||||
<ProjectPageDescription :description="projectV2.body" />
|
||||
</div>
|
||||
<p v-else class="ml-2">
|
||||
|
||||
@@ -590,14 +590,14 @@ onUnmounted(() => {
|
||||
gap: 0.5rem;
|
||||
|
||||
h2 {
|
||||
color: var(--dark-color-text-dark);
|
||||
color: #ffffff;
|
||||
font-size: 1.25rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--dark-color-text);
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<label
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-2xl border-dashed border-surface-5 transition-colors"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-[4px] border-dashed border-surface-5 transition-colors"
|
||||
:class="
|
||||
!deletedBanner && (bannerPreview || bannerGalleryImage?.url)
|
||||
? 'border-none'
|
||||
@@ -128,7 +128,7 @@
|
||||
>
|
||||
<div
|
||||
v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)"
|
||||
class="relative h-full w-full overflow-hidden rounded-2xl"
|
||||
class="relative h-full w-full overflow-hidden rounded-[4px]"
|
||||
>
|
||||
<img
|
||||
:src="bannerPreview || bannerGalleryImage?.url"
|
||||
|
||||
@@ -43,21 +43,17 @@
|
||||
:open-modal="currentMember ? () => handleOpenCreateVersionModal() : undefined"
|
||||
>
|
||||
<template #actions="{ version }">
|
||||
<ButtonStyled circular type="transparent">
|
||||
<ButtonStyled color="brand" size="small">
|
||||
<a
|
||||
v-tooltip="`Download`"
|
||||
:href="getPrimaryFile(version).url"
|
||||
class="hover:!bg-button-bg [&>svg]:!text-green"
|
||||
aria-label="Download"
|
||||
@click="emit('onDownload')"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="currentMember" circular type="transparent">
|
||||
<ButtonStyled v-if="currentMember" size="small">
|
||||
<OverflowMenu
|
||||
v-tooltip="'Edit version'"
|
||||
class="hover:!bg-button-bg"
|
||||
:dropdown-id="`${baseDropdownId}-edit-${version.id}`"
|
||||
:options="[
|
||||
{
|
||||
@@ -75,7 +71,7 @@
|
||||
]"
|
||||
aria-label="Edit version"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
Edit
|
||||
<template #edit-files>
|
||||
<FileIcon aria-hidden="true" />
|
||||
Edit files
|
||||
@@ -90,142 +86,6 @@
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<OverflowMenu
|
||||
v-tooltip="'More options'"
|
||||
class="hover:!bg-button-bg"
|
||||
:dropdown-id="`${baseDropdownId}-${version.id}`"
|
||||
:options="[
|
||||
{
|
||||
id: 'download',
|
||||
color: 'primary',
|
||||
hoverFilled: true,
|
||||
link: getPrimaryFile(version).url,
|
||||
action: () => {
|
||||
emit('onDownload')
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'new-tab',
|
||||
action: () => {},
|
||||
link: `/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/version/${encodeURI(version.displayUrlEnding)}`,
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
id: 'copy-link',
|
||||
action: () =>
|
||||
copyToClipboard(
|
||||
`https://modrinth.com/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/version/${encodeURI(version.displayUrlEnding)}`,
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'share',
|
||||
action: () => {},
|
||||
shown: false,
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
color: 'red',
|
||||
hoverFilled: true,
|
||||
action: () =>
|
||||
auth.user ? reportVersion(version.id) : navigateTo('/auth/sign-in'),
|
||||
shown: !currentMember,
|
||||
},
|
||||
{ divider: true, shown: currentMember || flags.developerMode },
|
||||
{
|
||||
id: 'copy-id',
|
||||
action: () => {
|
||||
copyToClipboard(version.id)
|
||||
},
|
||||
shown: currentMember || flags.developerMode,
|
||||
},
|
||||
{
|
||||
id: 'copy-maven',
|
||||
action: () => {
|
||||
copyToClipboard(`maven.modrinth:${project.slug}:${version.id}`)
|
||||
},
|
||||
shown: flags.developerMode,
|
||||
},
|
||||
{ divider: true, shown: !!currentMember },
|
||||
{
|
||||
id: 'edit-metadata',
|
||||
action: () => handleOpenEditVersionModal(version.id, project.id, 'metadata'),
|
||||
shown: !!currentMember,
|
||||
},
|
||||
{
|
||||
id: 'edit-details',
|
||||
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-details'),
|
||||
shown: !!currentMember,
|
||||
},
|
||||
{
|
||||
id: 'edit-files',
|
||||
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-files'),
|
||||
shown: !!currentMember,
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
color: 'red',
|
||||
hoverFilled: true,
|
||||
action: () => {
|
||||
selectedVersion = version.id
|
||||
deleteVersionModal?.show()
|
||||
},
|
||||
shown: !!currentMember,
|
||||
},
|
||||
]"
|
||||
aria-label="More options"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
<template #download>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</template>
|
||||
<template #new-tab>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
Open in new tab
|
||||
</template>
|
||||
<template #copy-link>
|
||||
<LinkIcon aria-hidden="true" />
|
||||
Copy link
|
||||
</template>
|
||||
<template #share>
|
||||
<ShareIcon aria-hidden="true" />
|
||||
Share
|
||||
</template>
|
||||
<template #report>
|
||||
<ReportIcon aria-hidden="true" />
|
||||
Report
|
||||
</template>
|
||||
<template #edit-files>
|
||||
<FileIcon aria-hidden="true" />
|
||||
Edit files
|
||||
</template>
|
||||
<template #edit-details>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
Edit details
|
||||
</template>
|
||||
<template #edit-metadata>
|
||||
<BoxIcon aria-hidden="true" />
|
||||
Edit metadata
|
||||
</template>
|
||||
<template #delete>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Delete
|
||||
</template>
|
||||
<template #copy-id>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
Copy ID
|
||||
</template>
|
||||
<template #copy-maven>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
Copy Maven coordinates
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</ProjectPageVersions>
|
||||
<template v-else>
|
||||
@@ -242,21 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
BoxIcon,
|
||||
ClipboardCopyIcon,
|
||||
DownloadIcon,
|
||||
EditIcon,
|
||||
ExternalIcon,
|
||||
FileIcon,
|
||||
InfoIcon,
|
||||
LinkIcon,
|
||||
MoreVerticalIcon,
|
||||
ReportIcon,
|
||||
ShareIcon,
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { BoxIcon, FileIcon, InfoIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
@@ -269,11 +115,9 @@ import {
|
||||
import { onMounted, useTemplateRef } from 'vue'
|
||||
|
||||
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
|
||||
import { reportVersion } from '~/utils/report-helpers.ts'
|
||||
|
||||
const tags = useGeneratedState()
|
||||
const flags = useFeatureFlags()
|
||||
const auth = await useAuth()
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
@@ -313,10 +157,6 @@ function getPrimaryFile(version) {
|
||||
return version.files.find((x) => x.primary) || version.files[0]
|
||||
}
|
||||
|
||||
async function copyToClipboard(text) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
}
|
||||
|
||||
async function deleteVersion() {
|
||||
const id = selectedVersion.value
|
||||
if (!id) return
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
<div
|
||||
v-for="notice in notices"
|
||||
:key="`notice-${notice.id}`"
|
||||
class="col-span-full grid grid-cols-subgrid gap-4 rounded-2xl bg-bg-raised p-4"
|
||||
class="col-span-full grid grid-cols-subgrid gap-4 rounded-[4px] bg-bg-raised p-4"
|
||||
>
|
||||
<div class="col-span-full grid grid-cols-subgrid items-center gap-4">
|
||||
<div>
|
||||
|
||||
+131
-2421
File diff suppressed because one or more lines are too long
@@ -8,30 +8,19 @@ useSeoMeta({
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<NuxtPage class="auth-container universal-card" />
|
||||
<div class="px-4 py-6">
|
||||
<NuxtPage
|
||||
class="auth-surface mx-auto flex max-w-lg flex-col gap-4 border border-solid border-[#c3c3c3] bg-[#EFEFEF] p-4 [&_h1]:m-0 [&_h1]:text-3xl [&_h1]:leading-tight [&_h1]:text-[var(--color-contrast)]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.auth-container {
|
||||
width: 26rem;
|
||||
max-width: calc(100% - 2rem);
|
||||
margin: 1rem auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.auth-container h1 {
|
||||
font-size: var(--font-size-xl);
|
||||
margin: 0 0 -1rem 0;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.auth-container p {
|
||||
.auth-surface p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-container .btn {
|
||||
.auth-surface .btn {
|
||||
font-weight: 700;
|
||||
min-height: 2.5rem;
|
||||
text-decoration: none;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="error" class="oauth-items">
|
||||
<div v-if="error" class="flex flex-col gap-4">
|
||||
<div>
|
||||
<h1>{{ formatMessage(commonMessages.errorLabel) }}</h1>
|
||||
</div>
|
||||
@@ -9,21 +9,17 @@
|
||||
{{ error.data?.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="app && createdBy && authorizationData" class="oauth-items">
|
||||
<div class="connected-items">
|
||||
<div class="profile-pics">
|
||||
<Avatar size="md" :src="app.icon_url" />
|
||||
<!-- <img class="profile-pic" :src="app.icon_url" alt="User profile picture" /> -->
|
||||
<div class="connection-indicator">→</div>
|
||||
<Avatar size="md" circle :src="auth.user.avatar_url" />
|
||||
<!-- <img class="profile-pic" :src="auth.user.avatar_url" alt="User profile picture" /> -->
|
||||
</div>
|
||||
<div v-else-if="app && createdBy && authorizationData" class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-evenly gap-2">
|
||||
<Avatar size="md" :src="app.icon_url" />
|
||||
<span class="select-none text-2xl text-brand" aria-hidden="true">→</span>
|
||||
<Avatar size="md" circle :src="auth.user.avatar_url" />
|
||||
</div>
|
||||
<div class="title">
|
||||
<div class="text-center">
|
||||
<h1>{{ formatMessage(messages.title, { appName: app.name }) }}</h1>
|
||||
</div>
|
||||
<div class="auth-info">
|
||||
<div class="scope-heading">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="text-sm">
|
||||
<IntlFormatted
|
||||
:message-id="messages.appInfo"
|
||||
:values="{
|
||||
@@ -43,32 +39,43 @@
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</div>
|
||||
<div class="scope-items">
|
||||
<div v-for="scopeItem in scopeDefinitions" :key="scopeItem">
|
||||
<div class="scope-item">
|
||||
<div class="scope-icon">
|
||||
<CheckIcon />
|
||||
</div>
|
||||
{{ scopeItem }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="m-0 flex list-none flex-col gap-2 p-0">
|
||||
<li
|
||||
v-for="scopeItem in scopeDefinitions"
|
||||
:key="scopeItem"
|
||||
class="flex items-start gap-2 border border-solid border-[#96CEE0] bg-[#E6F1F5] p-2 text-sm"
|
||||
>
|
||||
<CheckIcon class="mt-0.5 size-4 shrink-0 text-secondary" aria-hidden="true" />
|
||||
<span>{{ scopeItem }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button class="wide-button" large :action="onReject" :disabled="pending">
|
||||
<div class="flex flex-row flex-wrap justify-center gap-2">
|
||||
<Button
|
||||
class="!w-full min-w-[8rem] flex-1 sm:flex-none"
|
||||
large
|
||||
:action="onReject"
|
||||
:disabled="pending"
|
||||
>
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.decline) }}
|
||||
</Button>
|
||||
<Button class="wide-button" color="primary" large :action="onAuthorize" :disabled="pending">
|
||||
<Button
|
||||
class="!w-full min-w-[8rem] flex-1 sm:flex-none"
|
||||
color="primary"
|
||||
large
|
||||
:action="onAuthorize"
|
||||
:disabled="pending"
|
||||
>
|
||||
<CheckIcon />
|
||||
{{ formatMessage(messages.authorize) }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="redirection-notice">
|
||||
<p class="redirect-instructions">
|
||||
<div class="flex flex-col gap-1 text-center">
|
||||
<p class="text-sm">
|
||||
<IntlFormatted :message-id="messages.redirectUrl" :values="{ url: redirectUri }">
|
||||
<template #redirect-url="{ children }">
|
||||
<span class="redirect-url">
|
||||
<span class="font-bold">
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -243,130 +250,3 @@ definePageMeta({
|
||||
middleware: 'auth',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.oauth-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-xl);
|
||||
}
|
||||
|
||||
.scope-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.scope-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.scope-icon {
|
||||
display: flex;
|
||||
|
||||
color: var(--color-raised-bg);
|
||||
background-color: var(--color-green);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
padding: var(--gap-xs);
|
||||
}
|
||||
.title {
|
||||
margin-inline: auto;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
.redirection-notice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-xs);
|
||||
text-align: center;
|
||||
|
||||
.redirect-instructions {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.redirect-url {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.wide-button {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-xs);
|
||||
justify-content: center;
|
||||
}
|
||||
.auth-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.scope-heading {
|
||||
margin-bottom: var(--gap-sm);
|
||||
}
|
||||
|
||||
.profile-pics {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.connection-indicator {
|
||||
// Make sure the text sits in the middle and is centered.
|
||||
// Make the text large, and make sure it's not selectable.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
user-select: none;
|
||||
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-pic {
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
border-radius: 50%;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.dotted-border-line {
|
||||
width: 75%;
|
||||
border: 0.1rem dashed var(--color-divider);
|
||||
}
|
||||
|
||||
.connected-items {
|
||||
// Display dotted-border-line under profile-pics and centered behind them
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 1rem;
|
||||
|
||||
// Display profile-pics on top of dotted-border-line
|
||||
.profile-pics {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// Display dotted-border-line behind profile-pics
|
||||
.dotted-border-line {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,41 +23,56 @@
|
||||
@keyup.enter="begin2FASignIn"
|
||||
/>
|
||||
|
||||
<button class="btn btn-primary continue-btn" @click="begin2FASignIn">
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button type="button" class="w-full" @click="begin2FASignIn">
|
||||
{{ formatMessage(commonMessages.signInButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1>{{ formatMessage(messages.signInWithLabel) }}</h1>
|
||||
<h1 class="!text-xl">{{ formatMessage(messages.signInWithLabel) }}</h1>
|
||||
|
||||
<section class="third-party">
|
||||
<a class="btn" :href="getAuthUrl('discord', redirectTarget)">
|
||||
<DiscordColorIcon />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('github', redirectTarget)">
|
||||
<GitHubColorIcon />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)">
|
||||
<MicrosoftColorIcon />
|
||||
<span>Microsoft</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('google', redirectTarget)">
|
||||
<GoogleColorIcon />
|
||||
<span>Google</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('steam', redirectTarget)">
|
||||
<SteamColorIcon />
|
||||
<span>Steam</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)">
|
||||
<GitLabColorIcon />
|
||||
<span>GitLab</span>
|
||||
</a>
|
||||
<ButtonStyled>
|
||||
<a class="w-full" :href="getAuthUrl('discord', redirectTarget)">
|
||||
<DiscordColorIcon />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a class="w-full" :href="getAuthUrl('github', redirectTarget)">
|
||||
<GitHubColorIcon />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a class="w-full" :href="getAuthUrl('microsoft', redirectTarget)">
|
||||
<MicrosoftColorIcon />
|
||||
<span>Microsoft</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a class="w-full" :href="getAuthUrl('google', redirectTarget)">
|
||||
<GoogleColorIcon />
|
||||
<span>Google</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a class="w-full" :href="getAuthUrl('steam', redirectTarget)">
|
||||
<SteamColorIcon />
|
||||
<span>Steam</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a class="w-full" :href="getAuthUrl('gitlab', redirectTarget)">
|
||||
<GitLabColorIcon />
|
||||
<span>GitLab</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<h1>{{ formatMessage(messages.usePasswordLabel) }}</h1>
|
||||
<h1 class="!text-xl">{{ formatMessage(messages.usePasswordLabel) }}</h1>
|
||||
|
||||
<section class="auth-form">
|
||||
<label for="email" hidden>{{ formatMessage(commonMessages.emailUsernameLabel) }}</label>
|
||||
@@ -85,13 +100,17 @@
|
||||
|
||||
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
|
||||
|
||||
<button
|
||||
class="btn btn-primary continue-btn centered-btn"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="beginPasswordSignIn()"
|
||||
>
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="beginPasswordSignIn()"
|
||||
>
|
||||
{{ formatMessage(commonMessages.signInButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<div class="auth-form__additional-options">
|
||||
<IntlFormatted :message-id="messages.additionalOptionsLabel">
|
||||
@@ -137,6 +156,7 @@ import {
|
||||
SteamColorIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
<template>
|
||||
<div class="welcome-box has-bot">
|
||||
<img :src="WavingRinthbot" alt="Waving Modrinth Bot" class="welcome-box__waving-bot" />
|
||||
<div class="welcome-box__top-glow" />
|
||||
<div class="welcome-box__body">
|
||||
<h1 class="welcome-box__title">
|
||||
{{ formatMessage(messages.welcomeLongTitle) }}
|
||||
</h1>
|
||||
<div class="relative flex flex-col gap-4 pt-12 sm:pt-10">
|
||||
<img
|
||||
:src="WavingRinthbot"
|
||||
alt="Waving Modrinth Bot"
|
||||
class="absolute right-8 top-0 h-[70px] w-auto sm:right-20 sm:h-[112px]"
|
||||
/>
|
||||
|
||||
<p class="welcome-box__subtitle">
|
||||
<IntlFormatted :message-id="messages.welcomeDescription">
|
||||
<template #bold="{ children }">
|
||||
<strong>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</strong>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<h1 class="m-0 pr-24 text-3xl leading-tight">
|
||||
{{ formatMessage(messages.welcomeLongTitle) }}
|
||||
</h1>
|
||||
|
||||
<Checkbox
|
||||
v-model="subscribe"
|
||||
class="subscribe-btn"
|
||||
:label="formatMessage(messages.subscribeCheckbox)"
|
||||
:description="formatMessage(messages.subscribeCheckbox)"
|
||||
/>
|
||||
<p class="text-base leading-snug">
|
||||
<IntlFormatted :message-id="messages.welcomeDescription">
|
||||
<template #bold="{ children }">
|
||||
<strong>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</strong>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
|
||||
<button class="btn btn-primary centered-btn" @click="continueSignUp">
|
||||
{{ formatMessage(commonMessages.continueButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
<Checkbox
|
||||
v-model="subscribe"
|
||||
class="subscribe-btn"
|
||||
:label="formatMessage(messages.subscribeCheckbox)"
|
||||
:description="formatMessage(messages.subscribeCheckbox)"
|
||||
/>
|
||||
|
||||
<p class="tos-text">
|
||||
<IntlFormatted :message-id="messages.tosLabel">
|
||||
<template #terms-link="{ children }">
|
||||
<NuxtLink to="/legal/terms" class="text-link">
|
||||
<component :is="() => children" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template #privacy-policy-link="{ children }">
|
||||
<NuxtLink to="/legal/privacy" class="text-link">
|
||||
<component :is="() => children" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-primary centered-btn" @click="continueSignUp">
|
||||
{{ formatMessage(commonMessages.continueButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
|
||||
<p class="text-sm leading-normal">
|
||||
<IntlFormatted :message-id="messages.tosLabel">
|
||||
<template #terms-link="{ children }">
|
||||
<NuxtLink to="/legal/terms" class="text-link">
|
||||
<component :is="() => children" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template #privacy-policy-link="{ children }">
|
||||
<NuxtLink to="/legal/privacy" class="text-link">
|
||||
<component :is="() => children" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -119,84 +121,3 @@ async function continueSignUp() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.welcome-box {
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: var(--size-rounded-lg);
|
||||
padding: 1.75rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
box-shadow: var(--shadow-card);
|
||||
position: relative;
|
||||
|
||||
&.has-bot {
|
||||
margin-block: 120px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
a {
|
||||
color: var(--color-brand);
|
||||
font-weight: var(--weight-bold);
|
||||
&:hover,
|
||||
&:focus {
|
||||
filter: brightness(1.125);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&__waving-bot {
|
||||
--bot-height: 112px;
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--bot-height));
|
||||
right: 5rem;
|
||||
height: var(--bot-height);
|
||||
width: auto;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
--bot-height: 70px;
|
||||
right: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__top-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
opacity: 0.4;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 2rem,
|
||||
var(--color-green) calc(100% - 13rem),
|
||||
var(--color-green) calc(100% - 5rem),
|
||||
transparent calc(100% - 2rem)
|
||||
);
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: var(--text-32);
|
||||
font-weight: var(--weight-extrabold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: var(--text-18);
|
||||
}
|
||||
|
||||
.tos-text {
|
||||
font-size: var(--text-14);
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -254,7 +254,6 @@
|
||||
v-tooltip="formatDateTime(collection.created)"
|
||||
class="flex w-fit items-center gap-2"
|
||||
>
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
{{
|
||||
formatMessage(messages.createdAtLabel, {
|
||||
ago: formatRelativeTime(collection.created),
|
||||
@@ -266,7 +265,6 @@
|
||||
v-tooltip="formatDateTime(collection.updated)"
|
||||
class="flex w-fit items-center gap-2"
|
||||
>
|
||||
<UpdatedIcon aria-hidden="true" />
|
||||
{{
|
||||
formatMessage(messages.updatedAtLabel, {
|
||||
ago: formatRelativeTime(collection.updated),
|
||||
@@ -295,6 +293,7 @@
|
||||
<ProjectCardList
|
||||
v-if="projects && projects?.length > 0"
|
||||
:layout="cosmetics.searchDisplayMode.collection"
|
||||
class="rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3"
|
||||
>
|
||||
<ProjectCard
|
||||
v-for="project in (route.params.projectType !== undefined
|
||||
@@ -308,6 +307,7 @@
|
||||
.slice()
|
||||
.sort((a, b) => b.downloads - a.downloads)"
|
||||
:key="project.id"
|
||||
class="rounded-[4px] bg-white"
|
||||
:link="`/${project.project_type}/${project.slug ?? project.id}`"
|
||||
:title="project.title"
|
||||
:icon-url="project.icon_url"
|
||||
@@ -367,7 +367,6 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
CalendarIcon,
|
||||
ChevronLeftIcon,
|
||||
CompassIcon,
|
||||
EditIcon,
|
||||
@@ -378,7 +377,6 @@ import {
|
||||
SaveIcon,
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
UpdatedIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
}}</span>
|
||||
<div class="grid grid-cols-1 gap-x-4 gap-y-2 md:grid-cols-2 lg:grid-cols-3">
|
||||
<button
|
||||
class="relative flex flex-col overflow-hidden rounded-2xl bg-gradient-to-r from-green to-green-700 p-4 text-inverted shadow-md transition-all duration-200 hover:brightness-105 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:brightness-100 md:p-5"
|
||||
class="relative flex flex-col overflow-hidden rounded-[4px] bg-gradient-to-r from-green to-green-700 p-4 text-inverted transition-all duration-200 hover:brightness-105 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:brightness-100 md:p-5"
|
||||
:disabled="hasTinMismatch"
|
||||
@click="openWithdrawModal"
|
||||
>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="flex flex-col gap-3 rounded-2xl bg-surface-3 p-5">
|
||||
<div class="flex flex-col gap-3 rounded-[4px] bg-surface-3 p-5">
|
||||
<div class="flex w-full justify-between">
|
||||
<span class="my-auto font-medium">{{ formatMessage(messages.received) }}</span>
|
||||
<ArrowDownLeftIcon class="size-6" />
|
||||
@@ -33,7 +33,7 @@
|
||||
formatMoney(totalReceived)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 rounded-2xl bg-surface-3 p-5">
|
||||
<div class="flex flex-col gap-3 rounded-[4px] bg-surface-3 p-5">
|
||||
<div class="flex w-full justify-between">
|
||||
<span class="my-auto font-medium">{{ formatMessage(messages.withdrawn) }}</span>
|
||||
<ArrowUpRightIcon class="size-6" />
|
||||
@@ -42,7 +42,7 @@
|
||||
formatMoney(totalWithdrawn)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 rounded-2xl bg-surface-3 p-5">
|
||||
<div class="flex flex-col gap-3 rounded-[4px] bg-surface-3 p-5">
|
||||
<div class="flex w-full justify-between">
|
||||
<span class="my-auto font-medium">{{ formatMessage(messages.transactions) }}</span>
|
||||
<GenericListIcon class="size-6" />
|
||||
|
||||
@@ -4,7 +4,6 @@ import { commonProjectTypeCategoryMessages, NavTabs, useVIntl } from '@modrinth/
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
const cosmetics = useCosmetics()
|
||||
const route = useRoute()
|
||||
|
||||
const allowTabChanging = computed(() => !route.query.sid)
|
||||
@@ -48,16 +47,13 @@ const selectableProjectTypes = [
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="new-page sidebar" :class="{ 'alt-layout': !cosmetics.rightSearchLayout }">
|
||||
<section class="normal-page__header mb-4 flex flex-col gap-4">
|
||||
<div id="discover-header-prefix" class="empty:hidden"></div>
|
||||
<NavTabs
|
||||
v-if="!flags.projectTypesPrimaryNav && allowTabChanging"
|
||||
:links="selectableProjectTypes"
|
||||
replace
|
||||
class="hidden md:flex"
|
||||
/>
|
||||
</section>
|
||||
<div>
|
||||
<div id="discover-header-prefix" class="empty:hidden"></div>
|
||||
<NavTabs
|
||||
v-if="!flags.projectTypesPrimaryNav && allowTabChanging"
|
||||
:links="selectableProjectTypes"
|
||||
replace
|
||||
/>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,14 +4,10 @@ import {
|
||||
BookmarkIcon,
|
||||
CheckIcon,
|
||||
DownloadIcon,
|
||||
FilterIcon,
|
||||
GameIcon,
|
||||
GridIcon,
|
||||
HeartIcon,
|
||||
ImageIcon,
|
||||
InfoIcon,
|
||||
LeftArrowIcon,
|
||||
ListIcon,
|
||||
MinecraftServerIcon,
|
||||
MoreVerticalIcon,
|
||||
SearchIcon,
|
||||
@@ -30,7 +26,6 @@ import {
|
||||
Pagination,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
SearchFilterControl,
|
||||
SearchSidebarFilter,
|
||||
type SortType,
|
||||
StyledInput,
|
||||
@@ -39,7 +34,6 @@ import {
|
||||
useServerSearch,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { capitalizeString, cycleValue } from '@modrinth/utils'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useThrottleFn, useTimeoutFn } from '@vueuse/core'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
@@ -49,7 +43,7 @@ import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||
import { projectQueryOptions } from '~/composables/queries/project'
|
||||
import { versionQueryOptions } from '~/composables/queries/version'
|
||||
import { withLabrinthCanaryHeader } from '~/helpers/canary.ts'
|
||||
import type { DisplayLocation, DisplayMode } from '~/plugins/cosmetics.ts'
|
||||
import type { DisplayMode } from '~/plugins/cosmetics.ts'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const debug = useDebugLogger('Discover')
|
||||
@@ -62,7 +56,6 @@ const filtersMenuOpen = ref(false)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const cosmetics = useCosmetics()
|
||||
const tags = useGeneratedState()
|
||||
const flags = useFeatureFlags()
|
||||
const auth = await useAuth()
|
||||
@@ -120,14 +113,7 @@ const isServerType = computed(() => currentType.value === 'server')
|
||||
const projectType = computed(() => tags.value.projectTypes.find((x) => x.id === currentType.value))
|
||||
const projectTypes = computed(() => (projectType.value ? [projectType.value.id] : []))
|
||||
|
||||
const resultsDisplayLocation = computed<DisplayLocation | undefined>(
|
||||
() => projectType.value?.id as DisplayLocation,
|
||||
)
|
||||
const resultsDisplayMode = computed<DisplayMode>(() =>
|
||||
resultsDisplayLocation.value
|
||||
? cosmetics.value.searchDisplayMode[resultsDisplayLocation.value]
|
||||
: 'list',
|
||||
)
|
||||
const resultsDisplayMode = computed<DisplayMode>(() => 'list')
|
||||
|
||||
const currentServerId = computed(() => queryAsString(route.query.sid) || null)
|
||||
const fromContext = computed(() => queryAsString(route.query.from) || null)
|
||||
@@ -618,28 +604,6 @@ watch([effectiveCurrentFilters], () => {
|
||||
|
||||
const throttledSearch = useThrottleFn(() => updateSearchResults(), 500, true)
|
||||
|
||||
function cycleSearchDisplayMode() {
|
||||
if (!resultsDisplayLocation.value) {
|
||||
// if no display location, abort
|
||||
return
|
||||
}
|
||||
cosmetics.value.searchDisplayMode[resultsDisplayLocation.value] = cycleValue(
|
||||
cosmetics.value.searchDisplayMode[resultsDisplayLocation.value],
|
||||
tags.value.projectViewModes.filter((x) => x !== 'grid'),
|
||||
)
|
||||
setClosestMaxResults()
|
||||
}
|
||||
|
||||
function setClosestMaxResults() {
|
||||
const maxResultsOptions = maxResultsForView.value[resultsDisplayMode.value] ?? [20]
|
||||
const currentMax = maxResults.value
|
||||
if (!maxResultsOptions.includes(currentMax)) {
|
||||
maxResults.value = maxResultsOptions.reduce((prev: number, curr: number) => {
|
||||
return Math.abs(curr - currentMax) <= Math.abs(prev - currentMax) ? curr : prev
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const ogTitle = computed(
|
||||
() =>
|
||||
`Search ${projectType.value?.display ?? 'project'}s${query.value ? ' | ' + query.value : ''}`,
|
||||
@@ -760,334 +724,288 @@ useSeoMeta({
|
||||
}}
|
||||
</h1>
|
||||
</Teleport>
|
||||
|
||||
<aside
|
||||
:class="{
|
||||
'normal-page__sidebar': true,
|
||||
}"
|
||||
aria-label="Filters"
|
||||
>
|
||||
<AdPlaceholder v-if="!auth.user && !serverData" />
|
||||
<div v-if="filtersMenuOpen" class="fixed inset-0 z-40 bg-bg"></div>
|
||||
<div
|
||||
class="flex flex-col gap-3"
|
||||
:class="{
|
||||
'fixed inset-0 z-50 m-4 mb-0 overflow-auto rounded-t-3xl bg-bg-raised': filtersMenuOpen,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="filtersMenuOpen"
|
||||
class="sticky top-0 z-10 mx-1 flex items-center justify-between gap-3 border-0 border-b-[1px] border-solid border-divider bg-bg-raised px-6 py-4"
|
||||
<div class="mx-4 rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3">
|
||||
<StyledInput
|
||||
v-model="query"
|
||||
:icon="SearchIcon"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
:placeholder="`Search ${projectType?.display ?? 'project'}s...`"
|
||||
clearable
|
||||
wrapper-class="w-full mb-2"
|
||||
input-class="!h-12"
|
||||
@input="throttledSearch()"
|
||||
@clear="updateSearchResults()"
|
||||
/>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<DropdownSelect
|
||||
v-slot="{ selected }"
|
||||
v-model="effectiveCurrentSortType"
|
||||
class="!w-auto flex-grow md:flex-grow-0"
|
||||
name="Sort by"
|
||||
:options="[...effectiveSortTypes]"
|
||||
:display-name="(option?: SortType) => option?.display"
|
||||
@change="updateSearchResults()"
|
||||
>
|
||||
<h3 class="m-0 text-lg text-contrast">Filters</h3>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
filtersMenuOpen = false
|
||||
scrollToTop('instant')
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div
|
||||
v-if="serverData && projectType?.id !== 'modpack'"
|
||||
class="card-shadow rounded-2xl bg-bg-raised p-4"
|
||||
<span>Sort by: </span>
|
||||
<span>{{ selected }}</span>
|
||||
</DropdownSelect>
|
||||
<DropdownSelect
|
||||
v-slot="{ selected }"
|
||||
v-model="maxResults"
|
||||
name="Max results"
|
||||
:options="currentMaxResultsOptions"
|
||||
:default-value="maxResults"
|
||||
class="!w-auto flex-grow md:flex-grow-0"
|
||||
@change="updateSearchResults()"
|
||||
>
|
||||
<Checkbox
|
||||
v-model="serverHideInstalled"
|
||||
label="Hide installed content"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="updateSearchResults()"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="isServerType">
|
||||
<SearchSidebarFilter
|
||||
v-for="filterType in serverFilterTypes.filter((f) => f.options.length > 0)"
|
||||
:key="`server-filter-${filterType.id}`"
|
||||
v-model:selected-filters="serverCurrentFilters"
|
||||
v-model:toggled-groups="serverToggledGroups"
|
||||
:provided-filters="[]"
|
||||
:filter-type="filterType"
|
||||
:class="
|
||||
filtersMenuOpen
|
||||
? 'border-0 border-b-[1px] border-solid border-divider last:border-b-0'
|
||||
: 'card-shadow rounded-2xl bg-bg-raised'
|
||||
"
|
||||
button-class="button-animation flex flex-col gap-1 px-6 py-4 w-full bg-transparent cursor-pointer border-none"
|
||||
content-class="mb-4 mx-3"
|
||||
inner-panel-class="p-1"
|
||||
:open-by-default="
|
||||
![
|
||||
'server_category_minecraft_server_meta',
|
||||
'server_category_minecraft_server_community',
|
||||
'server_game_version',
|
||||
'server_status',
|
||||
].includes(filterType.id)
|
||||
"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="m-0 text-lg">{{ filterType.formatted_name }}</h3>
|
||||
</template>
|
||||
</SearchSidebarFilter>
|
||||
</template>
|
||||
<template v-else>
|
||||
<SearchSidebarFilter
|
||||
v-for="filter in filters.filter((f) => f.display !== 'none')"
|
||||
:key="`filter-${filter.id}`"
|
||||
v-model:selected-filters="currentFilters"
|
||||
v-model:toggled-groups="toggledGroups"
|
||||
v-model:overridden-provided-filter-types="overriddenProvidedFilterTypes"
|
||||
:provided-filters="serverFilters"
|
||||
:filter-type="filter"
|
||||
:class="
|
||||
filtersMenuOpen
|
||||
? 'border-0 border-b-[1px] border-solid border-divider last:border-b-0'
|
||||
: 'card-shadow rounded-2xl bg-bg-raised'
|
||||
"
|
||||
button-class="button-animation flex flex-col gap-1 px-6 py-4 w-full bg-transparent cursor-pointer border-none"
|
||||
content-class="mb-4 mx-3"
|
||||
inner-panel-class="p-1"
|
||||
:open-by-default="!(currentType === 'shader' && filter.id === 'game_version')"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="m-0 text-lg">{{ filter.formatted_name }}</h3>
|
||||
</template>
|
||||
<template v-if="currentType === 'shader' && filter.id === 'game_version'" #prefix>
|
||||
<div class="mb-4 grid grid-cols-[auto_1fr] gap-2 px-3 text-sm font-medium text-blue">
|
||||
<InfoIcon class="mt-1 size-4" />
|
||||
<span> {{ formatMessage(messages.gameVersionShaderMessage) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #locked-game_version>
|
||||
{{ formatMessage(messages.gameVersionProvidedByServer) }}
|
||||
</template>
|
||||
<template #locked-mod_loader>
|
||||
{{ formatMessage(messages.modLoaderProvidedByServer) }}
|
||||
</template>
|
||||
<template #sync-button> {{ formatMessage(messages.syncFilterButton) }}</template>
|
||||
</SearchSidebarFilter>
|
||||
</template>
|
||||
<span>View: </span>
|
||||
<span>{{ selected }}</span>
|
||||
</DropdownSelect>
|
||||
</div>
|
||||
</aside>
|
||||
<section class="normal-page__content">
|
||||
<div class="flex flex-col gap-3">
|
||||
<StyledInput
|
||||
v-model="query"
|
||||
:icon="SearchIcon"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
:placeholder="`Search ${projectType?.display ?? 'project'}s...`"
|
||||
clearable
|
||||
wrapper-class="w-full"
|
||||
input-class="!h-12"
|
||||
@input="throttledSearch()"
|
||||
@clear="updateSearchResults()"
|
||||
/>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<DropdownSelect
|
||||
v-slot="{ selected }"
|
||||
v-model="effectiveCurrentSortType"
|
||||
class="!w-auto flex-grow md:flex-grow-0"
|
||||
name="Sort by"
|
||||
:options="[...effectiveSortTypes]"
|
||||
:display-name="(option?: SortType) => option?.display"
|
||||
@change="updateSearchResults()"
|
||||
</div>
|
||||
<div class="grid grid-cols-[300px,1fr]">
|
||||
<aside aria-label="Filters">
|
||||
<div v-if="filtersMenuOpen" class="fixed inset-0 z-40 bg-bg"></div>
|
||||
<div
|
||||
class="flex flex-col"
|
||||
:class="{
|
||||
'fixed inset-0 z-50 m-4 mb-0 overflow-auto rounded-t-3xl bg-bg-raised': filtersMenuOpen,
|
||||
'h-full border-0 border-r border-solid border-[#b5b5b5] pr-4': !filtersMenuOpen,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="filtersMenuOpen"
|
||||
class="sticky top-0 z-10 mx-1 flex items-center justify-between gap-3 border-0 border-b-[1px] border-solid border-divider bg-bg-raised px-6 py-4"
|
||||
>
|
||||
<span class="font-semibold text-primary">Sort by: </span>
|
||||
<span class="font-semibold text-secondary">{{ selected }}</span>
|
||||
</DropdownSelect>
|
||||
<DropdownSelect
|
||||
v-slot="{ selected }"
|
||||
v-model="maxResults"
|
||||
name="Max results"
|
||||
:options="currentMaxResultsOptions"
|
||||
:default-value="maxResults"
|
||||
class="!w-auto flex-grow md:flex-grow-0"
|
||||
@change="updateSearchResults()"
|
||||
>
|
||||
<span class="font-semibold text-primary">View: </span>
|
||||
<span class="font-semibold text-secondary">{{ selected }}</span>
|
||||
</DropdownSelect>
|
||||
<div class="lg:hidden">
|
||||
<ButtonStyled>
|
||||
<button @click="filtersMenuOpen = true">
|
||||
<FilterIcon />
|
||||
Filter results...
|
||||
<h3 class="m-0 text-lg text-contrast">Filters</h3>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
filtersMenuOpen = false
|
||||
scrollToTop('instant')
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
:v-tooltip="capitalizeString(resultsDisplayMode + ' view')"
|
||||
:aria-label="capitalizeString(resultsDisplayMode + ' view')"
|
||||
@click="cycleSearchDisplayMode()"
|
||||
>
|
||||
<GridIcon v-if="resultsDisplayMode === 'grid'" />
|
||||
<ImageIcon v-else-if="resultsDisplayMode === 'gallery'" />
|
||||
<ListIcon v-else />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<Pagination
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
class="mx-auto sm:ml-auto sm:mr-0"
|
||||
@switch-page="updateSearchResults"
|
||||
/>
|
||||
</div>
|
||||
<SearchFilterControl
|
||||
v-if="isServerType"
|
||||
v-model:selected-filters="serverCurrentFilters"
|
||||
:filters="serverFilterTypes"
|
||||
:provided-filters="[]"
|
||||
:overridden-provided-filter-types="[]"
|
||||
/>
|
||||
<SearchFilterControl
|
||||
v-else
|
||||
v-model:selected-filters="currentFilters"
|
||||
:filters="filters.filter((f) => f.display !== 'none')"
|
||||
:provided-filters="serverFilters"
|
||||
:overridden-provided-filter-types="overriddenProvidedFilterTypes"
|
||||
:provided-message="messages.providedByServer"
|
||||
/>
|
||||
<LogoAnimated v-if="searchLoading && !noLoad" />
|
||||
<div v-else-if="results && results.hits && results.hits.length === 0" class="no-results">
|
||||
<p>No results found for your query!</p>
|
||||
</div>
|
||||
<div v-else class="search-results-container">
|
||||
<ProjectCardList
|
||||
aria-label="Search results"
|
||||
:layout="
|
||||
resultsDisplayMode === 'grid' || resultsDisplayMode === 'gallery' ? 'grid' : 'list'
|
||||
"
|
||||
<AdPlaceholder v-if="!auth.user && !serverData" class="ml-2" />
|
||||
<div
|
||||
v-if="serverData && projectType?.id !== 'modpack'"
|
||||
class="card-shadow rounded-[4px] bg-bg-raised p-4"
|
||||
>
|
||||
<template v-if="isServerType">
|
||||
<ProjectCard
|
||||
v-for="result in serverResults?.hits"
|
||||
:key="`server-${result.project_id}`"
|
||||
:link="`/server/${result.slug ?? result.project_id}`"
|
||||
:title="result.name"
|
||||
:icon-url="result.icon_url || undefined"
|
||||
:summary="result.summary"
|
||||
:tags="result.categories"
|
||||
:server-online-players="result.minecraft_java_server?.ping?.data?.players_online ?? 0"
|
||||
:server-region="result.minecraft_server?.region"
|
||||
:server-recent-plays="result.minecraft_java_server?.verified_plays_2w ?? 0"
|
||||
:server-status-online="!!result.minecraft_java_server?.ping?.data"
|
||||
:server-modpack-content="getServerModpackContent(result)"
|
||||
is-server-project
|
||||
exclude-loaders
|
||||
:color="result.color ?? undefined"
|
||||
:banner="result.featured_gallery ?? undefined"
|
||||
:layout="
|
||||
resultsDisplayMode === 'grid' || resultsDisplayMode === 'gallery' ? 'grid' : 'list'
|
||||
"
|
||||
:max-tags="2"
|
||||
@mouseenter="handleServerProjectMouseEnter(result)"
|
||||
@mouseleave="handleProjectHoverEnd"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ProjectCard
|
||||
v-for="result in projectResults?.hits"
|
||||
:key="result.project_id"
|
||||
:link="`/${projectType?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`"
|
||||
:title="result.title"
|
||||
:icon-url="result.icon_url"
|
||||
:author="{ name: result.author, link: `/user/${result.author}` }"
|
||||
:date-updated="result.date_modified"
|
||||
:date-published="result.date_created"
|
||||
:displayed-date="effectiveCurrentSortType.name === 'newest' ? 'published' : 'updated'"
|
||||
:downloads="result.downloads"
|
||||
:summary="result.description"
|
||||
:tags="result.display_categories"
|
||||
:all-tags="result.categories"
|
||||
:deprioritized-tags="deprioritizedTags"
|
||||
:exclude-loaders="excludeLoaders"
|
||||
:followers="result.follows"
|
||||
:banner="result.featured_gallery ?? undefined"
|
||||
:color="result.color ?? undefined"
|
||||
:environment="
|
||||
['mod', 'modpack'].includes(currentType)
|
||||
? {
|
||||
clientSide: result.client_side as Labrinth.Projects.v2.Environment,
|
||||
serverSide: result.server_side as Labrinth.Projects.v2.Environment,
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
:layout="
|
||||
resultsDisplayMode === 'grid' || resultsDisplayMode === 'gallery' ? 'grid' : 'list'
|
||||
"
|
||||
@mouseenter="handleProjectMouseEnter(result)"
|
||||
@mouseleave="handleProjectHoverEnd"
|
||||
>
|
||||
<template v-if="flags.showDiscoverProjectButtons || serverData" #actions>
|
||||
<template v-if="flags.showDiscoverProjectButtons">
|
||||
<ButtonStyled color="brand">
|
||||
<button>
|
||||
<DownloadIcon />
|
||||
Download
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button>
|
||||
<HeartIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button>
|
||||
<BookmarkIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<button>
|
||||
<MoreVerticalIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else-if="serverData">
|
||||
<ButtonStyled color="brand" type="outlined">
|
||||
<button
|
||||
v-if="
|
||||
(result as InstallableSearchResult).installed ||
|
||||
(serverContentData &&
|
||||
(serverContentData.addons ?? []).find(
|
||||
(x) => x.project_id === result.project_id,
|
||||
)) ||
|
||||
serverData.upstream?.project_id === result.project_id
|
||||
"
|
||||
disabled
|
||||
>
|
||||
<CheckIcon />
|
||||
Installed
|
||||
</button>
|
||||
<button v-else-if="(result as InstallableSearchResult).installing" disabled>
|
||||
Installing...
|
||||
</button>
|
||||
<button v-else @click="serverInstall(result as InstallableSearchResult)">
|
||||
<DownloadIcon />
|
||||
Install
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
</ProjectCard>
|
||||
</template>
|
||||
</ProjectCardList>
|
||||
<Checkbox
|
||||
v-model="serverHideInstalled"
|
||||
label="Hide installed content"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="updateSearchResults()"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="isServerType">
|
||||
<SearchSidebarFilter
|
||||
v-for="filterType in serverFilterTypes.filter((f) => f.options.length > 0)"
|
||||
:key="`server-filter-${filterType.id}`"
|
||||
v-model:selected-filters="serverCurrentFilters"
|
||||
v-model:toggled-groups="serverToggledGroups"
|
||||
:provided-filters="[]"
|
||||
:filter-type="filterType"
|
||||
:class="
|
||||
filtersMenuOpen
|
||||
? 'border-0 border-b-[1px] border-solid border-divider last:border-b-0'
|
||||
: 'card-shadow rounded-[4px] bg-bg-raised'
|
||||
"
|
||||
button-class="flex flex-col gap-1 px-6 py-2 w-full bg-transparent cursor-pointer border-none"
|
||||
content-class="mb-4 mx-3"
|
||||
inner-panel-class="p-1"
|
||||
:open-by-default="
|
||||
![
|
||||
'server_category_minecraft_server_meta',
|
||||
'server_category_minecraft_server_community',
|
||||
'server_game_version',
|
||||
'server_status',
|
||||
].includes(filterType.id)
|
||||
"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="m-0 text-base">{{ filterType.formatted_name }}</h3>
|
||||
</template>
|
||||
</SearchSidebarFilter>
|
||||
</template>
|
||||
<template v-else>
|
||||
<SearchSidebarFilter
|
||||
v-for="filter in filters.filter((f) => f.display !== 'none')"
|
||||
:key="`filter-${filter.id}`"
|
||||
v-model:selected-filters="currentFilters"
|
||||
v-model:toggled-groups="toggledGroups"
|
||||
v-model:overridden-provided-filter-types="overriddenProvidedFilterTypes"
|
||||
:provided-filters="serverFilters"
|
||||
:filter-type="filter"
|
||||
:class="
|
||||
filtersMenuOpen
|
||||
? 'border-0 border-b-[1px] border-solid border-divider last:border-b-0'
|
||||
: ''
|
||||
"
|
||||
button-class="flex flex-col gap-1 px-4 py-2 w-full bg-transparent cursor-pointer border-none"
|
||||
content-class="mb-4 mx-2"
|
||||
inner-panel-class="p-1"
|
||||
:open-by-default="!(currentType === 'shader' && filter.id === 'game_version')"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="m-0 text-base font-normal">{{ filter.formatted_name }}</h3>
|
||||
</template>
|
||||
<template v-if="currentType === 'shader' && filter.id === 'game_version'" #prefix>
|
||||
<div class="mb-4 grid grid-cols-[auto_1fr] gap-2 px-3 text-sm font-medium text-blue">
|
||||
<InfoIcon class="mt-1 size-4" />
|
||||
<span> {{ formatMessage(messages.gameVersionShaderMessage) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #locked-game_version>
|
||||
{{ formatMessage(messages.gameVersionProvidedByServer) }}
|
||||
</template>
|
||||
<template #locked-mod_loader>
|
||||
{{ formatMessage(messages.modLoaderProvidedByServer) }}
|
||||
</template>
|
||||
<template #sync-button> {{ formatMessage(messages.syncFilterButton) }}</template>
|
||||
</SearchSidebarFilter>
|
||||
</template>
|
||||
</div>
|
||||
<div class="pagination-after">
|
||||
<pagination
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
class="justify-end"
|
||||
@switch-page="updateSearchResults"
|
||||
/>
|
||||
</aside>
|
||||
<section>
|
||||
<div class="flex flex-col gap-3">
|
||||
<LogoAnimated v-if="searchLoading && !noLoad" />
|
||||
<div v-else-if="results && results.hits && results.hits.length === 0" class="no-results">
|
||||
<p>No results found for your query!</p>
|
||||
</div>
|
||||
<div v-else class="search-results-container">
|
||||
<ProjectCardList aria-label="Search results" layout="list">
|
||||
<template v-if="isServerType">
|
||||
<ProjectCard
|
||||
v-for="result in serverResults?.hits"
|
||||
:key="`server-${result.project_id}`"
|
||||
:link="`/server/${result.slug ?? result.project_id}`"
|
||||
:title="result.name"
|
||||
:icon-url="result.icon_url || undefined"
|
||||
:summary="result.summary"
|
||||
:tags="result.categories"
|
||||
:server-online-players="
|
||||
result.minecraft_java_server?.ping?.data?.players_online ?? 0
|
||||
"
|
||||
:server-region="result.minecraft_server?.region"
|
||||
:server-recent-plays="result.minecraft_java_server?.verified_plays_2w ?? 0"
|
||||
:server-status-online="!!result.minecraft_java_server?.ping?.data"
|
||||
:server-modpack-content="getServerModpackContent(result)"
|
||||
is-server-project
|
||||
exclude-loaders
|
||||
:color="result.color ?? undefined"
|
||||
:banner="result.featured_gallery ?? undefined"
|
||||
layout="list"
|
||||
:max-tags="2"
|
||||
@mouseenter="handleServerProjectMouseEnter(result)"
|
||||
@mouseleave="handleProjectHoverEnd"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ProjectCard
|
||||
v-for="result in projectResults?.hits"
|
||||
:key="result.project_id"
|
||||
:link="`/${projectType?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`"
|
||||
:title="result.title"
|
||||
:icon-url="result.icon_url"
|
||||
:author="{ name: result.author, link: `/user/${result.author}` }"
|
||||
:date-updated="result.date_modified"
|
||||
:date-published="result.date_created"
|
||||
:displayed-date="
|
||||
effectiveCurrentSortType.name === 'newest' ? 'published' : 'updated'
|
||||
"
|
||||
:downloads="result.downloads"
|
||||
:summary="result.description"
|
||||
:tags="result.display_categories"
|
||||
:all-tags="result.categories"
|
||||
:deprioritized-tags="deprioritizedTags"
|
||||
:exclude-loaders="excludeLoaders"
|
||||
:followers="result.follows"
|
||||
:banner="result.featured_gallery ?? undefined"
|
||||
:color="result.color ?? undefined"
|
||||
:environment="
|
||||
['mod', 'modpack'].includes(currentType)
|
||||
? {
|
||||
clientSide: result.client_side as Labrinth.Projects.v2.Environment,
|
||||
serverSide: result.server_side as Labrinth.Projects.v2.Environment,
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
layout="list"
|
||||
@mouseenter="handleProjectMouseEnter(result)"
|
||||
@mouseleave="handleProjectHoverEnd"
|
||||
>
|
||||
<template v-if="flags.showDiscoverProjectButtons || serverData" #actions>
|
||||
<template v-if="flags.showDiscoverProjectButtons">
|
||||
<ButtonStyled color="brand">
|
||||
<button>
|
||||
<DownloadIcon />
|
||||
Download
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button>
|
||||
<HeartIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button>
|
||||
<BookmarkIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<button>
|
||||
<MoreVerticalIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else-if="serverData">
|
||||
<ButtonStyled color="brand" type="outlined">
|
||||
<button
|
||||
v-if="
|
||||
(result as InstallableSearchResult).installed ||
|
||||
(serverContentData &&
|
||||
(serverContentData.addons ?? []).find(
|
||||
(x) => x.project_id === result.project_id,
|
||||
)) ||
|
||||
serverData.upstream?.project_id === result.project_id
|
||||
"
|
||||
disabled
|
||||
>
|
||||
<CheckIcon />
|
||||
Installed
|
||||
</button>
|
||||
<button v-else-if="(result as InstallableSearchResult).installing" disabled>
|
||||
Installing...
|
||||
</button>
|
||||
<button v-else @click="serverInstall(result as InstallableSearchResult)">
|
||||
<DownloadIcon />
|
||||
Install
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
</ProjectCard>
|
||||
</template>
|
||||
</ProjectCardList>
|
||||
</div>
|
||||
<div class="mb-4 flex justify-center">
|
||||
<pagination
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
class="justify-end"
|
||||
@switch-page="updateSearchResults"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<CreationFlowModal
|
||||
v-if="currentServerId && projectType?.id === 'modpack'"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -96,16 +96,15 @@
|
||||
<div
|
||||
v-else-if="serverData"
|
||||
data-pyro-server-manager-root
|
||||
class="experimental-styles-within mobile-blurred-servericon relative mx-auto mb-12 box-border flex min-h-screen w-full min-w-0 max-w-[1280px] flex-col gap-6 px-6 transition-all duration-300"
|
||||
class="experimental-styles-within mobile-blurred-servericon relative mx-auto mb-12 box-border flex min-h-screen w-full min-w-0 max-w-[1280px] flex-col px-6 transition-all duration-300"
|
||||
:style="{
|
||||
'--server-bg-image': serverImage
|
||||
? `url(${serverImage})`
|
||||
: `linear-gradient(180deg, rgba(153,153,153,1) 0%, rgba(87,87,87,1) 100%)`,
|
||||
}"
|
||||
>
|
||||
<NuxtLink to="/hosting/manage" class="breadcrumb goto-link flex w-fit items-center">
|
||||
<LeftArrowIcon />
|
||||
All servers
|
||||
<NuxtLink to="/hosting/manage" class="breadcrumb goto-link mb-4 mt-4 flex w-fit items-center">
|
||||
« All servers
|
||||
</NuxtLink>
|
||||
<ContentPageHeader>
|
||||
<template #icon>
|
||||
@@ -197,10 +196,13 @@
|
||||
<NavTabs :links="navLinks" replace />
|
||||
</div>
|
||||
|
||||
<div data-pyro-mount class="h-full w-full flex-1">
|
||||
<div
|
||||
data-pyro-mount
|
||||
class="mx-4 h-full w-full flex-1 rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3"
|
||||
>
|
||||
<div
|
||||
v-if="error"
|
||||
class="mx-auto mb-4 flex justify-between gap-2 rounded-2xl border-2 border-solid border-red bg-bg-red p-4 font-semibold text-contrast"
|
||||
class="mx-auto mb-4 flex justify-between gap-2 rounded-[4px] border-2 border-solid border-red bg-bg-red p-4 font-semibold text-contrast"
|
||||
>
|
||||
<div class="flex flex-row gap-4">
|
||||
<IssuesIcon class="hidden h-8 w-8 shrink-0 text-red sm:block" />
|
||||
@@ -295,7 +297,7 @@
|
||||
<div
|
||||
v-if="!isConnected && !isReconnecting && !isLoading"
|
||||
data-pyro-server-ws-error
|
||||
class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-red p-4 text-contrast"
|
||||
class="mb-4 flex w-full flex-row items-center gap-4 rounded-[4px] bg-bg-red p-4 text-contrast"
|
||||
>
|
||||
<IssuesIcon class="size-5 text-red" />
|
||||
Something went wrong...
|
||||
@@ -304,7 +306,7 @@
|
||||
<div
|
||||
v-if="isReconnecting"
|
||||
data-pyro-server-ws-reconnecting
|
||||
class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-orange p-4 text-sm text-contrast"
|
||||
class="mb-4 flex w-full flex-row items-center gap-4 rounded-[4px] bg-bg-orange p-4 text-sm text-contrast"
|
||||
>
|
||||
<PanelSpinner />
|
||||
Hang on, we're reconnecting to your server.
|
||||
@@ -352,7 +354,7 @@
|
||||
class="experimental-styles-within relative mx-auto mt-6 box-border w-full min-w-0 max-w-[1280px] px-6"
|
||||
>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Server data</h2>
|
||||
<pre class="markdown-body w-full overflow-auto rounded-2xl bg-bg-raised p-4 text-sm">{{
|
||||
<pre class="markdown-body w-full overflow-auto rounded-[4px] bg-bg-raised p-4 text-sm">{{
|
||||
safeStringify(serverData)
|
||||
}}</pre>
|
||||
</div>
|
||||
@@ -371,7 +373,6 @@ import {
|
||||
FolderOpenIcon,
|
||||
IssuesIcon,
|
||||
LayoutTemplateIcon,
|
||||
LeftArrowIcon,
|
||||
LinkIcon,
|
||||
LockIcon,
|
||||
RightArrowIcon,
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
/>
|
||||
|
||||
<div
|
||||
class="relative flex h-[700px] w-full flex-col gap-3 overflow-hidden rounded-2xl border border-divider bg-bg-raised p-4 transition-all duration-300 ease-in-out md:p-8"
|
||||
class="relative flex h-[700px] w-full flex-col gap-3 overflow-hidden rounded-[4px] border border-divider bg-bg-raised p-4 transition-all duration-300 ease-in-out md:p-8"
|
||||
:class="{ 'border-0': !isConnected || isWsAuthIncorrect }"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -106,7 +106,7 @@
|
||||
}}</span>
|
||||
<span> {{ bestSuggestion }} </span>
|
||||
<button
|
||||
class="text pointer-events-auto ml-2 cursor-pointer rounded-md border-none bg-white text-sm focus:outline-none dark:bg-highlight"
|
||||
class="text pointer-events-auto ml-2 cursor-pointer rounded-md border-none bg-white text-sm focus:outline-none"
|
||||
aria-label="Accept suggestion"
|
||||
style="transform: translateY(-1px)"
|
||||
@click="acceptSuggestion"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 rounded-2xl bg-surface-3 p-6">
|
||||
<div class="flex flex-col gap-6">
|
||||
<ConfirmModal
|
||||
ref="resetToOnboardingModal"
|
||||
:title="formatMessage(messages.resetToOnboardingModalTitle)"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
has more detailed information.
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 rounded-2xl bg-table-alternateRow p-4">
|
||||
<div class="flex flex-col gap-4 rounded-[4px] bg-table-alternateRow p-4">
|
||||
<div class="w-full text-sm">
|
||||
<label for="search-server-properties" class="sr-only"> Search server properties </label>
|
||||
<StyledInput
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="relative h-full w-full">
|
||||
<div class="flex h-full w-full flex-col gap-4">
|
||||
<div
|
||||
class="rounded-2xl border-[1px] border-solid border-orange bg-bg-orange p-4 text-contrast"
|
||||
class="rounded-[4px] border-[1px] border-solid border-orange bg-bg-orange p-4 text-contrast"
|
||||
>
|
||||
These settings are for advanced users. Changing them can break your server.
|
||||
</div>
|
||||
|
||||
+174
-1450
File diff suppressed because it is too large
Load Diff
@@ -166,7 +166,7 @@ onMounted(() => {
|
||||
<ShareArticleButtons :title="article.title" :url="articleUrl" />
|
||||
<img
|
||||
:src="article.thumbnail"
|
||||
class="aspect-video w-full rounded-xl border-[1px] border-solid border-button-border object-cover sm:rounded-2xl"
|
||||
class="aspect-video w-full rounded-xl border-[1px] border-solid border-button-border object-cover sm:rounded-[4px]"
|
||||
:alt="article.title"
|
||||
/>
|
||||
<div class="markdown-body" v-html="article.html" />
|
||||
|
||||
@@ -26,6 +26,5 @@ useSeoMeta({
|
||||
padding: 1rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 56rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,9 +49,11 @@ const changelogEntries = computed(() =>
|
||||
},
|
||||
]"
|
||||
query="filter"
|
||||
class="mb-4"
|
||||
/>
|
||||
<Timeline fade-out-end>
|
||||
<Timeline
|
||||
fade-out-end
|
||||
class="mx-4 rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3"
|
||||
>
|
||||
<ChangelogEntry
|
||||
v-for="(entry, index) in changelogEntries"
|
||||
:key="entry.date"
|
||||
|
||||
@@ -68,13 +68,13 @@ useSeoMeta({
|
||||
>
|
||||
<nuxt-link
|
||||
:to="`${featuredArticle.path}`"
|
||||
class="active:scale-[0.99]! group flex cursor-pointer transition-all ease-in-out hover:brightness-125"
|
||||
class="group flex cursor-pointer transition-all ease-in-out hover:brightness-125"
|
||||
>
|
||||
<article class="featured-article px-6">
|
||||
<div class="featured-image-container">
|
||||
<img
|
||||
:src="featuredArticle.thumbnail"
|
||||
class="aspect-video w-full rounded-2xl border-[1px] border-solid border-button-border object-cover"
|
||||
class="aspect-video w-full rounded-[4px] border-[1px] border-solid border-button-border object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="featured-content">
|
||||
@@ -118,14 +118,13 @@ useSeoMeta({
|
||||
.page {
|
||||
> *:not(.full-width-bg),
|
||||
> .full-width-bg > * {
|
||||
max-width: 56rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-gradient-bg {
|
||||
background: var(--brand-gradient-bg);
|
||||
border-color: var(--brand-gradient-border);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.featured-article {
|
||||
|
||||
@@ -80,14 +80,13 @@
|
||||
<div
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-divider pr-4 font-semibold"
|
||||
>
|
||||
<UsersIcon class="h-6 w-6 text-secondary" />
|
||||
<UserIcon class="h-6 w-6 text-secondary" />
|
||||
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
||||
members
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-divider pr-4 font-semibold"
|
||||
>
|
||||
<BoxIcon class="h-6 w-6 text-secondary" />
|
||||
{{ formatCompactNumber(projects?.length || 0) }}
|
||||
projects
|
||||
</div>
|
||||
@@ -95,66 +94,43 @@
|
||||
v-tooltip="formatNumber(sumDownloads)"
|
||||
class="flex items-center gap-2 font-semibold"
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||
{{ formatCompactNumber(sumDownloads) }}
|
||||
downloads
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<ButtonStyled v-if="auth.user && currentMember" size="large">
|
||||
<NuxtLink :to="`/organization/${organization.slug}/settings`">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
Manage
|
||||
</NuxtLink>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled size="large" circular type="transparent">
|
||||
<OverflowMenu
|
||||
:options="[
|
||||
{
|
||||
id: 'manage-projects',
|
||||
action: () =>
|
||||
router.push('/organization/' + organization?.slug + '/settings/projects'),
|
||||
hoverFilledOnly: true,
|
||||
shown: !!(auth.user && currentMember),
|
||||
},
|
||||
{ divider: true, shown: !!(auth?.user && currentMember) },
|
||||
{ id: 'copy-id', action: () => copyId() },
|
||||
{ id: 'copy-permalink', action: () => copyPermalink() },
|
||||
]"
|
||||
aria-label="More options"
|
||||
<div class="flex flex-col gap-2">
|
||||
<nuxt-link
|
||||
v-if="!!(auth.user && currentMember)"
|
||||
:to="'/organization/' + organization?.slug + '/settings'"
|
||||
class="text-link"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
<template #manage-projects>
|
||||
<BoxIcon aria-hidden="true" />
|
||||
Manage projects
|
||||
</template>
|
||||
<template #copy-id>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.copyIdButton) }}
|
||||
</template>
|
||||
<template #copy-permalink>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.copyPermalinkButton) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
Edit organization ►
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
v-if="!!(auth.user && currentMember)"
|
||||
:to="'/organization/' + organization?.slug + '/settings/projects'"
|
||||
class="text-link"
|
||||
>
|
||||
Manage projects ►
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
</ContentPageHeader>
|
||||
</div>
|
||||
<div class="normal-page__sidebar">
|
||||
<AdPlaceholder v-if="!auth.user" />
|
||||
|
||||
<div class="card flex-card">
|
||||
<h2>Members</h2>
|
||||
<div>
|
||||
<h2 class="m-0 mb-1 text-lg font-bold">Members</h2>
|
||||
<div class="details-list">
|
||||
<template v-for="member in acceptedMembers" :key="member?.user.id">
|
||||
<nuxt-link
|
||||
class="details-list__item details-list__item--type-large"
|
||||
class="details-list__item details-list__item--type-large group"
|
||||
:to="`/user/${member?.user?.username}`"
|
||||
>
|
||||
<Avatar :src="member?.user.avatar_url" circle />
|
||||
<div class="rows">
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="flex items-center gap-1 group-hover:underline">
|
||||
{{ member?.user?.username }}
|
||||
<CrownIcon
|
||||
v-if="member?.is_owner"
|
||||
@@ -186,10 +162,13 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||
<div v-if="navLinks.length > 2" class="max-w-full overflow-x-auto">
|
||||
<NavTabs :links="navLinks" replace />
|
||||
</div>
|
||||
<ProjectCardList v-if="projects && projects.length > 0">
|
||||
<ProjectCardList
|
||||
v-if="projects && projects.length > 0"
|
||||
class="mx-4 rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3"
|
||||
>
|
||||
<template
|
||||
v-for="project in (route.params.projectType !== undefined
|
||||
? (projects ?? []).filter((x) =>
|
||||
@@ -210,6 +189,7 @@
|
||||
>
|
||||
<ProjectCard
|
||||
v-if="isProjectServer(project)"
|
||||
class="rounded-[4px] bg-white"
|
||||
:link="`/server/${project.slug || project.id}`"
|
||||
:title="project.name"
|
||||
:icon-url="project.icon_url"
|
||||
@@ -234,6 +214,7 @@
|
||||
/>
|
||||
<ProjectCard
|
||||
v-else
|
||||
class="rounded-[4px] bg-white"
|
||||
:link="`/${project.project_types[0] ?? 'project'}/${project.slug || project.id}`"
|
||||
:title="project.name"
|
||||
:icon-url="project.icon_url"
|
||||
@@ -283,24 +264,20 @@ import {
|
||||
BoxIcon,
|
||||
ChartIcon,
|
||||
CheckIcon,
|
||||
ClipboardCopyIcon,
|
||||
CrownIcon,
|
||||
DownloadIcon,
|
||||
MoreVerticalIcon,
|
||||
OrganizationIcon,
|
||||
SettingsIcon,
|
||||
SpinnerIcon,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
ContentPageHeader,
|
||||
injectModrinthClient,
|
||||
NavTabs,
|
||||
OverflowMenu,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
useCompactNumber,
|
||||
@@ -319,6 +296,7 @@ import {
|
||||
OrganizationContext,
|
||||
provideOrganizationContext,
|
||||
} from '~/providers/organization-context.ts'
|
||||
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
||||
import { isPermission } from '~/utils/permissions.ts'
|
||||
|
||||
type ProjectV3 = Labrinth.Projects.v3.Project & {
|
||||
@@ -336,9 +314,7 @@ const auth: { user: any } & any = await useAuth()
|
||||
const user = await useUser()
|
||||
const cosmetics = useCosmetics()
|
||||
const route = useNativeRoute()
|
||||
const router = useRouter()
|
||||
const tags = useGeneratedState()
|
||||
const config = useRuntimeConfig()
|
||||
const modal_creation = useTemplateRef('modal_creation')
|
||||
|
||||
const orgId = useRouteId()
|
||||
@@ -557,16 +533,6 @@ const navLinks = computed(() => [
|
||||
.slice()
|
||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
])
|
||||
|
||||
async function copyId() {
|
||||
await navigator.clipboard.writeText(organization.value?.id ?? '')
|
||||
}
|
||||
|
||||
async function copyPermalink() {
|
||||
await navigator.clipboard.writeText(
|
||||
`${config.public.siteUrl}/organization/${organization.value?.id}`,
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup>
|
||||
import { TrashIcon, UploadIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
FileInput,
|
||||
injectNotificationManager,
|
||||
@@ -161,27 +161,29 @@ const onDeleteOrganization = useClientTry(async () => {
|
||||
size="md"
|
||||
class="project__icon"
|
||||
/>
|
||||
<div class="input-stack">
|
||||
<FileInput
|
||||
id="project-icon"
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="btn"
|
||||
prompt="Upload icon"
|
||||
:disabled="!hasPermission"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
<Button
|
||||
v-if="!deletedIcon && (previewImage || organization.icon_url)"
|
||||
:disabled="!hasPermission"
|
||||
@click="markIconForDeletion"
|
||||
>
|
||||
<TrashIcon />
|
||||
Remove icon
|
||||
</Button>
|
||||
<div class="input-stack flex flex-col gap-1">
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
id="project-icon"
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="button-like"
|
||||
prompt="Upload icon"
|
||||
:disabled="!hasPermission"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
v-if="!deletedIcon && (previewImage || organization.icon_url)"
|
||||
:disabled="!hasPermission"
|
||||
@click="markIconForDeletion"
|
||||
>
|
||||
Remove icon
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -231,10 +233,11 @@ const onDeleteOrganization = useClientTry(async () => {
|
||||
Deleting your organization will transfer all of its projects to the organization owner. This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
<Button color="danger" @click="() => $refs.modal_deletion.show()">
|
||||
<TrashIcon />
|
||||
Delete organization
|
||||
</Button>
|
||||
<ButtonStyled color="red">
|
||||
<Button color="danger" @click="() => $refs.modal_deletion.show()">
|
||||
Delete organization
|
||||
</Button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<UnsavedChangesPopup
|
||||
:original="originalState"
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
label: formatMessage(commonSettingsMessages.appearance),
|
||||
icon: PaintbrushIcon,
|
||||
},
|
||||
{
|
||||
link: '/settings/language',
|
||||
label: formatMessage(commonSettingsMessages.language),
|
||||
icon: LanguagesIcon,
|
||||
badge: `${formatMessage(commonMessages.beta)}`,
|
||||
},
|
||||
flags.developerMode
|
||||
? {
|
||||
link: '/settings/flags',
|
||||
@@ -93,7 +87,6 @@ import {
|
||||
CardIcon,
|
||||
GridIcon,
|
||||
KeyIcon,
|
||||
LanguagesIcon,
|
||||
MonitorSmartphoneIcon,
|
||||
PaintbrushIcon,
|
||||
ServerIcon,
|
||||
|
||||
@@ -321,125 +321,102 @@
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(messages.accountSecurityTitle) }}</h2>
|
||||
<section class="flex flex-col">
|
||||
<label>
|
||||
<p class="mb-2 mt-0 font-bold">{{ formatMessage(messages.emailFieldTitle) }}</p>
|
||||
</label>
|
||||
<ButtonStyled>
|
||||
<button type="button" class="iconified-button" @click="$refs.changeEmailModal.show()">
|
||||
<template v-if="auth.user.email">
|
||||
{{ formatMessage(messages.changeEmailButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatMessage(messages.addEmailButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="theme-selector">
|
||||
<span class="label__title">{{ formatMessage(messages.emailFieldTitle) }}</span>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.emailFieldDescription)
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<button class="iconified-button" @click="$refs.changeEmailModal.show()">
|
||||
<template v-if="auth.user.email">
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.changeEmailButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.addEmailButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="theme-selector">
|
||||
<span class="label__title">{{ formatMessage(messages.passwordFieldTitle) }}</span>
|
||||
<span v-if="auth.user.has_password" class="label__description">
|
||||
{{
|
||||
auth.user.auth_providers.length > 0
|
||||
? formatMessage(messages.passwordDescriptionChangeOrRemove)
|
||||
: formatMessage(messages.passwordDescriptionChange)
|
||||
}}
|
||||
</span>
|
||||
<span v-else class="label__description">
|
||||
{{ formatMessage(messages.passwordDescriptionSet) }}
|
||||
</span>
|
||||
</label>
|
||||
<div>
|
||||
<button
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
oldPassword = ''
|
||||
newPassword = ''
|
||||
confirmNewPassword = ''
|
||||
removePasswordMode = false
|
||||
$refs.managePasswordModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<KeyIcon />
|
||||
<template v-if="auth.user.has_password">{{
|
||||
formatMessage(messages.changePasswordButton)
|
||||
}}</template>
|
||||
<template v-else> {{ formatMessage(messages.addPasswordButton) }} </template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="theme-selector">
|
||||
<span class="label__title">{{ formatMessage(messages.twoFactorFieldTitle) }}</span>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.twoFactorFieldDescription)
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<button class="iconified-button" @click="showTwoFactorModal">
|
||||
<template v-if="auth.user.has_totp">
|
||||
<TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="theme-selector">
|
||||
<span class="label__title">{{ formatMessage(messages.manageProvidersFieldTitle) }}</span>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.manageProvidersFieldDescription)
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<button class="iconified-button" @click="$refs.manageProvidersModal.show()">
|
||||
<SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<p class="mb-2 mt-4 font-bold">{{ formatMessage(messages.passwordFieldTitle) }}</p>
|
||||
</label>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
oldPassword = ''
|
||||
newPassword = ''
|
||||
confirmNewPassword = ''
|
||||
removePasswordMode = false
|
||||
$refs.managePasswordModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-if="auth.user.has_password">{{
|
||||
formatMessage(messages.changePasswordButton)
|
||||
}}</template>
|
||||
<template v-else> {{ formatMessage(messages.addPasswordButton) }} </template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<label>
|
||||
<p class="mb-2 mt-4 font-bold">{{ formatMessage(messages.twoFactorFieldTitle) }}</p>
|
||||
</label>
|
||||
<ButtonStyled>
|
||||
<button type="button" class="iconified-button" @click="showTwoFactorModal">
|
||||
<template v-if="auth.user.has_totp">
|
||||
{{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatMessage(messages.twoFactorSetupButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<label>
|
||||
<p class="mb-2 mt-4 font-bold">{{ formatMessage(messages.manageProvidersFieldTitle) }}</p>
|
||||
</label>
|
||||
<ButtonStyled>
|
||||
<button type="button" class="iconified-button" @click="$refs.manageProvidersModal.show()">
|
||||
{{ formatMessage(messages.manageProvidersButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<section id="data-export" class="universal-card">
|
||||
<h2>{{ formatMessage(messages.dataExportTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.dataExportDescription) }}</p>
|
||||
<a v-if="generated" class="iconified-button" :href="generated" download="export.json">
|
||||
<DownloadIcon />
|
||||
{{ formatMessage(messages.downloadExportButton) }}
|
||||
</a>
|
||||
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData">
|
||||
<template v-if="generatingExport">
|
||||
<UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }}
|
||||
</template>
|
||||
</button>
|
||||
<section id="data-export" class="flex flex-col border-t border-divider">
|
||||
<p class="mb-1 font-bold">{{ formatMessage(messages.dataExportTitle) }}</p>
|
||||
<p class="mb-2 mt-0">{{ formatMessage(messages.dataExportDescription) }}</p>
|
||||
<ButtonStyled v-if="generated">
|
||||
<a class="iconified-button" :href="generated" download="export.json">
|
||||
{{ formatMessage(messages.downloadExportButton) }}
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button"
|
||||
:disabled="generatingExport"
|
||||
@click="exportData"
|
||||
>
|
||||
<template v-if="generatingExport">
|
||||
{{ formatMessage(messages.generatingExportButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatMessage(messages.generateExportButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<section id="delete-account" class="universal-card">
|
||||
<h2>{{ formatMessage(messages.deleteAccountSectionTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.deleteAccountSectionDescription) }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button danger-button"
|
||||
@click="$refs.modal_confirm.show()"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.deleteAccountButton) }}
|
||||
</button>
|
||||
<section id="delete-account" class="flex flex-col border-t border-divider">
|
||||
<p class="mb-1 font-bold">{{ formatMessage(messages.deleteAccountSectionTitle) }}</p>
|
||||
<p class="mb-2 mt-0">{{ formatMessage(messages.deleteAccountSectionDescription) }}</p>
|
||||
<ButtonStyled color="red">
|
||||
<button type="button" class="iconified-button" @click="$refs.modal_confirm.show()">
|
||||
{{ formatMessage(messages.deleteAccountButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -447,19 +424,15 @@
|
||||
<script setup>
|
||||
import {
|
||||
CheckIcon,
|
||||
DownloadIcon,
|
||||
EditIcon,
|
||||
ExternalIcon,
|
||||
LeftArrowIcon,
|
||||
PlusIcon,
|
||||
RightArrowIcon,
|
||||
SaveIcon,
|
||||
SettingsIcon,
|
||||
TrashIcon,
|
||||
UpdatedIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
@@ -468,7 +441,6 @@ import {
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import KeyIcon from 'assets/icons/auth/key.svg'
|
||||
import DiscordIcon from 'assets/icons/auth/sso-discord.svg'
|
||||
import GithubIcon from 'assets/icons/auth/sso-github.svg'
|
||||
import GitLabIcon from 'assets/icons/auth/sso-gitlab.svg'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="universal-card">
|
||||
<div class="flex flex-col">
|
||||
<ConfirmModal
|
||||
ref="modal_confirm"
|
||||
:title="formatMessage(messages.deleteConfirmTitle)"
|
||||
@@ -24,15 +24,18 @@
|
||||
</label>
|
||||
<div v-if="editingId" class="icon-submission">
|
||||
<Avatar size="md" :src="icon" />
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
class="btn"
|
||||
:prompt="formatMessage(messages.uploadIcon)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="onImageSelection"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
class="button-like"
|
||||
:prompt="formatMessage(messages.uploadIcon)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
:show-icon="true"
|
||||
@change="onImageSelection"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label v-if="editingId" for="app-url">
|
||||
<span class="label__title">{{ formatMessage(messages.urlLabel) }}</span>
|
||||
@@ -94,23 +97,24 @@
|
||||
autocomplete="off"
|
||||
:placeholder="formatMessage(messages.redirectUriPlaceholder)"
|
||||
/>
|
||||
<Button v-if="index !== 0" icon-only @click="() => redirectUris.splice(index, 1)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="index === 0"
|
||||
color="primary"
|
||||
icon-only
|
||||
@click="() => redirectUris.push('')"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.addMore) }}
|
||||
</Button>
|
||||
<ButtonStyled v-if="index !== 0" color="red">
|
||||
<button type="button" @click="() => redirectUris.splice(index, 1)">
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="index === 0" color="brand">
|
||||
<button type="button" @click="() => redirectUris.push('')">
|
||||
<PlusIcon /> {{ formatMessage(messages.addMore) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="redirectUris.length <= 0">
|
||||
<Button color="primary" icon-only @click="() => redirectUris.push('')">
|
||||
<PlusIcon /> {{ formatMessage(messages.addRedirectUri) }}
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button type="button" @click="() => redirectUris.push('')">
|
||||
<PlusIcon /> {{ formatMessage(messages.addRedirectUri) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,28 +147,28 @@
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<div class="header__row">
|
||||
<div class="header__title">
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.applications) }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
icon = null
|
||||
scopesVal = 0
|
||||
redirectUris = ['']
|
||||
editingId = null
|
||||
expires = null
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.newApplication) }}
|
||||
</button>
|
||||
<div class="mb-1 flex flex-wrap items-center justify-between gap-3">
|
||||
<p class="m-0 font-bold">{{ formatMessage(commonSettingsMessages.applications) }}</p>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
type="button"
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
icon = null
|
||||
scopesVal = 0
|
||||
redirectUris = ['']
|
||||
editingId = null
|
||||
expires = null
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.newApplication) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p>
|
||||
<p class="mb-2 mt-0">
|
||||
<IntlFormatted :message-id="messages.descriptionIntro">
|
||||
<template #docs-link="{ children }">
|
||||
<a class="text-link" href="https://docs.modrinth.com">
|
||||
@@ -173,12 +177,16 @@
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<div v-for="app in usersApps" :key="app.id" class="universal-card recessed token mt-4">
|
||||
<div
|
||||
v-for="app in usersApps"
|
||||
:key="app.id"
|
||||
class="token mt-2 border-t border-divider first:mt-0 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<div class="token-info">
|
||||
<div class="token-icon">
|
||||
<Avatar size="sm" :src="app.icon_url" />
|
||||
<div>
|
||||
<h2 class="token-title">{{ app.name }}</h2>
|
||||
<p class="token-title m-0 font-bold">{{ app.name }}</p>
|
||||
<div>
|
||||
{{
|
||||
formatMessage(messages.createdOn, {
|
||||
@@ -209,35 +217,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<Button
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
setForm({
|
||||
...app,
|
||||
redirect_uris: app.redirect_uris.map((u) => u.uri) || [],
|
||||
})
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.edit) }}
|
||||
</Button>
|
||||
<Button
|
||||
color="danger"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
editingId = app.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.delete) }}
|
||||
</Button>
|
||||
<div class="input-group flex flex-wrap gap-2">
|
||||
<ButtonStyled>
|
||||
<button
|
||||
type="button"
|
||||
@click="
|
||||
() => {
|
||||
setForm({
|
||||
...app,
|
||||
redirect_uris: app.redirect_uris.map((u) => u.uri) || [],
|
||||
})
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.edit) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
type="button"
|
||||
@click="
|
||||
() => {
|
||||
editingId = app.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.delete) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,7 +257,7 @@
|
||||
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="universal-card">
|
||||
<div class="flex flex-col">
|
||||
<ConfirmModal
|
||||
ref="modal_confirm"
|
||||
:title="formatMessage(messages.revokeConfirmTitle)"
|
||||
@@ -7,26 +7,26 @@
|
||||
:proceed-label="formatMessage(messages.revokeAction)"
|
||||
@proceed="revokeApp(revokingId)"
|
||||
/>
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.authorizedApps) }}</h2>
|
||||
<p>
|
||||
<p class="mb-1 mt-0 font-bold">{{ formatMessage(commonSettingsMessages.authorizedApps) }}</p>
|
||||
<p class="mb-2 mt-0">
|
||||
{{ formatMessage(messages.description) }}
|
||||
</p>
|
||||
<div v-if="appInfoLookup.length === 0" class="universal-card recessed">
|
||||
<p v-if="appInfoLookup.length === 0" class="text-secondary">
|
||||
{{ formatMessage(messages.emptyState) }}
|
||||
</div>
|
||||
</p>
|
||||
<div
|
||||
v-for="authorization in appInfoLookup"
|
||||
:key="authorization.id"
|
||||
class="universal-card recessed token mt-4"
|
||||
class="token mt-6 border-t border-divider pt-6 first:mt-0 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<div class="token-content">
|
||||
<div>
|
||||
<div class="icon-name">
|
||||
<Avatar :src="authorization.app.icon_url" />
|
||||
<div>
|
||||
<h2 class="token-title">
|
||||
<p class="token-title m-0 font-bold">
|
||||
{{ authorization.app.name }}
|
||||
</h2>
|
||||
</p>
|
||||
<div>
|
||||
{{ formatMessage(messages.byLabel) }}
|
||||
<nuxt-link class="text-link" :to="'/user/' + authorization.owner.id">{{
|
||||
@@ -69,19 +69,20 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<Button
|
||||
color="danger"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
revokingId = authorization.app_id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.revokeAction) }}
|
||||
</Button>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
type="button"
|
||||
@click="
|
||||
() => {
|
||||
revokingId = authorization.app_id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.revokeAction) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,7 +91,7 @@
|
||||
import { CheckIcon, TrashIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
ConfirmModal,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<section class="card">
|
||||
<section class="flex flex-col">
|
||||
<Breadcrumbs
|
||||
:current-title="formatMessage(messages.title)"
|
||||
:link-stack="[
|
||||
{ href: '/settings/billing', label: formatMessage(commonSettingsMessages.billing) },
|
||||
]"
|
||||
/>
|
||||
<h2>{{ formatMessage(messages.title) }}</h2>
|
||||
<p>{{ formatMessage(messages.description) }}</p>
|
||||
<p class="mb-2 mt-2 font-bold">{{ formatMessage(messages.title) }}</p>
|
||||
<p class="mb-4">{{ formatMessage(messages.description) }}</p>
|
||||
<div
|
||||
v-for="charge in charges"
|
||||
:key="charge.id"
|
||||
class="universal-card recessed flex items-center justify-between gap-4"
|
||||
class="flex items-center justify-between gap-4 border-t border-divider py-4 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-1">
|
||||
@@ -50,7 +50,6 @@ import {
|
||||
injectModrinthClient,
|
||||
useFormatDateTime,
|
||||
useFormatPrice,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
|
||||
@@ -73,7 +72,7 @@ const formatDate = useFormatDateTime({
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'settings.billing.charges.title',
|
||||
defaultMessage: 'Past charges',
|
||||
defaultMessage: 'Charge history',
|
||||
},
|
||||
description: {
|
||||
id: 'settings.billing.charges.description',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<ServersUpgradeModalWrapper ref="upgradeModal" />
|
||||
<section class="universal-card experimental-styles-within">
|
||||
<h2>{{ formatMessage(messages.subscriptionTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.subscriptionDescription) }}</p>
|
||||
<div class="universal-card recessed">
|
||||
<section class="experimental-styles-within flex flex-col">
|
||||
<p class="mb-1 mt-0 font-bold">{{ formatMessage(messages.subscriptionTitle) }}</p>
|
||||
<p class="mb-2 mt-0">{{ formatMessage(messages.subscriptionDescription) }}</p>
|
||||
<div class="rounded-[4px] border border-solid border-black p-4">
|
||||
<ConfirmModal
|
||||
ref="modalCancel"
|
||||
:title="formatMessage(cancelModalMessages.title)"
|
||||
@@ -274,7 +274,7 @@
|
||||
<div
|
||||
v-for="(subscription, index) in pyroSubscriptions"
|
||||
:key="index"
|
||||
class="universal-card recessed mt-4"
|
||||
class="mt-4 rounded-[4px] border border-solid border-black p-4"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -535,7 +535,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="universal-card experimental-styles-within">
|
||||
<section class="experimental-styles-within mt-8 flex flex-col border-t border-divider pt-8">
|
||||
<ConfirmModal
|
||||
ref="modal_confirm"
|
||||
:title="formatMessage(deleteModalMessages.title)"
|
||||
@@ -571,28 +571,27 @@
|
||||
:create-setup-intent="createSetupIntent"
|
||||
:on-error="handleError"
|
||||
/>
|
||||
<div class="header__row">
|
||||
<div class="header__title">
|
||||
<h2 class="text-2xl">{{ formatMessage(messages.paymentMethodTitle) }}</h2>
|
||||
<div class="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||
<p class="m-0 font-bold">{{ formatMessage(messages.paymentMethodTitle) }}</p>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<nuxt-link class="inline-flex items-center gap-1 text-link" to="/settings/billing/charges">
|
||||
<HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }}
|
||||
</nuxt-link>
|
||||
<ButtonStyled color="brand">
|
||||
<button type="button" @click="addPaymentMethod">
|
||||
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<nuxt-link class="btn" to="/settings/billing/charges">
|
||||
<HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }}
|
||||
</nuxt-link>
|
||||
<button class="btn" @click="addPaymentMethod">
|
||||
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="!paymentMethods || paymentMethods.length === 0"
|
||||
class="universal-card recessed !mb-0"
|
||||
>
|
||||
<p v-if="!paymentMethods || paymentMethods.length === 0" class="text-secondary">
|
||||
{{ formatMessage(messages.paymentMethodNone) }}
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-4">
|
||||
</p>
|
||||
<div v-else class="flex flex-col gap-0">
|
||||
<div
|
||||
v-for="(method, index) in paymentMethods"
|
||||
:key="index"
|
||||
class="universal-card recessed !mb-0 flex items-center justify-between"
|
||||
class="flex items-center justify-between gap-4 border-t border-divider py-4 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<component :is="getPaymentMethodIcon(method.type)" class="h-8 w-8" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { SearchIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, StyledInput, Toggle } from '@modrinth/ui'
|
||||
import { ButtonStyled, Checkbox, StyledInput } from '@modrinth/ui'
|
||||
import Fuse from 'fuse.js'
|
||||
import { computed, ref, shallowReactive } from 'vue'
|
||||
|
||||
@@ -47,17 +47,22 @@ useSeoMeta({
|
||||
type="search"
|
||||
:icon="SearchIcon"
|
||||
placeholder="Search flags..."
|
||||
wrapper-class="w-full rounded-xl bg-bg-raised"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
v-for="flag in filteredFlags"
|
||||
:key="`flag-${flag}`"
|
||||
class="flex flex-row flex-wrap items-center gap-2 rounded-2xl bg-bg-raised p-4"
|
||||
class="flex flex-row flex-wrap items-center gap-2"
|
||||
>
|
||||
<Checkbox
|
||||
:id="`toggle-${flag}`"
|
||||
v-model="flags[flag]"
|
||||
@update:model-value="() => saveFeatureFlags()"
|
||||
/>
|
||||
<label :for="`toggle-${flag}`" class="flex-1">
|
||||
<span class="block font-semibold capitalize">
|
||||
<span class="mb-1 block font-bold capitalize">
|
||||
{{ flag.replaceAll('_', ' ') }}
|
||||
</span>
|
||||
<p class="m-0 text-secondary">
|
||||
@@ -73,11 +78,6 @@ useSeoMeta({
|
||||
Reset to default
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<Toggle
|
||||
:id="`toggle-${flag}`"
|
||||
v-model="flags[flag]"
|
||||
@update:model-value="() => saveFeatureFlags()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="filteredFlags.length === 0" class="text-center text-secondary">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<MessageBanner v-if="flags.developerMode" message-type="warning" class="developer-message">
|
||||
<CodeIcon class="inline-flex" />
|
||||
<IntlFormatted :message-id="developerModeBanner.description">
|
||||
<template #strong="{ children }">
|
||||
<strong>
|
||||
@@ -9,166 +8,54 @@
|
||||
</strong>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
<Button :action="() => disableDeveloperMode()">
|
||||
{{ formatMessage(developerModeBanner.deactivate) }}
|
||||
</Button>
|
||||
<ButtonStyled color="red">
|
||||
<button type="button" class="mt-2" @click="disableDeveloperMode">
|
||||
{{ formatMessage(developerModeBanner.deactivate) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</MessageBanner>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(colorTheme.title) }}</h2>
|
||||
<p>{{ formatMessage(colorTheme.description) }}</p>
|
||||
<ThemeSelector
|
||||
:update-color-theme="updateColorTheme"
|
||||
:current-theme="theme.preferred"
|
||||
:theme-options="themeOptions"
|
||||
:system-theme-color="systemTheme"
|
||||
/>
|
||||
</section>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(projectListLayouts.title) }}</h2>
|
||||
<p class="mb-4">{{ formatMessage(projectListLayouts.description) }}</p>
|
||||
<div class="project-lists">
|
||||
<div v-for="projectType in listTypes" :key="projectType.id + '-project-list-layouts'">
|
||||
<div class="label">
|
||||
<div class="label__title">
|
||||
{{
|
||||
projectListLayouts[projectType.id]
|
||||
? formatMessage(projectListLayouts[projectType.id])
|
||||
: projectType.id
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-list-layouts">
|
||||
<button
|
||||
class="preview-radio button-base"
|
||||
:class="{
|
||||
selected: cosmetics.searchDisplayMode[projectType.id] === 'list',
|
||||
}"
|
||||
@click="() => (cosmetics.searchDisplayMode[projectType.id] = 'list')"
|
||||
>
|
||||
<div class="preview">
|
||||
<div class="layout-list-mode">
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">
|
||||
<RadioButtonCheckedIcon
|
||||
v-if="cosmetics.searchDisplayMode[projectType.id] === 'list'"
|
||||
class="radio shrink-0"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="radio shrink-0" />
|
||||
{{ formatMessage(layoutMode.rows) }}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="preview-radio button-base"
|
||||
:class="{
|
||||
selected:
|
||||
cosmetics.searchDisplayMode[projectType.id] === 'gallery' ||
|
||||
cosmetics.searchDisplayMode[projectType.id] === 'grid',
|
||||
}"
|
||||
@click="() => (cosmetics.searchDisplayMode[projectType.id] = 'grid')"
|
||||
>
|
||||
<div class="preview">
|
||||
<div class="layout-gallery-mode">
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">
|
||||
<RadioButtonCheckedIcon
|
||||
v-if="
|
||||
cosmetics.searchDisplayMode[projectType.id] === 'gallery' ||
|
||||
cosmetics.searchDisplayMode[projectType.id] === 'grid'
|
||||
"
|
||||
class="radio shrink-0"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="radio shrink-0" />
|
||||
{{ formatMessage(layoutMode.grid) }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(toggleFeatures.title) }}</h2>
|
||||
<p class="mb-4">{{ formatMessage(toggleFeatures.description) }}</p>
|
||||
<section>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row flex-wrap items-center justify-between gap-2">
|
||||
<Checkbox id="advanced-rendering" v-model="cosmetics.advancedRendering" />
|
||||
<label for="advanced-rendering" class="flex-1">
|
||||
<span class="block font-semibold text-contrast">
|
||||
<span class="mb-1 block font-bold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingTitle) }}
|
||||
</span>
|
||||
<span class="text-secondary">
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<Toggle id="advanced-rendering" v-model="cosmetics.advancedRendering" class="shrink-0" />
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap items-center justify-between gap-2">
|
||||
<Checkbox
|
||||
id="external-links-new-tab"
|
||||
v-model="cosmetics.externalLinksNewTab"
|
||||
class="shrink-0"
|
||||
/>
|
||||
<label for="external-links-new-tab" class="flex-1">
|
||||
<span class="block font-semibold text-contrast">
|
||||
<span class="mb-1 block font-bold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabTitle) }}
|
||||
</span>
|
||||
<span class="text-secondary">
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="external-links-new-tab"
|
||||
v-model="cosmetics.externalLinksNewTab"
|
||||
class="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="false" class="flex flex-row flex-wrap items-center justify-between gap-2">
|
||||
<Checkbox
|
||||
id="modrinth-app-promos"
|
||||
v-model="cosmetics.hideModrinthAppPromos"
|
||||
class="shrink-0"
|
||||
/>
|
||||
<label for="modrinth-app-promos" class="flex-1">
|
||||
<span class="block font-semibold text-contrast">
|
||||
<span class="mb-1 block font-bold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosTitle) }}
|
||||
</span>
|
||||
<span class="text-secondary">
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="modrinth-app-promos"
|
||||
v-model="cosmetics.hideModrinthAppPromos"
|
||||
class="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap items-center justify-between gap-2">
|
||||
<label for="search-layout-toggle" class="flex-1">
|
||||
<span class="block font-semibold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.rightAlignedFiltersSidebarTitle) }}
|
||||
</span>
|
||||
<span class="text-secondary">
|
||||
{{ formatMessage(toggleFeatures.rightAlignedFiltersSidebarDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="search-layout-toggle"
|
||||
v-model="cosmetics.rightSearchLayout"
|
||||
class="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap items-center justify-between gap-2">
|
||||
<label for="project-layout-toggle" class="">
|
||||
<span class="block font-semibold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.leftAlignedContentSidebarTitle) }}
|
||||
</span>
|
||||
<span class="text-secondary">
|
||||
{{ formatMessage(toggleFeatures.leftAlignedContentSidebarDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="project-layout-toggle"
|
||||
v-model="cosmetics.leftContentLayout"
|
||||
class="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -176,22 +63,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
normalizeChildren,
|
||||
ThemeSelector,
|
||||
Toggle,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
|
||||
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
||||
import type { DisplayLocation } from '~/plugins/cosmetics'
|
||||
import { isDarkTheme, type Theme } from '~/plugins/theme/index.ts'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -219,21 +101,6 @@ useHead({
|
||||
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||
})
|
||||
|
||||
const layoutMode = defineMessages({
|
||||
rows: {
|
||||
id: 'settings.display.project-list-layouts.mode.rows',
|
||||
defaultMessage: 'Rows',
|
||||
},
|
||||
grid: {
|
||||
id: 'settings.display.project-list-layouts.mode.grid',
|
||||
defaultMessage: 'Grid',
|
||||
},
|
||||
gallery: {
|
||||
id: 'settings.display.project-list-layouts.mode.gallery',
|
||||
defaultMessage: 'Gallery',
|
||||
},
|
||||
})
|
||||
|
||||
const notifications = defineMessages({
|
||||
developerModeDeactivatedTitle: {
|
||||
id: 'settings.display.notification.developer-mode-deactivated.title',
|
||||
@@ -245,65 +112,6 @@ const notifications = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const colorTheme = defineMessages({
|
||||
title: {
|
||||
id: 'settings.display.theme.title',
|
||||
defaultMessage: 'Color theme',
|
||||
},
|
||||
description: {
|
||||
id: 'settings.display.theme.description',
|
||||
defaultMessage: 'Select your preferred color theme for Modrinth on this device.',
|
||||
},
|
||||
})
|
||||
|
||||
const projectListLayouts = defineMessages({
|
||||
title: {
|
||||
id: 'settings.display.project-list-layouts.title',
|
||||
defaultMessage: 'Project list layouts',
|
||||
},
|
||||
description: {
|
||||
id: 'settings.display.project-list-layouts.description',
|
||||
defaultMessage:
|
||||
'Select your preferred layout for each page that displays project lists on this device.',
|
||||
},
|
||||
mod: {
|
||||
id: 'settings.display.project-list-layouts.mod',
|
||||
defaultMessage: 'Mods page',
|
||||
},
|
||||
plugin: {
|
||||
id: 'settings.display.project-list-layouts.plugin',
|
||||
defaultMessage: 'Plugins page',
|
||||
},
|
||||
datapack: {
|
||||
id: 'settings.display.project-list-layouts.datapack',
|
||||
defaultMessage: 'Data Packs page',
|
||||
},
|
||||
shader: {
|
||||
id: 'settings.display.project-list-layouts.shader',
|
||||
defaultMessage: 'Shaders page',
|
||||
},
|
||||
resourcepack: {
|
||||
id: 'settings.display.project-list-layouts.resourcepack',
|
||||
defaultMessage: 'Resource Packs page',
|
||||
},
|
||||
modpack: {
|
||||
id: 'settings.display.project-list-layouts.modpack',
|
||||
defaultMessage: 'Modpacks page',
|
||||
},
|
||||
server: {
|
||||
id: 'settings.display.project-list-layouts.server',
|
||||
defaultMessage: 'Servers page',
|
||||
},
|
||||
user: {
|
||||
id: 'settings.display.project-list-layouts.user',
|
||||
defaultMessage: 'User profile pages',
|
||||
},
|
||||
collection: {
|
||||
id: 'settings.display.project-list.layouts.collection',
|
||||
defaultMessage: 'Collection',
|
||||
},
|
||||
})
|
||||
|
||||
const toggleFeatures = defineMessages({
|
||||
title: {
|
||||
id: 'settings.display.flags.title',
|
||||
@@ -360,44 +168,6 @@ const toggleFeatures = defineMessages({
|
||||
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
const tags = useGeneratedState()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
// On the server the value of native theme can be 'unknown'. To hydrate
|
||||
// correctly, we need to make sure we aren't using 'unknown' and values between
|
||||
// server and client renders are in sync.
|
||||
|
||||
const serverSystemTheme = useState(() => {
|
||||
const theme_ = theme.native
|
||||
if (theme_ === 'unknown') return 'light'
|
||||
return theme_
|
||||
})
|
||||
|
||||
const systemTheme = useMountedValue((mounted): Theme => {
|
||||
const systemTheme_ = mounted ? theme.native : serverSystemTheme.value
|
||||
return systemTheme_ === 'light' ? theme.preferences.light : theme.preferences.dark
|
||||
})
|
||||
|
||||
const themeOptions = computed(() => {
|
||||
const options: ('system' | Theme)[] = ['system', 'light', 'dark', 'oled']
|
||||
if (flags.value.developerMode || theme.preferred === 'retro') {
|
||||
options.push('retro')
|
||||
}
|
||||
return options
|
||||
})
|
||||
|
||||
function updateColorTheme(value: Theme | 'system') {
|
||||
if (value !== 'system') {
|
||||
if (isDarkTheme(value)) {
|
||||
theme.preferences.dark = value
|
||||
} else {
|
||||
theme.preferences.light = value
|
||||
}
|
||||
}
|
||||
|
||||
theme.preferred = value
|
||||
}
|
||||
|
||||
function disableDeveloperMode() {
|
||||
flags.value.developerMode = !flags.value.developerMode
|
||||
@@ -408,24 +178,6 @@ function disableDeveloperMode() {
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
const listTypes = computed(() => {
|
||||
const types = tags.value.projectTypes.map((type) => {
|
||||
return {
|
||||
id: type.id as DisplayLocation,
|
||||
name: formatProjectType(type.id) + 's',
|
||||
display: 'the ' + formatProjectType(type.id).toLowerCase() + 's search page',
|
||||
}
|
||||
})
|
||||
|
||||
types.push({
|
||||
id: 'user' as DisplayLocation,
|
||||
name: 'User profiles',
|
||||
display: 'user pages',
|
||||
})
|
||||
|
||||
return types
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.project-lists {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Admonition,
|
||||
commonSettingsMessages,
|
||||
injectI18n,
|
||||
IntlFormatted,
|
||||
LanguageSelector,
|
||||
languageSelectorMessages,
|
||||
LOCALES,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { locale, setLocale } = injectI18n()
|
||||
|
||||
const platform = computed(() => formatMessage(languageSelectorMessages.platformSite))
|
||||
|
||||
const $isChanging = ref(false)
|
||||
|
||||
async function onLocaleChange(newLocale: string) {
|
||||
if (locale.value === newLocale) return
|
||||
|
||||
$isChanging.value = true
|
||||
try {
|
||||
await setLocale(newLocale)
|
||||
} finally {
|
||||
$isChanging.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.language) }}</h2>
|
||||
|
||||
<Admonition type="warning">
|
||||
{{ formatMessage(languageSelectorMessages.languageWarning, { platform }) }}
|
||||
</Admonition>
|
||||
|
||||
<div class="card-description mt-4">
|
||||
<IntlFormatted
|
||||
:message-id="languageSelectorMessages.languagesDescription"
|
||||
:values="{ platform }"
|
||||
>
|
||||
<template #~crowdin-link="{ children }">
|
||||
<a href="https://translate.modrinth.com">
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</div>
|
||||
|
||||
<LanguageSelector
|
||||
:current-locale="locale"
|
||||
:locales="LOCALES"
|
||||
:on-locale-change="onLocaleChange"
|
||||
:is-changing="$isChanging"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-description {
|
||||
margin-bottom: calc(var(--spacing-card-sm) + var(--spacing-card-md));
|
||||
|
||||
a {
|
||||
color: var(--color-link);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-link-hover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-link-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="universal-card">
|
||||
<div class="flex flex-col">
|
||||
<ConfirmModal
|
||||
ref="modal_confirm"
|
||||
:title="formatMessage(deleteModalMessages.title)"
|
||||
@@ -95,26 +95,26 @@
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
<div class="header__row">
|
||||
<div class="header__title">
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.pats) }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
scopesVal = 0
|
||||
expires = null
|
||||
editPatId = null
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.create) }}
|
||||
</button>
|
||||
<div class="mb-1 flex flex-wrap items-center justify-between gap-3">
|
||||
<p class="m-0 font-bold">{{ formatMessage(commonSettingsMessages.pats) }}</p>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
type="button"
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
scopesVal = 0
|
||||
expires = null
|
||||
editPatId = null
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ formatMessage(messages.create) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p>
|
||||
<p class="mb-2 mt-0">
|
||||
<IntlFormatted :message-id="messages.description">
|
||||
<template #doc-link="{ children }">
|
||||
<a class="text-link" href="https://docs.modrinth.com">
|
||||
@@ -123,7 +123,11 @@
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<div v-for="pat in displayPats" :key="pat.id" class="universal-card recessed token">
|
||||
<div
|
||||
v-for="pat in displayPats"
|
||||
:key="pat.id"
|
||||
class="token border-t border-divider pt-4 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<strong>{{ pat.name }}</strong>
|
||||
@@ -172,38 +176,45 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="token-actions ml-auto flex flex-col gap-2">
|
||||
<button
|
||||
class="iconified-button raised-button"
|
||||
@click="
|
||||
() => {
|
||||
editPatId = pat.id
|
||||
name = pat.name
|
||||
scopesVal = pat.scopes
|
||||
expires = $dayjs(pat.expires).format('YYYY-MM-DD')
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon /> {{ formatMessage(tokenMessages.edit) }}
|
||||
</button>
|
||||
<button
|
||||
class="iconified-button raised-button"
|
||||
@click="
|
||||
() => {
|
||||
deletePatIndex = pat.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon /> {{ formatMessage(tokenMessages.revoke) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
editPatId = pat.id
|
||||
name = pat.name
|
||||
scopesVal = pat.scopes
|
||||
expires = $dayjs(pat.expires).format('YYYY-MM-DD')
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ formatMessage(tokenMessages.edit) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
deletePatIndex = pat.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ formatMessage(tokenMessages.revoke) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, XIcon } from '@modrinth/assets'
|
||||
import { PlusIcon, SaveIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<section class="card">
|
||||
<h2 class="text-2xl">{{ formatMessage(messages.title) }}</h2>
|
||||
<p class="mb-4">
|
||||
<section class="flex flex-col">
|
||||
<p class="mb-2 mt-0">
|
||||
<IntlFormatted :message-id="messages.description">
|
||||
<template #docs-link="{ children }">
|
||||
<a href="https://docs.modrinth.com/" target="_blank" class="text-link">
|
||||
@@ -11,9 +10,6 @@
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<label>
|
||||
<span class="label__title">{{ formatMessage(messages.profilePicture) }}</span>
|
||||
</label>
|
||||
<div class="avatar-changer">
|
||||
<Avatar
|
||||
:src="previewImage ? previewImage : avatarUrl"
|
||||
@@ -21,53 +17,50 @@
|
||||
circle
|
||||
:alt="auth.user.username"
|
||||
/>
|
||||
<div class="input-stack">
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
class="btn"
|
||||
:prompt="formatMessage(commonMessages.uploadImageButton)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
<Button v-if="avatarUrl !== null" :action="removePreviewImage">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.removeImageButton) }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="previewImage"
|
||||
:action="
|
||||
() => {
|
||||
icon = null
|
||||
previewImage = null
|
||||
}
|
||||
"
|
||||
>
|
||||
<UndoIcon />
|
||||
{{ formatMessage(commonMessages.resetButton) }}
|
||||
</Button>
|
||||
<div class="flex flex-col gap-1">
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
class="button-like"
|
||||
:prompt="formatMessage(commonMessages.uploadImageButton)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button v-if="avatarUrl !== null" @click="removePreviewImage">
|
||||
{{ formatMessage(commonMessages.removeImageButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
v-if="previewImage"
|
||||
@click="
|
||||
() => {
|
||||
icon = null
|
||||
previewImage = null
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ formatMessage(commonMessages.resetButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<label for="username-field">
|
||||
<span class="label__title">{{ formatMessage(commonMessages.usernameLabel) }}</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(messages.usernameDescription) }}
|
||||
</span>
|
||||
<p class="mb-2 mt-4 font-bold">{{ formatMessage(commonMessages.usernameLabel) }}</p>
|
||||
</label>
|
||||
<StyledInput id="username-field" v-model="current.username" />
|
||||
<label for="bio-field">
|
||||
<span class="label__title">{{ formatMessage(messages.bioTitle) }}</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(messages.bioDescription) }}
|
||||
</span>
|
||||
<p class="mb-2 mt-4 font-bold">{{ formatMessage(messages.bioTitle) }}</p>
|
||||
</label>
|
||||
<StyledInput id="bio-field" v-model="current.bio" multiline />
|
||||
<div class="input-group mt-4">
|
||||
<Button :link="`/user/${auth.user.username}`">
|
||||
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
|
||||
</Button>
|
||||
<nuxt-link :to="`/user/${auth.user.username}`" class="text-link">
|
||||
{{ formatMessage(commonMessages.visitYourProfile) }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</section>
|
||||
<UnsavedChangesPopup
|
||||
@@ -81,10 +74,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { TrashIcon, UndoIcon, UploadIcon, UserIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
FileInput,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<div class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.sessions) }}</h2>
|
||||
<p class="preserve-lines">
|
||||
<div class="flex flex-col">
|
||||
<p class="mb-1 mt-0 font-bold">{{ formatMessage(commonSettingsMessages.sessions) }}</p>
|
||||
<p class="preserve-lines mb-2 mt-0">
|
||||
{{ formatMessage(messages.sessionsDescription) }}
|
||||
</p>
|
||||
<div v-for="session in sessions" :key="session.id" class="universal-card recessed session mt-4">
|
||||
<div
|
||||
v-for="session in sessions"
|
||||
:key="session.id"
|
||||
class="session mt-6 border-t border-divider first:mt-0 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<strong>
|
||||
@@ -34,16 +38,18 @@
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<i v-if="session.current">{{ formatMessage(messages.currentSessionLabel) }}</i>
|
||||
<button v-else class="iconified-button raised-button" @click="revokeSession(session.id)">
|
||||
<XIcon /> {{ formatMessage(messages.revokeSessionButton) }}
|
||||
</button>
|
||||
<ButtonStyled v-else color="red">
|
||||
<button type="button" class="iconified-button" @click="revokeSession(session.id)">
|
||||
{{ formatMessage(messages.revokeSessionButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
defineMessages,
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
{{
|
||||
user.bio
|
||||
? user.bio
|
||||
: projects.length === 0
|
||||
: projects?.length === 0
|
||||
? formatMessage(messages.bioFallbackUser)
|
||||
: formatMessage(messages.bioFallbackCreator)
|
||||
}}
|
||||
@@ -151,7 +151,6 @@
|
||||
<div
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-divider pr-4 font-semibold"
|
||||
>
|
||||
<BoxIcon class="h-6 w-6 text-secondary" />
|
||||
{{
|
||||
formatMessage(messages.profileProjectsLabel, {
|
||||
count: formatCompactNumber(projects?.length || 0),
|
||||
@@ -163,7 +162,6 @@
|
||||
v-tooltip="formatNumber(sumDownloads)"
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-divider pr-4 font-semibold"
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||
{{
|
||||
formatMessage(messages.profileDownloadsLabel, {
|
||||
count: formatCompactNumber(sumDownloads),
|
||||
@@ -175,274 +173,209 @@
|
||||
v-tooltip="formatDateTime(user.created)"
|
||||
class="flex items-center gap-2 font-semibold"
|
||||
>
|
||||
<CalendarIcon class="h-6 w-6 text-secondary" />
|
||||
{{ formatMessage(messages.profileJoinedLabel) }}
|
||||
{{ formatRelativeTime(user.created) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<ButtonStyled size="large">
|
||||
<NuxtLink v-if="auth.user && auth.user.id === user.id" to="/settings/profile">
|
||||
<EditIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.editButton) }}
|
||||
</NuxtLink>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled size="large" circular type="transparent">
|
||||
<OverflowMenu
|
||||
:options="[
|
||||
{
|
||||
id: 'manage-projects',
|
||||
action: () => navigateTo('/dashboard/projects'),
|
||||
hoverOnly: true,
|
||||
shown: auth.user && auth.user.id === user.id,
|
||||
},
|
||||
{ divider: true, shown: auth.user && auth.user.id === user.id },
|
||||
{
|
||||
id: 'report',
|
||||
action: () => (auth.user ? reportUser(user.id) : navigateTo('/auth/sign-in')),
|
||||
color: 'red',
|
||||
hoverOnly: true,
|
||||
shown: auth.user?.id !== user.id,
|
||||
},
|
||||
{ id: 'copy-id', action: () => copyId() },
|
||||
{ id: 'copy-permalink', action: () => copyPermalink() },
|
||||
{
|
||||
divider: true,
|
||||
shown: auth.user && isAdmin(auth.user),
|
||||
},
|
||||
{
|
||||
id: 'open-billing',
|
||||
action: () => navigateTo(`/admin/billing/${user.id}`),
|
||||
shown: auth.user && isStaff(auth.user),
|
||||
},
|
||||
{
|
||||
id: 'toggle-affiliate',
|
||||
action: () => toggleAffiliate(user.id),
|
||||
shown: isAdminViewing,
|
||||
remainOnClick: true,
|
||||
color: isAffiliate ? 'red' : 'orange',
|
||||
},
|
||||
{
|
||||
id: 'open-info',
|
||||
action: () => $refs.userDetailsModal.show(),
|
||||
shown: auth.user && isStaff(auth.user),
|
||||
},
|
||||
{
|
||||
id: 'edit-role',
|
||||
action: () => openRoleEditModal(),
|
||||
shown: auth.user && isAdmin(auth.user),
|
||||
},
|
||||
]"
|
||||
aria-label="More options"
|
||||
:dropdown-id="`${baseId}-more-options`"
|
||||
<div class="flex flex-col gap-2">
|
||||
<nuxt-link
|
||||
v-if="auth.user && auth.user.id === user.id"
|
||||
to="/settings/profile"
|
||||
class="text-link"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
<template #manage-projects>
|
||||
<BoxIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.profileManageProjectsButton) }}
|
||||
</template>
|
||||
<template #report>
|
||||
<ReportIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.reportButton) }}
|
||||
</template>
|
||||
<template #copy-id>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.copyIdButton) }}
|
||||
</template>
|
||||
<template #copy-permalink>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.copyPermalinkButton) }}
|
||||
</template>
|
||||
<template #open-billing>
|
||||
<CurrencyIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.billingButton) }}
|
||||
</template>
|
||||
<template #open-info>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.infoButton) }}
|
||||
</template>
|
||||
<template #toggle-affiliate>
|
||||
<AffiliateIcon aria-hidden="true" />
|
||||
{{
|
||||
formatMessage(
|
||||
isAffiliate ? messages.removeAffiliateButton : messages.setAffiliateButton,
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
<template #edit-role>
|
||||
<EditIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.editRoleButton) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
Edit profile ►
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
v-if="auth.user && auth.user.id === user.id"
|
||||
to="/dashboard/projects"
|
||||
class="text-link"
|
||||
>
|
||||
Manage projects ►
|
||||
</nuxt-link>
|
||||
<button
|
||||
v-if="auth.user?.id !== user.id"
|
||||
class="bg-transparent p-0 text-link"
|
||||
@click="() => (auth.user ? reportUser(user.id) : navigateTo('/auth/sign-in'))"
|
||||
>
|
||||
⚠︎ Report user
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</ContentPageHeader>
|
||||
</div>
|
||||
<div class="normal-page__content">
|
||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||
<div v-if="navLinks?.length > 2" class="max-w-full overflow-x-auto">
|
||||
<NavTabs :links="navLinks" replace />
|
||||
</div>
|
||||
<div v-if="projects?.length > 0">
|
||||
<ProjectCardList
|
||||
v-if="route.params.projectType !== 'collections'"
|
||||
:layout="cosmetics.searchDisplayMode.user"
|
||||
<div class="mx-4 rounded-[4px] border border-solid border-[#b5b5b5] bg-[#b5b5b5] p-3">
|
||||
<div v-if="projects?.length > 0">
|
||||
<ProjectCardList
|
||||
v-if="route.params.projectType !== 'collections'"
|
||||
:layout="cosmetics.searchDisplayMode.user"
|
||||
>
|
||||
<ProjectCard
|
||||
v-for="project in (route.params.projectType !== undefined
|
||||
? projects.filter(
|
||||
(x) =>
|
||||
x.project_type ===
|
||||
route.params.projectType.substr(0, route.params.projectType?.length - 1),
|
||||
)
|
||||
: projects
|
||||
)
|
||||
.slice()
|
||||
.sort((a, b) => b.downloads - a.downloads)"
|
||||
:key="project.id"
|
||||
class="rounded-[4px] bg-white"
|
||||
:link="`/${project.project_type ?? 'project'}/${project.slug ? project.slug : project.id}`"
|
||||
:title="project.title"
|
||||
:icon-url="project.icon_url"
|
||||
:date-updated="project.updated"
|
||||
:downloads="project.downloads"
|
||||
:summary="project.description"
|
||||
:tags="[...project.categories]"
|
||||
:all-tags="[
|
||||
...project.categories,
|
||||
...project.loaders,
|
||||
...project.additional_categories,
|
||||
]"
|
||||
:followers="project.followers"
|
||||
:banner="project.gallery.find((element) => element.featured)?.url"
|
||||
:color="project.color ?? undefined"
|
||||
:environment="{
|
||||
clientSide: project.client_side,
|
||||
serverSide: project.server_side,
|
||||
}"
|
||||
:layout="
|
||||
cosmetics.searchDisplayMode.user === 'grid' ||
|
||||
cosmetics.searchDisplayMode.user === 'gallery'
|
||||
? 'grid'
|
||||
: 'list'
|
||||
"
|
||||
:status="project.status"
|
||||
/>
|
||||
</ProjectCardList>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
(route.params.projectType && route.params.projectType !== 'collections') ||
|
||||
(!route.params.projectType && collections?.length === 0)
|
||||
"
|
||||
class="error"
|
||||
>
|
||||
<ProjectCard
|
||||
v-for="project in (route.params.projectType !== undefined
|
||||
? projects.filter(
|
||||
(x) =>
|
||||
x.project_type ===
|
||||
route.params.projectType.substr(0, route.params.projectType.length - 1),
|
||||
)
|
||||
: projects
|
||||
)
|
||||
.slice()
|
||||
.sort((a, b) => b.downloads - a.downloads)"
|
||||
:key="project.id"
|
||||
:link="`/${project.project_type ?? 'project'}/${project.slug ? project.slug : project.id}`"
|
||||
:title="project.title"
|
||||
:icon-url="project.icon_url"
|
||||
:date-updated="project.updated"
|
||||
:downloads="project.downloads"
|
||||
:summary="project.description"
|
||||
:tags="[...project.categories]"
|
||||
:all-tags="[
|
||||
...project.categories,
|
||||
...project.loaders,
|
||||
...project.additional_categories,
|
||||
]"
|
||||
:followers="project.followers"
|
||||
:banner="project.gallery.find((element) => element.featured)?.url"
|
||||
:color="project.color ?? undefined"
|
||||
:environment="{
|
||||
clientSide: project.client_side,
|
||||
serverSide: project.server_side,
|
||||
}"
|
||||
:layout="
|
||||
cosmetics.searchDisplayMode.user === 'grid' ||
|
||||
cosmetics.searchDisplayMode.user === 'gallery'
|
||||
? 'grid'
|
||||
: 'list'
|
||||
"
|
||||
:status="project.status"
|
||||
/>
|
||||
</ProjectCardList>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
(route.params.projectType && route.params.projectType !== 'collections') ||
|
||||
(!route.params.projectType && collections?.length === 0)
|
||||
"
|
||||
class="error"
|
||||
>
|
||||
<UpToDate class="icon" />
|
||||
<br />
|
||||
<span v-if="auth.user && auth.user.id === user.id" class="preserve-lines text">
|
||||
<IntlFormatted :message-id="messages.profileNoProjectsAuthLabel">
|
||||
<template #create-link="{ children }">
|
||||
<a class="link" @click.prevent="$refs.modal_creation.show()">
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</span>
|
||||
<span v-else class="text">{{ formatMessage(messages.profileNoProjectsLabel) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!route.params.projectType || route.params.projectType === 'collections'"
|
||||
class="collections-grid"
|
||||
>
|
||||
<nuxt-link
|
||||
v-for="collection in (collections ?? []).sort(
|
||||
(a, b) => new Date(b.created) - new Date(a.created),
|
||||
)"
|
||||
:key="collection.id"
|
||||
:to="`/collection/${collection.id}`"
|
||||
class="card collection-item"
|
||||
<UpToDate class="icon" />
|
||||
<br />
|
||||
<span v-if="auth.user && auth.user.id === user.id" class="preserve-lines text">
|
||||
<IntlFormatted :message-id="messages.profileNoProjectsAuthLabel">
|
||||
<template #create-link="{ children }">
|
||||
<a class="link" @click.prevent="$refs.modal_creation.show()">
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</span>
|
||||
<span v-else class="text">{{ formatMessage(messages.profileNoProjectsLabel) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!route.params.projectType || route.params.projectType === 'collections'"
|
||||
class="collections-grid"
|
||||
:class="{
|
||||
'mt-3': !route.params.projectType && projects?.length !== 0,
|
||||
}"
|
||||
>
|
||||
<div class="collection">
|
||||
<Avatar :src="collection.icon_url" size="64px" />
|
||||
<div class="details">
|
||||
<h2 class="title">{{ collection.name }}</h2>
|
||||
<div class="stats">
|
||||
<LibraryIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.collectionLabel) }}
|
||||
<nuxt-link
|
||||
v-for="collection in (collections ?? []).sort(
|
||||
(a, b) => new Date(b.created) - new Date(a.created),
|
||||
)"
|
||||
:key="collection.id"
|
||||
:to="`/collection/${collection.id}`"
|
||||
class="collection-item rounded-[4px] bg-white p-4"
|
||||
>
|
||||
<div class="collection">
|
||||
<Avatar :src="collection.icon_url" size="64px" />
|
||||
<div class="details">
|
||||
<h2 class="title">{{ collection.name }}</h2>
|
||||
<div class="stats">
|
||||
<LibraryIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.collectionLabel) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ collection.description }}
|
||||
</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stats">
|
||||
<BoxIcon />
|
||||
{{
|
||||
`${$formatNumber(collection.projects?.length || 0, false)} project${
|
||||
(collection.projects?.length || 0) !== 1 ? 's' : ''
|
||||
}`
|
||||
}}
|
||||
<div class="description">
|
||||
{{ collection.description }}
|
||||
</div>
|
||||
<div class="stats">
|
||||
<template v-if="collection.status === 'listed'">
|
||||
<GlobeIcon />
|
||||
<span> {{ formatMessage(commonMessages.publicLabel) }} </span>
|
||||
</template>
|
||||
<template v-else-if="collection.status === 'unlisted'">
|
||||
<LinkIcon />
|
||||
<span> {{ formatMessage(commonMessages.unlistedLabel) }} </span>
|
||||
</template>
|
||||
<template v-else-if="collection.status === 'private'">
|
||||
<LockIcon />
|
||||
<span> {{ formatMessage(commonMessages.privateLabel) }} </span>
|
||||
</template>
|
||||
<template v-else-if="collection.status === 'rejected'">
|
||||
<XIcon />
|
||||
<span> {{ formatMessage(commonMessages.rejectedLabel) }} </span>
|
||||
</template>
|
||||
<div class="stat-bar">
|
||||
<div class="stats">
|
||||
<BoxIcon />
|
||||
{{
|
||||
`${$formatNumber(collection.projects?.length || 0, false)} project${
|
||||
(collection.projects?.length || 0) !== 1 ? 's' : ''
|
||||
}`
|
||||
}}
|
||||
</div>
|
||||
<div class="stats">
|
||||
<template v-if="collection.status === 'listed'">
|
||||
<GlobeIcon />
|
||||
<span> {{ formatMessage(commonMessages.publicLabel) }} </span>
|
||||
</template>
|
||||
<template v-else-if="collection.status === 'unlisted'">
|
||||
<LinkIcon />
|
||||
<span> {{ formatMessage(commonMessages.unlistedLabel) }} </span>
|
||||
</template>
|
||||
<template v-else-if="collection.status === 'private'">
|
||||
<LockIcon />
|
||||
<span> {{ formatMessage(commonMessages.privateLabel) }} </span>
|
||||
</template>
|
||||
<template v-else-if="collection.status === 'rejected'">
|
||||
<XIcon />
|
||||
<span> {{ formatMessage(commonMessages.rejectedLabel) }} </span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<div
|
||||
v-if="route.params.projectType === 'collections' && collections?.length === 0"
|
||||
class="error"
|
||||
>
|
||||
<UpToDate class="icon" />
|
||||
<br />
|
||||
<span v-if="auth.user && auth.user.id === user.id" class="preserve-lines text">
|
||||
<IntlFormatted :message-id="messages.profileNoCollectionsAuthLabel">
|
||||
<template #create-link="{ children }">
|
||||
<a
|
||||
class="link"
|
||||
@click.prevent="(event) => $refs.modal_collection_creation.show(event)"
|
||||
>
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</span>
|
||||
<span v-else class="text">{{ formatMessage(messages.profileNoCollectionsLabel) }}</span>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<div
|
||||
v-if="route.params.projectType === 'collections' && collections?.length === 0"
|
||||
class="error"
|
||||
>
|
||||
<UpToDate class="icon" />
|
||||
<br />
|
||||
<span v-if="auth.user && auth.user.id === user.id" class="preserve-lines text">
|
||||
<IntlFormatted :message-id="messages.profileNoCollectionsAuthLabel">
|
||||
<template #create-link="{ children }">
|
||||
<a
|
||||
class="link"
|
||||
@click.prevent="(event) => $refs.modal_collection_creation.show(event)"
|
||||
>
|
||||
<component :is="() => children" />
|
||||
</a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</span>
|
||||
<span v-else class="text">{{ formatMessage(messages.profileNoCollectionsLabel) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="normal-page__sidebar">
|
||||
<div v-if="organizations?.length > 0" class="card flex-card">
|
||||
<h2 class="text-lg text-contrast">
|
||||
<div v-if="organizations?.length > 0" class="mb-4">
|
||||
<h2 class="mb-1 mt-0 text-lg text-contrast">
|
||||
{{ formatMessage(messages.profileOrganizations) }}
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<nuxt-link
|
||||
v-for="org in sortedOrgs"
|
||||
:key="org.id"
|
||||
v-tooltip="org.name"
|
||||
class="organization"
|
||||
class="flex items-center gap-2 font-bold text-link"
|
||||
:to="`/organization/${org.slug}`"
|
||||
>
|
||||
<Avatar :src="org.icon_url" :alt="'Icon for ' + org.name" size="3rem" />
|
||||
<Avatar :src="org.icon_url" :alt="'Icon for ' + org.name" size="2rem" />
|
||||
{{ org.name }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="badges.length > 0" class="card flex-card">
|
||||
<h2 class="text-lg text-contrast">
|
||||
<div v-if="badges?.length > 0">
|
||||
<h2 class="mb-1 mt-0 text-lg text-contrast">
|
||||
{{ formatMessage(messages.profileBadges) }}
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -468,19 +401,11 @@
|
||||
import {
|
||||
AffiliateIcon,
|
||||
BoxIcon,
|
||||
CalendarIcon,
|
||||
CheckIcon,
|
||||
ClipboardCopyIcon,
|
||||
CurrencyIcon,
|
||||
DownloadIcon,
|
||||
EditIcon,
|
||||
GlobeIcon,
|
||||
InfoIcon,
|
||||
LibraryIcon,
|
||||
LinkIcon,
|
||||
LockIcon,
|
||||
MoreVerticalIcon,
|
||||
ReportIcon,
|
||||
SaveIcon,
|
||||
SpinnerIcon,
|
||||
XIcon,
|
||||
@@ -497,7 +422,6 @@ import {
|
||||
IntlFormatted,
|
||||
NavTabs,
|
||||
NewModal,
|
||||
OverflowMenu,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
TagItem,
|
||||
@@ -508,7 +432,7 @@ import {
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { onServerPrefetch } from 'vue'
|
||||
|
||||
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
|
||||
@@ -522,6 +446,7 @@ import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
|
||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
||||
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
|
||||
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
||||
import { reportUser } from '~/utils/report-helpers.ts'
|
||||
|
||||
const data = useNuxtApp()
|
||||
@@ -529,8 +454,6 @@ const route = useNativeRoute()
|
||||
const auth = await useAuth()
|
||||
const cosmetics = useCosmetics()
|
||||
const tags = useGeneratedState()
|
||||
const config = useRuntimeConfig()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatNumber = useFormatNumber()
|
||||
@@ -543,8 +466,6 @@ const formatDateTime = useFormatDateTime({
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
|
||||
const baseId = useId()
|
||||
|
||||
const messages = defineMessages({
|
||||
profileProjectsLabel: {
|
||||
id: 'profile.label.projects',
|
||||
@@ -841,22 +762,9 @@ const badges = computed(() => {
|
||||
return badges
|
||||
})
|
||||
|
||||
async function copyId() {
|
||||
await navigator.clipboard.writeText(user.value.id)
|
||||
}
|
||||
|
||||
async function copyPermalink() {
|
||||
await navigator.clipboard.writeText(`${config.public.siteUrl}/user/${user.value.id}`)
|
||||
}
|
||||
|
||||
const isAffiliate = computed(() => user.value?.badges & UserBadge.AFFILIATE)
|
||||
const isAdminViewing = computed(() => isAdmin(auth.value.user))
|
||||
|
||||
async function toggleAffiliate(id) {
|
||||
await client.labrinth.users_v2.patch(id, { badges: user.value.badges ^ (1 << 7) })
|
||||
queryClient.invalidateQueries({ queryKey: ['user', route.params.id] })
|
||||
}
|
||||
|
||||
const navLinks = computed(() => [
|
||||
{
|
||||
label: formatMessage(commonMessages.allProjectType),
|
||||
@@ -884,11 +792,6 @@ const roleOptions = [
|
||||
|
||||
const editRoleModal = useTemplateRef('editRoleModal')
|
||||
|
||||
const openRoleEditModal = () => {
|
||||
selectedRole.value = user.value.role
|
||||
editRoleModal.value?.show()
|
||||
}
|
||||
|
||||
const cancelRoleEdit = () => {
|
||||
selectedRole.value = user.value.role
|
||||
editRoleModal.value?.hide()
|
||||
@@ -938,7 +841,6 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
gap: var(--gap-md);
|
||||
margin-top: var(--gap-md);
|
||||
|
||||
.collection-item {
|
||||
display: flex;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { DarkTheme } from './theme/index.ts'
|
||||
|
||||
export type DisplayMode = 'list' | 'gallery' | 'grid'
|
||||
|
||||
export type DisplayLocation =
|
||||
@@ -19,7 +17,6 @@ export interface Cosmetics {
|
||||
externalLinksNewTab: boolean
|
||||
notUsingBlockers: boolean
|
||||
hideModrinthAppPromos: boolean
|
||||
preferredDarkTheme: DarkTheme
|
||||
searchDisplayMode: Record<DisplayLocation, DisplayMode>
|
||||
hideStagingBanner: boolean
|
||||
}
|
||||
@@ -40,7 +37,6 @@ export default defineNuxtPlugin({
|
||||
externalLinksNewTab: true,
|
||||
notUsingBlockers: false,
|
||||
hideModrinthAppPromos: false,
|
||||
preferredDarkTheme: 'dark',
|
||||
searchDisplayMode: {
|
||||
mod: 'list',
|
||||
plugin: 'list',
|
||||
|
||||
@@ -168,23 +168,9 @@ export default defineNuxtPlugin({
|
||||
async function setLocale(newLocale: string): Promise<void> {
|
||||
debug('setLocale: called', { newLocale, currentLocale: locale.value })
|
||||
|
||||
if (!LOCALES.some((l) => l.code === newLocale)) {
|
||||
debug('setLocale: invalid locale', newLocale)
|
||||
return
|
||||
}
|
||||
|
||||
await loadLocale(newLocale)
|
||||
|
||||
debug('setLocale: loaded', {
|
||||
newLocale,
|
||||
cacheHas: messageCache.has(newLocale),
|
||||
cacheKeys: messageCache.get(newLocale)
|
||||
? Object.keys(messageCache.get(newLocale)!).length
|
||||
: 0,
|
||||
})
|
||||
|
||||
locale.value = newLocale
|
||||
useCookie('locale', { maxAge: 31536000, path: '/' }).value = newLocale
|
||||
locale.value = DEFAULT_LOCALE
|
||||
useCookie('locale', { maxAge: 31536000, path: '/' }).value = DEFAULT_LOCALE
|
||||
return
|
||||
}
|
||||
|
||||
// Detect initial locale (cookie > Accept-Language > default)
|
||||
@@ -205,8 +191,8 @@ export default defineNuxtPlugin({
|
||||
|
||||
// Load locales (hits cache after first request)
|
||||
await loadLocale(DEFAULT_LOCALE)
|
||||
if (detectedLocale !== DEFAULT_LOCALE) await loadLocale(detectedLocale)
|
||||
locale.value = detectedLocale
|
||||
locale.value = DEFAULT_LOCALE
|
||||
useCookie('locale', { maxAge: 31536000, path: '/' }).value = DEFAULT_LOCALE
|
||||
|
||||
debug('init: complete', { locale: locale.value })
|
||||
|
||||
|
||||
@@ -1,91 +1,6 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useNativeTheme } from './native-theme.ts'
|
||||
import { usePreferredThemes } from './preferred-theme.ts'
|
||||
import { useThemeSettings } from './theme-settings.ts'
|
||||
import { isDarkTheme } from './themes.ts'
|
||||
|
||||
export * from './themes.ts'
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
name: 'theme',
|
||||
dependsOn: ['cosmetics'],
|
||||
setup(nuxtApp) {
|
||||
const $nativeTheme = useNativeTheme()
|
||||
|
||||
const $preferredThemes = usePreferredThemes()
|
||||
|
||||
function getPreferredNativeTheme() {
|
||||
const nativeTheme = $nativeTheme.value
|
||||
switch (nativeTheme) {
|
||||
case 'light':
|
||||
return $preferredThemes.light
|
||||
case 'dark':
|
||||
case 'unknown':
|
||||
if (import.meta.dev && import.meta.server && nativeTheme === 'unknown') {
|
||||
console.warn(
|
||||
'[theme] no client hint is available for request, using dark theme as default',
|
||||
)
|
||||
}
|
||||
|
||||
return $preferredThemes.dark
|
||||
}
|
||||
}
|
||||
|
||||
const $settings = useThemeSettings(() => getPreferredNativeTheme())
|
||||
|
||||
useHead({ htmlAttrs: { class: () => [`${$settings.active}-mode`] } })
|
||||
|
||||
function syncTheme() {
|
||||
$settings.active =
|
||||
$settings.preferred === 'system' ? getPreferredNativeTheme() : $settings.preferred
|
||||
}
|
||||
|
||||
if (
|
||||
import.meta.server &&
|
||||
$settings.preferred === 'system' &&
|
||||
$nativeTheme.value !== 'unknown'
|
||||
) {
|
||||
// take advantage of the client hint
|
||||
syncTheme()
|
||||
}
|
||||
|
||||
if (import.meta.client) {
|
||||
const $clientReady = ref(false)
|
||||
|
||||
nuxtApp.hook('app:suspense:resolve', () => {
|
||||
$clientReady.value = true
|
||||
})
|
||||
|
||||
watchEffect(() => $clientReady.value && syncTheme())
|
||||
}
|
||||
|
||||
function cycle() {
|
||||
const nextTheme = isDarkTheme($settings.active)
|
||||
? $preferredThemes.light
|
||||
: $preferredThemes.dark
|
||||
|
||||
$settings.preferred = nextTheme
|
||||
|
||||
return nextTheme
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
theme: reactive({
|
||||
...toRefs($settings),
|
||||
/**
|
||||
* Preferred themes for each mode.
|
||||
*/
|
||||
preferences: $preferredThemes,
|
||||
/**
|
||||
* Current native (system) theme provided through client hint header or
|
||||
* `prefers-color-scheme` media query.
|
||||
*/
|
||||
native: $nativeTheme,
|
||||
cycle,
|
||||
}),
|
||||
},
|
||||
}
|
||||
setup() {
|
||||
useHead({ htmlAttrs: { class: 'light-mode' } })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
export type SystemTheme = 'unknown' | 'light' | 'dark'
|
||||
|
||||
function useNativeThemeServer() {
|
||||
let clientHint
|
||||
|
||||
switch (useRequestHeader('Sec-CH-Prefers-Color-Scheme')) {
|
||||
case 'light':
|
||||
clientHint = 'light'
|
||||
break
|
||||
case 'dark':
|
||||
clientHint = 'dark'
|
||||
break
|
||||
default:
|
||||
clientHint = 'unknown'
|
||||
}
|
||||
|
||||
return computed(() => clientHint as SystemTheme)
|
||||
}
|
||||
|
||||
function useNativeThemeClient() {
|
||||
const lightPreference = window.matchMedia('(prefers-color-scheme: light)')
|
||||
|
||||
const isLight = ref(lightPreference.matches)
|
||||
|
||||
const onPreferenceChange = ({ matches }: MediaQueryListEvent) => (isLight.value = matches)
|
||||
|
||||
lightPreference.addEventListener('change', onPreferenceChange)
|
||||
|
||||
onScopeDispose(() => lightPreference.removeEventListener('change', onPreferenceChange))
|
||||
|
||||
return computed<SystemTheme>(() => (isLight.value ? 'light' : 'dark'))
|
||||
}
|
||||
|
||||
export function useNativeTheme() {
|
||||
if (import.meta.server) return useNativeThemeServer()
|
||||
if (import.meta.client) return useNativeThemeClient()
|
||||
throw new Error('Cannot determine the side')
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { type DarkTheme, isDarkTheme } from './themes.ts'
|
||||
|
||||
export function usePreferredThemes() {
|
||||
// TODO: migrate theme preferences out of cosmetics to theme settings
|
||||
const cosmetics = useCosmetics()
|
||||
|
||||
const dark = computed({
|
||||
get(): DarkTheme {
|
||||
const theme = cosmetics.value?.preferredDarkTheme
|
||||
|
||||
if (theme == null) {
|
||||
console.warn('[theme] cosmetics.preferredDarkTheme is not defined')
|
||||
return 'dark'
|
||||
}
|
||||
|
||||
if (!isDarkTheme(theme)) {
|
||||
console.warn(`[theme] cosmetics.preferredDarkTheme contains invalid value: ${theme}`)
|
||||
return 'dark'
|
||||
}
|
||||
|
||||
return theme
|
||||
},
|
||||
set(value) {
|
||||
cosmetics.value.preferredDarkTheme = value
|
||||
},
|
||||
})
|
||||
|
||||
return reactive({
|
||||
dark,
|
||||
light: 'light' as const,
|
||||
})
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { Theme } from './themes.ts'
|
||||
|
||||
interface ThemeSettings {
|
||||
preference: Theme | 'system'
|
||||
value: Theme
|
||||
}
|
||||
|
||||
export function useThemeSettings(getDefaultTheme?: () => Theme) {
|
||||
getDefaultTheme ??= () => 'dark'
|
||||
|
||||
const $settings = useCookie<ThemeSettings>('color-mode', {
|
||||
maxAge: 60 * 60 * 24 * 365 * 10,
|
||||
sameSite: 'lax',
|
||||
secure: true,
|
||||
httpOnly: false,
|
||||
path: '/',
|
||||
})
|
||||
|
||||
// reset theme settings to a default value if the cookie is missing or contains invalid value
|
||||
if ($settings.value == null || typeof $settings.value !== 'object') {
|
||||
$settings.value = {
|
||||
preference: 'system',
|
||||
value: getDefaultTheme(),
|
||||
}
|
||||
}
|
||||
|
||||
return reactive({
|
||||
preferred: computed({
|
||||
get: () => $settings.value.preference ?? 'system',
|
||||
set: (value) => ($settings.value.preference = value),
|
||||
}),
|
||||
|
||||
active: computed({
|
||||
get: () => $settings.value.value ?? getDefaultTheme(),
|
||||
set: (value) => ($settings.value.value = value),
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
export const LightThemes = ['light'] as const
|
||||
|
||||
export type LightTheme = (typeof LightThemes)[number]
|
||||
|
||||
export const DarkThemes = ['dark', 'oled', 'retro'] as const
|
||||
|
||||
export type DarkTheme = (typeof DarkThemes)[number]
|
||||
|
||||
export type Theme = LightTheme | DarkTheme
|
||||
|
||||
export function isLightTheme(theme: Theme | (string & Record<never, never>)): theme is LightTheme {
|
||||
return LightThemes.includes(theme as any)
|
||||
}
|
||||
|
||||
export function isDarkTheme(theme: Theme | (string & Record<never, never>)): theme is DarkTheme {
|
||||
return DarkThemes.includes(theme as any)
|
||||
}
|
||||
|
||||
export type ThemeType = 'light' | 'dark'
|
||||
|
||||
export function getThemeType(
|
||||
theme: Theme | (string & Record<never, never>),
|
||||
): ThemeType | 'unknown' {
|
||||
if (isLightTheme(theme)) return 'light'
|
||||
if (isDarkTheme(theme)) return 'dark'
|
||||
return 'unknown'
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user