Datapack support (#815)

* Shader support PR

* Make search page work

* Fix env showing

* Make moderation look reasonable

* Fix search for shaders

* Datapack support

* Make file types work + datapack inferring

* Add editing to file types

* Finish datapack file generation

* Fix bugs, make forge support work

* Fix inconsistent data pack label

* Final fixes
This commit is contained in:
Geometrically
2022-12-29 09:59:41 -07:00
committed by GitHub
parent 879576b613
commit 1f133dbcd0
22 changed files with 978 additions and 262 deletions

View File

@@ -85,7 +85,14 @@
border-bottom: 1px solid var(--color-header-underline);
}
h1, h2, h3, h4, h5, h6, li, p {
h1,
h2,
h3,
h4,
h5,
h6,
li,
p {
word-wrap: break-word;
overflow-wrap: anywhere;
}
@@ -227,11 +234,17 @@
margin-bottom: 0 !important;
}
iframe,
video {
aspect-ratio: 16 / 9;
width: 850px;
height: auto;
}
@media screen and (max-width: 850px) {
iframe {
aspect-ratio: 16 / 9;
iframe,
video {
width: 100%;
height: auto;
}
}
}
@@ -332,8 +345,8 @@
}
.button-animation {
transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, transform 0.05s ease-in-out,
outline 0.2s ease-in-out;
transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out,
transform 0.05s ease-in-out, outline 0.2s ease-in-out;
&:active:not(&:disabled) {
transform: scale(0.95);
@@ -403,8 +416,8 @@ tr.button-transparent {
}
.button-within {
transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, transform 0.05s ease-in-out,
outline 0.2s ease-in-out;
transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out,
transform 0.05s ease-in-out, outline 0.2s ease-in-out;
&:focus-visible:not(&.disabled),
&:hover:not(&.disabled) {
@@ -541,6 +554,11 @@ tr.button-transparent {
--text-color: var(--color-brand-inverted);
}
.alt-brand-button {
--background-color: var(--color-brand-highlight);
--text-color: var(--color-text);
}
.button-group {
display: flex;
grid-gap: var(--spacing-card-sm);
@@ -563,6 +581,15 @@ tr.button-transparent {
}
.multiselect {
&.raised-multiselect {
.multiselect__tags,
.multiselect__content-wrapper,
.multiselect__spinner {
background-color: var(--color-raised-bg);
box-shadow: none;
}
}
color: var(--color-text) !important;
outline: 2px solid transparent;
@@ -584,7 +611,7 @@ tr.button-transparent {
padding-left: 7px;
padding-top: 10px;
transition: background-color .1s ease-in-out;
transition: background-color 0.1s ease-in-out;
&:active {
background: var(--color-button-bg-hover);
@@ -790,7 +817,7 @@ tr.button-transparent {
display: flex;
flex-direction: column;
align-items: flex-end;
grid-gap: .5rem;
grid-gap: 0.5rem;
z-index: 2;
}
@@ -929,7 +956,10 @@ h1 {
font-weight: bold;
}
.nuxt-link-exact-active, h1, h2, h3 {
.nuxt-link-exact-active,
h1,
h2,
h3 {
.beta-badge {
background-color: var(--color-button-text-active);
box-sizing: border-box;
@@ -939,7 +969,8 @@ h1 {
}
@media (prefers-reduced-motion) {
.button-animation, button {
.button-animation,
button {
transform: none !important;
}
}
@@ -965,7 +996,7 @@ h1 {
display: flex;
flex-direction: column;
align-items: flex-end;
grid-gap: .5rem;
grid-gap: 0.5rem;
z-index: 2;
}
@@ -986,7 +1017,8 @@ h1 {
}
.universal-labels {
label, .label {
label,
.label {
.label__title {
display: block;
margin-block: var(--spacing-card-md) var(--spacing-card-sm);
@@ -1074,13 +1106,16 @@ h1 {
}
.input-group {
.multiselect, input {
.multiselect,
input {
width: auto;
flex-basis: 0;
}
}
button, .button, .iconified-button {
button,
.button,
.iconified-button {
width: fit-content;
}
@@ -1095,12 +1130,14 @@ h1 {
}
}
.adjacent-input, &.adjacent-input {
.adjacent-input,
&.adjacent-input {
display: flex;
flex-direction: row;
align-items: center;
.iconified-button, .input-group {
.iconified-button,
.input-group {
flex-shrink: 0;
}
@@ -1152,7 +1189,9 @@ h1 {
}
.full-width-inputs {
.multiselect, input, .iconified-input {
.multiselect,
input,
.iconified-input {
width: 100%;
flex-basis: 100%;
}
@@ -1286,7 +1325,8 @@ button {
flex-shrink: 0;
}
input, textarea {
input,
textarea {
border-radius: 0;
background-color: transparent;
box-shadow: unset;
@@ -1294,8 +1334,10 @@ button {
flex-grow: 1;
}
&:focus, &:focus-visible, &:focus-within {
box-shadow: inset 0 0 0 transparent, 0 0 0 .25rem var(--color-brand-shadow);
&:focus,
&:focus-visible,
&:focus-within {
box-shadow: inset 0 0 0 transparent, 0 0 0 0.25rem var(--color-brand-shadow);
color: var(--color-button-text-active);
}
}
@@ -1365,7 +1407,8 @@ button {
}
@media screen and (max-width: 550px) {
grid-template-columns: repeat(1, minmax(0, 1fr));
display: flex;
flex-direction: column;
}
}

View File

@@ -61,8 +61,8 @@ html {
--color-special-orange: #e08325;
--color-special-green: var(--color-brand-green);
--color-special-blue: #1f68c0;
--color-special-purple: #8e32F3;
--color-special-gray: #595B61;
--color-special-purple: #8e32f3;
--color-special-gray: #595b61;
--color-warning-bg: hsl(355, 70%, 88%);
--color-warning-text: hsl(342, 70%, 35%);
@@ -86,8 +86,9 @@ html {
--shadow-raised: 0.3px 0.5px 0.6px hsl(var(--shadow-color) / 0.15),
1px 2px 2.2px -1.7px hsl(var(--shadow-color) / 0.12),
4.4px 8.8px 9.7px -3.4px hsl(var(--shadow-color) / 0.09);
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, hsla(0, 0%, 0%, 0.1) 0px 2px 4px -1px;
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px,
hsla(0, 0%, 0%, 0.1) 0px 2px 4px -1px;
--shadow-card: rgba(50, 50, 100, 0.1) 0px 2px 4px 0px;
}
@@ -109,9 +110,9 @@ html {
--color-special-red: #ff496e;
--color-special-orange: #ffa347;
--color-special-green: var(--color-brand-green);
--color-special-blue: #4F9CFF;
--color-special-purple: #C78AFF;
--color-special-gray: #9FA4B3;
--color-special-blue: #4f9cff;
--color-special-purple: #c78aff;
--color-special-gray: #9fa4b3;
--color-brand-green: #1bd96a;
--color-brand: var(--color-brand-green);
@@ -168,8 +169,9 @@ html {
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
--shadow-raised: 0px -2px 4px hsla(221, 39%, 11%, 0.1);
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px,
rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
--shadow-card: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px;
}
@@ -194,8 +196,10 @@ 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;
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
--font-standard: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen,
Ubuntu, Roboto, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
Liberation Mono, monospace;
font-family: var(--font-standard);
font-size: 16px;
font-weight: var(--font-weight-regular);
@@ -216,7 +220,7 @@ body {
--size-navbar-height: 3.5rem;
--size-mobile-navbar-height: 3.5rem;
--size-mobile-navbar-height-expanded: 11.75rem;
--size-mobile-navbar-height-expanded: 13.75rem;
--spacing-card-lg: 1.5rem;
--spacing-card-bg: 1rem;
@@ -245,11 +249,15 @@ body {
--font-weight-heading: var(--font-weight-extrabold);
--font-weight-title: var(--font-weight-extrabold);
@media screen and (min-width: 318px) {
@media screen and (min-width: 320px) {
--size-mobile-navbar-height-expanded: 11.5rem;
}
@media screen and (min-width: 432px) {
--size-mobile-navbar-height-expanded: 9.25rem;
}
@media screen and (min-width: 625px) {
@media screen and (min-width: 765px) {
--size-mobile-navbar-height-expanded: 7rem;
}
}
@@ -304,8 +312,9 @@ textarea {
transition: box-shadow 0.1s ease-in-out;
min-height: 40px;
&:focus, &:focus-visible {
box-shadow: inset 0 0 0 transparent, 0 0 0 .25rem var(--color-brand-shadow);
&: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);
}
@@ -326,7 +335,8 @@ textarea {
}
}
button, input[type=button] {
button,
input[type='button'] {
cursor: pointer;
border: none;
outline: 2px solid transparent;
@@ -348,7 +358,9 @@ kbd {
@import '~assets/styles/components.scss';
@import '~assets/styles/normalize.scss';
button:focus-visible, a:focus-visible, [tabindex="0"]:focus-visible {
outline: .25rem solid #ea80ff;
border-radius: .25rem;
button:focus-visible,
a:focus-visible,
[tabindex='0']:focus-visible {
outline: 0.25rem solid #ea80ff;
border-radius: 0.25rem;
}

View File

@@ -27,9 +27,12 @@ export default {
},
},
mounted() {
document.addEventListener('dragenter', () => {
this.$refs.drop_area.style.visibility = 'visible'
})
// eslint-disable-next-line nuxt/no-env-in-hooks
if (process.client) {
document.addEventListener('dragenter', () => {
this.$refs.drop_area.style.visibility = 'visible'
})
}
},
methods: {
allowDrag(event) {

View File

@@ -123,6 +123,8 @@ export default {
return 'required'
case 'shader':
return 'required'
case 'datapack':
return 'optional'
default:
return 'unknown'
}
@@ -135,6 +137,8 @@ export default {
return 'unsupported'
case 'shader':
return 'unsupported'
case 'datapack':
return 'required'
default:
return 'unknown'
}

View File

@@ -50,7 +50,15 @@
<p class="description">
{{ description }}
</p>
<Categories :categories="categories" :type="type" class="tags">
<Categories
:categories="
categories.filter(
(x) => !hideLoaders || !$tag.loaders.find((y) => y.name === x)
)
"
:type="type"
class="tags"
>
<span v-if="moderation" class="environment">
<InfoIcon aria-hidden="true" />
A {{ projectTypeDisplay }}
@@ -58,7 +66,8 @@
<span
v-else-if="
!['resourcepack', 'shader'].includes(type) &&
!(projectTypeDisplay === 'plugin' && search)
!(projectTypeDisplay === 'plugin' && search) &&
!categories.some((x) => $tag.loaderData.dataPackLoaders.includes(x))
"
class="environment"
>
@@ -261,6 +270,11 @@ export default {
required: false,
default: true,
},
hideLoaders: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
projectTypeDisplay() {

View File

@@ -31,8 +31,7 @@ export default {
.filter(
(x) =>
this.categories.includes(x.name) &&
(!x.project_type || x.project_type === this.type) &&
x.name !== 'minecraft'
(!x.project_type || x.project_type === this.type)
)
},
},

View File

@@ -32,12 +32,16 @@
label: 'Plugins',
href: '/plugins',
},
{
label: 'Data Packs',
href: '/datapacks',
},
{
label: 'Shaders',
href: '/shaders',
},
{
label: 'Resourcepacks',
label: 'Resource Packs',
href: '/resourcepacks',
},
{
@@ -201,6 +205,14 @@
>
<span>Plugins</span>
</NuxtLink>
<NuxtLink
:tabindex="isBrowseMenuOpen ? 0 : -1"
to="/datapacks"
class="tab iconified-button"
@click.native="isBrowseMenuOpen = false"
>
<span>Data packs</span>
</NuxtLink>
<NuxtLink
:tabindex="isBrowseMenuOpen ? 0 : -1"
to="/shaders"
@@ -215,7 +227,7 @@
class="tab iconified-button"
@click.native="isBrowseMenuOpen = false"
>
<span>Resourcepacks</span>
<span>Resource packs</span>
</NuxtLink>
<NuxtLink
:tabindex="isBrowseMenuOpen ? 0 : -1"
@@ -487,12 +499,13 @@ export default {
watch: {
$route() {
this.isMobileMenuOpen = false
document.body.style.overflowY = 'scroll'
this.$store.dispatch('user/fetchAll')
document.body.setAttribute('tabindex', '-1')
document.body.removeAttribute('tabindex')
if (process.client) {
document.body.style.overflowY = 'scroll'
document.body.setAttribute('tabindex', '-1')
document.body.removeAttribute('tabindex')
}
},
},
beforeCreate() {
@@ -848,7 +861,7 @@ export default {
}
}
@media screen and (max-width: 750px) {
@media screen and (max-width: 1024px) {
display: none;
}
}
@@ -963,7 +976,7 @@ export default {
}
}
@media screen and (max-width: 750px) {
@media screen and (max-width: 1024px) {
display: flex;
}

View File

@@ -32,7 +32,7 @@ export default {
hid: 'description',
name: 'description',
content:
'Download Minecraft mods, plugins, resource packs, and modpacks on Modrinth. Discover and publish projects on Modrinth with a modern, easy to use interface and API.',
'Download Minecraft mods, plugins, datapacks, shaders, resourcepacks, and modpacks on Modrinth. Discover and publish projects on Modrinth with a modern, easy to use interface and API.',
},
{
hid: 'publisher',
@@ -170,6 +170,11 @@ export default {
component: resolve(__dirname, 'pages/search/shaders.vue'),
name: 'shaders',
},
{
path: '/datapacks',
component: resolve(__dirname, 'pages/search/datapacks.vue'),
name: 'datapacks',
},
],
})

View File

@@ -45,7 +45,9 @@
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
project.project_type !== 'plugin' &&
project.project_type !== 'shader' &&
project.project_type !== 'datapack'
"
>
<div
@@ -502,7 +504,8 @@
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin' &&
project.project_type !== 'shader'
project.project_type !== 'shader' &&
project.project_type !== 'datapack'
"
class="info"
>
@@ -515,7 +518,8 @@
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin' &&
project.project_type !== 'shader'
project.project_type !== 'shader' &&
project.project_type !== 'datapack'
"
class="info"
>
@@ -843,10 +847,9 @@ export default {
this.featuredVersions = this.$computeVersions(this.featuredVersions)
},
head() {
const title = `${this.project.title} - Minecraft ${
this.projectTypeDisplay.charAt(0).toUpperCase() +
this.projectTypeDisplay.slice(1)
}`
const title = `${this.project.title} - Minecraft ${this.$formatProjectType(
this.projectTypeDisplay
)}`
return {
title,
@@ -869,9 +872,11 @@ export default {
{
hid: 'description',
name: 'description',
content: `${this.project.description} - Download the Minecraft ${
content: `${
this.project.description
} - Download the Minecraft ${this.$formatProjectType(
this.projectTypeDisplay
} ${this.project.title} by ${
)} ${this.project.title} by ${
this.members.find((x) => x.role === 'Owner').user.username
} on Modrinth`,
},

View File

@@ -209,7 +209,8 @@
<section
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'shader'
project.project_type !== 'shader' &&
project.project_type !== 'datapack'
"
class="card game-sides"
>
@@ -833,6 +834,9 @@ export default {
this.newProject.client_side = this.clientSideType.toLowerCase()
this.newProject.server_side = this.serverSideType.toLowerCase()
this.newProject.client_side = this.clientSideType.toLowerCase()
this.newProject.server_side = this.serverSideType.toLowerCase()
this.$emit('update:project', this.newProject)
this.isEditing = false

View File

@@ -371,7 +371,10 @@ export default {
}
}
document.addEventListener('keydown', this._keyListener.bind(this))
// eslint-disable-next-line nuxt/no-env-in-hooks
if (process.client) {
document.addEventListener('keydown', this._keyListener.bind(this))
}
},
methods: {
showPreviewImage(files, index) {

View File

@@ -1,6 +1,7 @@
<template>
<div v-if="version" class="version-page">
<ModalConfirm
v-if="$auth.user && currentMember"
ref="modal_confirm"
title="Are you sure you want to delete this version?"
description="This will remove this version forever (like really forever)."
@@ -14,6 +15,58 @@
:item-id="version.id"
item-type="version"
/>
<Modal
v-if="$auth.user && currentMember"
ref="modal_package_mod"
header="Package data pack"
>
<div class="modal-package-mod universal-labels">
<div class="markdown-body">
<p>
Package your data pack as a mod. This will create a new version with
support for the selected mod loaders. You will be redirected to the
new version and can edit it to your liking.
</p>
</div>
<label for="package-mod-loaders">
<span class="label__title">Mod loaders</span>
<span class="label__description">
The mod loaders you would like to package your data pack for.
</span>
</label>
<multiselect
id="package-mod-loaders"
v-model="packageLoaders"
:options="['fabric', 'forge', 'quilt']"
:custom-label="
(value) => value.charAt(0).toUpperCase() + value.slice(1)
"
:multiple="true"
:searchable="false"
:show-no-results="false"
:show-labels="false"
placeholder="Choose loaders.."
open-direction="top"
/>
<div class="button-group">
<button
class="iconified-button"
@click="$refs.modal_package_mod.hide()"
>
<CrossIcon />
Cancel
</button>
<button
class="iconified-button brand-button"
:disabled="!$nuxt.$loading"
@click="createDataPackVersion"
>
<RightArrowIcon />
Begin packaging data pack
</button>
</div>
</div>
</Modal>
<div class="version-page__title universal-card">
<div class="version-header">
<template v-if="isEditing">
@@ -143,7 +196,7 @@
Back to list
</nuxt-link>
<button
v-if="$auth.user"
v-if="$auth.user && !currentMember"
class="iconified-button"
@click="$refs.modal_version_report.show()"
>
@@ -164,6 +217,19 @@
<EditIcon aria-hidden="true" />
Edit
</nuxt-link>
<button
v-if="
currentMember &&
version.loaders.some((x) =>
$tag.loaderData.dataPackLoaders.includes(x)
)
"
class="iconified-button"
@click="$refs.modal_package_mod.show()"
>
<BoxIcon aria-hidden="true" />
Package as mod
</button>
<button
v-if="currentMember"
class="iconified-button danger-button"
@@ -222,7 +288,10 @@
></div>
</div>
<div
v-if="version.dependencies.length > 0 || isEditing"
v-if="
version.dependencies.length > 0 ||
(isEditing && project.project_type !== 'modpack')
"
class="version-page__dependencies universal-card"
>
<h3>Dependencies</h3>
@@ -269,7 +338,7 @@
</span>
</div>
<button
v-if="isEditing"
v-if="isEditing && project.project_type !== 'modpack'"
class="iconified-button"
@click="version.dependencies.splice(index, 1)"
>
@@ -308,7 +377,7 @@
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
:allow-empty="true"
/>
<input
v-model="newDependencyId"
@@ -334,7 +403,7 @@
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
:allow-empty="true"
/>
</div>
<div class="input-group">
@@ -385,7 +454,48 @@
<span class="filename">
<strong>{{ file.filename }}</strong>
<span class="file-size">({{ $formatBytes(file.size) }})</span>
<span
v-if="primaryFile.hashes.sha1 === file.hashes.sha1"
class="file-type"
>
Primary
</span>
<span
v-else-if="
file.file_type === 'required-resource-pack' && !isEditing
"
class="file-type"
>
Required resource pack
</span>
<span
v-else-if="
file.file_type === 'optional-resource-pack' && !isEditing
"
class="file-type"
>
Optional resource pack
</span>
</span>
<multiselect
v-if="
version.loaders.some((x) =>
$tag.loaderData.dataPackLoaders.includes(x)
) &&
isEditing &&
primaryFile.hashes.sha1 !== file.hashes.sha1
"
v-model="oldFileTypes[index]"
class="raised-multiselect"
placeholder="Select file type"
:options="fileTypes"
track-by="value"
label="display"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
<FileInput
v-if="isEditing && primaryFile.hashes.sha1 === file.hashes.sha1"
class="iconified-button raised-button"
@@ -397,6 +507,8 @@
(x) => {
deleteFiles.push(file.hashes.sha1)
version.files.splice(index, 1)
oldFileTypes.splice(index, 1)
replaceFile = x[0]
}
"
@@ -409,6 +521,7 @@
@click="
deleteFiles.push(file.hashes.sha1)
version.files.splice(index, 1)
oldFileTypes.splice(index, 1)
"
>
<TrashIcon />
@@ -432,9 +545,29 @@
<strong>{{ file.name }}</strong>
<span class="file-size">({{ $formatBytes(file.size) }})</span>
</span>
<multiselect
v-if="
version.loaders.some((x) =>
$tag.loaderData.dataPackLoaders.includes(x)
)
"
v-model="newFileTypes[index]"
class="raised-multiselect"
placeholder="Select file type"
:options="fileTypes"
track-by="value"
label="display"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
<button
class="iconified-button raised-button"
@click="newFiles.splice(index, 1)"
@click="
newFiles.splice(index, 1)
newFileTypes.splice(index, 1)
"
>
<TrashIcon />
Remove
@@ -442,14 +575,29 @@
</div>
<div class="additional-files">
<h4>Upload additional files</h4>
<span>Used for files such as sources or Javadocs.</span>
<span
v-if="
version.loaders.some((x) =>
$tag.loaderData.dataPackLoaders.includes(x)
)
"
>
Used for additional files such as required/optional resource packs
</span>
<span v-else>Used for files such as sources or Javadocs.</span>
<FileInput
prompt="Drag and drop to upload or click to select"
multiple
long-style
:accept="acceptFileFromProjectType(project.project_type)"
:max-size="524288000"
@change="(x) => x.forEach((y) => newFiles.push(y))"
@change="
(x) =>
x.forEach((y) => {
newFiles.push(y)
newFileTypes.push(null)
})
"
>
<UploadIcon />
</FileInput>
@@ -629,6 +777,7 @@ import Multiselect from 'vue-multiselect'
import {
acceptFileFromProjectType,
inferVersionInfo,
createDataPackVersion,
} from '~/plugins/fileUtils'
import VersionBadge from '~/components/ui/Badge'
@@ -654,9 +803,13 @@ import PlusIcon from '~/assets/images/utils/plus.svg?inline'
import TransferIcon from '~/assets/images/utils/transfer.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import BackIcon from '~/assets/images/utils/left-arrow.svg?inline'
import BoxIcon from '~/assets/images/utils/box.svg?inline'
import RightArrowIcon from '~/assets/images/utils/right-arrow.svg?inline'
import Modal from '~/components/ui/Modal.vue'
export default {
components: {
Modal,
FileInput,
Checkbox,
Chips,
@@ -680,6 +833,8 @@ export default {
ModalConfirm,
ModalReport,
Multiselect,
BoxIcon,
RightArrowIcon,
},
props: {
project: {
@@ -722,6 +877,7 @@ export default {
data() {
return {
primaryFile: {},
alternateFile: {},
version: {},
isEditing: false,
@@ -738,6 +894,21 @@ export default {
deleteFiles: [],
replaceFile: null,
newFileTypes: [],
oldFileTypes: [],
fileTypes: [
{
display: 'Required resource pack',
value: 'required-resource-pack',
},
{
display: 'Optional resource pack',
value: 'optional-resource-pack',
},
],
packageLoaders: ['forge', 'fabric', 'quilt'],
showKnownErrors: false,
}
},
@@ -814,6 +985,7 @@ export default {
acceptFileFromProjectType,
reset() {
this.primaryFile = {}
this.alternateFile = {}
this.version = {}
this.changelogViewMode = 'source'
@@ -827,10 +999,14 @@ export default {
this.newFiles = []
this.deleteFiles = []
this.replaceFile = null
this.oldFileTypes = []
this.newFileTypes = []
this.isEditing = false
this.isCreating = false
this.packageLoaders = ['forge', 'fabric', 'quilt']
this.showKnownErrors = false
},
async setVersion() {
@@ -916,6 +1092,9 @@ export default {
this.version = JSON.parse(JSON.stringify(this.version))
this.primaryFile =
this.version.files.find((file) => file.primary) ?? this.version.files[0]
this.alternateFile = this.version.files.find(
(file) => file.file_type && file.file_type.includes('resource-pack')
)
this.version.author_member = this.members.find(
(x) => x.user.id === this.version.author_id
@@ -948,6 +1127,10 @@ export default {
}`
: ''
}
this.oldFileTypes = this.version.files.map((x) =>
this.fileTypes.find((y) => y.value === x.file_type)
)
},
async addDependency(
dependencyAddMode,
@@ -1037,12 +1220,24 @@ export default {
if (this.newFiles.length > 0 || this.replaceFile) {
const formData = new FormData()
const fileParts = this.newFiles.map((f, idx) => `${f.name}-${idx}`)
formData.append('data', JSON.stringify({}))
formData.append(
'data',
JSON.stringify({
file_types: this.newFileTypes.reduce(
(acc, x, i) => ({
...acc,
[fileParts[i]]: x ? x.value : null,
}),
{}
),
})
)
for (let i = 0; i < this.newFiles.length; i++) {
formData.append(
this.newFiles[i].name.concat('-' + i),
fileParts[i],
new Blob([this.newFiles[i]]),
this.newFiles[i].name
)
@@ -1079,6 +1274,13 @@ export default {
loaders: this.version.loaders,
primary_file: ['sha1', this.primaryFile.hashes.sha1],
featured: this.version.featured,
file_types: this.oldFileTypes.map((x, i) => {
return {
algorithm: 'sha1',
hash: this.version.files[i].hashes.sha1,
file_type: x ? x.value : null,
}
}),
},
this.$defaultHeaders()
)
@@ -1090,7 +1292,7 @@ export default {
)
}
const [versions, featuredVersions] = (
const [versions, featuredVersions, dependencies] = (
await Promise.all([
this.$axios.get(
`project/${this.version.project_id}/version`,
@@ -1100,12 +1302,17 @@ export default {
`project/${this.version.project_id}/version?featured=true`,
this.$defaultHeaders()
),
this.$axios.get(
`project/${this.version.project_id}/dependencies`,
this.$defaultHeaders()
),
])
).map((it) => it.data)
const newEditedVersions = this.$computeVersions(versions)
this.$emit('update:versions', newEditedVersions)
this.$emit('update:featuredVersions', featuredVersions)
this.$emit('update:dependencies', dependencies)
await this.$router.replace(
`/${this.project.project_type}/${
@@ -1128,7 +1335,6 @@ export default {
},
async createVersion() {
this.$nuxt.$loading.start()
if (this.fieldErrors) {
this.showKnownErrors = true
@@ -1136,6 +1342,21 @@ export default {
return
}
try {
await this.createVersionRaw(this.version)
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.response ? err.response.data.description : err,
type: 'error',
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}
this.$nuxt.$loading.finish()
},
async createVersionRaw(version) {
const formData = new FormData()
const fileParts = this.newFiles.map((f, idx) => `${f.name}-${idx}`)
@@ -1144,20 +1365,27 @@ export default {
}
if (this.project.project_type === 'resourcepack') {
this.version.loaders = ['minecraft']
version.loaders = ['minecraft']
}
const newVersion = {
project_id: this.version.project_id,
project_id: version.project_id,
file_parts: fileParts,
version_number: this.version.version_number,
version_title: this.version.name || this.version.version_number,
version_body: this.version.changelog,
dependencies: this.version.dependencies,
game_versions: this.version.game_versions,
loaders: this.version.loaders,
release_channel: this.version.version_type,
featured: this.version.featured,
version_number: version.version_number,
version_title: version.name || version.version_number,
version_body: version.changelog,
dependencies: version.dependencies,
game_versions: version.game_versions,
loaders: version.loaders,
release_channel: version.version_type,
featured: version.featured,
file_types: this.newFileTypes.reduce(
(acc, x, i) => ({
...acc,
[fileParts[this.replaceFile ? i + 1 : i]]: x ? x.value : null,
}),
{}
),
}
formData.append('data', JSON.stringify(newVersion))
@@ -1172,58 +1400,51 @@ export default {
for (let i = 0; i < this.newFiles.length; i++) {
formData.append(
fileParts[i],
fileParts[this.replaceFile ? i + 1 : i],
new Blob([this.newFiles[i]]),
this.newFiles[i].name
)
}
try {
const data = (
await this.$axios({
url: 'version',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
Authorization: this.$auth.token,
},
})
).data
const [versions, featuredVersions] = (
await Promise.all([
this.$axios.get(
`project/${this.version.project_id}/version`,
this.$defaultHeaders()
),
this.$axios.get(
`project/${this.version.project_id}/version?featured=true`,
this.$defaultHeaders()
),
])
).map((it) => it.data)
const newCreatedVersions = this.$computeVersions(versions)
this.$emit('update:versions', newCreatedVersions)
this.$emit('update:featuredVersions', featuredVersions)
await this.$router.push(
`/${this.project.project_type}/${
this.project.slug ? this.project.slug : this.project.project_id
}/version/${data.id}`
)
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.response.data.description,
type: 'error',
const data = (
await this.$axios({
url: 'version',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
Authorization: this.$auth.token,
},
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}
).data
this.$nuxt.$loading.finish()
const [versions, featuredVersions, dependencies] = (
await Promise.all([
this.$axios.get(
`project/${this.version.project_id}/version`,
this.$defaultHeaders()
),
this.$axios.get(
`project/${this.version.project_id}/version?featured=true`,
this.$defaultHeaders()
),
this.$axios.get(
`project/${this.version.project_id}/dependencies`,
this.$defaultHeaders()
),
])
).map((it) => it.data)
const newCreatedVersions = this.$computeVersions(versions)
this.$emit('update:versions', newCreatedVersions)
this.$emit('update:featuredVersions', featuredVersions)
this.$emit('update:dependencies', dependencies)
await this.$router.push(
`/${this.project.project_type}/${
this.project.slug ? this.project.slug : this.project.project_id
}/version/${data.id}`
)
},
async deleteVersion() {
this.$nuxt.$loading.start()
@@ -1238,6 +1459,56 @@ export default {
)
this.$nuxt.$loading.finish()
},
async createDataPackVersion() {
this.$nuxt.$loading.start()
try {
const blob = await createDataPackVersion(
this.project,
this.version,
this.primaryFile,
this.members,
this.$tag.gameVersions,
this.packageLoaders
)
this.newFiles = []
this.newFileTypes = []
this.replaceFile = new File(
[blob],
`${this.project.slug}-${this.version.version_number}.jar`
)
await this.createVersionRaw({
project_id: this.project.id,
author_id: this.currentMember.user.id,
name: this.version.name,
version_number: `${this.version.version_number}+mod`,
changelog: this.version.changelog,
version_type: this.version.version_type,
dependencies: this.version.dependencies,
game_versions: this.version.game_versions,
loaders: this.packageLoaders,
featured: this.version.featured,
})
this.$refs.modal_package_mod.hide()
this.$notify({
group: 'main',
title: 'Packaging Success',
text: 'Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.',
type: 'success',
})
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.response ? err.response.data.description : err,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
},
}
</script>
@@ -1383,8 +1654,7 @@ export default {
}
.filename {
word-wrap: break-word;
overflow-wrap: anywhere;
word-wrap: anywhere;
}
.file-size {
@@ -1392,6 +1662,19 @@ export default {
white-space: nowrap;
}
.file-type {
font-style: italic;
font-weight: 300;
}
.raised-multiselect {
display: none;
margin: 0 0.5rem;
height: 40px;
max-height: 40px;
min-width: 235px;
}
.iconified-button {
margin-left: auto;
}
@@ -1399,6 +1682,13 @@ export default {
&:not(:nth-child(2)) {
margin-top: 0.5rem;
}
// TODO: Make file type editing work on mobile
@media (min-width: 600px) {
.raised-multiselect {
display: block;
}
}
}
.additional-files {
@@ -1454,4 +1744,23 @@ export default {
.separator {
margin: var(--spacing-card-sm) 0;
}
.modal-package-mod {
padding: var(--spacing-card-bg);
display: flex;
flex-direction: column;
.markdown-body {
margin-bottom: 1rem;
}
.multiselect {
max-width: 20rem;
}
.button-group {
margin-left: auto;
margin-top: 1.5rem;
}
}
</style>

View File

@@ -1,98 +0,0 @@
<template>
<div class="main">
<div class="card">
<h1>About</h1>
<p>
Founded in 2020, Modrinth was created to provide modders with an open
and intuitive platform to publish their projects on.
</p>
<p>
Our primary goal is to be as open as possible, with all our code being
<a :target="$external()" href="https://github.com/modrinth">
open source</a
>, while giving back to the modding community as much as possible.
</p>
<p>
While we still are in beta, we hope we can be a major modding platform
for all modders.
</p>
<h2>How does Modrinth work?</h2>
<p>
Modrinth is not just a website: it is made of several different
components that all come together to make a fast and flexible modding
platform.
</p>
<p>
On the technical level, Modrinth is made up of two main components: the
Rust-based backend named
<a :target="$external()" href="https://github.com/modrinth/labrinth"
>Labrinth</a
>, and the Vue-based frontend named
<a :target="$external()" href="https://github.com/modrinth/knossos">
Knossos</a
>.
</p>
<p>
Additionally, some other custom-created resources exist, including but
not limited to:
<a :target="$external()" href="https://github.com/modrinth/minotaur"
>Minotaur</a
>, a Gradle plugin for easily publishing mods to Modrinth, and
<a :target="$external()" href="https://github.com/modrinth/minos">
Minos</a
>, an authentication provider. All of Modrinth's code can be found on
<a :target="$external()" href="https://github.com/modrinth"
>our GitHub page</a
>.
</p>
<h2>Backend Documentation</h2>
<p>
Documentation for the Modrinth API (Labrinth) can be found on the GitHub
repository's wiki
<a :target="$external()" href="https://docs.modrinth.com">here</a>.
</p>
</div>
</div>
</template>
<script>
export default {
auth: false,
head: {
title: 'About - Modrinth',
meta: [
{
hid: 'apple-mobile-web-app-title',
name: 'apple-mobile-web-app-title',
content: 'About',
},
{
hid: 'og:title',
name: 'og:title',
content: 'About',
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/about`,
},
],
},
}
</script>
<style lang="scss" scoped>
.main {
margin: var(--spacing-card-sm) auto;
max-width: 800px;
}
a {
text-decoration: underline;
color: var(--color-link);
}
</style>

View File

@@ -126,7 +126,7 @@
<div class="title">
<h3>
{{ item.item_type }}
<a :href="item.url">{{ item.item_id }}</a>
<nuxt-link :to="item.url">{{ item.item_id }}</nuxt-link>
</h3>
reported by
<a :href="`/user/${item.reporter}`">{{ item.reporter }}</a>

View File

@@ -78,7 +78,9 @@
</div>
</section>
<section
v-if="projectType.id !== 'resourcepack'"
v-if="
projectType.id !== 'resourcepack' && projectType.id !== 'datapack'
"
aria-label="Loader filters"
>
<h3
@@ -101,15 +103,15 @@
x.name !== 'quilt'
) {
return false
}
if (projectType.id === 'mod' && showAllLoaders) {
} else if (projectType.id === 'mod' && showAllLoaders) {
return $tag.loaderData.modLoaders.includes(x.name)
} else if (projectType.id === 'plugin') {
return $tag.loaderData.pluginLoaders.includes(x.name)
} else if (projectType.id === 'datapack') {
return $tag.loaderData.dataPackLoaders.includes(x.name)
} else {
return x.supported_project_types.includes(projectType.actual)
}
return projectType.id === 'plugin'
? $tag.loaderData.pluginLoaders.includes(x.name)
: x.supported_project_types.includes(projectType.actual)
})"
:key="loader.name"
ref="loaderFilters"
@@ -158,7 +160,9 @@
</section>
<section
v-if="
!['resourcepack', 'plugin', 'shader'].includes(projectType.id)
!['resourcepack', 'plugin', 'shader', 'datapack'].includes(
projectType.id
)
"
aria-label="Environment filters"
>
@@ -376,6 +380,9 @@
:categories="result.display_categories"
:search="true"
:show-updated-date="sortType.name !== 'newest'"
:hide-loaders="
['resourcepack', 'datapack'].includes(projectType.id)
"
/>
<div v-if="results && results.length === 0" class="no-results">
<p>No results found for your query!</p>
@@ -714,6 +721,12 @@ export default {
(x) => `categories:'${encodeURIComponent(x)}'`
)
)
} else if (this.projectType.id === 'datapack') {
formattedFacets.push(
this.$tag.loaderData.dataPackLoaders.map(
(x) => `categories:'${encodeURIComponent(x)}'`
)
)
}
if (this.selectedVersions.length > 0) {

View File

@@ -0,0 +1,11 @@
<template>
<div></div>
</template>
<script>
export default {
name: 'Datapacks',
}
</script>
<style lang="scss" scoped></style>

View File

@@ -161,6 +161,7 @@ export default {
resourcepack: 'gallery',
modpack: 'list',
shader: 'gallery',
datapack: 'list',
user: 'list',
},
}

View File

@@ -160,7 +160,7 @@
},
...projectTypes.map((x) => {
return {
label: x === 'resourcepack' ? 'Resource Packs' : x + 's',
label: $formatProjectType(x) + 's',
href: x,
}
}),
@@ -326,13 +326,47 @@ export default {
}
let gitHubUser = {}
let versions = []
try {
gitHubUser = (
await data.$axios.get(`https://api.github.com/user/` + user.github_id)
).data
const [gitHubUserData, versionsData] = (
await Promise.all([
data.$axios.get(`https://api.github.com/user/` + user.github_id),
data.$axios.get(
`versions?ids=${JSON.stringify(
[].concat.apply(
[],
projects.map((x) => x.versions)
)
)}`
),
])
).map((it) => it.data)
gitHubUser = gitHubUserData
versions = versionsData
} catch {}
for (const version of versions) {
const projectIndex = projects.findIndex(
(x) => x.id === version.project_id
)
if (projects[projectIndex].loaders) {
for (const loader of version.loaders) {
if (!projects[projectIndex].loaders.includes(loader)) {
projects[projectIndex].loaders.push(loader)
}
}
} else {
projects[projectIndex].loaders = version.loaders
}
}
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
project.project_type = data.$getProjectTypeForUrl(
project.project_type,
project.categories
)
}
return {
user,
projects,

View File

@@ -15,7 +15,6 @@ import { formatBytes } from '~/plugins/shorthands'
export const fileIsValid = (file, validationOptions) => {
const { maxSize, alertOnInvalid } = validationOptions
if (maxSize !== null && maxSize !== undefined && file.size > maxSize) {
console.log(`File size: ${file.size}, max size: ${maxSize}`)
if (alertOnInvalid) {
alert(
`File ${file.name} is too big! Must be less than ${formatBytes(
@@ -39,6 +38,8 @@ export const acceptFileFromProjectType = (projectType) => {
return '.zip,application/zip'
case 'shader':
return '.zip,application/zip'
case 'datapack':
return '.zip,application/zip'
case 'modpack':
return '.mrpack,application/x-modrinth-modpack+zip'
default:
@@ -108,7 +109,7 @@ export const inferVersionInfo = async function (
'META-INF/mods.toml': (file, zip) => {
const metadata = TOML.parse(file)
console.log(JSON.stringify(metadata))
// ${file.jarVersion} -> Implementation-Version from manifest
// TODO: Parse minecraft version ranges, handle if version is set to value from manifest
if (metadata.mods && metadata.mods.length > 0) {
@@ -165,8 +166,10 @@ export const inferVersionInfo = async function (
version_type: versionType(metadata.quilt_loader.version),
game_versions: metadata.quilt_loader.depends
? gameVersionRange(
metadata.depends.find((x) => x.id === 'minecraft')
? metadata.depends.find((x) => x.id === 'minecraft').versions
metadata.quilt_loader.depends.find((x) => x.id === 'minecraft')
? metadata.quilt_loader.depends.find(
(x) => x.id === 'minecraft'
).versions
: [],
gameVersions
)
@@ -222,6 +225,112 @@ export const inferVersionInfo = async function (
.map((x) => x.version),
}
},
// Resource Packs + Data Packs
'pack.mcmeta': (file) => {
const metadata = JSON.parse(file)
function getRange(versionA, versionB) {
const startingIndex = gameVersions.findIndex(
(x) => x.version === versionA
)
const endingIndex = gameVersions.findIndex(
(x) => x.version === versionB
)
const final = []
const filterOnlyRelease =
gameVersions[startingIndex].version_type === 'release'
for (let i = startingIndex; i >= endingIndex; i--) {
if (
gameVersions[i].version_type === 'release' ||
!filterOnlyRelease
) {
final.push(gameVersions[i].version)
}
}
return final
}
const loaders = []
let newGameVersions = []
if (project.actualProjectType === 'mod') {
loaders.push('datapack')
switch (metadata.pack.pack_format) {
case 4:
newGameVersions = getRange('1.13', '1.14.4')
break
case 5:
newGameVersions = getRange('1.15', '1.16.1')
break
case 6:
newGameVersions = getRange('1.16.2', '1.16.5')
break
case 7:
newGameVersions = getRange('1.17', '1.17.1')
break
case 8:
newGameVersions = getRange('1.18', '1.18.1')
break
case 9:
newGameVersions.push('1.18.2')
break
case 10:
newGameVersions = getRange('1.19', '1.19.3')
break
default:
}
}
if (project.actualProjectType === 'resourcepack') {
loaders.push('minecraft')
switch (metadata.pack.pack_format) {
case 1:
newGameVersions = getRange('1.6.1', '1.8.9')
break
case 2:
newGameVersions = getRange('1.9', '1.10.2')
break
case 3:
newGameVersions = getRange('1.11', '1.12.2')
break
case 4:
newGameVersions = getRange('1.13', '1.14.4')
break
case 5:
newGameVersions = getRange('1.15', '1.16.1')
break
case 6:
newGameVersions = getRange('1.16.2', '1.16.5')
break
case 7:
newGameVersions = getRange('1.17', '1.17.1')
break
case 8:
newGameVersions = getRange('1.18', '1.18.2')
break
case 9:
newGameVersions = getRange('1.19', '1.19.2')
break
case 11:
newGameVersions = getRange('22w42a', '22w44a')
break
case 12:
newGameVersions.push('1.19.3')
break
default:
}
}
return {
loaders,
game_versions: newGameVersions,
}
},
}
const zipReader = new JSZip()
@@ -237,3 +346,201 @@ export const inferVersionInfo = async function (
}
}
}
export const createDataPackVersion = async function (
project,
version,
primaryFile,
members,
allGameVersions,
loaders
) {
// force version to start with number, as required by FML
const newVersionNumber = version.version_number.match(/^\d/)
? version.version_number
: `1-${version.version_number}`
const newSlug = `${project.slug
.replace('-', '_')
.replace(/\W/g, '')
.substring(0, 63)}_mr`
const iconPath = `${project.slug}_pack.png`
const fabricModJson = {
schemaVersion: 1,
id: newSlug,
version: newVersionNumber,
name: project.title,
description: project.description,
authors: members.map((x) => x.name),
contact: {
homepage: `${process.env.domain}/${project.project_type}/${
project.slug ?? project.id
}`,
},
license: project.license.id,
icon: iconPath,
environment: '*',
depends: {
'fabric-resource-loader-v0': '*',
},
}
const quiltModJson = {
schema_version: 1,
quilt_loader: {
group: 'com.modrinth',
id: newSlug,
version: newVersionNumber,
metadata: {
name: project.title,
description: project.description,
contributors: members.reduce(
(acc, x) => ({
...acc,
[x.name]: x.role,
}),
{}
),
contact: {
homepage: `${process.env.domain}/${project.project_type}/${
project.slug ?? project.id
}`,
},
icon: iconPath,
},
intermediate_mappings: 'net.fabricmc:intermediary',
depends: [
{
id: 'quilt_resource_loader',
versions: '*',
unless: 'fabric-resource-loader-v0',
},
],
},
}
const cutoffIndex = allGameVersions.findIndex((x) => x.version === '1.18.2')
let maximumIndex = Number.MIN_VALUE
for (const val of version.game_versions) {
const index = allGameVersions.findIndex((x) => x.version === val)
if (index > maximumIndex) {
maximumIndex = index
}
}
const newForge = maximumIndex < cutoffIndex
const forgeModsToml = {
modLoader: newForge ? 'lowcodefml' : 'javafml',
loaderVersion: newForge ? '[40,)' : '[25,)',
license: project.license.id,
showAsResourcePack: true,
mods: [
{
modId: newSlug,
version: newVersionNumber,
displayName: project.title,
description: project.description,
logoFile: iconPath,
updateJSONURL: `${process.env.authURLBase.replace(
'/v2/',
''
)}/updates/${project.id}/forge_updates.json`,
credits: 'Generated by Modrinth',
authors: members.map((x) => x.name).join(', '),
displayURL: `${process.env.domain}/${project.project_type}/${
project.slug ?? project.id
}`,
},
],
}
if (project.source_url) {
quiltModJson.quilt_loader.metadata.contact.sources = project.source_url
fabricModJson.contact.sources = project.source_url
}
if (project.issues_url) {
quiltModJson.quilt_loader.metadata.contact.issues = project.issues_url
fabricModJson.contact.issues = project.issues_url
forgeModsToml.issueTrackerURL = project.issues_url
}
const primaryFileData = await (await fetch(primaryFile.url)).blob()
const primaryZipReader = new JSZip()
await primaryZipReader.loadAsync(primaryFileData)
if (loaders.includes('fabric'))
primaryZipReader.file('fabric.mod.json', JSON.stringify(fabricModJson))
if (loaders.includes('quilt'))
primaryZipReader.file('quilt.mod.json', JSON.stringify(quiltModJson))
if (loaders.includes('forge'))
primaryZipReader.file('META-INF/mods.toml', TOML.stringify(forgeModsToml))
if (!newForge && loaders.includes('forge')) {
const classFile = new Uint8Array(
await (
await fetch(
'https://cdn.modrinth.com/wrapper/ModrinthWrapperRestiched.class'
)
).arrayBuffer()
)
let binary = ''
for (let i = 0; i < classFile.byteLength; i++) {
binary += String.fromCharCode(classFile[i])
}
binary = binary
.replace(
String.fromCharCode(32) + 'needs1to1be1changed1modrinth1mod',
String.fromCharCode(newSlug.length) + newSlug
)
.replace('/wrappera/', `/${project.id.substring(0, 8)}/`)
const newArr = []
for (let i = 0; i < binary.length; i++) {
newArr.push(binary.charCodeAt(i))
}
primaryZipReader.file(
`com/modrinth/${project.id.substring(0, 8)}/ModrinthWrapper.class`,
new Uint8Array(newArr)
)
}
const resourcePack = version.files.find(
(x) => x.file_type === 'required-resource-pack'
)
const resourcePackData = resourcePack
? await (await fetch(resourcePack.url)).blob()
: null
if (resourcePackData) {
const resourcePackReader = new JSZip()
await resourcePackReader.loadAsync(resourcePackData)
for (const [path, file] of Object.entries(resourcePackReader.files)) {
if (!primaryZipReader.file(path) && !path.includes('.mcassetsroot')) {
primaryZipReader.file(path, await file.async('uint8array'))
}
}
}
if (primaryZipReader.file('pack.png')) {
primaryZipReader.file(
iconPath,
await primaryZipReader.file('pack.png').async('uint8array')
)
}
return await primaryZipReader.generateAsync({
type: 'blob',
mimeType: 'application/java-archive',
})
}

View File

@@ -153,24 +153,46 @@ export default (ctx, inject) => {
const isMod = categories.some((category) => {
return ctx.store.state.tag.loaderData.modLoaders.includes(category)
})
return isPlugin && isMod ? 'mod and plugin' : isPlugin ? 'plugin' : 'mod'
} else {
return formatProjectType(type)
const isDataPack = categories.some((category) => {
return ctx.store.state.tag.loaderData.dataPackLoaders.includes(category)
})
if (isMod && isPlugin && isDataPack) {
return 'mod, plugin, and data pack'
} else if (isMod && isPlugin) {
return 'mod and plugin'
} else if (isMod && isDataPack) {
return 'mod and datapack'
}
}
return type
})
inject('getProjectTypeForUrl', (type, categories) => {
if (type === 'mod') {
const isMod = categories.some((category) => {
return ctx.store.state.tag.loaderData.modLoaders.includes(category)
})
const isPlugin = categories.some((category) => {
return ctx.store.state.tag.loaderData.allPluginLoaders.includes(
category
)
})
const isMod = categories.some((category) => {
return ctx.store.state.tag.loaderData.modLoaders.includes(category)
const isDataPack = categories.some((category) => {
return ctx.store.state.tag.loaderData.dataPackLoaders.includes(category)
})
return isPlugin && isMod ? 'mod' : isPlugin ? 'plugin' : 'mod'
if (isDataPack) {
return 'datapack'
} else if (isPlugin) {
return 'plugin'
} else if (isMod) {
return 'mod'
} else {
return 'mod'
}
} else {
return type
}
@@ -232,7 +254,10 @@ export const formatWallet = (name) => {
export const formatProjectType = (name) => {
if (name === 'resourcepack') {
return 'Resource Pack'
} else if (name === 'datapack') {
return 'Data Pack'
}
return capitalizeString(name)
}
@@ -261,6 +286,8 @@ export const formatCategory = (name) => {
return 'Path Tracing'
} else if (name === 'pbr') {
return 'PBR'
} else if (name === 'datapack') {
return 'Data pack'
}
return capitalizeString(name)

View File

@@ -19,6 +19,7 @@ export const defaults = {
resourcepack: 'gallery',
modpack: 'list',
shader: 'gallery',
datapack: 'list',
user: 'list',
},
}

View File

@@ -17,6 +17,11 @@ export const state = () => ({
id: 'plugin',
display: 'plugin',
},
{
actual: 'mod',
id: 'datapack',
display: 'datapack',
},
{
actual: 'resourcepack',
id: 'resourcepack',
@@ -46,6 +51,7 @@ export const state = () => ({
'waterfall',
'velocity',
],
dataPackLoaders: ['datapack'],
modLoaders: ['forge', 'fabric', 'quilt', 'liteloader', 'modloader', 'rift'],
},
projectViewModes: ['list', 'grid', 'gallery'],