New gallery creation/editing/deleting UI (#826)

* New gallery creation/editing/deleting UI

* Finish new gallery UI

* port dp changes here

* Add ordering fix optional fields

* Fix dropping

* Fix fmt

* Fix version creation broken, edit issues, project type setting

* Update gallery in search
This commit is contained in:
Geometrically 2022-12-31 23:47:47 -07:00 committed by GitHub
parent f11aab6c19
commit d2b1404907
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 488 additions and 321 deletions

View File

@ -513,8 +513,10 @@ tr.button-transparent {
box-shadow: var(--shadow-inset-sm), 0 0 0 0 transparent;
svg {
width: 1.25rem;
height: 1.25rem;
min-width: 1.25rem;
max-width: 1.25rem;
min-height: 1.25rem;
max-height: 1.25rem;
margin: auto;
}

View File

@ -6,7 +6,7 @@
(event) => {
$refs.drop_area.style.visibility = 'hidden'
if (event.dataTransfer && event.dataTransfer.files) {
if (event.dataTransfer && event.dataTransfer.files && fileAllowed) {
$emit('change', event.dataTransfer.files)
}
}
@ -26,30 +26,40 @@ export default {
default: '',
},
},
mounted() {
// eslint-disable-next-line nuxt/no-env-in-hooks
if (process.client) {
document.addEventListener('dragenter', () => {
this.$refs.drop_area.style.visibility = 'visible'
})
data() {
return {
fileAllowed: false,
}
},
mounted() {
document.addEventListener('dragenter', this.allowDrag)
},
methods: {
allowDrag(event) {
const file = event.dataTransfer?.items[0]
console.log(file)
if (
file &&
this.accept
.split(',')
.reduce(
(acc, t) => acc || file.type.startsWith(t) || file.type === t,
(acc, t) =>
acc || file.type.startsWith(t) || file.type === t || t === '*',
false
)
) {
// Adds file dropping indicator
this.fileAllowed = true
event.dataTransfer.dropEffect = 'copy'
event.preventDefault()
if (this.$refs.drop_area)
this.$refs.drop_area.style.visibility = 'visible'
} else {
this.fileAllowed = false
if (this.$refs.drop_area)
this.$refs.drop_area.style.visibility = 'hidden'
}
},
},

View File

@ -206,7 +206,14 @@ Questions? [Join the Modrinth Discord for support!](https://discord.gg/EUHuJHt)`
})
this.$refs.modal.hide()
await this.$router.replace(`/${projectType.actual}/${this.slug}`)
await this.$router.push({
name: 'type-id',
params: {
type: projectType.id,
id: this.slug,
overrideProjectType: projectType.id,
},
})
} catch (err) {
this.$notify({
group: 'main',

View File

@ -15,12 +15,9 @@
class="gallery"
tabindex="-1"
:to="`/${$getProjectTypeForUrl(type, categories)}/${id}`"
:style="color ? `background-color: ${toColor};` : ''"
>
<img
v-if="galleryImages.length > 0"
:src="galleryImages[0]"
alt="Gallery image TODO: improve this lol"
/>
<img v-if="featuredImage" :src="featuredImage" alt="gallery image" />
</nuxt-link>
<div class="title">
<nuxt-link :to="`/${$getProjectTypeForUrl(type, categories)}/${id}`">
@ -99,6 +96,14 @@
<ServerIcon aria-hidden="true" />
Server
</template>
<template
v-else-if="
serverSide === 'unsupported' && clientSide === 'unsupported'
"
>
<GlobeIcon aria-hidden="true" />
Unsupported
</template>
<template v-else-if="moderation">
<InfoIcon aria-hidden="true" />
A {{ projectTypeDisplay }}
@ -258,12 +263,10 @@ export default {
required: false,
default: false,
},
galleryImages: {
type: Array,
featuredImage: {
type: String,
required: false,
default() {
return []
},
default: null,
},
showUpdatedDate: {
type: Boolean,
@ -275,11 +278,25 @@ export default {
required: false,
default: false,
},
color: {
type: Number,
required: false,
default: null,
},
},
computed: {
projectTypeDisplay() {
return this.$getProjectTypeForDisplay(this.type, this.categories)
},
toColor() {
let color = this.color
color >>>= 0
const b = color & 0xff
const g = (color & 0xff00) >>> 8
const r = (color & 0xff0000) >>> 16
return 'rgba(' + [r, g, b, 1].join(',') + ')'
},
},
}
</script>
@ -338,7 +355,10 @@ export default {
height: 10rem;
background-color: var(--color-button-bg-active);
filter: brightness(0.7);
img {
box-shadow: none;
width: 100%;
height: 10rem;
object-fit: cover;
@ -349,6 +369,13 @@ export default {
margin-left: var(--spacing-card-bg);
margin-top: -3rem;
z-index: 1;
img,
svg {
border-radius: var(--size-rounded-lg);
border: 0.25rem solid var(--color-raised-bg);
border-bottom: none;
}
}
.title {

View File

@ -508,9 +508,9 @@ export default {
}
},
},
beforeCreate() {
async beforeCreate() {
if (this.$route.query.code) {
this.$router.push(this.$route.path)
await this.$router.push(this.$route.path)
}
},
created() {
@ -619,10 +619,6 @@ export default {
margin-left: auto;
margin-right: auto;
@media screen and (max-width: 750px) {
justify-content: center;
}
section.logo {
display: flex;
justify-content: space-between;
@ -1082,7 +1078,7 @@ export default {
&.active {
display: flex;
@media screen and (min-width: 750px) {
@media screen and (min-width: 1024px) {
display: none;
}
}

View File

@ -755,10 +755,12 @@ export default {
JSON.stringify(project.project_type)
)
project.project_type = data.$getProjectTypeForUrl(
project.project_type,
Object.keys(projectLoaders)
)
project.project_type = data.params.overrideProjectType
? data.params.overrideProjectType
: data.$getProjectTypeForUrl(
project.project_type,
Object.keys(projectLoaders)
)
if (
project.project_type !== data.params.type ||
@ -922,6 +924,33 @@ export default {
},
},
methods: {
async resetProject() {
const project = (
await this.$axios.get(
`project/${this.project.id}`,
this.$defaultHeaders()
)
).data
const projectLoaders = {}
for (const version of this.versions) {
for (const loader of version.loaders) {
projectLoaders[loader] = true
}
}
project.actualProjectType = JSON.parse(
JSON.stringify(project.project_type)
)
project.project_type = this.$getProjectTypeForUrl(
project.project_type,
Object.keys(projectLoaders)
)
this.project = project
},
findPrimary(version) {
let file = version.files.find((x) => x.primary)

View File

@ -150,9 +150,9 @@ export default {
}
},
methods: {
switchPage(page, toTop) {
async switchPage(page, toTop) {
this.currentPage = page
this.$router.replace(this.getPageLink(page))
await this.$router.replace(this.getPageLink(page))
if (toTop) {
setTimeout(() => window.scrollTo({ top: 0, behavior: 'smooth' }), 50)

View File

@ -829,19 +829,7 @@ export default {
)
}
// While the emit below will take care of most changes,
// some items require manually updating
this.newProject.license.id = this.licenseId
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.newProject.client_side = this.clientSideType.toLowerCase()
this.newProject.server_side = this.serverSideType.toLowerCase()
this.$emit('update:project', this.newProject)
await this.$parent.resetProject()
this.isEditing = false

View File

@ -1,5 +1,136 @@
<template>
<div>
<Modal
v-if="$auth.user && currentMember"
ref="modal_edit_item"
:header="editIndex === -1 ? 'Upload gallery image' : 'Edit gallery item'"
>
<div class="modal-gallery universal-labels">
<div class="gallery-file-input">
<div class="file-header">
<ImageIcon />
<strong>{{ editFile ? editFile.name : 'Current image' }}</strong>
<FileInput
v-if="editIndex === -1"
class="iconified-button raised-button"
prompt="Replace"
:accept="acceptFileTypes"
:max-size="524288000"
should-always-reset
@change="
(x) => {
editFile = x[0]
showPreviewImage()
}
"
>
<TransferIcon />
</FileInput>
</div>
<img
:src="
previewImage
? previewImage
: project.gallery[editIndex] && project.gallery[editIndex].url
? project.gallery[editIndex].url
: 'https://cdn.modrinth.com/placeholder-banner.svg'
"
alt="gallery-preview"
/>
</div>
<label for="gallery-image-title">
<span class="label__title">Title</span>
</label>
<input
id="gallery-image-title"
v-model="editTitle"
type="text"
maxlength="64"
placeholder="Enter title..."
/>
<label for="gallery-image-desc">
<span class="label__title">Description</span>
</label>
<div class="textarea-wrapper">
<textarea
id="gallery-image-desc"
v-model="editDescription"
maxlength="255"
placeholder="Enter description..."
/>
</div>
<label for="gallery-image-ordering">
<span class="label__title">Order Index</span>
</label>
<input
id="gallery-image-ordering"
v-model="editOrder"
type="number"
placeholder="Enter order index..."
/>
<label for="gallery-image-featured">
<span class="label__title">Featured</span>
<span class="label__description">
A featured gallery image shows up in search and your project card.
Only one gallery image can be featured.
</span>
</label>
<button
v-if="!editFeatured"
id="gallery-image-featured"
class="iconified-button"
@click="editFeatured = true"
>
<StarIcon aria-hidden="true" />
Feature image
</button>
<button
v-else
id="gallery-image-featured"
class="iconified-button"
@click="editFeatured = false"
>
<StarIcon fill="currentColor" aria-hidden="true" />
Unfeature image
</button>
<div class="button-group">
<button
class="iconified-button"
@click="$refs.modal_edit_item.hide()"
>
<CrossIcon />
Cancel
</button>
<button
v-if="editIndex === -1"
class="iconified-button brand-button"
:disabled="!$nuxt.$loading"
@click="createGalleryItem"
>
<PlusIcon />
Add gallery image
</button>
<button
v-else
class="iconified-button brand-button"
:disabled="!$nuxt.$loading"
@click="editGalleryItem"
>
<SaveIcon />
Save changes
</button>
</div>
</div>
</Modal>
<ModalConfirm
v-if="$auth.user && currentMember"
ref="modal_confirm"
title="Are you sure you want to delete this gallery image?"
description="This will remove this gallery image forever (like really forever)."
:has-to-type="false"
proceed-label="Delete"
@proceed="deleteGalleryImage"
/>
<div
v-if="expandedGalleryItem != null"
class="expanded-image-modal"
@ -55,14 +186,14 @@
<ContractIcon v-else aria-hidden="true" />
</button>
<button
v-if="gallery.length > 1"
v-if="project.gallery.length > 1"
class="previous circle-button"
@click="previousImage()"
>
<LeftArrowIcon aria-hidden="true" />
</button>
<button
v-if="gallery.length > 1"
v-if="project.gallery.length > 1"
class="next circle-button"
@click="nextImage()"
>
@ -73,61 +204,24 @@
</div>
</div>
</div>
<div v-if="currentMember" class="card buttons header-buttons">
<button
class="iconified-button"
:class="{
'brand-button':
newGalleryItems.length === 0 &&
editGalleryIndexes.length === 0 &&
deleteGalleryUrls.length === 0,
}"
@click="
newGalleryItems.push({
title: '',
description: '',
featured: false,
url: '',
})
"
<div v-if="currentMember" class="card header-buttons">
<FileInput
:max-size="524288000"
:accept="acceptFileTypes"
prompt="Upload an image"
class="brand-button iconified-button"
@change="handleFiles"
>
<PlusIcon />
{{
newGalleryItems.length === 0 &&
editGalleryIndexes.length === 0 &&
deleteGalleryUrls.length === 0
? 'Add an image'
: 'Add another image'
}}
</button>
<button
v-if="
newGalleryItems.length > 0 ||
editGalleryIndexes.length > 0 ||
deleteGalleryUrls.length > 0
"
class="action iconified-button"
@click="resetGallery"
>
<CrossIcon />
Cancel
</button>
<button
v-if="
newGalleryItems.length > 0 ||
editGalleryIndexes.length > 0 ||
deleteGalleryUrls.length > 0
"
class="action brand-button iconified-button"
@click="saveGallery"
>
<CheckIcon />
Save changes
</button>
<UploadIcon />
</FileInput>
<span class="indicator">
<InfoIcon /> Click to choose an image or drag one onto this page
</span>
<DropArea :accept="acceptFileTypes" @change="handleFiles" />
</div>
<div class="items">
<div
v-for="(item, index) in gallery"
v-for="(item, index) in project.gallery"
:key="index"
class="card gallery-item"
>
@ -142,22 +236,7 @@
/>
</a>
<div class="gallery-body">
<div v-if="editGalleryIndexes.includes(index)" class="gallery-info">
<input
v-model="item.title"
type="text"
placeholder="Enter the title..."
/>
<div class="textarea-wrapper">
<textarea
id="body"
v-model="item.description"
placeholder="Enter the description..."
/>
</div>
<Checkbox v-model="item.featured" label="Featured" />
</div>
<div v-else class="gallery-info">
<div class="gallery-info">
<h2 v-if="item.title">{{ item.title }}</h2>
<p v-if="item.description">{{ item.description }}</p>
</div>
@ -169,22 +248,16 @@
</div>
<div v-if="currentMember" class="gallery-buttons input-group">
<button
v-if="editGalleryIndexes.includes(index)"
class="iconified-button"
@click="
editGalleryIndexes.splice(editGalleryIndexes.indexOf(index), 1)
gallery[index] = JSON.parse(
JSON.stringify(project.gallery[index])
)
resetEdit()
editIndex = index
editTitle = item.title
editDescription = item.description
editFeatured = item.featured
editOrder = item.ordering
$refs.modal_edit_item.show()
"
>
<CrossIcon />
Cancel
</button>
<button
v-else
class="iconified-button"
@click="editGalleryIndexes.push(index)"
>
<EditIcon />
Edit
@ -192,8 +265,8 @@
<button
class="iconified-button"
@click="
deleteGalleryUrls.push(item.url)
gallery.splice(index, 1)
deleteIndex = index
$refs.modal_confirm.show()
"
>
<TrashIcon />
@ -202,59 +275,6 @@
</div>
</div>
</div>
<div
v-for="(item, index) in newGalleryItems"
:key="index + 'new'"
class="card gallery-item"
>
<img
:src="
newGalleryItems[index].preview
? newGalleryItems[index].preview
: 'https://cdn.modrinth.com/placeholder-banner.svg'
"
:alt="item.title ? item.title : 'gallery-image'"
/>
<div class="gallery-body">
<div class="gallery-info">
<input
v-model="item.title"
type="text"
placeholder="Enter the title..."
/>
<div class="resizable-textarea-wrapper">
<textarea
id="body"
v-model="item.description"
placeholder="Enter the description..."
/>
</div>
<Checkbox v-model="item.featured" label="Featured" />
</div>
</div>
<div class="gallery-bottom">
<FileInput
:max-size="5242880"
accept="image/png,image/jpeg,image/gif,image/webp,.png,.jpeg,.gif,.webp"
prompt="Choose image or drag it here"
class="iconified-button"
@change="(files) => showPreviewImage(files, index)"
>
<UploadIcon />
</FileInput>
<div class="gallery-buttons">
<div class="delete-button-container">
<button
class="iconified-button"
@click="newGalleryItems.splice(index, 1)"
>
<TrashIcon />
Remove
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@ -267,24 +287,29 @@ import CrossIcon from '~/assets/images/utils/x.svg?inline'
import RightArrowIcon from '~/assets/images/utils/right-arrow.svg?inline'
import LeftArrowIcon from '~/assets/images/utils/left-arrow.svg?inline'
import EditIcon from '~/assets/images/utils/edit.svg?inline'
import CheckIcon from '~/assets/images/utils/check.svg?inline'
import SaveIcon from '~/assets/images/utils/save.svg?inline'
import ExternalIcon from '~/assets/images/utils/external.svg?inline'
import ExpandIcon from '~/assets/images/utils/expand.svg?inline'
import ContractIcon from '~/assets/images/utils/contract.svg?inline'
import StarIcon from '~/assets/images/utils/star.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import InfoIcon from '~/assets/images/utils/info.svg?inline'
import ImageIcon from '~/assets/images/utils/image.svg?inline'
import TransferIcon from '~/assets/images/utils/transfer.svg?inline'
import FileInput from '~/components/ui/FileInput'
import Checkbox from '~/components/ui/Checkbox'
import DropArea from '~/components/ui/DropArea'
import ModalConfirm from '~/components/ui/ModalConfirm'
import Modal from '~/components/ui/Modal'
export default {
components: {
CalendarIcon,
PlusIcon,
Checkbox,
EditIcon,
TrashIcon,
CheckIcon,
FileInput,
SaveIcon,
StarIcon,
CrossIcon,
RightArrowIcon,
LeftArrowIcon,
@ -292,13 +317,15 @@ export default {
ExpandIcon,
ContractIcon,
UploadIcon,
InfoIcon,
ImageIcon,
TransferIcon,
ModalConfirm,
Modal,
FileInput,
DropArea,
},
auth: false,
beforeRouteLeave(to, from, next) {
this.resetGallery()
next()
},
props: {
project: {
type: Object,
@ -315,18 +342,21 @@ export default {
},
data() {
return {
gallery: [],
newGalleryItems: [],
editGalleryIndexes: [],
deleteGalleryUrls: [],
expandedGalleryItem: null,
expandedGalleryIndex: 0,
zoomedIn: false,
deleteIndex: -1,
editIndex: -1,
editTitle: '',
editDescription: '',
editFeatured: false,
editOrder: null,
editFile: null,
previewImage: null,
}
},
fetch() {
this.gallery = JSON.parse(JSON.stringify(this.project.gallery))
},
head() {
const title = `${this.project.title} - Gallery`
const description = `View ${this.project.gallery.length} images of ${this.project.title} on Modrinth.`
@ -357,6 +387,11 @@ export default {
],
}
},
computed: {
acceptFileTypes() {
return 'image/png,image/jpeg,image/gif,image/webp,.png,.jpeg,.gif,.webp'
},
},
mounted() {
this._keyListener = function (e) {
if (this.expandedGalleryItem) {
@ -371,125 +406,156 @@ export default {
}
}
// eslint-disable-next-line nuxt/no-env-in-hooks
if (process.client) {
document.addEventListener('keydown', this._keyListener.bind(this))
}
document.addEventListener('keydown', this._keyListener.bind(this))
},
methods: {
showPreviewImage(files, index) {
const reader = new FileReader()
this.newGalleryItems[index].icon = files[0]
if (this.newGalleryItems[index].icon instanceof Blob) {
reader.readAsDataURL(this.newGalleryItems[index].icon)
reader.onload = (event) => {
this.newGalleryItems[index].preview = event.target.result
// TODO: Find an alternative for this!
this.$forceUpdate()
}
}
},
async saveGallery() {
this.$nuxt.$loading.start()
try {
for (const item of this.newGalleryItems) {
let url = `project/${this.project.id}/gallery?ext=${
item.icon
? item.icon.type.split('/')[item.icon.type.split('/').length - 1]
: null
}&featured=${item.featured}`
if (item.title) url += `&title=${encodeURIComponent(item.title)}`
if (item.description)
url += `&description=${encodeURIComponent(item.description)}`
await this.$axios.post(url, item.icon, this.$defaultHeaders())
}
for (const index of this.editGalleryIndexes) {
const item = this.gallery[index]
let url = `project/${
this.project.id
}/gallery?url=${encodeURIComponent(item.url)}&featured=${
item.featured
}`
if (item.title) url += `&title=${encodeURIComponent(item.title)}`
if (item.description)
url += `&description=${encodeURIComponent(item.description)}`
await this.$axios.patch(url, {}, this.$defaultHeaders())
}
for (const url of this.deleteGalleryUrls) {
await this.$axios.delete(
`project/${this.project.id}/gallery?url=${encodeURIComponent(url)}`,
this.$defaultHeaders()
)
}
const project = (
await this.$axios.get(
`project/${this.project.id}`,
this.$defaultHeaders()
)
).data
this.$emit('update:project', project)
this.gallery = JSON.parse(JSON.stringify(project.gallery))
this.deleteGalleryUrls = []
this.editGalleryIndexes = []
this.newGalleryItems = []
} catch (err) {
const description = err.response.data.description
this.$notify({
group: 'main',
title: 'An error occurred',
text: description,
type: 'error',
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}
this.$nuxt.$loading.finish()
},
resetGallery() {
this.newGalleryItems = []
this.editGalleryIndexes = []
this.deleteGalleryUrls = []
this.gallery = JSON.parse(JSON.stringify(this.project.gallery))
},
nextImage() {
this.expandedGalleryIndex++
if (this.expandedGalleryIndex >= this.gallery.length) {
if (this.expandedGalleryIndex >= this.project.gallery.length) {
this.expandedGalleryIndex = 0
}
this.expandedGalleryItem = this.gallery[this.expandedGalleryIndex]
this.expandedGalleryItem = this.project.gallery[this.expandedGalleryIndex]
},
previousImage() {
this.expandedGalleryIndex--
if (this.expandedGalleryIndex < 0) {
this.expandedGalleryIndex = this.gallery.length - 1
this.expandedGalleryIndex = this.project.gallery.length - 1
}
this.expandedGalleryItem = this.gallery[this.expandedGalleryIndex]
this.expandedGalleryItem = this.project.gallery[this.expandedGalleryIndex]
},
expandImage(item, index) {
this.expandedGalleryItem = item
this.expandedGalleryIndex = index
this.zoomedIn = false
},
resetEdit() {
this.editIndex = -1
this.editTitle = ''
this.editDescription = ''
this.editFeatured = false
this.editOrder = null
this.editFile = null
this.previewImage = null
},
handleFiles(files) {
this.resetEdit()
this.editFile = files[0]
this.showPreviewImage()
this.$refs.modal_edit_item.show()
},
showPreviewImage() {
const reader = new FileReader()
if (this.editFile instanceof Blob) {
reader.readAsDataURL(this.editFile)
reader.onload = (event) => {
this.previewImage = event.target.result
}
}
},
async createGalleryItem() {
this.$nuxt.$loading.start()
try {
let url = `project/${this.project.id}/gallery?ext=${
this.editFile
? this.editFile.type.split('/')[
this.editFile.type.split('/').length - 1
]
: null
}&featured=${this.editFeatured}`
if (this.editTitle) url += `&title=${this.editTitle}`
if (this.editDescription) url += `&description=${this.editDescription}`
if (this.editOrder) url += `&ordering=${this.editOrder}`
await this.$axios.post(url, this.editFile, this.$defaultHeaders())
await this.updateProject()
this.$refs.modal_edit_item.hide()
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.response ? err.response.data.description : err,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
async editGalleryItem() {
this.$nuxt.$loading.start()
try {
let url = `project/${this.project.id}/gallery?url=${encodeURIComponent(
this.project.gallery[this.editIndex].url
)}&featured=${this.editFeatured}`
if (this.editTitle) url += `&title=${this.editTitle}`
if (this.editDescription) url += `&description=${this.editDescription}`
if (this.editOrder) url += `&ordering=${this.editOrder}`
await this.$axios.patch(url, {}, this.$defaultHeaders())
await this.updateProject()
this.$refs.modal_edit_item.hide()
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.response ? err.response.data.description : err,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
async deleteGalleryImage() {
this.$nuxt.$loading.start()
try {
await this.$axios.delete(
`project/${this.project.id}/gallery?url=${encodeURIComponent(
this.project.gallery[this.deleteIndex].url
)}`,
this.$defaultHeaders()
)
await this.updateProject()
} catch (err) {
this.$notify({
group: 'main',
title: 'An error occurred',
text: err.response ? err.response.data.description : err,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
async updateProject() {
await this.$parent.resetProject()
this.resetEdit()
},
},
}
</script>
<style lang="scss" scoped>
.header-buttons {
display: flex;
align-items: center;
gap: 1rem;
.indicator {
display: flex;
gap: 0.5ch;
align-items: center;
color: var(--color-text-inactive);
}
}
.expanded-image-modal {
position: fixed;
z-index: 20;
@ -656,15 +722,6 @@ export default {
width: calc(100% - 2 * var(--spacing-card-md));
padding: var(--spacing-card-sm) var(--spacing-card-md);
textarea {
border-radius: var(--size-rounded-sm);
}
input {
width: 100%;
margin: 0 0 0.25rem;
}
.gallery-info {
h2 {
margin-bottom: 0.5rem;
@ -716,8 +773,46 @@ export default {
}
}
.header-buttons {
.modal-gallery {
padding: var(--spacing-card-bg);
display: flex;
justify-content: right;
flex-direction: column;
.gallery-file-input {
.file-header {
border-radius: var(--size-rounded-card) var(--size-rounded-card) 0 0;
display: flex;
align-items: center;
gap: 0.5rem;
background-color: var(--color-button-bg);
padding: var(--spacing-card-md);
svg {
min-width: 1rem;
}
strong {
word-wrap: anywhere;
}
.iconified-button {
margin-left: auto;
}
}
img {
border-radius: 0 0 var(--size-rounded-card) var(--size-rounded-card);
width: 100%;
height: auto;
max-height: 15rem;
object-fit: contain;
background-color: #000000;
}
}
.button-group {
margin-left: auto;
margin-top: 1.5rem;
}
}
</style>

View File

@ -1287,7 +1287,7 @@ export default {
for (const hash of this.deleteFiles) {
await this.$axios.delete(
`version_file/${hash}`,
`version_file/${hash}?version_id=${this.version.id}`,
this.$defaultHeaders()
)
}

View File

@ -197,8 +197,8 @@ export default {
updateVersions(updatedVersions) {
this.filteredVersions = updatedVersions
},
handleFiles(files) {
this.$router.push({
async handleFiles(files) {
await this.$router.push({
name: 'type-id-version-create',
params: {
type: this.project.project_type,

View File

@ -86,6 +86,7 @@
:client-side="project.client_side"
:server-side="project.server_side"
:type="project.project_type"
:color="project.color"
:moderation="true"
>
<button

View File

@ -365,7 +365,11 @@
:id="result.slug ? result.slug : result.project_id"
:key="result.project_id"
:display="$cosmetics.searchDisplayMode[projectType.id]"
:gallery-images="result.gallery"
:gallery-images="
result.featured_gallery
? result.featured_gallery
: result.gallery[0]
"
:type="result.project_type"
:author="result.author"
:name="result.title"
@ -383,6 +387,7 @@
:hide-loaders="
['resourcepack', 'datapack'].includes(projectType.id)
"
:color="result.color"
/>
<div v-if="results && results.length === 0" class="no-results">
<p>No results found for your query!</p>
@ -966,6 +971,11 @@ export default {
white-space: nowrap;
}
}
.square-button {
margin-top: auto;
margin-bottom: 0.25rem;
}
}
}

View File

@ -14,6 +14,7 @@
:name="project.title"
:client-side="project.client_side"
:server-side="project.server_side"
:color="project.color"
>
<button
class="iconified-button"

View File

@ -213,7 +213,7 @@
project.gallery
.slice()
.sort((a, b) => b.featured - a.featured)
.map((x) => x.url)
.map((x) => x.url)[0]
"
:description="project.description"
:created-at="project.published"
@ -234,6 +234,7 @@
"
:has-mod-message="project.moderator_message"
:type="project.project_type"
:color="project.color"
/>
</div>
<div v-else class="error">

View File

@ -31,9 +31,9 @@ export const fileIsValid = (file, validationOptions) => {
export const acceptFileFromProjectType = (projectType) => {
switch (projectType) {
case 'mod':
return '.jar,.zip,.litemod,application/java-archive,application/zip'
return '.jar,.zip,.litemod,application/java-archive,application/x-java-archive,application/zip'
case 'plugin':
return '.jar,.zip,application/java-archive,application/zip'
return '.jar,.zip,application/java-archive,application/x-java-archive,application/zip'
case 'resourcepack':
return '.zip,application/zip'
case 'shader':
@ -41,7 +41,7 @@ export const acceptFileFromProjectType = (projectType) => {
case 'datapack':
return '.zip,application/zip'
case 'modpack':
return '.mrpack,application/x-modrinth-modpack+zip'
return '.mrpack,application/x-modrinth-modpack+zip,application/zip'
default:
return '*'
}
@ -437,7 +437,7 @@ export const createDataPackVersion = async function (
modLoader: newForge ? 'lowcodefml' : 'javafml',
loaderVersion: newForge ? '[40,)' : '[25,)',
license: project.license.id,
showAsResourcePack: true,
showAsResourcePack: false,
mods: [
{
modId: newSlug,

View File

@ -287,7 +287,7 @@ export const formatCategory = (name) => {
} else if (name === 'pbr') {
return 'PBR'
} else if (name === 'datapack') {
return 'Data pack'
return 'Data Pack'
}
return capitalizeString(name)

View File

@ -20,7 +20,7 @@ export const state = () => ({
{
actual: 'mod',
id: 'datapack',
display: 'datapack',
display: 'data pack',
},
{
actual: 'resourcepack',