Move everything to components, (WIP) Version creation

This commit is contained in:
Jai A
2020-10-22 22:46:10 -07:00
parent 969bec248a
commit 663418e943
13 changed files with 550 additions and 104 deletions

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@@ -55,6 +55,11 @@
}
}
.required:after {
content: ' *';
color: red;
}
.markdown-body {
p {
padding: 0;

View File

@@ -104,6 +104,8 @@ input {
}
}
@import "~assets/styles/layout.scss";
@import "~assets/styles/utils.scss";
@import "~assets/styles/components.scss";

86
components/FileInput.vue Normal file
View File

@@ -0,0 +1,86 @@
<template>
<div>
<slot></slot>
<input
:id="id"
class="file-input"
type="file"
:accept="accept"
:multiple="multiple"
@change="onChange"
/>
<label :for="id">{{ text }}</label>
</div>
</template>
<script>
export default {
name: 'FileInput',
props: {
defaultText: {
type: String,
default: '',
},
inputId: {
type: String,
default: '',
},
inputAccept: {
type: String,
default: '',
},
inputMultiple: {
type: Boolean,
default: true,
},
},
data() {
return {
text: this.defaultText,
}
},
methods: {
onChange(files) {
const length = files.target.length
if (length === 0) {
this.text = this.defaultText
} else if (length === 1) {
this.text = '1 file selected'
} else if (length > 1) {
this.text = length + ' files selected'
}
this.$emit('change', files)
},
},
}
</script>
<style lang="scss" scoped>
[type='file'] {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
overflow: hidden;
padding: 0;
position: absolute !important;
white-space: nowrap;
width: 1px;
+ label {
cursor: pointer;
border-radius: 5px;
color: var(--color-grey-5);
background-color: var(--color-grey-1);
padding: 10px 20px;
}
&:focus + label,
+ label:hover,
&:focus + label {
background-color: var(--color-grey-2);
color: var(--color-text);
}
}
</style>

View File

@@ -6,7 +6,7 @@
:src="
mod.icon_url
? mod.icon_url
: 'https://cdn.modrinth.com/placeholder.png'
: 'https://cdn.modrinth.com/placeholder.svg'
"
alt="mod-icon"
/>
@@ -268,6 +268,7 @@ export default {
.team-member {
margin-left: 5px;
margin-bottom: 10px;
border: 1px solid var(--color-grey-1);
border-radius: var(--size-rounded-sm);
@@ -297,6 +298,7 @@ export default {
.featured-version {
margin-left: 5px;
margin-bottom: 10px;
border: 1px solid var(--color-grey-1);
border-radius: var(--size-rounded-sm);

View File

@@ -2,7 +2,7 @@
<div class="result rows">
<img
class="result-icon"
:src="iconUrl ? iconUrl : 'https://cdn.modrinth.com/placeholder.png'"
:src="iconUrl ? iconUrl : 'https://cdn.modrinth.com/placeholder.svg'"
:alt="name"
/>
<div class="rows result-name-author">

50
components/Popup.vue Normal file
View File

@@ -0,0 +1,50 @@
<template>
<div v-if="showPopup">
<div class="popup-overlay" />
<div class="popup-body">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Popup',
props: {
showPopup: {
type: Boolean,
default: false,
},
},
}
</script>
<style lang="scss" scoped>
.popup-overlay {
top: 0;
left: 0;
z-index: 1;
position: fixed;
width: 100%;
height: 100%;
background-color: var(--color-grey-3);
border: none;
opacity: 0.6;
overflow-x: hidden;
cursor: pointer;
}
.popup-body {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
padding: 5px 60px 60px 20px;
border-radius: 10px;
max-height: 80%;
overflow-y: auto;
background-color: var(--color-bg);
}
</style>

View File

@@ -31,6 +31,7 @@ export default {
content: 'Modrinth',
},
{ hid: 'theme-color', name: 'theme-color', content: '#4d9227' },
{ hid: 'color-scheme', name: 'color-scheme', content: 'light dark' },
{ hid: 'og:site_name', name: 'og:site_name', content: 'Modrinth' },
{
@@ -45,6 +46,8 @@ export default {
name: 'og:image',
content: 'https://cdn.modrinth.com/modrinth.png',
},
{ hid: 'twitter:card', name: 'twitter:card', content: 'summary' },
{ hid: 'twitter:site', name: 'twitter:site', content: '@modrinth' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },

View File

@@ -73,6 +73,11 @@ export default {
name: 'og:site_name',
content: this.mod.title,
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/mod/${this.mod.id}`,
},
{
hid: 'og:description',
name: 'og:description',

View File

@@ -0,0 +1,50 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members"></ModPage>
</template>
<script>
import ModPage from '@/components/ModPage'
import axios from 'axios'
export default {
components: { ModPage },
auth: false,
async asyncData(data) {
let res = await axios.get(
`https://api.modrinth.com/api/v1/mod/${data.params.id}`
)
const mod = res.data
res = await axios.get(
`https://api.modrinth.com/api/v1/team/${mod.team}/members`
)
const members = res.data
for (let i = 0; i < members.length; i++) {
res = await axios.get(
`https://api.modrinth.com/api/v1/user/${members[i].user_id}`
)
members[i].avatar_url = res.data.avatar_url
}
res = await axios.get(mod.body_url)
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
}
return {
mod,
versions,
members,
}
},
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,7 +1,17 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<div class="version">
<h3>{{ version.name }}</h3>
<div class="header">
<h3>{{ version.name }}</h3>
<div class="user-actions">
<button class="trash red">
<TrashIcon />
</button>
<button class="upload">
<UploadIcon />
</button>
</div>
</div>
<div class="markdown-body" v-html="changelog"></div>
<hr />
<div class="columns metadata">
@@ -46,6 +56,8 @@ import xss from 'xss'
import marked from 'marked'
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'
@@ -56,6 +68,8 @@ export default {
ForgeIcon,
FabricIcon,
DownloadIcon,
UploadIcon,
TrashIcon,
},
auth: false,
async asyncData(data) {
@@ -121,12 +135,17 @@ export default {
name: 'og:site_name',
content: this.mod.title + ' - Modrinth',
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/mod/${this.mod.id}`,
},
{
hid: 'og:description',
name: 'og:description',
content: this.mod.description,
},
{ hid: 'og:type', name: 'og:type', content: 'article' },
{ hid: 'og:type', name: 'og:type', content: 'website' },
{
hid: 'og:image',
name: 'og:image',
@@ -147,6 +166,36 @@ export default {
box-shadow: 0 2px 3px 1px var(--color-grey-2);
padding: 1em;
.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);

View File

@@ -53,23 +53,159 @@
</tr>
</tbody>
</table>
<Popup
v-if="showPopup"
:show-popup="showPopup"
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"
title="The title of your version"
>
Version Title
</label>
<input
id="version-title"
v-model="createdVersion.version_title"
required
type="text"
placeholder="Combat Update"
/>
<label
for="version-number"
class="required"
title="The version number of this version. Preferably following semantic versioning"
>
Version Number
</label>
<input
id="version-number"
v-model="createdVersion.version_number"
required
type="text"
placeholder="v1.9"
/>
<label class="required" title="The release channel of this version.">
Release Channel
</label>
<Multiselect
v-model="createdVersion.release_channel"
class="categories-input"
placeholder="Select one"
:options="['release', 'beta', 'alpha']"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
<label
title="The version number of this version. Preferably following semantic versioning"
>
Loaders
</label>
<multiselect
v-model="createdVersion.loaders"
class="categories-input"
:options="selectableLoaders"
:loading="selectableLoaders.length === 0"
:multiple="true"
:searchable="false"
:show-no-results="false"
:close-on-select="true"
:clear-on-select="false"
:show-labels="false"
:limit="6"
:hide-selected="true"
placeholder="Choose loaders..."
/>
<label title="The versions of minecraft that this mod version supports">
Game Versions
</label>
<multiselect
v-model="createdVersion.game_versions"
class="categories-input"
:options="selectableVersions"
:loading="selectableVersions.length === 0"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-on-select="false"
:show-labels="false"
:limit="6"
:hide-selected="true"
placeholder="Choose versions..."
/>
<label for="version-body" title="A list of changes for this version">
Changelog
</label>
<textarea
id="version-body"
v-model="createdVersion.version_body"
class="changelog-editor"
/>
<FileInput
input-id="version-files"
input-accept="application/java-archive,application/zip"
default-text="Upload Files"
:input-multiple="true"
@change="updateVersionFiles"
>
<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
createdVersion = {}
"
>
<TrashIcon />
</button>
<button class="default-button" @click="createVersion">
Create Version
</button>
</div>
</Popup>
<button class="default-button" @click="showPopup = !showPopup">
New Version
</button>
</ModPage>
</template>
<script>
import axios from 'axios'
import Multiselect from 'vue-multiselect'
import ModPage from '@/components/ModPage'
import Popup from '@/components/Popup'
import FileInput from '@/components/FileInput'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import ForgeIcon from '~/assets/images/categories/forge.svg?inline'
import FabricIcon from '~/assets/images/categories/fabric.svg?inline'
export default {
components: {
Multiselect,
FileInput,
Popup,
ModPage,
ForgeIcon,
FabricIcon,
DownloadIcon,
TrashIcon,
},
auth: false,
async asyncData(data) {
@@ -98,12 +234,78 @@ export default {
versions.push(res.data)
}
res = await axios.get(`https://api.modrinth.com/api/v1/tag/loader`)
const selectableLoaders = res.data
res = await axios.get(`https://api.modrinth.com/api/v1/tag/game_version`)
const selectableVersions = res.data
return {
mod,
versions,
members,
selectableLoaders,
selectableVersions,
}
},
data() {
return {
showPopup: false,
currentError: null,
createdVersion: {},
}
},
methods: {
updateVersionFiles(e) {
this.createdVersion.raw_files = e.target.files
const newFileParts = []
for (let i = 0; i < e.target.files.length; i++) {
newFileParts.push(e.target.files[i].name.concat('-' + i))
}
this.createdVersion.file_parts = newFileParts
},
async createVersion() {
this.$nuxt.$loading.start()
this.currentError = null
const formData = new FormData()
this.createdVersion.mod_id = this.$route.params.id
formData.append('data', JSON.stringify(this.createdVersion))
if (this.createdVersion.raw_files) {
for (let i = 0; i < this.createdVersion.raw_files.length; i++) {
formData.append(
this.createdVersion.file_parts[i],
new Blob([this.createdVersion.raw_files[i]]),
this.createdVersion.raw_files[i].name
)
}
}
try {
await axios({
url: 'https://api.modrinth.com/api/v1/version/version',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
Authorization: this.$auth.getToken('local'),
},
})
await this.$router.go(null)
} catch (err) {
this.currentError = err.response.data.description
window.scrollTo({ top: 0, behavior: 'smooth' })
}
this.$nuxt.$loading.finish()
},
},
head() {
return {
title: this.mod.title + ' - Modrinth',
@@ -126,6 +328,11 @@ export default {
name: 'og:site_name',
content: this.mod.title,
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/mod/${this.mod.id}`,
},
{
hid: 'og:description',
name: 'og:description',
@@ -209,4 +416,71 @@ table {
}
}
}
.multiselect {
margin-bottom: 20px;
}
input {
width: calc(100% - 15px);
padding: 0.5rem 5px;
margin-bottom: 20px;
}
.changelog-editor {
padding: 20px;
width: calc(100% - 40px);
height: 200px;
resize: none;
outline: none;
border: none;
margin: 10px 0 30px;
background-color: var(--color-grey-1);
color: var(--color-text);
font-family: monospace;
}
.popup-buttons {
margin-top: 20px;
margin-left: auto;
display: flex;
justify-content: right;
align-items: center;
.default-button {
float: none;
margin-top: 0;
}
.trash-button {
cursor: pointer;
margin-right: 10px;
padding: 5px;
border: none;
border-radius: var(--size-rounded-sm);
color: #9b2c2c;
background-color: var(--color-bg);
}
}
.default-button {
float: right;
margin-top: 20px;
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);
}
}
.error {
margin: 20px 0;
border-left: #e04e3e 7px solid;
padding: 5px 20px 20px 20px;
}
</style>

View File

@@ -13,18 +13,17 @@
:src="
previewImage
? previewImage
: 'https://cdn.modrinth.com/placeholder.png'
: 'https://cdn.modrinth.com/placeholder.svg'
"
alt="preview-image"
/>
<input
<FileInput
id="icon-file"
class="file-input"
type="file"
accept="image/x-png,image/gif,image/jpeg"
default-text="Upload Icon"
:multiple="false"
@change="showPreviewImage"
/>
<label for="icon-file">Upload Icon</label>
</div>
<div class="mod-data">
<label for="name" class="required" title="The name of your mod">
@@ -100,12 +99,11 @@
<div v-html="compiledBody"></div>
</section>
<section>
<button
<Popup
v-if="currentVersionIndex > -1"
class="create-version-popup"
@click="currentVersionIndex = -1"
/>
<div v-if="currentVersionIndex > -1" class="create-version-popup-body">
:show-popup="currentVersionIndex > -1"
class="create-version-popup-body"
>
<div class="versions-header">
<h3>New Version</h3>
@@ -208,20 +206,14 @@
<label class="required" title="The files associated with the version">
Version Files
</label>
<input
id="version-files"
type="file"
accept="application/java-archive,application/zip"
multiple
<FileInput
input-id="version-files"
input-accept="application/java-archive,application/zip"
:input-multiple="true"
default-text="Upload Files"
@change="updateVersionFiles"
/>
<label for="version-files">{{
getFilesSelectedText(
versions[currentVersionIndex].file_parts.length,
'Upload Files'
)
}}</label>
</div>
</Popup>
<div class="versions-header">
<h3>Versions</h3>
<button title="New Version" class="new-version" @click="createVersion">
@@ -286,6 +278,8 @@ import Multiselect from 'vue-multiselect'
import xss from 'xss'
import marked from 'marked'
import Popup from '@/components/Popup'
import FileInput from '@/components/FileInput'
import SaveIcon from '~/assets/images/utils/save.svg?inline'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import EditIcon from '~/assets/images/utils/edit.svg?inline'
@@ -293,6 +287,8 @@ import PlusIcon from '~/assets/images/utils/plus.svg?inline'
export default {
components: {
FileInput,
Popup,
Multiselect,
TrashIcon,
EditIcon,
@@ -436,22 +432,6 @@ export default {
setMarkdownBody() {
this.compiledBody = xss(marked(this.body))
},
getFilesSelectedText(length, defaultText) {
if (length === 0) {
return defaultText
} else if (length === 1) {
return '1 file selected'
} else if (length > 1) {
return length + ' files selected'
}
},
},
head() {
return {
bodyAttrs: {
class: this.currentVersionIndex > -1 ? 'no-scroll' : '',
},
}
},
}
</script>
@@ -483,32 +463,6 @@ input {
margin-top: 0.5rem;
}
[type='file'] {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
overflow: hidden;
padding: 0;
position: absolute !important;
white-space: nowrap;
width: 1px;
+ label {
cursor: pointer;
border-radius: 5px;
color: var(--color-grey-5);
background-color: var(--color-grey-1);
padding: 10px 20px;
}
&:focus + label,
+ label:hover,
&:focus + label {
background-color: var(--color-grey-2);
color: var(--color-text);
}
}
.initial {
display: flex;
.image-data {
@@ -587,38 +541,7 @@ input {
padding: 10px;
}
.required:after {
content: ' *';
color: red;
}
.create-version-popup {
top: 0;
left: 0;
z-index: 1;
position: fixed;
width: 100%;
height: 100%;
background-color: var(--color-grey-3);
border: none;
opacity: 0.6;
overflow-x: hidden;
cursor: pointer;
}
.create-version-popup-body {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
padding: 5px 60px 60px 20px;
border-radius: 10px;
max-height: 80%;
overflow-y: auto;
background-color: var(--color-bg);
.popup-icons {
margin-top: 10px;
margin-right: -20px;
@@ -627,7 +550,7 @@ input {
.changelog-editor {
padding: 20px;
width: 100%;
width: calc(100% - 40px);
height: 200px;
resize: none;
outline: none;
@@ -689,8 +612,4 @@ button {
.categories-input {
margin-bottom: 15px;
}
.no-scroll {
overflow: hidden !important;
}
</style>