* Teams

* Fix errors on versions with no files
This commit is contained in:
Geometrically
2020-12-28 10:50:59 -07:00
committed by GitHub
parent 0b160a6741
commit 12840f2428
14 changed files with 787 additions and 82 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="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -7,6 +7,10 @@
<ModIcon />
My mods
</nuxt-link>
<nuxt-link :to="'/dashboard/notifications'" class="tab last">
<NotificationsIcon />
Notifications
</nuxt-link>
<nuxt-link
v-if="
$auth.user.role === 'admin' || $auth.user.role === 'moderator'
@@ -37,6 +41,7 @@
import ModIcon from '~/assets/images/sidebar/mod.svg?inline'
import ModerationIcon from '~/assets/images/sidebar/admin.svg?inline'
import SettingsIcon from '~/assets/images/sidebar/settings.svg?inline'
import NotificationsIcon from '~/assets/images/sidebar/notifications.svg?inline'
export default {
name: 'DashboardPage',
@@ -44,6 +49,7 @@ export default {
ModIcon,
ModerationIcon,
SettingsIcon,
NotificationsIcon,
},
}
</script>

View File

@@ -14,7 +14,7 @@
/>
</div>
<div class="info">
<h2 class="title">{{ mod.title }}</h2>
<h1 class="title">{{ mod.title }}</h1>
<p class="description">
{{ mod.description }}
</p>
@@ -60,20 +60,14 @@
Source
</a>
<nuxt-link
v-if="
this.$auth.loggedIn &&
members.find((x) => x.user_id === this.$auth.user.id)
"
v-if="currentMember"
:to="'/mod/' + mod.id + '/edit'"
class="tab"
>
Edit
</nuxt-link>
<nuxt-link
v-if="
this.$auth.loggedIn &&
members.find((x) => x.user_id === this.$auth.user.id)
"
v-if="currentMember"
:to="'/mod/' + mod.id + '/settings'"
class="tab"
>
@@ -280,6 +274,12 @@ export default {
return []
},
},
currentMember: {
type: Object,
default() {
return null
},
},
},
methods: {
formatNumber(x) {

View File

@@ -47,6 +47,12 @@
<span>Profile</span>
</NuxtLink>
</li>
<li>
<NuxtLink to="/dashboard/notifications">
<NotificationIcon />
<span>Notifications</span>
</NuxtLink>
</li>
<li v-tooltip="'Not implemented yet'" class="hidden">
<NuxtLink :to="userTeamsUrl" disabled>
<UsersIcon />
@@ -154,6 +160,7 @@ import ClickOutside from 'vue-click-outside'
import ModrinthLogo from '~/assets/images/text-logo.svg?inline'
import ModrinthLogoWhite from '~/assets/images/text-logo-white.svg?inline'
import NotificationIcon from '~/assets/images/sidebar/notifications.svg?inline'
import ModpackIcon from '~/assets/images/sidebar/modpack.svg?inline'
import ProjectsIcon from '~/assets/images/sidebar/projects.svg?inline'
import AnalyticsIcon from '~/assets/images/sidebar/analytics.svg?inline'
@@ -185,6 +192,7 @@ export default {
UsersIcon,
LogOutIcon,
GitHubIcon,
NotificationIcon,
},
directives: {
ClickOutside,

View File

@@ -124,6 +124,7 @@ export default {
'@nuxtjs/robots',
'@nuxtjs/sitemap',
'nuxt-clipboard2',
'@nuxtjs/style-resources',
],
robots: {
Sitemap: 'https://modrinth.com/sitemap.xml',

167
package-lock.json generated
View File

@@ -2274,6 +2274,16 @@
}
}
},
"@nuxtjs/style-resources": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@nuxtjs/style-resources/-/style-resources-1.0.0.tgz",
"integrity": "sha512-tDRcC/pm8B0Kpxtzb/1/HOBkv3/kPD+2FiCiUBGMB7YriRud9OUPw1pnYCsAH9ftwpMJS4k4XOyUY8FCTk6OxA==",
"requires": {
"consola": "^2.4.0",
"glob-all": "^3.1.0",
"sass-resources-loader": "^2.0.0"
}
},
"@nuxtjs/svg": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@nuxtjs/svg/-/svg-0.1.12.tgz",
@@ -3164,6 +3174,11 @@
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"async-cache": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/async-cache/-/async-cache-1.1.0.tgz",
@@ -4837,8 +4852,7 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decode-uri-component": {
"version": "0.2.0",
@@ -6668,8 +6682,7 @@
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-port-please": {
"version": "1.0.0",
@@ -6742,6 +6755,117 @@
"path-is-absolute": "^1.0.0"
}
},
"glob-all": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.2.1.tgz",
"integrity": "sha512-x877rVkzB3ipid577QOp+eQCR6M5ZyiwrtaYgrX/z3EThaSPFtLDwBXFHc3sH1cG0R0vFYI5SRYeWMMSEyXkUw==",
"requires": {
"glob": "^7.1.2",
"yargs": "^15.3.1"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
@@ -11090,14 +11214,12 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"requires-port": {
"version": "1.0.0",
@@ -11268,6 +11390,29 @@
}
}
},
"sass-resources-loader": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/sass-resources-loader/-/sass-resources-loader-2.1.1.tgz",
"integrity": "sha512-/KrD5mEBTj3ZQ49thKSThhpv1OFhc82JbWA0bmv9yANRuPIlQrydNpZG82jdy4pEWY0QcQTGyd5OmCb3xVeZsw==",
"requires": {
"async": "^3.2.0",
"chalk": "^4.1.0",
"glob": "^7.1.6",
"loader-utils": "^2.0.0"
},
"dependencies": {
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
}
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -11388,8 +11533,7 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"set-value": {
"version": "2.0.1",
@@ -13675,8 +13819,7 @@
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"which-typed-array": {
"version": "1.1.2",

View File

@@ -17,6 +17,7 @@
"@nuxtjs/dayjs": "^1.2.0",
"@nuxtjs/robots": "^2.4.2",
"@nuxtjs/sitemap": "^2.4.0",
"@nuxtjs/style-resources": "^1.0.0",
"highlight.js": "^10.3.2",
"marked": "^1.2.0",
"nuxt": "^2.14.7",

View File

@@ -42,8 +42,8 @@
>
</p>
<button @click="logout">Continue</button>
</section></DashboardPage
>
</section>
</DashboardPage>
</template>
<script>
@@ -54,8 +54,8 @@ export default {
},
methods: {
async logout() {
await this.$auth. setToken('local', false)
await this.$router.go(
await this.$auth.setToken('local', false)
await this.$router.replace(
'https://api.modrinth.com/api/v1/auth/init?url=https://modrinth.com/'
)
},

View File

@@ -0,0 +1,146 @@
<template>
<DashboardPage>
<div class="section-header columns">
<h3 class="column-grow-1">My invites</h3>
</div>
<div v-for="invite in invites" :key="invite.team_id" class="invite columns">
<div class="text">
<p>
Invite to join <strong>{{ invite.username }}'s</strong> team.
</p>
</div>
<div class="actions">
<button @click="declineInvite(invite.team_id)">Decline</button>
<button @click="acceptInvite(invite.team_id)">Accept</button>
</div>
</div>
</DashboardPage>
</template>
<script>
import axios from 'axios'
import DashboardPage from '@/components/DashboardPage'
export default {
components: {
DashboardPage,
},
async asyncData(data) {
const config = {
headers: {
Authorization: data.$auth.getToken('local')
? data.$auth.getToken('local')
: '',
},
}
const teams = (
await axios.get(
`https://api.modrinth.com/api/v1/user/${data.$auth.user.id}/teams`,
config
)
).data.filter((it) => !it.accepted)
const members = (
await Promise.all(
teams.map((it) =>
axios.get(
`https://api.modrinth.com/api/v1/team/${it.team_id}/members`,
config
)
)
)
).map((it) => it.data)
const invites = []
for (let i = 0; i++; i < members.length) {
const owner = members[i].find((it) => it.role === 'Owner')
const ownerData = (
await axios.get(
`https://api.modrinth.com/api/v1/user/${owner.id}`,
config
)
).data
invites.push({
team_id: owner.team_id,
username: ownerData.username,
})
}
return {
invites,
}
},
methods: {
async acceptInvite(teamId) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
this.$nuxt.$loading.start()
try {
await axios.post(
`https://api.modrinth.com/api/v1/team/${teamId}/join`,
config
)
await this.$router.go(null)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
async declineInvite(teamId) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
this.$nuxt.$loading.start()
try {
await axios.delete(
`https://api.modrinth.com/api/v1/team/${teamId}/members/${this.$auth.user.id}`,
config
)
await this.$router.go(null)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
},
}
</script>
<style lang="scss" scoped>
.invite {
@extend %card;
padding: var(--spacing-card-sm) var(--spacing-card-lg);
margin-bottom: var(--spacing-card-sm);
align-items: center;
justify-content: space-between;
p {
margin: 0;
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<ModPage
:mod="mod"
:versions="versions"
:members="members"
:current-member="currentMember"
>
<div v-compiled-markdown="body" class="markdown-body"></div>
</ModPage>
</template>
@@ -51,15 +56,20 @@ export default {
)
).data
users.forEach((it, index) => {
users.reverse().forEach((it, index) => {
members[index].avatar_url = it.avatar_url
members[index].name = it.username
})
const currentMember = data.$auth.loggedIn
? members.find((x) => x.user_id === data.$auth.user.id)
: null
return {
mod,
versions: versions.reverse(),
members,
currentMember,
}
},
data() {

View File

@@ -1,13 +1,215 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members"> </ModPage>
<ModPage
:mod="mod"
:versions="versions"
:members="members.filter((it) => it.accepted)"
:current-member="currentMember"
>
<div class="section-header columns">
<h3 class="column-grow-1">Team members</h3>
<div class="column">
<input
id="username"
v-model="currentUsername"
type="text"
placeholder="Username"
/>
<label for="username" class="hidden">Username</label>
<button class="brand-button column" @click="inviteTeamMember">
Invite
</button>
</div>
</div>
<div
v-for="(member, index) in members"
:key="member.user_id"
class="member"
:class="{ open: openTeamMembers.includes(member.user_id) }"
>
<div class="member-header">
<div class="info">
<img :src="member.avatar_url" :alt="member.name" />
<div class="text">
<h4>{{ member.name }}</h4>
<h3>{{ member.role }}</h3>
</div>
</div>
<div class="side-buttons">
<span v-if="member.accepted" class="badge green">Accepted</span>
<span v-else class="badge yellow">Pending</span>
<button
class="dropdown-icon"
@click="
openTeamMembers.indexOf(member.user_id) === -1
? openTeamMembers.push(member.user_id)
: (openTeamMembers = openTeamMembers.filter(
(it) => it !== member.user_id
))
"
>
<DropdownIcon />
</button>
</div>
</div>
<div class="content">
<div class="main-info">
<label>
Role:
<input
v-model="members[index].role"
type="text"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER
"
/>
</label>
</div>
<h3>Permissions</h3>
<div class="permissions">
<label>
<input
type="checkbox"
:checked="
(member.permissions & UPLOAD_VERSION) === UPLOAD_VERSION
"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & UPLOAD_VERSION) !== UPLOAD_VERSION
"
@change="members[index].permissions ^= UPLOAD_VERSION"
/>
Upload Version
</label>
<label>
<input
type="checkbox"
:checked="
(member.permissions & DELETE_VERSION) === DELETE_VERSION
"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & DELETE_VERSION) !== DELETE_VERSION
"
@change="members[index].permissions ^= DELETE_VERSION"
/>
Delete Version
</label>
<label>
<input
type="checkbox"
:checked="(member.permissions & EDIT_DETAILS) === EDIT_DETAILS"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & EDIT_DETAILS) !== EDIT_DETAILS
"
@change="members[index].permissions ^= EDIT_DETAILS"
/>
Edit Details
</label>
<label>
<input
type="checkbox"
:checked="(member.permissions & EDIT_BODY) === EDIT_BODY"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & EDIT_BODY) !== EDIT_BODY
"
@change="members[index].permissions ^= EDIT_BODY"
/>
Edit Body
</label>
<label>
<input
type="checkbox"
:checked="
(member.permissions & MANAGE_INVITES) === MANAGE_INVITES
"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & MANAGE_INVITES) !== MANAGE_INVITES
"
@change="members[index].permissions ^= MANAGE_INVITES"
/>
Manage Invites
</label>
<label>
<input
type="checkbox"
:checked="(member.permissions & REMOVE_MEMBER) === REMOVE_MEMBER"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & REMOVE_MEMBER) !== REMOVE_MEMBER
"
@change="members[index].permissions ^= REMOVE_MEMBER"
/>
Remove Member
</label>
<label>
<input
type="checkbox"
:checked="(member.permissions & EDIT_MEMBER) === EDIT_MEMBER"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER
"
@change="members[index].permissions ^= EDIT_MEMBER"
/>
Edit Member
</label>
<label>
<input
type="checkbox"
:checked="(member.permissions & DELETE_MOD) === DELETE_MOD"
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
(currentMember.permissions & DELETE_MOD) !== DELETE_MOD
"
@change="members[index].permissions ^= DELETE_MOD"
/>
Delete Mod
</label>
</div>
<div class="actions">
<button
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER
"
@click="removeTeamMember(index)"
>
Remove Member
</button>
<button
:disabled="
member.role === 'Owner' ||
(currentMember.permissions & EDIT_MEMBER) !== EDIT_MEMBER
"
@click="updateTeamMember(index)"
>
Save Changes
</button>
</div>
</div>
</div>
</ModPage>
</template>
<script>
import axios from 'axios'
import ModPage from '@/components/ModPage'
import DropdownIcon from '~/assets/images/utils/dropdown.svg?inline'
export default {
components: { ModPage },
components: { ModPage, DropdownIcon },
async asyncData(data) {
const config = {
headers: {
@@ -24,9 +226,8 @@ export default {
)
).data
const [members, allMembers, versions] = (
const [members, versions] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/team/${mod.team}/members`),
axios.get(
`https://api.modrinth.com/api/v1/team/${mod.team}/members`,
config
@@ -40,7 +241,7 @@ export default {
])
).map((it) => it.data)
const [users, allUsers] = (
const [users] = (
await Promise.all([
axios.get(
`https://api.modrinth.com/api/v1/users?ids=${JSON.stringify(
@@ -48,44 +249,229 @@ export default {
)}`,
config
),
axios.get(
`https://api.modrinth.com/api/v1/users?ids=${JSON.stringify(
allMembers.map((it) => it.user_id)
)}`,
config
),
])
).map((it) => it.data)
users.forEach((it, index) => {
users.reverse().forEach((it, index) => {
members[index].avatar_url = it.avatar_url
members[index].name = it.username
})
allUsers.forEach((it, index) => {
allMembers[index].avatar_url = it.avatar_url
allMembers[index].name = it.username
allMembers[index].accepted = members.find(
(it) => allMembers[index].user_id === it.user_id
)
})
const currentMember = data.$auth.loggedIn
? members.find((x) => x.user_id === data.$auth.user.id)
: null
return {
mod,
versions: versions.reverse(),
members,
allMembers,
allUsers,
currentMember,
}
},
data() {
return {
currentUsername: '',
openTeamMembers: [],
}
},
created() {
this.UPLOAD_VERSION = 1 << 0
this.DELETE_VERSION = 1 << 1
this.EDIT_DETAILS = 1 << 2
this.EDIT_BODY = 1 << 3
this.MANAGE_INVITES = 1 << 4
this.REMOVE_MEMBER = 1 << 5
this.EDIT_MEMBER = 1 << 6
this.DELETE_MOD = 1 << 7
},
methods: {
async inviteTeamMember() {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
this.$nuxt.$loading.start()
try {
const user = (
await axios.get(
`https://api.modrinth.com/api/v1/user/${this.currentUsername}`
)
).data
const data = {
user_id: user.id,
}
await axios.post(
`https://api.modrinth.com/api/v1/team/${this.mod.team}/members`,
data,
config
)
await this.$router.go(null)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
async removeTeamMember(index) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
this.$nuxt.$loading.start()
try {
await axios.delete(
`https://api.modrinth.com/api/v1/team/${this.mod.team}/members/${this.members[index].user_id}`,
config
)
await this.$router.go(null)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
async updateTeamMember(index) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
this.$nuxt.$loading.start()
try {
const data = {
permissions: this.members[index].permissions,
role: this.members[index].role,
}
await axios.patch(
`https://api.modrinth.com/api/v1/team/${this.mod.team}/members/${this.members[index].user_id}`,
data,
config
)
await this.$router.go(null)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
}
this.$nuxt.$loading.finish()
},
},
}
</script>
<style lang="scss" scoped>
.markdown-body {
padding: 1rem;
.section-header {
@extend %card;
padding: var(--spacing-card-md) var(--spacing-card-lg);
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
h3 {
margin: auto 0;
color: var(--color-text-dark);
font-weight: var(--font-weight-extrabold);
}
}
.member {
@extend %card;
padding: var(--spacing-card-md) var(--spacing-card-lg);
margin-bottom: var(--spacing-card-md);
.member-header {
display: flex;
justify-content: space-between;
.info {
display: flex;
img {
border-radius: var(--size-rounded-icon);
height: 50px;
width: 50px;
}
.text {
margin: auto 0 auto 0.5rem;
h4 {
font-weight: normal;
margin: 0;
}
h3 {
text-transform: uppercase;
margin-top: 0.1rem;
margin-bottom: 0;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-extrabold);
letter-spacing: 0.02rem;
}
}
}
.side-buttons {
display: flex;
align-items: center;
.dropdown-icon {
margin-left: 1rem;
cursor: pointer;
color: var(--color-text-dark);
background-color: unset;
transition: 150ms ease transform;
padding: unset;
}
}
}
.content {
display: none;
.main-info {
margin-bottom: var(--spacing-card-lg);
}
.permissions {
margin: 1rem 0;
display: grid;
grid-template-columns: 10rem 10rem 10rem;
grid-template-rows: 1.5rem 1.5rem 1.5rem;
}
}
&.open {
.member-header {
.dropdown-icon {
transform: rotate(180deg);
}
}
.content {
display: unset;
margin: var(--spacing-card-lg);
}
}
}
input,
button {
&:disabled {
cursor: not-allowed !important;
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<ModPage
:mod="mod"
:versions="versions"
:members="members"
:current-member="currentMember"
>
<div class="version">
<div class="version-header">
<h4>{{ version.name }}</h4>
@@ -17,6 +22,7 @@
</span>
<Categories :categories="version.loaders" />
<a
v-if="primaryFile"
:href="primaryFile.url"
class="download-button"
@click.prevent="
@@ -63,16 +69,10 @@
</div>
<div v-compiled-markdown="changelog" class="markdown-body"></div>
<div class="files">
<div v-for="file in version.files" :key="file.hashes.sha1">
<div v-for="file in version.files" :key="file.hashes.sha1" class="file">
<div class="text-wrapper">
<p>{{ file.filename }}</p>
<div
v-if="
$auth.loggedIn &&
members.find((x) => x.user_id === $auth.user.id)
"
class="actions"
>
<div v-if="currentMember" class="actions">
<button @click="deleteFile(file.hashes.sha1)">Delete File</button>
<button @click="makePrimary(file.hashes.sha1)">
Make Primary
@@ -87,13 +87,7 @@
</a>
</div>
</div>
<FileInput
v-if="
$auth.loggedIn && members.find((x) => x.user_id === $auth.user.id)
"
class="file-input"
@change="addFiles"
/>
<FileInput v-if="currentMember" class="file-input" @change="addFiles" />
</div>
</ModPage>
</template>
@@ -160,7 +154,7 @@ export default {
)
).data
users.forEach((it, index) => {
users.reverse().forEach((it, index) => {
members[index].avatar_url = it.avatar_url
members[index].name = it.username
})
@@ -175,12 +169,17 @@ export default {
primaryFile = version.files[0]
}
const currentMember = data.$auth.loggedIn
? members.find((x) => x.user_id === data.$auth.user.id)
: null
return {
mod,
versions,
members,
version,
primaryFile,
currentMember,
}
},
data() {
@@ -367,11 +366,11 @@ export default {
.files {
display: flex;
div {
.file {
display: flex;
margin-right: 0.5rem;
background: var(--color-bg);
border-radius: var(--size-rounded-control);
border: 1px solid var(--color-divider);
.text-wrapper {
display: flex;

View File

@@ -1,5 +1,10 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<ModPage
:mod="mod"
:versions="versions"
:members="members"
:current-member="currentMember"
>
<table>
<thead>
<tr>
@@ -64,10 +69,7 @@
</tbody>
</table>
<Popup
v-if="
this.$auth.loggedIn &&
members.find((x) => x.user_id === this.$auth.user.id)
"
v-if="currentMember"
:show-popup="showPopup"
class="create-version-popup-body"
>
@@ -187,10 +189,7 @@
</div>
</Popup>
<button
v-if="
this.$auth.loggedIn &&
members.find((x) => x.user_id === this.$auth.user.id)
"
v-if="currentMember"
class="default-button"
@click="showPopup = !showPopup"
>
@@ -263,17 +262,22 @@ export default {
)
).data
users.forEach((it, index) => {
users.reverse().forEach((it, index) => {
members[index].avatar_url = it.avatar_url
members[index].name = it.username
})
const currentMember = data.$auth.loggedIn
? members.find((x) => x.user_id === data.$auth.user.id)
: null
return {
mod,
versions: versions.reverse(),
members,
selectableLoaders,
selectableVersions,
currentMember,
}
},
data() {

View File

@@ -581,7 +581,7 @@ export default {
discord_url: this.discord_url,
client_side: this.clientSideType.id,
server_side: this.serverSideType.id,
license_id: this.license.short,
license_id: this.license ? this.license.short : null,
license_url: this.license_url,
is_draft: this.draft,
})