1.0.0-prerelease4

This commit is contained in:
Troplo 2021-02-04 18:23:11 +11:00
parent 1d910927fe
commit e482952c2f
18 changed files with 418 additions and 75 deletions

View file

@ -17,6 +17,7 @@
"crypto-random-string": "^3.3.0",
"dotenv-webpack": "^6.0.0",
"lodash.throttle": "^4.1.1",
"nprogress": "^0.2.0",
"socket.io": "^3.1.0",
"tiptap": "^1.32.0",
"tiptap-extensions": "^1.35.0",
@ -27,6 +28,7 @@
"vue-i18n": "^8.17.3",
"vue-keypress": "^2.1.1",
"vue-matomo": "^3.14.0-0",
"vue-nprogress": "^0.2.0",
"vue-router": "^3.2.0",
"vue-socket.io": "^3.0.10",
"vuex": "^3.4.0"

View file

@ -1,5 +1,5 @@
<template>
<trpl-badges>
<span>
<b-modal :active="modifyUserModal" @update:active="value => modifyUserModal = value" :width="640" scroll="keep" style="z-index: 100">
<div class="modal-card subtitle" style="width: auto">
<header class="modal-card-head">
@ -44,15 +44,16 @@
<b-tag v-if="booster" class="is-success" rounded>{{ $t('badges.booster') }}</b-tag>&nbsp;
<b-tag v-if="system" class="is-success" rounded>{{ $t('badges.system') }}</b-tag>&nbsp;
<b-tag v-if="hidden" rounded>{{ $t('badges.hidden') }}</b-tag>&nbsp;
<b-button @click="modal()" v-if="$store.state.user.admin" class="is-info tag" rounded><i class="fas fa-plus"></i></b-button>
</trpl-badges>
<b-tag v-if="verified" rounded><i class="fas fa-check"></i>&nbsp;Verified</b-tag>
<b-button @click="modal()" v-if="$store.state.user.admin & !noPlus" class="is-info tag" rounded><i class="fas fa-plus"></i></b-button>
</span>
</template>
<script>
import AjaxErrorHandler from "../../assets/js/errorHandler";
export default {
name: 'UserBadges',
props: ['admin', 'booster', 'bot', 'hidden', 'banned', 'system', 'username'],
props: ['admin', 'booster', 'bot', 'hidden', 'banned', 'system', 'username', 'noPlus', 'verified'],
data() {
return {
modifyUserModal: false

View file

@ -8,19 +8,6 @@
>
<slot></slot>
</div>
<div
class='c_menu__menu'
:style='{ "left": left, "right": right }'
:class='{ "c_menu__menu--show": showMenu }'
>
<div
class='c_menu__item'
:v-for='item in items'
@click='showMenu = false; $emit(item.event)'
>
{{item.text}}
</div>
</div>
<div
class='c_menu__overlay'
:class='{ "c_menu__overlay--show": showMenu }'
@ -32,7 +19,6 @@
<script>
export default {
name: 'c-menu',
props: ['items'],
data () {
return {
showMenu: false,

View file

@ -155,17 +155,16 @@
<section class="modal-card-body has-text-centered">
<h1 class="subtitle">What's new in {{$store.state.client.clientVersion}}?</h1>
<ul>
<li>Improved pagination</li>
<li>Add friend page</li>
<li>Remove koin icon in buttons</li>
<li>Add global wall</li>
<li>Add blog</li>
<li>Add new friend count in Navbar User Menu</li>
<li>Add developmental forum</li>
<hr>
<li>{{$store.state.client.clientVersion}}-patch fixes:</li>
<li>Patch pagination page crash bug with 'wait' prop to not attempt to load more when its loading</li>
<li>Improve forum thread page</li>
<li>404 page on invalid user</li>
<li>Add early teams page</li>
<li>Add loading bar on router navigation change</li>
<li>Add early avatar page</li>
<li>Add early chat/conversation page</li>
<li>Add unauthenticated home page with site stats</li>
<li>Fix users page avatar size</li>
<li>Implement websockets</li>
<li>Improve forums</li>
<li>Fix statistics page</li>
</ul>
</section>
<footer class="modal-card-foot">
@ -339,7 +338,7 @@
<b-navbar-item tag="router-link" :to="'/u/' + $store.state.user.username">{{$t('navbar.user.profile')}}</b-navbar-item>
<b-navbar-item @click="settingsModal = true">{{$t('navbar.user.settings')}}</b-navbar-item>
<b-navbar-item tag="router-link" to="/transactions">{{$t('navbar.user.transactions')}}</b-navbar-item>
<b-navbar-item tag="router-link" to="/character">{{$t('navbar.user.avatar')}}</b-navbar-item>
<b-navbar-item tag="router-link" to="/avatar">{{$t('navbar.user.avatar')}}</b-navbar-item>
<b-navbar-item tag="router-link" to="/creations">{{$t('navbar.user.creations')}}</b-navbar-item>
<b-navbar-item tag="router-link" to="/downloads">{{$t('navbar.user.downloads')}}</b-navbar-item>
<b-navbar-item tag="router-link" to="/admin" v-if="$store.state.user.admin">{{$t('navbar.user.admin')}}</b-navbar-item>

View file

@ -1,18 +1,22 @@
<template>
<div class="is-vcentered has-text-centered columns">
<div class="column is-vcentered has-text-centered" v-if="!connection">
<div class="column is-vcentered has-text-centered" v-if="!connection && !notFound">
<i class="far fa-times-square large-icon"></i>
<h1 class="subtitle is-centered">{{$t('generic.noItemsStart')}} {{type}} {{$t('generic.noItemsEnd')}}</h1>
</div>
<div class="column is-vcentered" v-if="connection">
<div class="column is-vcentered" v-if="connection && !notFound">
<i class="far fa-times-square large-icon"></i>
<h1 class="subtitle">{{$t('generic.noItemsConnection')}}</h1>
</div>
<div class="column is-vcentered" v-if="notFound">
<i class="far fa-times-square large-icon"></i>
<h1 class="subtitle">{{$t('generic.notFound')}}</h1>
</div>
</div>
</template>
<script>
export default {
name: 'NoItems',
props: ['type', 'connection'],
props: ['type', 'connection', 'notFound'],
}
</script>

View file

@ -1,13 +1,6 @@
<template>
<div class=''>
<div class='side_panel__header'>
<c-menu
class='side_panel__username'
:items='userMenu'
@logout='logout'
>
{{$store.state.username}} <font-awesome-icon icon='angle-down'></font-awesome-icon>
</c-menu>
<b-button
class='side_panel__add button button--blue_border'
@click='$router.push("/chat/conversation")'
@ -47,7 +40,8 @@
@load='getConversations'
>
<side-panel-conversation
:v-for='conversation in $store.state.user.conversations'
v-for='conversation in $store.state.user.conversations'
:key='"conversation-list-" + conversation.id'
:conversation='conversation'
tabindex='0'
></side-panel-conversation>
@ -60,14 +54,12 @@
</template>
<script>
import CMenu from './ChatMenu';
import CScrollLoad from './ChatPagination';
import SidePanelConversation from './SidebarChatComp';
export default {
name: 'side-panel',
name: 'ChatSidebar',
components: {
CMenu,
CScrollLoad,
SidePanelConversation
},
@ -76,10 +68,6 @@ export default {
page: 0,
loading: false,
userMenu: [
{ text: 'Settings', event: 'settings' },
{ text: 'Log out', event: 'logout' }
],
showCloseButton: false,
searchQuery: ''
@ -151,7 +139,7 @@ export default {
},
mounted () {
this.getConversations();
this.$io.on('conversation', this.updateConversations);
this.$socket.on('conversation', this.updateConversations);
}
};
</script>

View file

@ -12,7 +12,8 @@
<div class='side_panel_conversation__profile_picture'>
<div
class='side_panel_conversation__profile_picture__letter'
:v-for='userLetter in userLetters'
v-for='userLetter in userLetters'
:key='"userLetter-" + userLetter.letter'
:class='[
"side_panel_conversation__profile_picture__letter--" + userLetters.length
]'
@ -53,7 +54,7 @@ export default {
username () {
let username = this.conversation.Messages[0].User.username;
if(username === this.$store.state.username) {
if(username === this.$store.state.user.username) {
return 'You';
} else {
return username;
@ -61,11 +62,10 @@ export default {
},
userLetters () {
return this.conversation.Users
.filter(u => u.username !== this.$store.state.username)
.filter(u => u.username !== this.$store.state.user.username)
.map(u => {
return {
letter: u.username[0].toUpperCase(),
color: u.color
};
})
.slice(0, 4);
@ -73,7 +73,7 @@ export default {
},
methods: {
goToConversation () {
this.$router.push("/chat/" + this.conversation.id);
this.$router.push("/chat/conversation/" + this.conversation.id);
}
}
};

View file

@ -165,7 +165,8 @@
"down": "There has appeared to be an issue communicating with Kaverti, please try again later.",
"canaryBuild": "You are using the Canary client, if you are expecting a stable experience, please use the regular one on Kaverti.com.",
"logout": "You have been logged out of Kaverti.",
"register": "You have been registered to Kaverti, Welcome!"
"register": "You have been registered to Kaverti, Welcome!",
"emailVerify": "Please verify your email to get full access to Kaverti!"
},
"generic": {
"name": "Kaverti",
@ -299,5 +300,6 @@
"close": "Close",
"tos": "Terms of Service",
"gotIt": "Got it!",
"OK": "OK",
"errorModalTitle": "Something went wrong..."
}

View file

@ -15,6 +15,9 @@ import json from 'highlight.js/lib/languages/json';
const hljsTheme = createHljsTheme();
import io from 'socket.io-client'
import VueSocketIO from "vue-socket.io";
import NProgress from "vue-nprogress";
Vue.use(NProgress)
const nprogress = new NProgress()
Vue.use(
new VueSocketIO({
debug: true,
@ -57,6 +60,7 @@ Vue.filter('formatDate', function(value) {
}
})
new Vue({
nprogress,
router,
store,
i18n,

View file

@ -1,7 +1,9 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css';
Vue.use(VueRouter)
NProgress.configure({ showSpinner: false });
function route(view){
return() => import(`@/views/${view}.vue`)
@ -42,6 +44,23 @@ const routes = [
name: 'Teams',
component: route('Teams')
},
{
path: '/character',
redirect: '/avatar',
name: 'Character',
component: route('Avatar')
},
{
path: '/avatar',
redirect: '/avatar/hats',
name: 'Avatar',
component: route('Avatar')
},
{
path: '/avatar/:category',
name: 'Avatar',
component: route('Avatar')
},
{ path: '/t/:username', component: route('Team'), children: [
{ path: 'posts', component: route('UserPosts') },
{ path: 'threads', component: route('UserThreads') },
@ -147,4 +166,16 @@ const router = new VueRouter({
routes
})
router.beforeResolve((to, from, next) => {
// If this isn't an initial page load.
if (to.name) {
// Start the route progress bar.
NProgress.start()
}
next()
})
router.afterEach(() => {
NProgress.done()
})
export default router

View file

@ -8,7 +8,7 @@ export default new Vuex.Store({
wind: false,
enableBrokenRoutes: false,
client: {
clientVersion: '1.0.0-prerelease3',
clientVersion: '1.0.0-prerelease4',
latestClientVersion: '',
latestAPIVersion: '',
bannerText: '',
@ -47,7 +47,8 @@ export default new Vuex.Store({
developerMode: false,
executive: false,
description: '',
conversations: []
conversations: [],
currentConversation: 0
}
},
mutations: {
@ -169,6 +170,9 @@ export default new Vuex.Store({
conversation.name = name;
state.user.conversations.splice(index, 1, conversation);
},
setCurrentConversation (state, id) {
state.user.currentConversation = id
}
},
actions: {

145
src/views/Avatar.vue Normal file
View file

@ -0,0 +1,145 @@
<template>
<main>
<div class="section">
<div class="columns">
<div class="column is-3 has-text-centered">
<h1 class="title">{{$store.state.user.username}}</h1>
<div class="box has-text-centered">
<img :src="'https://cdn.kaverti.com/user/avatars/full/' + $store.state.user.avatar + '.png'">
<b-button :loading="refreshAvatarLoading" class="is-info" @click="refresh()">Re-render</b-button>
</div>
</div>
<div class="column">
<h1 class="title has-text-centered">{{name}} ({{count}})</h1>
<div class="box">
<div class="tabs is-centered">
<ul>
<router-link tag="li" :to="'/avatar/hats'" exact><a>{{ $t('avatar.hats') }}</a></router-link>
<router-link tag="li" :to="'/avatar/faces'" exact><a>{{ $t('avatar.faces') }}</a></router-link>
<router-link tag="li" :to="'/avatar/shirts'" exact><a>{{ $t('avatar.shirts') }}</a></router-link>
<router-link tag="li" :to="'/avatar/pants'" exact><a>{{ $t('avatar.pants') }}</a></router-link>
<router-link tag="li" :to="'/avatar/collections'" exact><a>{{ $t('avatar.collections') }}</a></router-link>
</ul>
</div>
<div class="columns is-multiline">
<div class="column is-4 has-text-centered" v-for='(item) in items' :key='"inventory-item-" + item.id'>
<h1 class="subtitle">{{item.Item.name}}</h1>
<div class="box">
<img :src="'https://cdn.kaverti.com/marketplace/avatars/full/' + item.Item.previewFile + '.png'">
<br>
<b-button>Apply</b-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
import AjaxErrorHandler from "../../../website/legacyfrontend/src/assets/js/errorHandler";
export default {
name: "Avatar",
data() {
return {
name: 'Hats',
loading: true,
items: [],
count: 0,
coreCategory: 0,
refreshAvatarLoading: false,
user: []
}
},
methods: {
refresh() {
this.refreshAvatarLoading = true
this.axios
.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/render/refresh')
.then(() => {
this.refreshAvatarLoading = false
})
.catch(e => {
this.refreshAvatarLoading = false
AjaxErrorHandler(this.$store)(e, error => {
this.refreshAvatar.error = error.message
})
})
},
getItems() {
this.loading = true;
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'inventory/' + this.coreCategory)
.then(res => {
this.items = res.data.rows;
this.count = res.data.count
this.loading = false;
})
.catch(e => {
AjaxErrorHandler(this.$store)(e);
this.loading = false;
});
}
},
mounted() {
this.loading = true
this.items = []
this.category = this.$route.params.category
if(this.category === 'hats') {
this.name = "Hats"
this.coreCategory = 0
} else if(this.category === 'shirts') {
this.name = "Shirts"
this.coreCategory = 1
} else if(this.category === 'faces') {
this.name = "Faces"
this.coreCategory = 2
} else if(this.category === 'pants') {
this.name = "Pants"
this.coreCategory = 3
} else if(this.category === 'collections') {
this.name = "Collections"
this.coreCategory = 4
} else {
this.name = "Unknown"
this.coreCategory = 0
}
this.getItems()
},
watch: {
$route () {
this.loading = true
this.items = []
this.category = this.$route.params.category
if(this.category === 'hats') {
this.name = "Hats"
this.coreCategory = 0
} else if(this.category === 'shirts') {
this.name = "Shirts"
this.coreCategory = 1
} else if(this.category === 'faces') {
this.name = "Faces"
this.coreCategory = 2
} else if(this.category === 'pants') {
this.name = "Pants"
this.coreCategory = 3
} else if(this.category === 'collections') {
this.name = "Collections"
this.coreCategory = 4
} else {
this.name = "Unknown"
this.coreCategory = 0
}
this.getItems()
}
},
}
</script>
<style scoped>
</style>

View file

@ -1,5 +1,5 @@
<template>
<main class="section">
<main class="section" v-if="$store.state.enableBrokenRoutes">
<div class="columns">
<div class="column">
<div class="box">

View file

@ -58,7 +58,8 @@
>
<div class='conversation__main__conversations'>
<conversation-message
:v-for='message in messages'
v-for='message in messages'
:key='"conversation-message-" + message.id'
:context='messages'
:message='message'
:users='users'
@ -79,12 +80,12 @@
@keydown='sendTyping'
v-model='input'
></textarea>
<button
class='conversation__submit button button--blue'
<b-button
rounded
@click='() => $route.params.id ? sendMessage() : createConversation()'
>
<font-awesome-icon icon='paper-plane'></font-awesome-icon>
</button>
<i class="fas fa-paper-plane"></i>
</b-button>
</div>
</div>
</template>
@ -334,19 +335,19 @@ export default {
mounted () {
this.pageLoad();
this.$io.on('message', message => {
this.$socket.on('message', message => {
if(message.ConversationId !== +this.$route.params.id) return;
this.messages.push(message);
this.scrollToBottom();
this.updateLastRead();
});
this.$io.on('startTyping', ({ userId }) => {
this.$socket.on('startTyping', ({ userId }) => {
let user = this.users.find(u => u.id === userId);
this.typingUsers.push(user);
this.scrollToBottom();
});
this.$io.on('stopTyping', ({ userId }) => {
this.$socket.on('stopTyping', ({ userId }) => {
let index = this.typingUsers.findIndex(u => u.id === userId);
this.typingUsers.splice(index, 1);
});

View file

@ -221,7 +221,7 @@ export default {
],
content: '<p>What do you have to say</p>'
}),
postModal: true
postModal: false
}
},
methods: {

View file

@ -1,5 +1,162 @@
<template>
<main>
<main class="section">
<div class="columns is-multiline" v-if="!loading">
<div v-if="!teams.length" class="column">
<br>
<NoItems :connection="true" type="users">
</NoItems>
</div>
<Pagination
class='columns is-multiline'
v-if='teams.length'
:loading='loading'
:paginate="paginate"
:wait="wait"
@loadNext='getTeams(false)'
>
<div class="column is-3" v-for='(team) in teams' :key='"team-" + team.id'>
<div class="box">
<h1 class="title">{{team.username}}&nbsp;<Badges :username="team.username" :verified="team.verified" :noPlus="true"></Badges></h1>
<img :src="'/api/v1/teams/view/' + team.username + '/picture'" width="64" length="64"><br>
<b-button tag="router-link" :to='"/u/" + team.username' class="is-centered is-info">View Profile</b-button>
</div>
</div>
</Pagination>
</div>
<div class="columns is-multiline" v-if="loading">
<div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div>
<div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div> <div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div>
<div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div>
<div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div> <div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div> <div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div> <div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div>
<div class="column is-4">
<div class="box">
<h1 class="title">
<b-skeleton></b-skeleton>
</h1>
<b-skeleton height="100px"></b-skeleton>
</div>
</div>
</div>
</main>
</template>
<script>
import AjaxErrorHandler from "../../assets/js/errorHandler";
import Badges from "../components/Badges"
import NoItems from "../components/NoItems"
import Pagination from "../components/Pagination"
export default {
name: 'Users',
components: {
Badges,
NoItems,
Pagination
},
data() {
return {
teams: [],
offset: 0,
paginate: true,
limit: 30,
loading: true,
wait: true
}
},
methods: {
getTeams(initial) {
if(initial) {
this.teams = []
this.loading = true
this.offset = 0
this.paginate = true
}
if(!initial) {
this.wait = true
}
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'teams/' + '?offset=' + this.offset)
.then(res => {
if(res.data < this.limit) {
this.offset = null;
} else {
this.offset+= this.limit;
}
if(!initial && !res.data.length) {
this.paginate = false
}
if(initial) {
this.teams = res.data
} else {
this.teams.push(...res.data)
}
this.loading = false
this.wait = false
})
.catch((e) => {
this.loading = false
this.wait = false
AjaxErrorHandler(this.$store)(e)
})
}
},
mounted() {
this.loading = true
this.getTeams()
}
}
</script>

View file

@ -6,11 +6,11 @@
</style>
<template>
<main class="section">
<div class="columns is-centered">
<div class="columns is-centered" v-if="exists">
<div class="column is-4 is-vcentered has-text-centered">
<h1 class="title">{{user.username}}&nbsp;<Badges :username="user.username" :system="user.system" :hidden="user.hidden" :admin="user.admin" :booster="user.booster" :bot="user.bot"></Badges></h1>
<div class="box">
<img :src="'https://cdn.kaverti.com/user/avatars/full/' + user.picture + '.png'" :alt="user.username + '\'s avatar'" width=25%>
<img :src="'https://cdn.kaverti.com/user/avatars/full/' + user.picture + '.png'" :alt="user.username + '\'s avatar'" width="50%">
</div>
<div class="buttons is-centered">
<b-button @click="doRelationship" class="is-success" v-if="relationships.type === 'notFriends' && user.username !== $store.state.user.username"><i class="fas fa-plus"></i> &nbsp;{{$t('relationships.notFriends')}}</b-button>
@ -52,16 +52,22 @@
</div>
</div>
</div>
<div class="column">
<NoItems notFound="true"></NoItems>
</div>
</main>
</template>
<script>
import AjaxErrorHandler from '../../assets/js/errorHandler'
import Badges from '../components/Badges'
import NoItems from '../components/NoItems'
export default {
name: 'User',
components: {
Badges
Badges,
NoItems
},
data () {
return {
@ -71,6 +77,7 @@ export default {
description: "Loading",
createdAt: "2020-01-01T00:00:00.000Z"
},
exists: true,
relationship: false,
relationships: {
type: ''
@ -122,7 +129,7 @@ export default {
})
if(invalidId) {
this.$store.commit('set404Page', true)
this.exists = false
} else {
AjaxErrorHandler(this.$store)(e)
}

View file

@ -9569,6 +9569,11 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
gauge "~2.7.3"
set-blocking "~2.0.0"
nprogress@0.2.0, nprogress@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E=
nth-check@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@ -13429,6 +13434,13 @@ vue-matomo@^3.14.0-0:
resolved "https://registry.yarnpkg.com/vue-matomo/-/vue-matomo-3.14.0-0.tgz#f8e668c26ec1f2f7b4498f758edfb7c387259735"
integrity sha512-i1IkZGSXNY84zg1gVU8TOuaqajYDWQYl4Vs7M1mEb21cNhlMZKUxxgElvj+xmv7ytYUc/6ekZbxIS+y6W4qTMQ==
vue-nprogress@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/vue-nprogress/-/vue-nprogress-0.2.0.tgz#92a99f605dd152bdd4fc940ab0ca535723a5b394"
integrity sha512-lTJvEQEt7Pxdc6jwUZf3m4t8emrrBecXxzSLT1G9eIjRQE4kOh7Y+ECjc5R1qrNyyuDqHCTKZMaH5bXy17IS5g==
dependencies:
nprogress "0.2.0"
vue-router@^3.2.0:
version "3.4.9"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.9.tgz#c016f42030ae2932f14e4748b39a1d9a0e250e66"