Files
Modrinth-plus/packages/utils/utils.ts
Prospector 9c99518497 chore: improve moderation ux (#6035)
* feat: save project review queue filters

* reduce unnecessary network calls + prepr

* missed file

* ui tweaks

* add fucked up

* add label + prepr

* prepr

* update legacy badge labels

* globe

* fix margin

* be more reasonable

* pending state

* fix double review, prepr

* small badge text
2026-05-08 01:40:28 -07:00

304 lines
8.3 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 headerCompare = a.header.localeCompare(b.header)
if (headerCompare !== 0) {
const aServerIdx = SERVER_HEADER_ORDER.indexOf(a.header)
const bServerIdx = SERVER_HEADER_ORDER.indexOf(b.header)
if (aServerIdx !== -1 && bServerIdx !== -1) {
return aServerIdx - bServerIdx
}
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)
}
if (a.name === 'pokemon') return -1
if (b.name === 'pokemon') return 1
const aFormatted = formatCategoryName(a.name)
const bFormatted = formatCategoryName(b.name)
return aFormatted.localeCompare(bFormatted, locale, { numeric: true })
})
}
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'
} else if (name === 'server') {
return 'SRV'
}
}
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."