Fix creation + make it more accessible. Also added User Pages. User Context Menu moved to settings cog

This commit is contained in:
Jai A
2020-10-17 11:28:05 -07:00
parent e95643f198
commit 608ab8f4ad
12 changed files with 209 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"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>

Before

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>

After

Width:  |  Height:  |  Size: 921 B

View File

@@ -34,6 +34,11 @@
letter-spacing: 0.02rem;
padding: 0.25rem 0.5rem;
&.gray {
background-color: #c8c1c1;
color: #646161;
}
&.red {
background-color: #fed7d7;
color: #9b2c2c;

View File

@@ -1,3 +1,7 @@
.rounded-md {
border-radius: 0.5rem;
}
.hidden {
display: none;
}

View File

@@ -1,11 +1,19 @@
<template>
<div class="result rows">
<img class="result-icon" :src="iconUrl" />
<img
class="result-icon"
:src="
iconUrl
? iconUrl
: 'https://cdn.modrinth.com/file/modrinth/placeholder.png'
"
:alt="name"
/>
<div class="rows result-name-author">
<h2 class="mod-name">
<a :href="pageUrl">{{ name }}</a>
</h2>
<p class="author" v-if="author">
<p v-if="author" class="author">
by <a :href="authorUrl">{{ author }}</a>
</p>
</div>
@@ -47,7 +55,7 @@
</svg>
<p>{{ $dayjs(createdAt).fromNow() }}</p>
</div>
<div class="result-image columns" v-if="updatedAt">
<div v-if="updatedAt" class="result-image columns">
<svg
viewBox="0 0 24 24"
fill="none"

View File

@@ -1,7 +1,9 @@
<template>
<div class="layout">
<aside>
<label class="hidden" for="toggle-nav-menu">Toggle Nav Menu</label>
<input
id="toggle-nav-menu"
class="hamburger-button"
alt="Open navigation menu"
type="checkbox"
@@ -10,7 +12,7 @@
<!-- TODO: Probably shouldn't be a Unicode symbol -->
<div class="hamburger-icon">☰</div>
<nuxt-link to="/" class="logo-wrapper">
<img class="logo" src="~/assets/images/logo.svg" />
<img class="logo" src="~/assets/images/logo.svg" alt="modrinth-logo" />
<span class="name">modrinth</span>
</nuxt-link>
<nav>
@@ -134,41 +136,31 @@
this.$route.path
"
class="log-in-button"
>Log In</a
>
Log In
</a>
<div v-if="this.$auth.loggedIn" class="avatar">
<img
:src="this.$auth.user.avatar_url"
alt="avatar"
@click="showPopup = !showPopup"
/>
<img :src="this.$auth.user.avatar_url" alt="avatar" />
<span> {{ this.$auth.user.username }} </span>
</div>
<div v-if="this.$auth.loggedIn" class="notifications">
<div v-if="showPopup" class="user-actions-popup">
<div class="popup-inner">
<p>
Modrinth ID: <strong>{{ this.$auth.user.id }}</strong>
</p>
<hr />
<p>My profile</p>
<p>My teams</p>
<p class="hover">
<nuxt-link :to="'/user/' + this.$auth.user.id">
My profile
</nuxt-link>
</p>
<p class="hover">My teams</p>
<hr />
<p>Settings</p>
<p @click="logout">Logout</p>
<p class="hover" @click="logout">Logout</p>
</div>
</div>
<span> {{ this.$auth.user.username }} </span>
</div>
<div v-if="this.$auth.loggedIn" class="notifications">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
<SettingsIcon @click="showPopup = !showPopup" />
</div>
<div class="theme">
<svg
@@ -214,29 +206,18 @@
</nav>
</aside>
<main>
<!-- <header>
<div class="search-wrapper">
<input type="search" name="search" id="search" placeholder="Search...">
<!/-- Icon follows and not precedes the input so we can target it with CSS' ~ selector --/>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
</div>
</header> -->
<nuxt />
</main>
</div>
</template>
<script>
import SettingsIcon from '~/assets/images/utils/settings.svg?inline'
export default {
components: {
SettingsIcon,
},
async fetch() {
if (this.$route.query.code)
await this.$auth.setUserToken(this.$route.query.code)
@@ -420,7 +401,6 @@ export default {
.avatar {
img {
cursor: pointer;
border-radius: 50%;
height: 2rem;
margin-right: 0.5rem;
@@ -441,11 +421,17 @@ export default {
margin-left: 2.5rem;
}
.notifications {
svg {
cursor: pointer;
}
}
.user-actions-popup {
position: relative;
.popup-inner {
width: 140px;
width: 120px;
border: 2px var(--color-grey-2) solid;
background-color: var(--color-bg);
color: var(--color-grey-5);
@@ -462,12 +448,15 @@ export default {
height: 1px;
}
p {
cursor: pointer;
padding: 8px;
margin: 0;
}
.hover {
cursor: pointer;
&:hover,
&:focus {
color: var(--color-text-inverted);
background-color: var(--color-brand);
}
}
@@ -476,7 +465,7 @@ export default {
content: '';
position: absolute;
top: 100%;
right: 80%;
left: 45%;
border-width: 7px;
border-style: solid;
border-color: var(--color-grey-2) transparent transparent

View File

@@ -9,6 +9,9 @@ export default {
** See https://nuxtjs.org/api/configuration-head
*/
head: {
htmlAttrs: {
lang: 'en',
},
title: 'Modrinth',
meta: [
{ charset: 'utf-8' },
@@ -131,7 +134,7 @@ export default {
ackee: {
server: 'https://analytics.modrinth.com',
domainId: '1840cc3a-64b1-431e-97a4-c122bb64d4c0',
ignoreLocalhost: false,
ignoreLocalhost: true,
detailed: true,
},
robots: {

View File

@@ -26,7 +26,26 @@
</td>
<td>{{ mod.title }}</td>
<td>Owner</td>
<td><span class="badge green">Active</span></td>
<td>
<span v-if="mod.status === 'approved'" class="badge green">
Approved
</span>
<span v-if="mod.status === 'rejected'" class="badge red">
Rejected
</span>
<span v-if="mod.status === 'draft'" class="badge yellow">
Draft
</span>
<span v-if="mod.status === 'processing'" class="badge yellow">
Processing
</span>
<span v-if="mod.status === 'unlisted'" class="badge gray">
Unlisted
</span>
<span v-if="mod.status === 'unknown'" class="badge gray">
Unknown
</span>
</td>
<td>{{ mod.downloads }}</td>
<td>{{ $dayjs(mod.published).format('YYYY-MM-DD') }}</td>
</tr>

View File

@@ -13,8 +13,9 @@
:src="
previewImage
? previewImage
: 'https://i0.kym-cdn.com/entries/icons/facebook/000/013/564/aP2dv.gif'
: 'https://cdn.modrinth.com/file/modrinth/placeholder.png'
"
alt="preview-image"
/>
<input
id="icon-file"
@@ -89,7 +90,7 @@
You can type the of the long form of your description here. This editor
supports markdown. You can find the syntax
<a
href="https://github.com/dimerapp/markdown/blob/develop/syntax.md"
href="https://guides.github.com/features/mastering-markdown/"
target="_blank"
rel="noopener noreferrer"
>here</a
@@ -99,14 +100,22 @@
<div v-html="compiledBody"></div>
</section>
<section>
<div v-if="currentVersionIndex > -1" class="create-version-popup"></div>
<button
v-if="currentVersionIndex > -1"
class="create-version-popup"
@click="currentVersionIndex = -1"
/>
<div v-if="currentVersionIndex > -1" class="create-version-popup-body">
<div class="versions-header">
<h3>New Version</h3>
<div class="popup-icons">
<TrashIcon title="Discard Version" @click="deleteVersion" />
<SaveIcon title="Save Version" @click="currentVersionIndex = -1" />
<button title="Discard Version" @click="deleteVersion">
<TrashIcon />
</button>
<button title="Exit Version" @click="currentVersionIndex = -1">
<ExitIcon />
</button>
</div>
</div>
<label
@@ -151,7 +160,6 @@
:allow-empty="false"
/>
<label
class="required"
title="The version number of this version. Preferably following semantic versioning"
>
Loaders
@@ -171,10 +179,7 @@
:hide-selected="true"
placeholder="Choose loaders..."
/>
<label
class="required"
title="The versions of minecraft that this mod version supports"
>
<label title="The versions of minecraft that this mod version supports">
Game Versions
</label>
<multiselect
@@ -199,7 +204,6 @@
id="version-body"
v-model="versions[currentVersionIndex].version_body"
class="changelog-editor"
placeholder="This editor supports markdown."
/>
<label class="required" title="The files associated with the version">
Version Files
@@ -211,24 +215,35 @@
multiple
@change="updateVersionFiles"
/>
<label for="version-files">Upload files</label>
<label for="version-files">{{
getFilesSelectedText(
versions[currentVersionIndex].file_parts.length,
'Upload Files'
)
}}</label>
</div>
<div class="versions-header">
<h3>Versions</h3>
<PlusIcon @click="createVersion" />
<button title="New Version" class="new-version" @click="createVersion">
<PlusIcon />
</button>
</div>
<div v-for="(value, index) in versions" :key="index" class="version">
<p>{{ value.version_number }}</p>
<p class="column-grow-4">{{ value.version_title }}</p>
<p>Forge</p>
<p>{{ value.loaders.join(', ') }}</p>
<p v-if="value.release_channel === 'beta'" class="badge yellow">Beta</p>
<p v-if="value.release_channel === 'release'" class="badge green">
Release
</p>
<p v-if="value.release_channel === 'alpha'" class="badge red">Alpha</p>
<div>
<TrashIcon @click="versions.splice(index, 1)" />
<EditIcon @click="currentVersionIndex = index" />
<button title="Delete Version" @click="versions.splice(index, 1)">
<TrashIcon />
</button>
<button title="Edit Version" @click="currentVersionIndex = index">
<EditIcon />
</button>
</div>
</div>
</section>
@@ -253,7 +268,12 @@
</label>
</div>
</section>
<button :disabled="!this.$nuxt.$loading" @click="createMod">
<button
title="Create Mod"
class="create-mod"
:disabled="!this.$nuxt.$loading"
@click="createMod"
>
Create mod
</button>
</div>
@@ -266,10 +286,10 @@ import Multiselect from 'vue-multiselect'
import DOMPurify from 'dompurify'
import marked from 'marked'
import ExitIcon from '~/assets/images/utils/exit.svg?inline'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import EditIcon from '~/assets/images/utils/edit.svg?inline'
import PlusIcon from '~/assets/images/utils/plus.svg?inline'
import SaveIcon from '~/assets/images/utils/save.svg?inline'
export default {
components: {
@@ -277,7 +297,7 @@ export default {
TrashIcon,
EditIcon,
PlusIcon,
SaveIcon,
ExitIcon,
},
async asyncData() {
let res = await axios.get(`https://api.modrinth.com/api/v1/tag/category`)
@@ -347,16 +367,17 @@ export default {
formData.append('icon', new Blob([this.icon]), this.icon.name)
for (const version of this.versions) {
for (let i = 0; i < version.file_parts; i++) {
for (let i = 0; i < version.raw_files.length; i++) {
formData.append(
version.file_parts[i],
new Blob([version.raw_files[i]])
new Blob([version.raw_files[i]]),
version.raw_files[i].name
)
}
}
try {
const result = await axios({
await axios({
url: 'https://api.modrinth.com/api/v1/mod',
method: 'POST',
data: formData,
@@ -367,14 +388,12 @@ export default {
})
await this.$router.replace('/dashboard/projects')
} catch (err) {
this.currentError = err.response.data.description
window.scrollTo({ top: 0, behavior: 'smooth' })
this.$nuxt.$loading.stop()
}
this.$nuxt.$loading.stop()
this.$nuxt.$loading.finish()
},
showPreviewImage(e) {
const reader = new FileReader()
@@ -389,15 +408,8 @@ export default {
this.versions[this.currentVersionIndex].raw_files = e.target.files
const newFileParts = []
for (const rawFile of e.target.files) {
newFileParts.push(
rawFile.name.concat(
Math.random()
.toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, 5)
)
)
for (let i = 0; i < e.target.files.length; i++) {
newFileParts.push(e.target.files[i].name.concat('-' + i))
}
this.versions[this.currentVersionIndex].file_parts = newFileParts
@@ -424,6 +436,15 @@ export default {
setMarkdownBody() {
this.compiledBody = DOMPurify.sanitize(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 {
@@ -453,7 +474,7 @@ section {
}
input {
width: 100%;
width: calc(100% - 15px);
padding: 0.5rem 5px;
margin-bottom: 20px;
}
@@ -473,6 +494,7 @@ input {
width: 1px;
+ label {
cursor: pointer;
border-radius: 5px;
color: var(--color-grey-5);
background-color: var(--color-grey-1);
@@ -549,21 +571,16 @@ input {
}
}
button {
.create-mod {
float: right;
margin: -10px 25px 20px 0;
cursor: pointer;
padding: 10px;
outline: none;
color: var(--color-grey-5);
background-color: var(--color-grey-1);
color: #fff;
background-color: var(--color-brand);
border: none;
border-radius: 5px;
&:hover {
background-color: var(--color-grey-2);
color: var(--color-text);
}
}
.extras {
@@ -582,9 +599,11 @@ button {
position: fixed;
width: 100%;
height: 100%;
background-color: var(--color-grey-0);
background-color: var(--color-grey-3);
border: none;
opacity: 0.6;
overflow-x: hidden;
cursor: pointer;
}
.create-version-popup-body {
@@ -618,6 +637,10 @@ button {
color: var(--color-text);
font-family: monospace;
}
button {
background-color: var(--color-bg);
}
}
.versions-header {
@@ -651,9 +674,11 @@ button {
background-color: var(--color-grey-1);
}
svg {
button {
background-color: var(--color-grey-1);
color: var(--color-grey-5);
cursor: pointer;
border: none;
&:hover,
&:focus {
@@ -666,6 +691,6 @@ svg {
}
.no-scroll {
overflow: hidden;
overflow: hidden !important;
}
</style>

View File

@@ -4,6 +4,7 @@
<h2>Mods</h2>
<section class="search-bar">
<div class="iconified-input column-grow-2">
<label class="hidden" for="search">Search Mods</label>
<input
id="search"
v-model="query"
@@ -95,7 +96,6 @@
</section>
</div>
<section id="filters" class="filters">
<!--#region filters -->
<div class="filters-wrapper">
<section class="filter-group">
<button class="filter-button-done" @click="toggleFiltersMenu">
@@ -247,7 +247,6 @@
@input="onSearchChange(1)"
></multiselect>
</div>
<!--#endregion -->
</section>
</div>
</template>
@@ -684,6 +683,11 @@ select {
overflow-x: hidden;
}
.multiselect__tags {
border: 2px solid var(--color-grey-3);
border-radius: var(--size-rounded-sm);
}
.multiselect__tags,
.multiselect__spinner {
background: var(--color-bg);

View File

@@ -1,7 +1,18 @@
<template>
<div>
<img :src="user.avatar_url" />
<div>
<div class="content">
<div class="user-profile">
<img :src="user.avatar_url" :alt="user.username" />
<div class="info">
<h1>{{ user.username }}</h1>
<p>{{ user.bio }}</p>
<p>Joined {{ $dayjs(user.created).fromNow() }}</p>
<p></p>
<p v-if="user.role === 'admin'" class="badge red">Admin</p>
<p v-if="user.role === 'moderator'" class="badge yellow">Moderator</p>
<p v-if="user.role === 'developer'" class="badge green">Developer</p>
</div>
</div>
<div class="user-mods">
<SearchResult
v-for="(result, index) in mods"
:id="result.mod_id"
@@ -56,4 +67,40 @@ export default {
}
</script>
<style lang="scss"></style>
<style lang="scss" scoped>
.user-profile {
@media screen and (min-width: 900px) {
display: inline-flex;
text-align: left;
}
text-align: center;
margin-bottom: 20px;
margin-left: 15px;
img {
width: 250px;
height: 250px;
}
.info {
margin-left: 15px;
p {
margin-right: auto;
}
.badge {
display: inline-block;
}
}
}
.user-mods {
border-top: 1px solid var(--color-grey-1);
padding-top: 10px;
margin: 10px;
* {
margin-left: 0;
}
}
</style>