[WIP] Rework design (#34)

* WIP: Redesign the default layout

* Merge old & new default layouts

* Fix login logic; add proper user controls dropdown

* Fix latest version listing (#31) (#32)

Co-authored-by: Aeledfyr <45501007+Aeledfyr@users.noreply.github.com>

* First pass of design cleanup

* Improve ad integration and fix light theme

* Begin splitting up variables, change some styling to new mockup

* Continue redesign progress

* Work on some more pages

* Add missing dark theme variables for text

* Continue working on modularizing

* Continue progress, redo pagination

* Fix auth buttons in navbar layout

* Continue progress

* Continue progress more

* Redo ModResult

* Scope ModPage :irritater:

* Continue Dashboard

* Continue progress on Dashboard and cleanup

* Add missing variables for dark theme

* Small tweaks, cleanup, and continue mod page progress

* Fix user not being able to see hidden mods that they own

* Start reworking mod creation

* Continue revamp of mod creation page

* Yank v-html out

* Hotfix markdown rendering and some spacing issues

* Move legal; continue with mod creation; create reusable footer

* Create README.md

* Update README.md

* Update README.md

* Add in basic usage instructions

* Fix some stuff

* Continue with mod creation; fix some CSS errors

* Start user page

* Start transition to vue-select; fix a few bugs

* Continue mod creation page

* Finish mod pages

* Add very raw version editing

* Mod editing + creation

* Fixed versions that were in processing causing a 404 (#39)

Co-authored-by: Mikhail Oleynikov <falseresync@gmail.com>
Co-authored-by: Aeledfyr <45501007+Aeledfyr@users.noreply.github.com>
Co-authored-by: Jai A <jai.a@tuta.io>
Co-authored-by: MulverineX <mulverin3@gmail.com>
Co-authored-by: diabolical17 <calumproh28@gmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Prospector
2020-11-30 13:55:01 -08:00
committed by GitHub
parent e025df0824
commit 7b84d8c3d5
70 changed files with 10339 additions and 3284 deletions

571
pages/mod/_id/edit.vue Normal file
View File

@@ -0,0 +1,571 @@
<template>
<div class="page-container">
<div class="page-contents">
<header class="columns">
<h2 class="column-grow-1">Edit Mod</h2>
<button
title="Save"
class="brand-button column"
:disabled="!this.$nuxt.$loading"
@click="saveMod"
>
Save
</button>
</header>
<EthicalAd class="advert" />
<section class="essentials">
<h3>Name</h3>
<label>
<span>
Be creative. TechCraft v7 won't be searchable and won't be clicked
on
</span>
<input v-model="mod.title" type="text" placeholder="Enter the name" />
</label>
<h3>Summary</h3>
<label>
<span>
Give a quick description to your mod. It will appear in the search
</span>
<input
v-model="mod.description"
type="text"
placeholder="Enter the summary"
/>
</label>
<h3>Categories</h3>
<label>
<span>
Select up to 3 categories. They will help to find your mod
</span>
<multiselect
id="categories"
v-model="mod.categories"
:options="availableCategories"
:loading="availableCategories.length === 0"
:multiple="true"
:searchable="false"
:show-no-results="false"
:close-on-select="false"
:clear-on-select="false"
:show-labels="false"
:max="3"
:limit="6"
:hide-selected="true"
placeholder="Choose categories"
/>
</label>
<h3>Vanity URL (slug)</h3>
<label>
<span>
Set this to something pretty, so URLs to your mod are more readable
</span>
<input
id="name"
v-model="mod.slug"
type="text"
placeholder="Enter the vanity URL's last bit"
/>
</label>
</section>
<section class="mod-icon rows">
<h3>Icon</h3>
<div class="columns row-grow-1">
<div class="column-grow-1 rows">
<file-input
accept="image/png,image/jpeg,image/gif"
class="choose-image"
prompt="Choose image or drag it here"
@change="showPreviewImage"
/>
<ul class="row-grow-1">
<li>Must be a square</li>
<li>Minimum size is 100x100</li>
<li>Acceptable formats are PNG, JPEG and GIF</li>
</ul>
<button
class="transparent-button"
@click="
icon = null
previewImage = null
"
>
Reset icon
</button>
</div>
<img
:src="
mod.icon_url
? mod.icon_url
: 'https://cdn.modrinth.com/placeholder.svg'
"
alt="preview-image"
/>
</div>
</section>
<section class="game-sides">
<h3>Supported environments</h3>
<div class="columns">
<span>
Let others know if your mod is for clients, servers or universal.
For example, IC2 will be required + required, while OptiFine will be
required + no functionality
</span>
<div class="labeled-control">
<h3>Client</h3>
<Multiselect
v-model="clientSideType"
placeholder="Select one"
track-by="id"
label="label"
:options="sideTypes"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
</div>
<div class="labeled-control">
<h3>Server</h3>
<Multiselect
v-model="serverSideType"
placeholder="Select one"
track-by="id"
label="label"
:options="sideTypes"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
</div>
</div>
</section>
<section class="description">
<h3>
<label
for="body"
title="You can type the of the long form of your description here."
>
Description
</label>
</h3>
<span>
You can type the of the long form of your description here. This
editor supports markdown. You can find the syntax
<a
href="https://guides.github.com/features/mastering-markdown/"
target="_blank"
rel="noopener noreferrer"
>here</a
>.
</span>
<div class="columns">
<div class="textarea-wrapper">
<textarea id="body" v-model="body"></textarea>
</div>
<div v-compiled-markdown="body" class="markdown-body"></div>
</div>
</section>
<section class="extra-links">
<div class="title">
<h3>External links</h3>
</div>
<label
title="A place for users to report bugs, issues, and concerns about your mod."
>
<span>Issue tracker</span>
<input
v-model="mod.issues_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
<label title="A page/repository containing the source code">
<span>Source code</span>
<input
v-model="mod.source_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
<label
title="A page containing information, documentation, and help for the mod."
>
<span>Wiki page</span>
<input
v-model="mod.wiki_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
<label title="An inivitation link to your Discord server.">
<span>Discord invite</span>
<input
v-model="mod.wiki_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
</section>
<section class="license">
<div class="title">
<h3>License</h3>
</div>
<label>
<span>
It is really important to choose a proper license for your mod. You
may choose one from our list or provide a URL to your own license.
URL field will be filled automatically for provided licenses
</span>
<div class="input-group">
<Multiselect
v-model="mod.license"
placeholder="Select one"
track-by="short"
label="name"
:options="availableLicenses"
:searchable="true"
:close-on-select="true"
:show-labels="false"
/>
<input
v-model="mod.license.url"
type="url"
placeholder="License URL"
/>
</div>
</label>
</section>
<!--
<section class="donations">
<div class="title">
<h3>Donation links</h3>
<i> this section is optional</i>
</div>
</section>
-->
<m-footer class="footer" centered />
</div>
</div>
</template>
<script>
import axios from 'axios'
import Multiselect from 'vue-multiselect'
import MFooter from '@/components/MFooter'
import FileInput from '@/components/FileInput'
import EthicalAd from '@/components/EthicalAd'
export default {
components: {
MFooter,
FileInput,
EthicalAd,
Multiselect,
},
async asyncData(data) {
const [
mod,
availableCategories,
availableLoaders,
availableGameVersions,
availableLicenses,
// availableDonationPlatforms,
] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/mod/${data.params.id}`),
axios.get(`https://api.modrinth.com/api/v1/tag/category`),
axios.get(`https://api.modrinth.com/api/v1/tag/loader`),
axios.get(`https://api.modrinth.com/api/v1/tag/game_version`),
axios.get(`https://api.modrinth.com/api/v1/tag/license`),
// axios.get(`https://api.modrinth.com/api/v1/tag/donation_platform`),
])
).map((it) => it.data)
mod.license = {
short: mod.license.id,
name: mod.license.name,
}
const res = await axios.get(mod.body_url)
return {
mod,
body: res.data,
clientSideType: {
label: mod.client_side,
id: mod.client_side,
},
serverSideType: {
label: mod.server_side,
id: mod.server_side,
},
availableCategories,
availableLoaders,
availableGameVersions,
availableLicenses,
// availableDonationPlatforms,
}
},
data() {
return {
previewImage: null,
compiledBody: '',
icon: null,
sideTypes: [
{ label: 'Required', id: 'required' },
{ label: 'No functionality', id: 'no-functionality' },
{ label: 'Unsupported', id: 'unsupported' },
],
}
},
watch: {
license(newValue, oldValue) {
if (newValue == null) {
this.license_url = ''
return
}
switch (newValue.short) {
case 'custom':
this.license_url = ''
break
default:
this.license_url = `https://cdn.modrinth.com/licenses/${newValue.short}.txt`
}
},
},
methods: {
async saveMod() {
this.$nuxt.$loading.start()
try {
await axios.patch(
`https://api.modrinth.com/api/v1/mod/${this.mod.id}`,
{
title: this.mod.title,
description: this.mod.description,
body: this.body,
categories: this.mod.categories,
issues_url: this.mod.issues_url,
source_url: this.mod.source_url,
wiki_url: this.mod.wiki_url,
license_url: this.mod.license_url,
discord_url: this.mod.discord_url,
license_id: this.mod.license.short,
client_side: this.clientSideType.id,
server_side: this.serverSideType.id,
slug: this.mod.mod_slug,
},
{
headers: {
Authorization: this.$auth.getToken('local'),
},
}
)
await this.$router.replace(`/mod/${this.mod.id}`)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}
this.$nuxt.$loading.finish()
},
showPreviewImage(e) {
const reader = new FileReader()
this.icon = e.target.files[0]
reader.readAsDataURL(this.icon)
reader.onload = (event) => {
this.previewImage = event.target.result
}
},
},
}
</script>
<style lang="scss" scoped>
.title {
* {
display: inline;
}
}
label {
display: flex;
span {
flex: 2;
padding-right: var(--spacing-card-lg);
}
input,
.multiselect,
.input-group {
flex: 3;
height: fit-content;
}
}
.input-group {
display: flex;
flex-direction: column;
* {
margin-bottom: var(--spacing-card-sm);
}
}
.textarea-wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
textarea {
flex: 1;
overflow-y: auto;
resize: none;
max-width: 100%;
}
}
.page-contents {
display: grid;
grid-template:
'header header header' auto
'advert advert advert' auto
'essentials essentials mod-icon' auto
'game-sides game-sides game-sides' auto
'description description description' auto
'versions versions versions' auto
'extra-links license license' auto
'donations donations .' auto
'footer footer footer' auto
/ 4fr 1fr 4fr;
column-gap: var(--spacing-card-md);
row-gap: var(--spacing-card-md);
}
header {
@extend %card;
grid-area: header;
padding: var(--spacing-card-md) var(--spacing-card-lg);
h2 {
margin: auto 0;
color: var(--color-text-dark);
font-weight: var(--font-weight-extrabold);
}
button {
margin-left: 0.5rem;
}
}
.advert {
grid-area: advert;
}
section {
@extend %card;
padding: var(--spacing-card-md) var(--spacing-card-lg);
}
section.essentials {
grid-area: essentials;
}
section.mod-icon {
grid-area: mod-icon;
img {
align-self: flex-start;
max-width: 50%;
margin-left: var(--spacing-card-lg);
}
}
section.game-sides {
grid-area: game-sides;
.columns {
flex-wrap: wrap;
span {
flex: 2;
}
.labeled-control {
flex: 2;
margin-left: var(--spacing-card-lg);
}
}
}
section.description {
grid-area: description;
& > .columns {
align-items: stretch;
min-height: 10rem;
max-height: 40rem;
& > * {
flex: 1;
max-width: 50%;
}
}
.markdown-body {
overflow-y: auto;
padding: 0 var(--spacing-card-sm);
}
}
section.extra-links {
grid-area: extra-links;
label {
align-items: center;
margin-top: var(--spacing-card-sm);
span {
flex: 1;
}
}
}
section.license {
grid-area: license;
label {
margin-top: var(--spacing-card-sm);
}
}
section.donations {
grid-area: donations;
}
.footer {
grid-area: footer;
}
.choose-image {
cursor: pointer;
}
</style>

View File

@@ -1,14 +1,11 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<div class="markdown-body" v-html="body"></div>
<div v-compiled-markdown="body" class="markdown-body"></div>
</ModPage>
</template>
<script>
import axios from 'axios'
import xss from 'xss'
import marked from 'marked'
import ModPage from '@/components/ModPage'
export default {
@@ -31,17 +28,20 @@ export default {
members[i].avatar_url = res.data.avatar_url
}
res = await axios.get(mod.body_url)
const body = xss(marked(res.data))
const body = (await axios.get(mod.body_url)).data
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
return {
@@ -102,9 +102,9 @@ export default {
<style lang="scss" scoped>
.markdown-body {
padding: 20px;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
background: var(--color-bg);
border-radius: 0 0 var(--size-rounded-sm) var(--size-rounded-sm);
padding: 1rem;
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
}
</style>

View File

@@ -31,11 +31,15 @@ export default {
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
return {

View File

@@ -1,109 +1,115 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<div class="version">
<div class="header">
<h3>{{ version.name }}</h3>
<div
v-if="
this.$auth.loggedIn &&
members.find((x) => x.user_id === this.$auth.user.id)
<div class="version-header">
<h4>{{ version.name }}</h4>
<span v-if="version.version_type === 'release'" class="badge green">
Release
</span>
<span v-if="version.version_type === 'beta'" class="badge yellow">
Beta
</span>
<span v-if="version.version_type === 'alpha'" class="badge red">
Alpha
</span>
<span>
{{ version.version_number }}
</span>
<Categories :categories="version.loaders" />
<a
:href="primaryFile.url"
class="download-button"
@click.prevent="
downloadFile(primaryFile.hashes.sha1, primaryFile.url)
"
class="user-actions"
>
<button class="trash red">
<TrashIcon />
</button>
<button class="upload" @click="showPopup = !showPopup">
<UploadIcon />
</button>
<DownloadIcon />
Download
</a>
</div>
<div class="stats">
<div class="stat">
<DownloadIcon />
<div class="info">
<h4>Downloads</h4>
<p class="value">{{ version.downloads }}</p>
</div>
</div>
<div class="stat">
<CalendarIcon />
<div class="info">
<h4>Created</h4>
<p
v-tooltip="
$dayjs(version.published).format(
'[Created on] YYYY-MM-DD [at] HH:mm A'
)
"
class="value"
>
{{ $dayjs(version.published).fromNow() }}
</p>
</div>
</div>
<div class="stat">
<TagIcon />
<div class="info">
<h4>Available For</h4>
<p class="value">
{{ version.game_versions.join(', ') }}
</p>
</div>
</div>
</div>
<div class="markdown-body" v-html="changelog"></div>
<hr />
<div class="columns metadata">
<div class="author">
<img :src="version.author.avatar_url" />
<p>{{ version.author.name }}</p>
</div>
<p>{{ version.downloads }} Downloads</p>
<div>
<FabricIcon
v-if="version.loaders.includes('fabric')"
stroke="#AC6C3A"
/>
<ForgeIcon
v-if="version.loaders.includes('forge')"
stroke="#8B81E6"
/>
</div>
<div class="game-versions">
<p v-for="gameVersion in version.game_versions" :key="gameVersion">
{{ gameVersion }}
</p>
</div>
</div>
<hr />
<div v-compiled-markdown="changelog" class="markdown-body"></div>
<div class="files">
<div v-for="file in version.files" :key="file.hashes.sha1">
<p>{{ file.filename }}</p>
<a :href="file.url" download>
<div class="text-wrapper">
<p>{{ file.filename }}</p>
<div
v-if="
$auth.loggedIn &&
members.find((x) => x.user_id === $auth.user.id)
"
class="actions"
>
<button @click="deleteFile(file.hashes.sha1)">Delete File</button>
<button @click="makePrimary(file.hashes.sha1)">
Make Primary
</button>
</div>
</div>
<a
:href="file.url"
@click.prevent="downloadFile(file.hash, file.url)"
>
<DownloadIcon />
</a>
</div>
</div>
<FileInput class="file-input" @change="addFiles" />
</div>
<Popup :show-popup="showPopup">
<h3 class="popup-title">Upload Files</h3>
<FileInput
input-id="version-files"
input-accept="application/*"
default-text="Upload Files"
:input-multiple="true"
@change="addFiles"
>
<label class="required" title="The files associated with the version">
Version Files
</label>
</FileInput>
<div class="popup-buttons">
<button
class="trash-button"
@click="
showPopup = false
filesToUpload = []
"
>
<TrashIcon />
</button>
<button class="default-button" @click="uploadFiles">Upload</button>
</div>
</Popup>
</ModPage>
</template>
<script>
import axios from 'axios'
import ModPage from '@/components/ModPage'
import xss from 'xss'
import marked from 'marked'
import Popup from '@/components/Popup'
import Categories from '@/components/Categories'
import FileInput from '@/components/FileInput'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import ForgeIcon from '~/assets/images/categories/forge.svg?inline'
import FabricIcon from '~/assets/images/categories/fabric.svg?inline'
import CalendarIcon from '~/assets/images/utils/calendar.svg?inline'
import TagIcon from '~/assets/images/utils/tag.svg?inline'
export default {
components: {
Popup,
FileInput,
Categories,
ModPage,
ForgeIcon,
FabricIcon,
DownloadIcon,
UploadIcon,
TrashIcon,
CalendarIcon,
TagIcon,
},
auth: false,
async asyncData(data) {
@@ -125,11 +131,15 @@ export default {
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
const version = versions.find((x) => x.id === data.params.version)
@@ -138,8 +148,13 @@ export default {
let changelog = ''
if (version.changelog_url) {
res = await axios.get(version.changelog_url)
changelog = xss(marked(res.data))
changelog = (await axios.get(version.changelog_url)).data
}
let primaryFile = version.files.find((file) => file.primary)
if (!primaryFile) {
primaryFile = version.files[0]
}
return {
@@ -148,16 +163,55 @@ export default {
members,
version,
changelog,
primaryFile,
}
},
data() {
return {
showPopup: false,
filesToUpload: [],
}
},
methods: {
addFiles(e) {
async downloadFile(hash, url) {
await axios.get(
`https://api.modrinth.com/api/v1/version_file/${hash}/download`
)
const elem = document.createElement('a')
elem.download = hash
elem.href = url
elem.click()
},
async deleteFile(hash) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
await axios.delete(
`https://api.modrinth.com/api/v1/version_file/${hash}`,
config
)
},
async makePrimary(hash) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
await axios.patch(
`https://api.modrinth.com/api/v1/version/${this.version.id}`,
{
primary_file: {
sha1: hash,
},
},
config
)
},
async addFiles(e) {
this.filesToUpload = e.target.files
for (let i = 0; i < e.target.files.length; i++) {
@@ -165,8 +219,7 @@ export default {
'-' + i
)
}
},
async uploadFiles() {
this.$nuxt.$loading.start()
const formData = new FormData()
@@ -254,70 +307,38 @@ export default {
<style lang="scss" scoped>
.version {
background: var(--color-bg);
border-radius: 0 0 0.5rem 0.5rem;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
padding: 1em;
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
padding: 1rem;
.header {
h3 {
display: inline-block;
}
.user-actions {
float: right;
button {
cursor: pointer;
margin-right: 10px;
padding: 5px;
border: none;
border-radius: var(--size-rounded-sm);
}
.trash {
color: #9b2c2c;
background-color: var(--color-bg);
}
.upload {
color: var(--color-text);
background-color: var(--color-grey-1);
* {
margin: auto 0;
}
}
}
}
hr {
margin: 20px 0;
color: var(--color-grey-1);
}
.metadata {
.version-header {
display: flex;
align-items: center;
justify-content: space-between;
.author {
h4,
span {
margin: auto 0.5rem auto 0;
}
.download-button {
margin-left: auto;
padding: 0.5rem;
color: var(--color-button-text);
background-color: var(--color-button-bg);
justify-self: flex-end;
display: flex;
align-items: center;
img {
height: 50px;
width: 50px;
margin-right: 10px;
}
}
}
border-radius: var(--size-rounded-sm);
.game-versions {
max-width: 200px;
p {
margin: 0 0 0 10px;
padding: 4px;
font-size: 15px;
color: var(--color-text);
background-color: var(--color-grey-1);
display: inline-block;
&:hover,
&:focus {
background-color: var(--color-button-bg-hover);
}
svg {
margin-right: 0.25rem;
}
}
}
@@ -326,71 +347,77 @@ export default {
div {
display: flex;
margin-right: 10px;
border: 1px solid var(--color-grey-1);
border-radius: var(--size-rounded-sm);
margin-right: 0.5rem;
background: var(--color-bg);
border-radius: var(--size-rounded-control);
p {
margin-left: 10px;
margin-right: 10px;
.text-wrapper {
display: flex;
flex-direction: column;
padding: 0.5rem;
.actions {
width: 100%;
display: flex;
justify-content: space-between;
max-height: 3rem;
font-size: var(--font-size-sm);
button {
display: flex;
align-items: center;
svg {
margin-left: 0.25rem;
}
}
}
}
a {
display: table-cell;
display: flex;
align-items: center;
margin-left: auto;
width: 40px;
height: 60px;
background-color: var(--color-grey-1);
color: var(--color-grey-3);
width: 2.5rem;
height: auto;
background-color: var(--color-button-bg);
color: var(--color-button-text);
border-radius: 0 var(--size-rounded-control) var(--size-rounded-control)
0;
svg {
margin-top: 15px;
vertical-align: center;
height: 30px;
width: 40px;
}
&:hover,
&:focus {
background-color: var(--color-grey-3);
color: var(--color-grey-4);
background-color: var(--color-button-bg-hover);
color: var(--color-button-text-hover);
}
}
}
}
}
.popup-title {
margin-bottom: 40px;
}
.popup-buttons {
margin-top: 40px;
.stats {
display: flex;
justify-content: left;
align-items: center;
flex-wrap: wrap;
margin: 0.5rem 0;
.stat {
margin-right: 0.75rem;
@extend %stat;
.default-button {
border-radius: var(--size-rounded-sm);
cursor: pointer;
border: none;
padding: 10px;
background-color: var(--color-grey-1);
color: var(--color-grey-5);
&:hover,
&:focus {
color: var(--color-grey-4);
svg {
padding: 0.25rem;
border-radius: 50%;
background-color: var(--color-button-bg);
}
}
}
.trash-button {
cursor: pointer;
margin-right: 10px;
padding: 5px;
border: none;
border-radius: var(--size-rounded-sm);
color: #9b2c2c;
background-color: var(--color-bg);
}
.file-input {
margin-top: 2rem;
}
</style>

View File

@@ -5,20 +5,29 @@
<tr>
<th></th>
<th>Name</th>
<th>Number</th>
<th>Loaders</th>
<th>Game Versions</th>
<th>Version</th>
<th>Mod Loader</th>
<th>Minecraft Version</th>
<th>Status</th>
<th>Downloads</th>
<th>Published</th>
<th>Date Published</th>
</tr>
</thead>
<tbody>
<tr v-for="version in versions" :key="version.id">
<td>
<nuxt-link :to="'/mod/' + mod.id + '/version/' + version.id">
<a
:href="findPrimary(version).url"
class="download"
@click.prevent="
downloadFile(
findPrimary(version).hashes.sha1,
findPrimary(version).url
)
"
>
<DownloadIcon />
</nuxt-link>
</a>
</td>
<td>
<nuxt-link :to="'/mod/' + mod.id + '/version/' + version.id">
@@ -27,14 +36,8 @@
</td>
<td>{{ version.version_number }}</td>
<td>
<FabricIcon
v-if="version.loaders.includes('fabric')"
stroke="#AC6C3A"
/>
<ForgeIcon
v-if="version.loaders.includes('forge')"
stroke="#8B81E6"
/>
<FabricIcon v-if="version.loaders.includes('fabric')" />
<ForgeIcon v-if="version.loaders.includes('forge')" />
</td>
<td>{{ version.game_versions.join(', ') }}</td>
<td>
@@ -62,10 +65,6 @@
class="create-version-popup-body"
>
<h3>New Version</h3>
<div v-if="currentError" class="error">
<h4>Error</h4>
<p>{{ currentError }}</p>
</div>
<label
for="version-title"
class="required"
@@ -110,7 +109,7 @@
<label
title="The version number of this version. Preferably following semantic versioning"
>
Loaders
Mod Loaders
</label>
<multiselect
v-model="createdVersion.loaders"
@@ -128,7 +127,7 @@
placeholder="Choose loaders..."
/>
<label title="The versions of minecraft that this mod version supports">
Game Versions
Minecraft Versions
</label>
<multiselect
v-model="createdVersion.game_versions"
@@ -155,7 +154,7 @@
/>
<FileInput
input-id="version-files"
input-accept="application/java-archive,application/zip"
accept="application/java-archive,application/zip"
default-text="Upload Files"
:input-multiple="true"
@change="updateVersionFiles"
@@ -237,11 +236,15 @@ export default {
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
res = await axios.get(`https://api.modrinth.com/api/v1/tag/loader`)
@@ -320,6 +323,29 @@ export default {
this.$nuxt.$loading.finish()
},
findPrimary(version) {
let file = version.files.find((x) => x.primary)
if (!file) {
file = version.files[0]
}
if (!file) {
file = { url: `/mod/${this.mod.id}/version/${version.id}` }
}
return file
},
async downloadFile(hash, url) {
await axios.get(
`https://api.modrinth.com/api/v1/version_file/${hash}/download`
)
const elem = document.createElement('a')
elem.download = hash
elem.href = url
elem.click()
},
},
head() {
return {
@@ -368,10 +394,10 @@ export default {
<style lang="scss" scoped>
table {
background: var(--color-bg);
border-collapse: collapse;
border-radius: 0 0 0.5rem 0.5rem;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
table-layout: fixed;
width: 100%;
@@ -383,7 +409,7 @@ table {
tr:first-child {
th,
td {
border-bottom: 1px solid var(--color-grey-2);
border-bottom: 1px solid var(--color-divider);
}
}
@@ -391,14 +417,14 @@ table {
td {
&:first-child {
text-align: center;
width: 5%;
width: 7%;
svg {
color: var(--color-grey-3);
color: var(--color-text);
&:hover,
&:focus {
color: var(--color-grey-5);
color: var(--color-text-hover);
}
}
}
@@ -406,23 +432,23 @@ table {
&:nth-child(2),
&:nth-child(5) {
padding-left: 0;
width: 15%;
width: 12%;
}
}
th {
color: #718096;
color: var(--color-heading);
font-size: 0.8rem;
letter-spacing: 0.02rem;
margin-bottom: 0.5rem;
margin-top: 1.5rem;
padding: 1rem 1rem;
padding: 0.75rem 1rem;
text-transform: uppercase;
}
td {
overflow: hidden;
padding: 0.25rem 1rem;
padding: 0.75rem 1rem;
img {
height: 3rem;
@@ -448,7 +474,7 @@ input {
outline: none;
border: none;
margin: 10px 0 30px;
background-color: var(--color-grey-1);
background-color: var(--color-button-bg);
color: var(--color-text);
font-family: monospace;
}
@@ -482,21 +508,15 @@ input {
cursor: pointer;
border: none;
padding: 10px;
background-color: var(--color-grey-1);
color: var(--color-grey-5);
background-color: var(--color-button-bg);
color: var(--color-button-text);
&:hover,
&:focus {
color: var(--color-grey-4);
background-color: var(--color-button-bg-hover);
}
}
.error {
margin: 20px 0;
border-left: #e04e3e 7px solid;
padding: 5px 20px 20px 20px;
}
@media screen and (max-width: 400px) {
th,
td {

File diff suppressed because it is too large Load Diff