Merge commit 'b9d90aa6356c88c8d661c04ab84194cf08ea0198' into feature-clean

This commit is contained in:
2025-03-30 04:38:20 +03:00
209 changed files with 3401 additions and 1795 deletions

View File

@@ -850,6 +850,7 @@ import {
UsersIcon,
VersionIcon,
WrenchIcon,
ModrinthIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -870,7 +871,6 @@ import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.v
import { formatCategory, isRejected, isStaff, isUnderReview, renderString } from "@modrinth/utils";
import { navigateTo } from "#app";
import dayjs from "dayjs";
import ModrinthIcon from "~/assets/images/utils/modrinth.svg?component";
import Accordion from "~/components/ui/Accordion.vue";
// import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
@@ -1105,14 +1105,19 @@ let project,
featuredVersions,
versions,
organization,
resetOrganization;
resetOrganization,
projectError,
membersError,
dependenciesError,
featuredVersionsError,
versionsError;
try {
[
{ data: project, refresh: resetProject },
{ data: allMembers, refresh: resetMembers },
{ data: dependencies },
{ data: featuredVersions },
{ data: versions },
{ data: project, error: projectError, refresh: resetProject },
{ data: allMembers, error: membersError, refresh: resetMembers },
{ data: dependencies, error: dependenciesError },
{ data: featuredVersions, error: featuredVersionsError },
{ data: versions, error: versionsError },
{ data: organization, refresh: resetOrganization },
] = await Promise.all([
useAsyncData(`project/${route.params.id}`, () => useBaseFetch(`project/${route.params.id}`), {
@@ -1159,14 +1164,30 @@ try {
versions = shallowRef(toRaw(versions));
featuredVersions = shallowRef(toRaw(featuredVersions));
} catch {
} catch (err) {
throw createError({
fatal: true,
statusCode: 404,
message: "Project not found",
statusCode: err.statusCode ?? 500,
message: "Error loading project data" + (err.message ? `: ${err.message}` : ""),
});
}
function handleError(err, project = false) {
if (err.value && err.value.statusCode) {
throw createError({
fatal: true,
statusCode: err.value.statusCode,
message: err.value.statusCode === 404 && project ? "Project not found" : err.value.message,
});
}
}
handleError(projectError, true);
handleError(membersError);
handleError(dependenciesError);
handleError(featuredVersionsError);
handleError(versionsError);
if (!project.value) {
throw createError({
fatal: true,
@@ -1605,10 +1626,12 @@ const navLinks = computed(() => {
width: 25rem;
height: 25rem;
}
.animation-ring-2 {
width: 50rem;
height: 50rem;
}
.animation-ring-3 {
width: 100rem;
height: 100rem;

View File

@@ -163,14 +163,11 @@
v-if="visibility === 'approved' || visibility === 'archived'"
class="good"
/>
<ExitIcon v-else class="bad" />
<XIcon v-else class="bad" />
{{ hasModifiedVisibility() ? "Will be v" : "V" }}isible in search
</li>
<li>
<ExitIcon
v-if="visibility === 'unlisted' || visibility === 'private'"
class="bad"
/>
<XIcon v-if="visibility === 'unlisted' || visibility === 'private'" class="bad" />
<CheckIcon v-else class="good" />
{{ hasModifiedVisibility() ? "Will be v" : "V" }}isible on profile
</li>
@@ -242,20 +239,13 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import { formatProjectStatus } from "@modrinth/utils";
import { UploadIcon, SaveIcon, TrashIcon, XIcon, IssuesIcon, CheckIcon } from "@modrinth/assets";
import { Multiselect } from "vue-multiselect";
import Avatar from "~/components/ui/Avatar.vue";
import ModalConfirm from "~/components/ui/ModalConfirm.vue";
import FileInput from "~/components/ui/FileInput.vue";
import UploadIcon from "~/assets/images/utils/upload.svg?component";
import SaveIcon from "~/assets/images/utils/save.svg?component";
import TrashIcon from "~/assets/images/utils/trash.svg?component";
import ExitIcon from "~/assets/images/utils/x.svg?component";
import IssuesIcon from "~/assets/images/utils/issues.svg?component";
import CheckIcon from "~/assets/images/utils/check.svg?component";
const props = defineProps({
project: {
type: Object,

View File

@@ -155,6 +155,7 @@
<script setup lang="ts">
import { Checkbox, DropdownSelect } from "@modrinth/ui";
import { SaveIcon } from "@modrinth/assets";
import {
TeamMemberPermission,
builtinLicenses,
@@ -164,7 +165,6 @@ import {
type TeamMember,
} from "@modrinth/utils";
import { computed, ref, type Ref } from "vue";
import SaveIcon from "~/assets/images/utils/save.svg?component";
const props = defineProps<{
project: Project;

View File

@@ -123,7 +123,7 @@
<script setup>
import { DropdownSelect } from "@modrinth/ui";
import SaveIcon from "~/assets/images/utils/save.svg?component";
import { SaveIcon } from "@modrinth/assets";
const tags = useTags();
@@ -247,6 +247,7 @@ function updateDonationLinks() {
}
donationLinks.value = links;
}
function checkDifference(newLink, existingLink) {
if (newLink === "" && existingLink !== null) {
return true;

View File

@@ -55,7 +55,7 @@
"
@click="leaveProject()"
>
<UserRemoveIcon />
<UserXIcon />
Leave project
</button>
</div>
@@ -231,7 +231,7 @@
:disabled="(props.currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
@click="removeTeamMember(index)"
>
<UserRemoveIcon />
<UserXIcon />
Remove member
</button>
<button
@@ -254,8 +254,9 @@
This project is managed by {{ props.organization.name }}. The defaults for member
permissions are set in the
<nuxt-link :to="`/organization/${props.organization.slug}/settings/members`">
organization settings</nuxt-link
>. You may override them below.
organization settings
</nuxt-link>
. You may override them below.
</p>
<nuxt-link
:to="`/organization/${props.organization.slug}`"
@@ -518,17 +519,19 @@
<script setup>
import { Multiselect } from "vue-multiselect";
import { TransferIcon, CheckIcon, UsersIcon } from "@modrinth/assets";
import {
TransferIcon,
CheckIcon,
UsersIcon,
DropdownIcon,
SaveIcon,
UserPlusIcon,
UserXIcon,
OrganizationIcon,
CrownIcon,
} from "@modrinth/assets";
import { Avatar, Badge, Card, Checkbox } from "@modrinth/ui";
import ModalConfirm from "~/components/ui/ModalConfirm.vue";
import DropdownIcon from "~/assets/images/utils/dropdown.svg?component";
import SaveIcon from "~/assets/images/utils/save.svg?component";
import UserPlusIcon from "~/assets/images/utils/user-plus.svg?component";
import UserRemoveIcon from "~/assets/images/utils/user-x.svg?component";
import OrganizationIcon from "~/assets/images/utils/organization.svg?component";
import CrownIcon from "~/assets/images/utils/crown.svg?component";
import { removeSelfFromTeam } from "~/helpers/teams.js";
const props = defineProps({
@@ -894,11 +897,14 @@ const updateMembers = async () => {
.member-header {
display: flex;
justify-content: space-between;
.info {
display: flex;
.text {
margin: auto 0 auto 0.5rem;
font-size: var(--font-size-sm);
.name {
font-weight: bold;
@@ -910,14 +916,17 @@ const updateMembers = async () => {
color: var(--color-orange);
}
}
p {
margin: 0.2rem 0;
}
}
}
.side-buttons {
display: flex;
align-items: center;
.dropdown-icon {
margin-left: 1rem;
@@ -936,6 +945,7 @@ const updateMembers = async () => {
.main-info {
margin-bottom: var(--spacing-card-lg);
}
.permissions {
margin-bottom: var(--spacing-card-md);
max-width: 45rem;
@@ -951,6 +961,7 @@ const updateMembers = async () => {
transform: rotate(180deg);
}
}
.content {
display: flex;
}

View File

@@ -113,9 +113,8 @@
</template>
<script>
import { StarIcon, SaveIcon } from "@modrinth/assets";
import Checkbox from "~/components/ui/Checkbox.vue";
import StarIcon from "~/assets/images/utils/star.svg?component";
import SaveIcon from "~/assets/images/utils/save.svg?component";
export default defineNuxtComponent({
components: {
@@ -271,11 +270,14 @@ export default defineNuxtComponent({
:deep(.category-selector) {
margin-bottom: 0.5rem;
.category-selector__label {
display: flex;
align-items: center;
.icon {
height: 1rem;
svg {
margin-right: 0.25rem;
width: 1rem;
@@ -283,6 +285,7 @@ export default defineNuxtComponent({
}
}
}
span {
user-select: none;
}

View File

@@ -39,7 +39,7 @@
<div class="button-group">
<ButtonStyled>
<button @click="$refs.modal_package_mod.hide()">
<CrossIcon aria-hidden="true" />
<XIcon aria-hidden="true" />
Cancel
</button>
</ButtonStyled>
@@ -109,7 +109,7 @@
v-if="auth.user"
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/versions`"
>
<CrossIcon aria-hidden="true" />
<XIcon aria-hidden="true" />
Cancel
</nuxt-link>
</ButtonStyled>
@@ -136,7 +136,7 @@
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`"
>
<CrossIcon aria-hidden="true" />
<XIcon aria-hidden="true" />
Discard changes
</nuxt-link>
</ButtonStyled>
@@ -516,7 +516,7 @@
v-model="version.version_number"
type="text"
autocomplete="off"
maxlength="54"
maxlength="32"
/>
</div>
<span v-else>{{ version.version_number }}</span>
@@ -627,6 +627,23 @@
</template>
<script>
import { ButtonStyled, ConfirmModal, MarkdownEditor } from "@modrinth/ui";
import {
FileIcon,
TrashIcon,
EditIcon,
DownloadIcon,
StarIcon,
ReportIcon,
SaveIcon,
XIcon,
HashIcon,
PlusIcon,
TransferIcon,
UploadIcon,
BoxIcon,
RightArrowIcon,
ChevronRightIcon,
} from "@modrinth/assets";
import { Multiselect } from "vue-multiselect";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
import { inferVersionInfo } from "~/helpers/infer.js";
@@ -636,32 +653,14 @@ import { reportVersion } from "~/utils/report-helpers.ts";
import { useImageUpload } from "~/composables/image-upload.ts";
import Avatar from "~/components/ui/Avatar.vue";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import Badge from "~/components/ui/Badge.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import CopyCode from "~/components/ui/CopyCode.vue";
import Categories from "~/components/ui/search/Categories.vue";
import Checkbox from "~/components/ui/Checkbox.vue";
import FileInput from "~/components/ui/FileInput.vue";
import FileIcon from "~/assets/images/utils/file.svg?component";
import TrashIcon from "~/assets/images/utils/trash.svg?component";
import EditIcon from "~/assets/images/utils/edit.svg?component";
import DownloadIcon from "~/assets/images/utils/download.svg?component";
import StarIcon from "~/assets/images/utils/star.svg?component";
import ReportIcon from "~/assets/images/utils/report.svg?component";
import SaveIcon from "~/assets/images/utils/save.svg?component";
import CrossIcon from "~/assets/images/utils/x.svg?component";
import HashIcon from "~/assets/images/utils/hash.svg?component";
import PlusIcon from "~/assets/images/utils/plus.svg?component";
import TransferIcon from "~/assets/images/utils/transfer.svg?component";
import UploadIcon from "~/assets/images/utils/upload.svg?component";
import BackIcon from "~/assets/images/utils/left-arrow.svg?component";
import BoxIcon from "~/assets/images/utils/box.svg?component";
import RightArrowIcon from "~/assets/images/utils/right-arrow.svg?component";
import Modal from "~/components/ui/Modal.vue";
import ChevronRightIcon from "~/assets/images/utils/chevron-right.svg?component";
// import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
export default defineNuxtComponent({
components: {
@@ -678,12 +677,11 @@ export default defineNuxtComponent({
FileIcon,
ReportIcon,
SaveIcon,
CrossIcon,
XIcon,
HashIcon,
PlusIcon,
TransferIcon,
UploadIcon,
BackIcon,
Avatar,
Badge,
Breadcrumbs,

View File

@@ -25,7 +25,8 @@
</template>
<template v-else-if="canEdit && isEditing === true">
<PopoutMenu class="btn">
<EditIcon aria-hidden="true" /> {{ formatMessage(messages.editIconButton) }}
<EditIcon aria-hidden="true" />
{{ formatMessage(messages.editIconButton) }}
<template #menu>
<span class="icon-edit-menu">
<FileInput
@@ -135,7 +136,7 @@
<div v-if="canEdit" class="primary-stat">
<template v-if="collection.status === 'listed'">
<WorldIcon class="primary-stat__icon" aria-hidden="true" />
<GlobeIcon class="primary-stat__icon" aria-hidden="true" />
<div class="primary-stat__text">
<strong> {{ formatMessage(commonMessages.publicLabel) }} </strong>
</div>
@@ -346,7 +347,8 @@
</ProjectCard>
</div>
<div v-else class="error">
<UpToDate class="icon" /><br />
<UpToDate class="icon" />
<br />
<span v-if="auth.user && auth.user.id === creator.id" class="preserve-lines text">
<IntlFormatted :message-id="messages.noProjectsAuthLabel">
<template #create-link="{ children }">
@@ -379,6 +381,7 @@ import {
UpdatedIcon,
UploadIcon,
XIcon,
GlobeIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -391,7 +394,6 @@ import {
} from "@modrinth/ui";
import { isAdmin } from "@modrinth/utils";
import WorldIcon from "assets/images/utils/world.svg";
import UpToDate from "assets/images/illustrations/up_to_date.svg";
import { addNotification } from "~/composables/notifs.js";
import NavRow from "~/components/ui/NavRow.vue";

View File

@@ -17,7 +17,8 @@
</Button>
</div>
<Button color="primary" @click="(event) => $refs.modal_creation.show(event)">
<PlusIcon aria-hidden="true" /> {{ formatMessage(messages.createNewButton) }}
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNewButton) }}
</Button>
</div>
<div class="collections-grid">
@@ -73,7 +74,7 @@
</div>
<div class="stats">
<template v-if="collection.status === 'listed'">
<WorldIcon aria-hidden="true" />
<GlobeIcon aria-hidden="true" />
<span> {{ formatMessage(commonMessages.publicLabel) }} </span>
</template>
<template v-else-if="collection.status === 'unlisted'">
@@ -96,9 +97,16 @@
</div>
</template>
<script setup>
import { BoxIcon, SearchIcon, XIcon, PlusIcon, LinkIcon, LockIcon } from "@modrinth/assets";
import {
BoxIcon,
SearchIcon,
XIcon,
PlusIcon,
LinkIcon,
LockIcon,
GlobeIcon,
} from "@modrinth/assets";
import { Avatar, Button, commonMessages } from "@modrinth/ui";
import WorldIcon from "~/assets/images/utils/world.svg?component";
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
const { formatMessage } = useVIntl();

View File

@@ -21,7 +21,8 @@
class="goto-link"
to="/dashboard/notifications"
>
See all <ChevronRightIcon />
See all
<ChevronRightIcon />
</nuxt-link>
</div>
<template v-if="notifications.length > 0">
@@ -48,7 +49,8 @@
<div v-else class="universal-body">
<p>You have no unread notifications.</p>
<nuxt-link class="iconified-button !mt-4" to="/dashboard/notifications/history">
<HistoryIcon /> View notification history
<HistoryIcon />
View notification history
</nuxt-link>
</div>
</section>
@@ -94,8 +96,7 @@
</div>
</template>
<script setup>
import ChevronRightIcon from "~/assets/images/utils/chevron-right.svg?component";
import HistoryIcon from "~/assets/images/utils/history.svg?component";
import { ChevronRightIcon, HistoryIcon } from "@modrinth/assets";
import Avatar from "~/components/ui/Avatar.vue";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import { fetchExtraNotificationData, groupNotifications } from "~/helpers/notifications.js";

View File

@@ -12,9 +12,13 @@
<h2 v-else class="text-2xl">Notifications</h2>
</div>
<template v-if="!history">
<Button v-if="hasRead" @click="updateRoute()"> <HistoryIcon /> View history </Button>
<Button v-if="hasRead" @click="updateRoute()">
<HistoryIcon />
View history
</Button>
<Button v-if="notifications.length > 0" color="danger" @click="readAll()">
<CheckCheckIcon /> Mark all as read
<CheckCheckIcon />
Mark all as read
</Button>
</template>
</div>
@@ -51,14 +55,13 @@
</template>
<script setup>
import { Button, Chips } from "@modrinth/ui";
import { HistoryIcon } from "@modrinth/assets";
import { HistoryIcon, CheckCheckIcon } from "@modrinth/assets";
import {
fetchExtraNotificationData,
groupNotifications,
markAsRead,
} from "~/helpers/notifications.js";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import CheckCheckIcon from "~/assets/images/utils/check-check.svg?component";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import Pagination from "~/components/ui/Pagination.vue";

View File

@@ -80,7 +80,7 @@
</div>
</div>
<div class="label">
<RadioButtonChecked v-if="selectedMethodId === method.id" class="radio" />
<RadioButtonCheckedIcon v-if="selectedMethodId === method.id" class="radio" />
<RadioButtonIcon v-else class="radio" />
<span>{{ method.name }}</span>
</div>
@@ -192,7 +192,7 @@ import {
PayPalIcon,
SearchIcon,
RadioButtonIcon,
RadioButtonChecked,
RadioButtonCheckedIcon,
XIcon,
TransferIcon,
} from "@modrinth/assets";

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="landing-hero">
<ModrinthIcon class="modrinth-icon" />
<ModrinthIcon class="modrinth-icon text-brand" />
<h1 class="main-header">
The place for Minecraft
<div class="animate-strong">
@@ -24,7 +24,10 @@
</h2>
<div class="button-group">
<ButtonStyled color="brand" size="large">
<nuxt-link to="/mods"> <CompassIcon aria-hidden="true" /> Discover mods </nuxt-link>
<nuxt-link to="/mods">
<CompassIcon aria-hidden="true" />
Discover mods
</nuxt-link>
</ButtonStyled>
<ButtonStyled size="large" type="outlined">
<nuxt-link v-if="!auth.user" to="/auth/sign-up" rel="noopener nofollow">
@@ -39,7 +42,7 @@
</div>
</div>
<div class="users-section-outer">
<div class="projects-showcase">
<div v-if="rows" class="projects-showcase">
<div v-for="(row, index) in rows" :key="index" class="row">
<div v-for="n in 2" :key="n" class="row__content" :class="{ offset: index % 2 }">
<nuxt-link
@@ -61,6 +64,9 @@
</div>
</div>
</div>
<div v-else class="relative z-[10] w-full text-center text-xl font-bold text-contrast">
Failed to load random projects :(
</div>
<div class="projects-transition" />
<div class="users-section">
<div class="section-header">
@@ -194,8 +200,8 @@
<p>
Modrinth's open-source API lets launchers add deep integration with Modrinth. You can
use Modrinth through
<nuxt-link class="title-link" to="/app">our own app</nuxt-link> and some of the most
popular launchers like ATLauncher, MultiMC, and Prism Launcher.
<nuxt-link class="title-link" to="/app">our own app</nuxt-link>
and some of the most popular launchers like ATLauncher, MultiMC, and Prism Launcher.
</p>
</div>
<div class="blob-demonstration gradient-border">
@@ -222,7 +228,11 @@
>
<PrismLauncherLogo aria-hidden="true" />
</a>
<nuxt-link to="/app" class="graphic gradient-border" aria-label="Modrinth App">
<nuxt-link
to="/app"
class="graphic gradient-border text-brand"
aria-label="Modrinth App"
>
<ModrinthIcon aria-hidden="true" />
</nuxt-link>
<a
@@ -518,10 +528,15 @@
<script setup>
import { Multiselect } from "vue-multiselect";
import { ButtonStyled } from "@modrinth/ui";
import { CompassIcon, LogInIcon, DashboardIcon, NewspaperIcon } from "@modrinth/assets";
import SearchIcon from "~/assets/images/utils/search.svg?component";
import CalendarIcon from "~/assets/images/utils/calendar.svg?component";
import ModrinthIcon from "~/assets/images/logo.svg?component";
import {
CompassIcon,
LogInIcon,
DashboardIcon,
NewspaperIcon,
SearchIcon,
CalendarIcon,
ModrinthIcon,
} from "@modrinth/assets";
import PrismLauncherLogo from "~/assets/images/external/prism.svg?component";
import ATLauncherLogo from "~/assets/images/external/atlauncher.svg?component";
import Avatar from "~/components/ui/Avatar.vue";
@@ -535,23 +550,27 @@ const sortType = ref("relevance");
const auth = await useAuth();
const tags = useTags();
const newProjects = homePageProjects.slice(0, 40);
const val = Math.ceil(newProjects.length / 3);
const rows = ref([
newProjects.slice(0, val),
newProjects.slice(val, val * 2),
newProjects.slice(val * 2, val * 3),
]);
const newProjects = homePageProjects?.slice(0, 40);
const val = Math.ceil(newProjects?.length / 3);
const rows = ref(
newProjects.length > 0
? [
newProjects.slice(0, val),
newProjects.slice(val, val * 2),
newProjects.slice(val * 2, val * 3),
]
: undefined,
);
const notifications = ref(homePageNotifs.hits ?? []);
const searchProjects = ref(homePageSearch.hits ?? []);
const notifications = ref(homePageNotifs?.hits ?? []);
const searchProjects = ref(homePageSearch?.hits ?? []);
async function updateSearchProjects() {
const res = await useBaseFetch(
`search?limit=3&query=${searchQuery.value}&index=${sortType.value}`,
);
searchProjects.value = res.hits ?? [];
searchProjects.value = res?.hits ?? [];
}
</script>
@@ -1112,6 +1131,7 @@ async function updateSearchProjects() {
background: var(--landing-green-label-bg);
color: var(--landing-green-label);
}
&.blue {
background: var(--landing-blue-label-bg);
color: var(--landing-blue-label);
@@ -1228,6 +1248,7 @@ async function updateSearchProjects() {
h3 {
font-size: 3rem;
}
p {
font-size: 1.5rem;
}
@@ -1295,6 +1316,7 @@ async function updateSearchProjects() {
h3 {
font-size: 4rem;
}
p {
font-size: 1.625rem;
}

View File

@@ -2,7 +2,7 @@
<div class="markdown-body">
<h1>Privacy Notice for California Residents</h1>
<p><strong>Effective Date: </strong><em>August 5th, 2023</em></p>
<p><strong>Last reviewed on: </strong><em>August 5th, 2023</em></p>
<p><strong>Last reviewed on: </strong><em>March 11th, 2025</em></p>
<p>
This Privacy Notice for California Residents supplements the information contained in the
<nuxt-link to="/legal/privacy">Privacy Policy</nuxt-link> of Rinth, Inc. (the "Company," "we,"
@@ -49,7 +49,7 @@
information, medical information, or health insurance information. <br /><br />
Some personal information included in this category may overlap with other categories.
</td>
<td>NO</td>
<td>YES</td>
</tr>
<tr>
<td>C. Protected classification characteristics.</td>
@@ -68,7 +68,7 @@
Records of personal property, products or services purchased, obtained, or considered,
or other purchasing or consuming histories or tendencies.
</td>
<td>NO</td>
<td>YES</td>
</tr>
<tr>
<td>E. Biometric information.</td>

View File

@@ -92,32 +92,38 @@
</p>
<table>
<tr>
<th>Timeline</th>
<th>Date</th>
</tr>
<tr>
<td>Revenue earned on</td>
<td>
<input id="revenue-date-picker" v-model="rawSelectedDate" type="date" />
<noscript
>(JavaScript must be enabled for the date picker to function, example date: 2024-07-15)
</noscript>
</td>
</tr>
<tr>
<td>End of the month</td>
<td>{{ formatDate(endOfMonthDate) }}</td>
</tr>
<tr>
<td>NET 60 policy applied</td>
<td>+ 60 days</td>
</tr>
<tr class="final-result">
<td>Available for withdrawal</td>
<td>{{ formatDate(withdrawalDate) }}</td>
</tr>
<thead>
<tr>
<th>Timeline</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr>
<td>Revenue earned on</td>
<td>
<input id="revenue-date-picker" v-model="rawSelectedDate" type="date" />
<noscript
>(JavaScript must be enabled for the date picker to function, example date:
2024-07-15)
</noscript>
</td>
</tr>
<tr>
<td>End of the month</td>
<td>{{ formatDate(endOfMonthDate) }}</td>
</tr>
<tr>
<td>NET 60 policy applied</td>
<td>+ 60 days</td>
</tr>
<tr class="final-result">
<td>Available for withdrawal</td>
<td>{{ formatDate(withdrawalDate) }}</td>
</tr>
</tbody>
</table>
<h3>How do I know Modrinth is being transparent about revenue?</h3>
<p>
We aim to be as transparent as possible with creator revenue. All of our code is open source,
@@ -125,9 +131,9 @@
<a href="https://github.com/modrinth/code/blob/main/apps/labrinth/src/queue/payouts.rs#L598">
revenue distribution system</a
>. We also have an
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a> that allows users
to query exact daily revenue for the site - so far, Modrinth has generated
<strong>{{ formatMoney(platformRevenue) }}</strong> in revenue.
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a>
to query the exact daily advertising revenue for the site - so far, creators on Modrinth have
earned a total of <strong>{{ formatMoney(platformRevenue) }}</strong> in ad revenue.
</p>
<table>
<thead>
@@ -148,7 +154,7 @@
</tbody>
</table>
<small
>Modrinth's total revenue in the previous 5 days, for the entire dataset, use the
>Modrinth's total ad revenue in the previous 5 days, for the entire dataset, use the
aforementioned
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a>.</small
>

View File

@@ -8,7 +8,7 @@
<ModrinthIcon aria-hidden="true" />
</NavStackItem>
<NavStackItem link="/moderation/review" label="Review projects">
<ModerationIcon aria-hidden="true" />
<ScaleIcon aria-hidden="true" />
</NavStackItem>
<NavStackItem link="/moderation/reports" label="Reports">
<ReportIcon aria-hidden="true" />
@@ -21,14 +21,12 @@
</div>
</div>
</template>
<script setup>
import { ModrinthIcon, ScaleIcon, ReportIcon } from "@modrinth/assets";
import NavStack from "~/components/ui/NavStack.vue";
import NavStackItem from "~/components/ui/NavStackItem.vue";
import ModrinthIcon from "~/assets/images/utils/modrinth.svg?component";
import ModerationIcon from "~/assets/images/sidebar/admin.svg?component";
import ReportIcon from "~/assets/images/utils/report.svg?component";
definePageMeta({
middleware: "auth",
});

View File

@@ -8,17 +8,20 @@
:format-label="(x) => (x === 'all' ? 'All' : $formatProjectType(x) + 's')"
/>
<button v-if="oldestFirst" class="iconified-button push-right" @click="oldestFirst = false">
<SortDescIcon />Sorting by oldest
<SortDescendingIcon />
Sorting by oldest
</button>
<button v-else class="iconified-button push-right" @click="oldestFirst = true">
<SortAscIcon />Sorting by newest
<SortAscendingIcon />
Sorting by newest
</button>
<button
class="btn btn-highlight"
:disabled="projectsFiltered.length === 0"
@click="goToProjects()"
>
<ModerationIcon /> Start moderating
<ScaleIcon />
Start moderating
</button>
</div>
<p v-if="projectType !== 'all'" class="project-count">
@@ -27,11 +30,13 @@
</p>
<p v-else class="project-count">There are {{ projects.length }} projects in the queue.</p>
<p v-if="projectsOver24Hours.length > 0" class="warning project-count">
<WarningIcon /> {{ projectsOver24Hours.length }} {{ projectTypePlural }}
<IssuesIcon />
{{ projectsOver24Hours.length }} {{ projectTypePlural }}
have been in the queue for over 24 hours.
</p>
<p v-if="projectsOver48Hours.length > 0" class="danger project-count">
<WarningIcon /> {{ projectsOver48Hours.length }} {{ projectTypePlural }}
<IssuesIcon />
{{ projectsOver48Hours.length }} {{ projectTypePlural }}
have been in the queue for over 48 hours.
</p>
<div
@@ -86,11 +91,13 @@
<nuxt-link
:to="`/${project.inferred_project_type}/${project.slug}`"
class="iconified-button raised-button"
><EyeIcon /> View project</nuxt-link
>
<EyeIcon />
View project
</nuxt-link>
</div>
<span v-if="project.queued" :class="`submitter-info ${project.age_warning}`">
<WarningIcon v-if="project.age_warning" />
<IssuesIcon v-if="project.age_warning" />
Submitted
<span v-tooltip="$dayjs(project.queued).format('MMMM D, YYYY [at] h:mm A')">{{
fromNow(project.queued)
@@ -100,15 +107,18 @@
</div>
</section>
</template>
<script setup>
import { Chips } from "@modrinth/ui";
import {
UnknownIcon,
EyeIcon,
SortAscendingIcon,
SortDescendingIcon,
IssuesIcon,
ScaleIcon,
} from "@modrinth/assets";
import Avatar from "~/components/ui/Avatar.vue";
import UnknownIcon from "~/assets/images/utils/unknown.svg?component";
import EyeIcon from "~/assets/images/utils/eye.svg?component";
import SortAscIcon from "~/assets/images/utils/sort-asc.svg?component";
import SortDescIcon from "~/assets/images/utils/sort-desc.svg?component";
import WarningIcon from "~/assets/images/utils/issues.svg?component";
import ModerationIcon from "~/assets/images/sidebar/admin.svg?component";
import Badge from "~/components/ui/Badge.vue";
import { formatProjectType } from "~/plugins/shorthands.js";
@@ -164,7 +174,7 @@ const projectTypes = computed(() => {
return [...set];
});
function segmentData(data, segmentSize = 900) {
function segmentData(data, segmentSize = 800) {
return data.reduce((acc, curr, index) => {
const segment = Math.floor(index / segmentSize);
@@ -224,6 +234,7 @@ if (projects.value) {
});
}
}
async function goToProjects() {
const project = projectsFiltered.value[0];
await router.push({

View File

@@ -76,7 +76,8 @@
</template>
<template #title-suffix>
<div class="ml-1 flex items-center gap-2 font-semibold">
<OrganizationIcon /> Organization
<OrganizationIcon />
Organization
</div>
</template>
<template #summary>
@@ -177,10 +178,12 @@
<p>You have been invited to join {{ organization.name }}.</p>
<div class="input-group">
<button class="iconified-button brand-button" @click="onAcceptInvite">
<CheckIcon />Accept
<CheckIcon />
Accept
</button>
<button class="iconified-button danger-button" @click="onDeclineInvite">
<XIcon />Decline
<XIcon />
Decline
</button>
</div>
</div>
@@ -227,7 +230,8 @@
</template>
<div v-else-if="true" class="error">
<UpToDate class="icon" /><br />
<UpToDate class="icon" />
<br />
<span class="preserve-lines text">
This organization doesn't have any projects yet.
<template v-if="isPermission(currentMember?.organization_permissions, 1 << 4)">
@@ -251,6 +255,9 @@ import {
CheckIcon,
XIcon,
ClipboardCopyIcon,
OrganizationIcon,
DownloadIcon,
CrownIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -266,10 +273,6 @@ import ModalCreation from "~/components/ui/ModalCreation.vue";
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
import ProjectCard from "~/components/ui/ProjectCard.vue";
// import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import OrganizationIcon from "~/assets/images/utils/organization.svg?component";
import DownloadIcon from "~/assets/images/utils/download.svg?component";
import CrownIcon from "~/assets/images/utils/crown.svg?component";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
import NavTabs from "~/components/ui/NavTabs.vue";
@@ -535,9 +538,11 @@ async function copyId() {
display: flex;
flex-direction: column;
padding: var(--gap-xl);
h3 {
margin: 0 0 var(--gap-sm);
}
.creator {
display: grid;
gap: var(--gap-xs);
@@ -549,9 +554,11 @@ async function copyId() {
"avatar name" auto
"avatar role" auto
/ auto 1fr;
p {
margin: 0;
}
.name {
grid-area: name;
align-self: flex-end;
@@ -619,9 +626,11 @@ async function copyId() {
display: flex;
justify-content: space-between;
align-items: center;
h3 {
margin: 0;
}
a {
display: flex;
align-items: center;
@@ -629,25 +638,30 @@ async function copyId() {
color: var(--color-blue);
}
}
.project-overview {
gap: var(--gap-md);
padding: var(--gap-xl);
.project-card {
padding: 0;
border-radius: 0;
background-color: transparent;
box-shadow: none;
:deep(.title) {
font-size: var(--font-size-nm) !important;
}
}
}
.popout-heading {
padding: var(--gap-sm) var(--gap-md);
margin: 0;
font-size: var(--font-size-md);
color: var(--color-text);
}
.popout-checkbox {
padding: var(--gap-sm) var(--gap-md);
}

View File

@@ -225,11 +225,10 @@ import {
UserPlusIcon,
UserXIcon as UserRemoveIcon,
DropdownIcon,
CrownIcon,
} from "@modrinth/assets";
import { Button, Badge, Avatar, Checkbox } from "@modrinth/ui";
import { ref } from "vue";
import CrownIcon from "~/assets/images/utils/crown.svg?component";
import { removeTeamMember } from "~/helpers/teams.js";
import { isPermission } from "~/utils/permissions.ts";
@@ -363,8 +362,10 @@ const onTransferOwnership = useClientTry(async (teamId, uid) => {
.member-header {
display: flex;
justify-content: space-between;
.info {
display: flex;
.text {
margin: auto 0 auto 0.5rem;
font-size: var(--font-size-sm);
@@ -386,24 +387,30 @@ const onTransferOwnership = useClientTry(async (teamId, uid) => {
}
}
}
.side-buttons {
display: flex;
align-items: center;
.dropdown-icon {
margin-left: 1rem;
svg {
transition: 150ms ease transform;
}
}
}
}
.content {
display: none;
flex-direction: column;
padding-top: var(--gap-md);
.main-info {
margin-bottom: var(--gap-lg);
}
.permissions {
margin-bottom: var(--gap-md);
max-width: 45rem;
@@ -412,17 +419,20 @@ const onTransferOwnership = useClientTry(async (teamId, uid) => {
grid-gap: 0.5rem;
}
}
&.open {
.member-header {
.dropdown-icon svg {
transform: rotate(180deg);
}
}
.content {
display: flex;
}
}
}
:deep(.checkbox-outer) {
button.checkbox {
border: none;

View File

@@ -34,7 +34,8 @@
</nuxt-link>
<ButtonStyled>
<nuxt-link :to="`/servers/manage/${server.serverId}/content`">
<LeftArrowIcon /> Back to server
<LeftArrowIcon />
Back to server
</nuxt-link>
</ButtonStyled>
</div>
@@ -139,7 +140,7 @@
<template #locked-mod_loader>
{{ formatMessage(messages.modLoaderProvidedByServer) }}
</template>
<template #sync-button> {{ formatMessage(messages.syncFilterButton) }} </template>
<template #sync-button> {{ formatMessage(messages.syncFilterButton) }}</template>
</SearchSidebarFilter>
</div>
</aside>
@@ -188,7 +189,10 @@
</DropdownSelect>
<div class="lg:hidden">
<ButtonStyled>
<button @click="filtersMenuOpen = true"><FilterIcon /> Filter results...</button>
<button @click="filtersMenuOpen = true">
<FilterIcon />
Filter results...
</button>
</ButtonStyled>
</div>
<ButtonStyled circular>
@@ -286,7 +290,7 @@
:to="`/${projectType.id}/${result.slug ? result.slug : result.project_id}`"
>
<NewProjectCard :project="result" :categories="result.display_categories">
<template v-if="false" #actions> </template>
<template v-if="false" #actions></template>
</NewProjectCard>
</NuxtLink>
</template>
@@ -317,16 +321,21 @@ import {
NewProjectCard,
SearchFilterControl,
} from "@modrinth/ui";
import { CheckIcon, DownloadIcon, GameIcon, LeftArrowIcon, XIcon } from "@modrinth/assets";
import {
CheckIcon,
DownloadIcon,
GameIcon,
LeftArrowIcon,
XIcon,
SearchIcon,
FilterIcon,
GridIcon,
ListIcon,
ImageIcon,
} from "@modrinth/assets";
import { computed } from "vue";
import ProjectCard from "~/components/ui/ProjectCard.vue";
import LogoAnimated from "~/components/brand/LogoAnimated.vue";
import SearchIcon from "~/assets/images/utils/search.svg?component";
import FilterIcon from "~/assets/images/utils/filter.svg?component";
import GridIcon from "~/assets/images/utils/grid.svg?component";
import ListIcon from "~/assets/images/utils/list.svg?component";
import ImageIcon from "~/assets/images/utils/image.svg?component";
// import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import NavTabs from "~/components/ui/NavTabs.vue";
@@ -344,6 +353,7 @@ const flags = useFeatureFlags();
const auth = await useAuth();
const projectType = ref();
function setProjectType() {
const projType = tags.value.projectTypes.find(
(x) => x.id === route.path.replaceAll(/^\/|s\/?$/g, ""), // Removes prefix `/` and suffixes `s` and `s/`
@@ -353,6 +363,7 @@ function setProjectType() {
projectType.value = projType;
}
}
setProjectType();
router.afterEach(() => {
setProjectType();
@@ -398,7 +409,7 @@ async function updateServerContext() {
const serverFilters = computed(() => {
const filters = [];
if (server.value) {
if (server.value && projectType.value.id !== "modpack") {
const gameVersion = server.value.general?.mc_version;
if (gameVersion) {
filters.push({
@@ -418,6 +429,15 @@ const serverFilters = computed(() => {
});
}
const pluginLoaders = ["paper", "purpur"];
if (platform && pluginLoaders.includes(platform)) {
filters.push({
type: "plugin_loader",
option: platform,
});
}
if (serverHideInstalled.value) {
const installedMods = server.value.content?.data
.filter((x) => x.project_id)

View File

@@ -62,7 +62,6 @@
<BoxIcon aria-hidden="true" /> Manage your servers
</nuxt-link>
</ButtonStyled>
<UiServersPoweredByPyro class="mx-0 !mt-0" />
</div>
</div>
</div>
@@ -209,9 +208,7 @@
<polygon points="13 19 22 12 13 5 13 19" />
<polygon points="2 19 11 12 2 5 2 19" />
</svg>
<h2 class="m-0 text-lg font-bold">
Experience modern, reliable hosting powered by Pyro
</h2>
<h2 class="m-0 text-lg font-bold">Experience modern, reliable hosting</h2>
<h3 class="m-0 text-base font-normal text-secondary">
Modrinth Servers are hosted on
<span class="text-contrast">2023 Ryzen 7/9 CPUs with DDR5 RAM</span>, running on
@@ -223,15 +220,8 @@
<ServerIcon class="size-8 text-brand" />
<h2 class="m-0 text-lg font-bold">Consistently fast</h2>
<h3 class="m-0 text-base font-normal text-secondary">
Under Pyro, infrastructure is never overloaded, meaning each Modrinth server always
runs at its full performance.
<a
class="mt-2 flex items-center gap-2 font-medium text-contrast transition-all hover:gap-3"
href="https://status.pyro.host/"
target="_blank"
>
See the infrastructure <RightArrowIcon class="flex-none" />
</a>
Our infrastructure is never overloaded, meaning each server hosted with Modrinth
always runs at its full performance.
</h3>
</div>
</div>
@@ -443,8 +433,8 @@
</summary>
<p class="m-0 !leading-[190%]">
Yes. All Modrinth Servers come with DDoS protection. Protection is powered by a
combination of in-house network filtering by Pyro as well as with our data center
provider. Your server is safe on Modrinth.
combination of in-house network filtering as well as with our data center provider.
Your server is safe on Modrinth.
</p>
</details>

View File

@@ -381,8 +381,6 @@
@reinstall="onReinstall"
/>
</div>
<UiServersPoweredByPyro />
</div>
</div>
</template>

View File

@@ -109,8 +109,6 @@
<p class="text-contrast">No servers found.</p>
</div>
</template>
<UiServersPoweredByPyro />
</div>
</template>

View File

@@ -88,11 +88,11 @@ import {
KeyIcon,
LanguagesIcon,
CardIcon,
MonitorSmartphoneIcon,
} from "@modrinth/assets";
import { commonMessages, commonSettingsMessages } from "@modrinth/ui";
import NavStack from "~/components/ui/NavStack.vue";
import NavStackItem from "~/components/ui/NavStackItem.vue";
import MonitorSmartphoneIcon from "~/assets/images/utils/monitor-smartphone.svg?component";
const { formatMessage } = useVIntl();

View File

@@ -52,7 +52,7 @@
</div>
</div>
<div class="label">
<RadioButtonChecked
<RadioButtonCheckedIcon
v-if="cosmetics.searchDisplayMode[projectType.id] === 'list'"
class="radio"
/>
@@ -76,7 +76,7 @@
</div>
</div>
<div class="label">
<RadioButtonChecked
<RadioButtonCheckedIcon
v-if="cosmetics.searchDisplayMode[projectType.id] === 'grid'"
class="radio"
/>
@@ -98,7 +98,7 @@
</div>
</div>
<div class="label">
<RadioButtonChecked
<RadioButtonCheckedIcon
v-if="cosmetics.searchDisplayMode[projectType.id] === 'gallery'"
class="radio"
/>
@@ -198,7 +198,7 @@
</template>
<script setup lang="ts">
import { CodeIcon, RadioButtonChecked, RadioButtonIcon } from "@modrinth/assets";
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from "@modrinth/assets";
import { Button, ThemeSelector } from "@modrinth/ui";
import MessageBanner from "~/components/ui/MessageBanner.vue";
import type { DisplayLocation } from "~/plugins/cosmetics";

View File

@@ -1,9 +1,7 @@
<script setup lang="ts">
import Fuse from "fuse.js/dist/fuse.basic";
import { commonSettingsMessages } from "@modrinth/ui";
import RadioButtonIcon from "~/assets/images/utils/radio-button.svg?component";
import RadioButtonCheckedIcon from "~/assets/images/utils/radio-button-checked.svg?component";
import WarningIcon from "~/assets/images/utils/issues.svg?component";
import { RadioButtonIcon, RadioButtonCheckedIcon, IssuesIcon } from "@modrinth/assets";
import { isModifierKeyDown } from "~/helpers/events.ts";
const vintl = useVIntl();
@@ -376,7 +374,8 @@ function getItemLabel(locale: Locale) {
:id="`language__${locale.tag}__fail`"
class="language-load-error"
>
<WarningIcon /> {{ formatMessage(messages.loadFailed) }}
<IssuesIcon />
{{ formatMessage(messages.loadFailed) }}
</div>
</template>
</template>

View File

@@ -209,7 +209,8 @@
</div>
</div>
<div v-else-if="route.params.projectType !== 'collections'" class="error">
<UpToDate class="icon" /><br />
<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 }">
@@ -252,7 +253,7 @@
</div>
<div class="stats">
<template v-if="collection.status === 'listed'">
<WorldIcon />
<GlobeIcon />
<span> Public </span>
</template>
<template v-else-if="collection.status === 'unlisted'">
@@ -275,7 +276,8 @@
v-if="route.params.projectType === 'collections' && collections.length === 0"
class="error"
>
<UpToDate class="icon" /><br />
<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 }">
@@ -343,6 +345,9 @@ import {
CurrencyIcon,
InfoIcon,
CheckIcon,
ReportIcon,
EditIcon,
GlobeIcon,
} from "@modrinth/assets";
import {
OverflowMenu,
@@ -364,10 +369,7 @@ import EarlyAdopterBadge from "~/assets/images/badges/early-adopter.svg?componen
import AlphaTesterBadge from "~/assets/images/badges/alpha-tester.svg?component";
import BetaTesterBadge from "~/assets/images/badges/beta-tester.svg?component";
import ReportIcon from "~/assets/images/utils/report.svg?component";
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
import EditIcon from "~/assets/images/utils/edit.svg?component";
import WorldIcon from "~/assets/images/utils/world.svg?component";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import Avatar from "~/components/ui/Avatar.vue";
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";