Files
Modrinth-plus/packages/utils/utils.ts
Truman Gao 83d53dafe7 fix: servers misc fixes (#5475)
* fix: tags in project settings to have icons and ordered correctly

* fix copy in project list layout settings

* fix tag item in header navigation

* adjust ping ranges

* add handle click tag

* fix: dont show offline in project page for draft status

* move tags above creators in app

* preload server project page on load and optimize queries

* add server project card to organization page

* fix minecraft_java_server label

* pnpm prepr

* have user option in project create modal be circle

* feat: implement better mobile project page view

* disable summary line clamp for servers

* fix: unlink instance doesnt update instance

* increase icon upload size

* small fix on button size

* improve how server ping info loads

* remove unnecessary pings for instance page

* fix order of computing dependency diff

* remove linked_project_id from world, use name+address to match for managed world instead

* pnpm prepr

* hide duplicate worlds with same domain name in worlds list

* add install content warning for server instance

* increase summary max width

* add handling for server projects for bulk editing links

* implement include user unlisted projects in published modpack select

* pnpm prepr

* filter to only user unlisted status

* add bad link warnings

* fix modpack tags appearing in server

* cargo fmt
2026-03-07 02:11:45 +00:00

335 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// noinspection JSUnusedGlobalSymbols
// @ts-nocheck
import dayjs from 'dayjs'
export const external = (cosmetics) => (cosmetics.externalLinksNewTab ? '_blank' : '')
// Only use on the complete list of versions for a project,
// partial lists will generate the wrong version slugs
export const computeVersions = (versions, members) => {
const visitedVersions = []
const returnVersions = []
const authorMembers = {}
for (const version of versions.sort(
(a, b) => dayjs(a.date_published) - dayjs(b.date_published),
)) {
if (visitedVersions.includes(version.version_number)) {
visitedVersions.push(version.version_number)
version.displayUrlEnding = version.id
} else {
visitedVersions.push(version.version_number)
version.displayUrlEnding = version.version_number
}
version.primaryFile = version.files.find((file) => file.primary) ?? version.files[0]
if (!version.primaryFile) {
version.primaryFile = {
hashes: {
sha1: '',
sha512: '',
},
url: '#',
filename: 'unknown',
primary: false,
size: 0,
file_type: null,
}
}
version.author = authorMembers[version.author_id]
if (!version.author) {
version.author = members.find((x) => x.user.id === version.author_id)
authorMembers[version.author_id] = version.author
}
returnVersions.push(version)
}
return returnVersions
.reverse()
.map((version, index) => {
const nextVersion = returnVersions[index + 1]
if (nextVersion && version.changelog && nextVersion.changelog === version.changelog) {
return { duplicate: true, ...version }
}
return { duplicate: false, ...version }
})
.sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
}
const SERVER_HEADER_ORDER = [
'minecraft_server_features',
'minecraft_server_gameplay',
'minecraft_server_meta',
'minecraft_server_community',
]
export const sortedCategories = (tags, formatCategoryName, locale) => {
return tags.categories.slice().sort((a, b) => {
const aServerIdx = SERVER_HEADER_ORDER.indexOf(a.header)
const bServerIdx = SERVER_HEADER_ORDER.indexOf(b.header)
if (aServerIdx !== -1 || bServerIdx !== -1) {
return (
(aServerIdx === -1 ? Infinity : aServerIdx) - (bServerIdx === -1 ? Infinity : bServerIdx)
)
}
const headerCompare = a.header.localeCompare(b.header)
if (headerCompare !== 0) {
return headerCompare
}
if (a.header === 'performance impact' && b.header === 'performance impact') {
const x = ['potato', 'low', 'medium', 'high', 'screenshot']
return x.indexOf(a.name) - x.indexOf(b.name)
}
const aFormatted = formatCategoryName(a.name)
const bFormatted = formatCategoryName(b.name)
return aFormatted.localeCompare(bFormatted, locale, { numeric: true })
})
}
export const formatNumber = (number, abbreviate = true) => {
const x = Number(number)
if (x >= 1000000 && abbreviate) {
return `${(x / 1000000).toFixed(2).toString()}M`
} else if (x >= 10000 && abbreviate) {
return `${(x / 1000).toFixed(1).toString()}k`
}
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
export function formatDate(
date: dayjs.Dayjs,
options: Intl.DateTimeFormatOptions = {
month: 'long',
day: 'numeric',
year: 'numeric',
},
): string {
return date.toDate().toLocaleDateString(undefined, options)
}
export function formatMoney(number, abbreviate = false) {
const x = Number(number)
if (x >= 1000000 && abbreviate) {
return `$${(x / 1000000).toFixed(2).toString()}M`
} else if (x >= 10000 && abbreviate) {
return `$${(x / 1000).toFixed(2).toString()}k`
}
return `$${x
.toFixed(2)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`
}
export const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
}
export const capitalizeString = (name) => {
return name ? name.charAt(0).toUpperCase() + name.slice(1) : name
}
export const formatWallet = (name) => {
if (name === 'paypal') {
return 'PayPal'
}
return capitalizeString(name)
}
export const formatProjectType = (name, short = false) => {
if (short) {
if (name === 'resourcepack') {
return 'RPK'
} else if (name === 'mod') {
return 'MOD'
} else if (name === 'modpack') {
return 'MPK'
} else if (name === 'shader') {
return 'SHD'
} else if (name === 'plugin') {
return 'PLG'
} else if (name === 'datapack') {
return 'DPK'
}
}
if (name === 'resourcepack') {
return 'Resource Pack'
} else if (name === 'datapack') {
return 'Data Pack'
} else if (name === 'modpack') {
return 'Modpack'
} else if (name === 'minecraft_java_server') {
return 'Server'
}
return capitalizeString(name)
}
export const formatProjectStatus = (name) => {
if (name === 'approved') {
return 'Public'
} else if (name === 'processing') {
return 'Under review'
}
return capitalizeString(name)
}
export const formatVersions = (versionArray, gameVersions) => {
const allVersions = gameVersions.slice().reverse()
const allReleases = allVersions.filter((x) => x.version_type === 'release')
const intervals = []
let currentInterval = 0
for (let i = 0; i < versionArray.length; i++) {
const index = allVersions.findIndex((x) => x.version === versionArray[i])
const releaseIndex = allReleases.findIndex((x) => x.version === versionArray[i])
if (i === 0) {
intervals.push([[versionArray[i], index, releaseIndex]])
} else {
const intervalBase = intervals[currentInterval]
if (
(index - intervalBase[intervalBase.length - 1][1] === 1 ||
releaseIndex - intervalBase[intervalBase.length - 1][2] === 1) &&
(allVersions[intervalBase[0][1]].version_type === 'release' ||
allVersions[index].version_type !== 'release')
) {
intervalBase[1] = [versionArray[i], index, releaseIndex]
} else {
currentInterval += 1
intervals[currentInterval] = [[versionArray[i], index, releaseIndex]]
}
}
}
const newIntervals = []
for (let i = 0; i < intervals.length; i++) {
const interval = intervals[i]
if (interval.length === 2 && interval[0][2] !== -1 && interval[1][2] === -1) {
let lastSnapshot = null
for (let j = interval[1][1]; j > interval[0][1]; j--) {
if (allVersions[j].version_type === 'release') {
newIntervals.push([
interval[0],
[
allVersions[j].version,
j,
allReleases.findIndex((x) => x.version === allVersions[j].version),
],
])
if (lastSnapshot !== null && lastSnapshot !== j + 1) {
newIntervals.push([[allVersions[lastSnapshot].version, lastSnapshot, -1], interval[1]])
} else {
newIntervals.push([interval[1]])
}
break
} else {
lastSnapshot = j
}
}
} else {
newIntervals.push(interval)
}
}
const output = []
for (const interval of newIntervals) {
if (interval.length === 2) {
output.push(`${interval[0][0]}${interval[1][0]}`)
} else {
output.push(interval[0][0])
}
}
return (output.length === 0 ? versionArray : output).join(', ')
}
export function cycleValue<T extends string>(value: T, values: T[]): T {
const index = values.indexOf(value) + 1
return values[index % values.length]
}
export const fileIsValid = (file, validationOptions) => {
const { maxSize, alertOnInvalid } = validationOptions
if (maxSize !== null && maxSize !== undefined && file.size > maxSize) {
if (alertOnInvalid) {
alert(`File ${file.name} is too big! Must be less than ${formatBytes(maxSize)}`)
}
return false
}
return true
}
export const acceptFileFromProjectType = (projectType) => {
const commonTypes = '.sig,.asc,.gpg,application/pgp-signature,application/pgp-keys'
switch (projectType) {
case 'mod':
return `.jar,.zip,.litemod,application/java-archive,application/x-java-archive,application/zip,${commonTypes}`
case 'plugin':
return `.jar,.zip,application/java-archive,application/x-java-archive,application/zip,${commonTypes}`
case 'resourcepack':
return `.zip,application/zip,${commonTypes}`
case 'shader':
return `.zip,application/zip,${commonTypes}`
case 'datapack':
return `.jar,.zip,.litemod,application/java-archive,application/x-java-archive,application/zip,${commonTypes}`
case 'modpack':
return `.mrpack,application/x-modrinth-modpack+zip,application/zip,${commonTypes}`
default:
// all of the above
return `.jar,.zip,.litemod,.mrpack,application/java-archive,application/x-java-archive,application/zip,application/x-modrinth-modpack+zip,${commonTypes}`
}
}
export const getArrayOrString = (x: string[] | string): string[] => {
if (typeof x === 'string') {
return [x]
} else {
return x
}
}
export function getPingLevel(ping: number) {
if (ping < 120) {
return 5
} else if (ping < 200) {
return 4
} else if (ping < 300) {
return 3
} else if (ping < 400) {
return 2
} else {
return 1
}
}
export function arrayBufferToBase64(buffer: Uint8Array | ArrayBuffer): string {
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
return btoa(String.fromCharCode(...bytes))
}
export const DEFAULT_CREDIT_EMAIL_MESSAGE =
"We're really sorry about the recent issues with your server."