From 1f133dbcd07d9b93e785820480086ec34ab886fb Mon Sep 17 00:00:00 2001
From: Geometrically <18202329+Geometrically@users.noreply.github.com>
Date: Thu, 29 Dec 2022 09:59:41 -0700
Subject: [PATCH] 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
---
assets/styles/components.scss | 89 ++++--
assets/styles/global.scss | 52 ++--
components/ui/DropArea.vue | 9 +-
components/ui/ModalCreation.vue | 4 +
components/ui/ProjectCard.vue | 18 +-
components/ui/search/Categories.vue | 3 +-
layouts/default.vue | 29 +-
nuxt.config.js | 7 +-
pages/_type/_id.vue | 23 +-
pages/_type/_id/edit.vue | 6 +-
pages/_type/_id/gallery.vue | 5 +-
pages/_type/_id/version.vue | 447 +++++++++++++++++++++++-----
pages/about.vue | 98 ------
pages/moderation.vue | 2 +-
pages/search.vue | 31 +-
pages/search/datapacks.vue | 11 +
pages/settings/index.vue | 1 +
pages/user/_id.vue | 44 ++-
plugins/fileUtils.js | 315 +++++++++++++++++++-
plugins/shorthands.js | 39 ++-
store/cosmetics.js | 1 +
store/tag.js | 6 +
22 files changed, 978 insertions(+), 262 deletions(-)
delete mode 100644 pages/about.vue
create mode 100644 pages/search/datapacks.vue
diff --git a/assets/styles/components.scss b/assets/styles/components.scss
index e194e2d1d..975d0f947 100644
--- a/assets/styles/components.scss
+++ b/assets/styles/components.scss
@@ -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;
}
}
diff --git a/assets/styles/global.scss b/assets/styles/global.scss
index 687e11a6d..9a6c05c04 100644
--- a/assets/styles/global.scss
+++ b/assets/styles/global.scss
@@ -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;
}
diff --git a/components/ui/DropArea.vue b/components/ui/DropArea.vue
index 375f6d000..4d2245744 100644
--- a/components/ui/DropArea.vue
+++ b/components/ui/DropArea.vue
@@ -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) {
diff --git a/components/ui/ModalCreation.vue b/components/ui/ModalCreation.vue
index 88e744db4..eee6843d3 100644
--- a/components/ui/ModalCreation.vue
+++ b/components/ui/ModalCreation.vue
@@ -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'
}
diff --git a/components/ui/ProjectCard.vue b/components/ui/ProjectCard.vue
index 79d40ebf6..2985576a2 100644
--- a/components/ui/ProjectCard.vue
+++ b/components/ui/ProjectCard.vue
@@ -50,7 +50,15 @@
{{ description }}
-
+
A {{ projectTypeDisplay }}
@@ -58,7 +66,8 @@
@@ -261,6 +270,11 @@ export default {
required: false,
default: true,
},
+ hideLoaders: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
projectTypeDisplay() {
diff --git a/components/ui/search/Categories.vue b/components/ui/search/Categories.vue
index 997c13491..3f909ca11 100644
--- a/components/ui/search/Categories.vue
+++ b/components/ui/search/Categories.vue
@@ -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)
)
},
},
diff --git a/layouts/default.vue b/layouts/default.vue
index f1f126d7b..6d4c7952a 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -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 @@
>
Plugins
+
+ Data packs
+
- Resourcepacks
+ Resource packs
@@ -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`,
},
diff --git a/pages/_type/_id/edit.vue b/pages/_type/_id/edit.vue
index d73dfdea3..31894c68e 100644
--- a/pages/_type/_id/edit.vue
+++ b/pages/_type/_id/edit.vue
@@ -209,7 +209,8 @@
@@ -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
diff --git a/pages/_type/_id/gallery.vue b/pages/_type/_id/gallery.vue
index 826d51088..e975cc2d1 100644
--- a/pages/_type/_id/gallery.vue
+++ b/pages/_type/_id/gallery.vue
@@ -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) {
diff --git a/pages/_type/_id/version.vue b/pages/_type/_id/version.vue
index 9cfbb2c4d..bf578ed50 100644
--- a/pages/_type/_id/version.vue
+++ b/pages/_type/_id/version.vue
@@ -1,6 +1,7 @@
+
+
+
+
+ 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.
+
+
+
+ Mod loaders
+
+ The mod loaders you would like to package your data pack for.
+
+
+
+
+
+
+ Cancel
+
+
+
+ Begin packaging data pack
+
+
+
+
Dependencies
@@ -269,7 +338,7 @@
@@ -308,7 +377,7 @@
:searchable="false"
:close-on-select="true"
:show-labels="false"
- :allow-empty="false"
+ :allow-empty="true"
/>
@@ -385,7 +454,48 @@
{{ file.filename }}
({{ $formatBytes(file.size) }})
+
+ Primary
+
+
+ Required resource pack
+
+
+ Optional resource pack
+
+
{
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)
"
>
@@ -432,9 +545,29 @@
{{ file.name }}
({{ $formatBytes(file.size) }})
+
Remove
@@ -442,14 +575,29 @@
Upload additional files
-
Used for files such as sources or Javadocs.
+
+ Used for additional files such as required/optional resource packs
+
+
Used for files such as sources or Javadocs.
x.forEach((y) => newFiles.push(y))"
+ @change="
+ (x) =>
+ x.forEach((y) => {
+ newFiles.push(y)
+ newFileTypes.push(null)
+ })
+ "
>
@@ -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()
+ },
},
}
@@ -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;
+ }
+}
diff --git a/pages/about.vue b/pages/about.vue
deleted file mode 100644
index a1f1ed071..000000000
--- a/pages/about.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
About
-
- Founded in 2020, Modrinth was created to provide modders with an open
- and intuitive platform to publish their projects on.
-
-
-
- Our primary goal is to be as open as possible, with all our code being
-
- open source , while giving back to the modding community as much as possible.
-
-
-
- While we still are in beta, we hope we can be a major modding platform
- for all modders.
-
-
How does Modrinth work?
-
- 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.
-
-
-
- On the technical level, Modrinth is made up of two main components: the
- Rust-based backend named
- Labrinth , and the Vue-based frontend named
-
- Knossos .
-
-
-
- Additionally, some other custom-created resources exist, including but
- not limited to:
- Minotaur , a Gradle plugin for easily publishing mods to Modrinth, and
-
- Minos , an authentication provider. All of Modrinth's code can be found on
- our GitHub page .
-
-
Backend Documentation
-
- Documentation for the Modrinth API (Labrinth) can be found on the GitHub
- repository's wiki
- here .
-
-
-
-
-
-
-
-
diff --git a/pages/moderation.vue b/pages/moderation.vue
index 22ba8bea4..1f7df44b8 100644
--- a/pages/moderation.vue
+++ b/pages/moderation.vue
@@ -126,7 +126,7 @@
{{ item.item_type }}
- {{ item.item_id }}
+ {{ item.item_id }}
reported by
{{ item.reporter }}
diff --git a/pages/search.vue b/pages/search.vue
index 0de0cd7bd..f5fa625f3 100644
--- a/pages/search.vue
+++ b/pages/search.vue
@@ -78,7 +78,9 @@
@@ -376,6 +380,9 @@
:categories="result.display_categories"
:search="true"
:show-updated-date="sortType.name !== 'newest'"
+ :hide-loaders="
+ ['resourcepack', 'datapack'].includes(projectType.id)
+ "
/>
No results found for your query!
@@ -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) {
diff --git a/pages/search/datapacks.vue b/pages/search/datapacks.vue
new file mode 100644
index 000000000..204f14812
--- /dev/null
+++ b/pages/search/datapacks.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/pages/settings/index.vue b/pages/settings/index.vue
index e5c83a2c7..4db45052c 100644
--- a/pages/settings/index.vue
+++ b/pages/settings/index.vue
@@ -161,6 +161,7 @@ export default {
resourcepack: 'gallery',
modpack: 'list',
shader: 'gallery',
+ datapack: 'list',
user: 'list',
},
}
diff --git a/pages/user/_id.vue b/pages/user/_id.vue
index 6ef1be06f..509e15fb1 100644
--- a/pages/user/_id.vue
+++ b/pages/user/_id.vue
@@ -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,
diff --git a/plugins/fileUtils.js b/plugins/fileUtils.js
index 14ceedfa7..0f21df1ef 100644
--- a/plugins/fileUtils.js
+++ b/plugins/fileUtils.js
@@ -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',
+ })
+}
diff --git a/plugins/shorthands.js b/plugins/shorthands.js
index c7d68145f..73145e828 100644
--- a/plugins/shorthands.js
+++ b/plugins/shorthands.js
@@ -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)
diff --git a/store/cosmetics.js b/store/cosmetics.js
index cf9c4f390..8b2314c7b 100644
--- a/store/cosmetics.js
+++ b/store/cosmetics.js
@@ -19,6 +19,7 @@ export const defaults = {
resourcepack: 'gallery',
modpack: 'list',
shader: 'gallery',
+ datapack: 'list',
user: 'list',
},
}
diff --git a/store/tag.js b/store/tag.js
index 160a05333..5a646c205 100644
--- a/store/tag.js
+++ b/store/tag.js
@@ -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'],