🔨 Add snakbar

This commit is contained in:
SebClem 2022-05-28 16:25:45 +02:00
parent 157f4c615c
commit ec9a9b59e1
Signed by: sebclem
GPG Key ID: 5A4308F6A359EA50
12 changed files with 198 additions and 41 deletions

File diff suppressed because one or more lines are too long

5
.gitignore vendored
View File

@ -20,6 +20,7 @@ coverage
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
!.vscode/settings.json
.idea .idea
*.suo *.suo
*.ntvs* *.ntvs*
@ -27,4 +28,6 @@ coverage
*.sln *.sln
*.sw? *.sw?
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.eslintcache

View File

@ -1,7 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"johnsoncodehk.volar", "vue.volar",
"johnsoncodehk.vscode-typescript-vue-plugin",
"arcanis.vscode-zipfs", "arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode" "esbenp.prettier-vscode"

17
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true,
"**/node_modules": true,
},
"typescript.inlayHints.enumMemberValues.enabled": true,
"typescript.inlayHints.propertyDeclarationTypes.enabled": true,
"typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true,
"typescript.inlayHints.parameterNames.enabled": "literals",
"typescript.inlayHints.parameterTypes.enabled": true,
"javascript.inlayHints.enumMemberValues.enabled": true,
"javascript.inlayHints.propertyDeclarationTypes.enabled": true,
"javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true,
"javascript.inlayHints.parameterNames.enabled": "literals",
}

View File

@ -17,6 +17,7 @@
"dependencies": { "dependencies": {
"@mdi/font": "5.9.55", "@mdi/font": "5.9.55",
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",
"@types/uuid": "^8.3.4",
"@vue/cli": "^5.0.4", "@vue/cli": "^5.0.4",
"axios": "^0.27.2", "axios": "^0.27.2",
"jose": "^4.8.1", "jose": "^4.8.1",
@ -24,6 +25,7 @@
"pinia": "^2.0.13", "pinia": "^2.0.13",
"pinia-plugin-persistedstate": "^1.6.0", "pinia-plugin-persistedstate": "^1.6.0",
"roboto-fontface": "*", "roboto-fontface": "*",
"uuid": "^8.3.2",
"vue": "^3.2.33", "vue": "^3.2.33",
"vue-router": "^4.0.14", "vue-router": "^4.0.14",
"vuetify": "^3.0.0-beta.2", "vuetify": "^3.0.0-beta.2",

View File

@ -3,7 +3,11 @@
<header-component /> <header-component />
<v-main> <v-main>
<v-container> <v-container>
<router-view /> <router-view v-slot="{ Component }">
<v-fade-transition>
<component :is="Component" />
</v-fade-transition>
</router-view>
</v-container> </v-container>
</v-main> </v-main>
</v-app> </v-app>

View File

@ -3,9 +3,6 @@
<v-app-bar-title class="text-yellow font-weight-black text-h4"> <v-app-bar-title class="text-yellow font-weight-black text-h4">
Claptrap Bot Claptrap Bot
</v-app-bar-title> </v-app-bar-title>
<v-btn icon v-if="isLoggedIn">
<v-icon>mdi-exit-to-app</v-icon>
</v-btn>
</v-app-bar> </v-app-bar>
<v-navigation-drawer expand-on-hover rail position="right" v-if="isLoggedIn"> <v-navigation-drawer expand-on-hover rail position="right" v-if="isLoggedIn">
<v-list class="overflow-hidden"> <v-list class="overflow-hidden">
@ -16,39 +13,57 @@
> >
<template v-slot:append> <template v-slot:append>
<v-list-item-avatar end> <v-list-item-avatar end>
<v-btn icon="mdi-exit-to-app"></v-btn> <v-btn
icon="mdi-exit-to-app"
class="text-high-emphasis text-white"
v-on:click="logout(false, false)"
></v-btn>
</v-list-item-avatar> </v-list-item-avatar>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>
<v-divider></v-divider> <v-divider></v-divider>
<v-list nav> <v-list nav>
<v-list-item <v-list-item>
prepend-icon="mdi-folder" <v-list-item-avatar :start="true">
title="My Files" <v-progress-circular
value="myfiles" indeterminate
></v-list-item> color="primary"
<v-list-item ></v-progress-circular>
prepend-icon="mdi-account-multiple" </v-list-item-avatar>
title="Shared with me" <v-list-item-subtitle> Loading... </v-list-item-subtitle>
value="shared" </v-list-item>
></v-list-item>
<v-list-item
prepend-icon="mdi-star"
title="Starred"
value="starred"
></v-list-item>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
<v-snackbar
<v-main style="height: 250px"></v-main> v-for="(data, index) of snacks"
:key="data.uuid"
v-model="data.snack"
top="true"
left="true"
:style="{ 'margin-top': calcMargin(index) }"
:color="data.color"
>
<div>{{ data.text }}</div>
<template v-slot:actions>
<v-btn
variant="text"
icon="mdi-close"
@click="data.snack = false"
></v-btn>
</template>
</v-snackbar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { logout } from "@/services/authService";
import { useEventQueuStore } from "@/stores/eventQueu";
import { ref, watch } from "vue";
const userStore = useUserStore(); const userStore = useUserStore();
const eventQueuStore = useEventQueuStore();
const { userName, avatar, discriminator, isLoggedIn } = storeToRefs(userStore); const { userName, avatar, discriminator, isLoggedIn } = storeToRefs(userStore);
@ -56,6 +71,51 @@ function getAvatar() {
const avatarBaseUrl = import.meta.env.VITE_DISCORD_USER_AVATAR_URL; const avatarBaseUrl = import.meta.env.VITE_DISCORD_USER_AVATAR_URL;
return avatarBaseUrl + userStore.discordId + "/" + avatar.value + ".png"; return avatarBaseUrl + userStore.discordId + "/" + avatar.value + ".png";
} }
function calcMargin(i: number) {
return i * 60 + 70 + "px";
}
let snacks = ref<
{ text: string; color: string; snack: boolean; uuid: string }[]
>([]);
eventQueuStore.$subscribe((mutation, state) => {
if (state.events.length != 0) {
let event = eventQueuStore.shift();
if (event?.uuid) {
snacks.value.push({
snack: true,
text: event.text,
color: event.type,
uuid: event.uuid,
});
}
}
});
watch(
snacks,
(newValue, oldValue) => {
let filtered = snacks.value.filter((value) => value.snack);
if (JSON.stringify(filtered) != JSON.stringify(snacks.value)) {
snacks.value = filtered;
}
},
{ deep: true }
);
if (eventQueuStore.size != 0) {
let event = eventQueuStore.shift();
if (event?.uuid) {
snacks.value.push({
snack: true,
text: event.text,
color: event.type,
uuid: event.uuid,
});
}
}
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,5 +1,7 @@
import { useEventQueuStore } from "@/stores/eventQueu";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import axios from "axios"; import axios from "axios";
import { useRouter } from "vue-router";
async function login(code: string): Promise<boolean> { async function login(code: string): Promise<boolean> {
const userStore = useUserStore(); const userStore = useUserStore();
@ -21,16 +23,45 @@ async function login(code: string): Promise<boolean> {
userStore.loginFail = false; userStore.loginFail = false;
console.log("Loggin success !"); console.log("Loggin success !");
const eventQueuStore = useEventQueuStore();
eventQueuStore.push({
uuid: undefined,
type: "success",
text: "You are now logged in",
});
return true; return true;
} catch (reason) { } catch (reason) {
console.log("Loggin fail !"); console.log("Loggin fail !");
console.log(reason); console.log(reason);
userStore.token = ""; logout(false, true);
userStore.userName = "";
userStore.discordId = ""; const eventQueuStore = useEventQueuStore();
userStore.loginFail = true; eventQueuStore.push({
uuid: undefined,
type: "error",
text: "Login fail, Please try aguain.",
});
return false; return false;
} }
} }
export { login }; function logout(expired: boolean, loginFail: boolean): void {
const userStore = useUserStore();
const router = useRouter();
userStore.token = "";
userStore.userName = "";
userStore.discordId = "";
userStore.loginFail = loginFail;
userStore.asExpired = expired;
const eventQueuStore = useEventQueuStore();
eventQueuStore.push({
uuid: undefined,
type: "success",
text: "Disconnected",
});
}
export { login, logout };

31
src/stores/eventQueu.ts Normal file
View File

@ -0,0 +1,31 @@
import { defineStore } from "pinia";
import { v4 as uuidv4 } from "uuid";
interface Event {
uuid: string | undefined;
type: string;
text: string;
}
export const useEventQueuStore = defineStore({
id: "eventQueu",
state: () => ({
events: [] as Array<Event>,
}),
getters: {
size: (state) => {
return state.events.length;
},
},
actions: {
shift(): Event | undefined {
return this.events.shift();
},
push(event: Event) {
if (!event.uuid) {
event.uuid = uuidv4();
}
this.events.push(event);
},
},
});

View File

@ -1,6 +1,5 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import * as jose from "jose"; import * as jose from "jose";
import axios from "axios";
import { cookiesStorage } from "./coockiesStorage"; import { cookiesStorage } from "./coockiesStorage";
export const useUserStore = defineStore("user", { export const useUserStore = defineStore("user", {
@ -11,6 +10,7 @@ export const useUserStore = defineStore("user", {
avatar: "", avatar: "",
token: "", token: "",
loginFail: false, loginFail: false,
asExpired: false,
}), }),
getters: { getters: {
isLoggedIn(): boolean { isLoggedIn(): boolean {
@ -22,12 +22,13 @@ export const useUserStore = defineStore("user", {
isExpired(): boolean { isExpired(): boolean {
if (this.getTokenPayload?.exp) { if (this.getTokenPayload?.exp) {
const exp = new Date(this.getTokenPayload.exp * 1000); const exp = new Date(this.getTokenPayload.exp * 1000);
return exp < new Date(); const expired = exp < new Date();
this.asExpired = expired;
return expired;
} }
return true; return true;
}, },
getTokenPayload: (state) => { getTokenPayload: (state) => {
const token = state.token;
return jose.decodeJwt(state.token); return jose.decodeJwt(state.token);
}, },
}, },

View File

@ -1,17 +1,18 @@
<template> <template>
<v-row v-if="loginFail" titl> <v-row v-if="!userStore.isLoggedIn">
<v-col> <v-col>
<v-alert title="Login Fail" closable type="error" density="compact"> <v-btn to="/oauth2/redirect" prepend-icon="mdi-discord" color="primary">
Login fail, please try again. Login
</v-alert> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import { storeToRefs } from "pinia"; import { onBeforeMount } from "vue";
const userStore = useUserStore(); const userStore = useUserStore();
const { loginFail } = storeToRefs(userStore); onBeforeMount(() => {
userStore.isLoggedIn;
});
</script> </script>

View File

@ -2026,6 +2026,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/uuid@npm:^8.3.4":
version: 8.3.4
resolution: "@types/uuid@npm:8.3.4"
checksum: 6f11f3ff70f30210edaa8071422d405e9c1d4e53abbe50fdce365150d3c698fe7bbff65c1e71ae080cbfb8fded860dbb5e174da96fdbbdfcaa3fb3daa474d20f
languageName: node
linkType: hard
"@types/webfontloader@npm:^1.0.0": "@types/webfontloader@npm:^1.0.0":
version: 1.6.34 version: 1.6.34
resolution: "@types/webfontloader@npm:1.6.34" resolution: "@types/webfontloader@npm:1.6.34"
@ -3609,6 +3616,7 @@ __metadata:
"@types/js-cookie": ^3.0.2 "@types/js-cookie": ^3.0.2
"@types/jsdom": ^16.2.14 "@types/jsdom": ^16.2.14
"@types/node": ^16.11.27 "@types/node": ^16.11.27
"@types/uuid": ^8.3.4
"@types/webfontloader": ^1.0.0 "@types/webfontloader": ^1.0.0
"@vitejs/plugin-vue": ^2.3.1 "@vitejs/plugin-vue": ^2.3.1
"@vue/cli": ^5.0.4 "@vue/cli": ^5.0.4
@ -3633,6 +3641,7 @@ __metadata:
sass: ^1.38.0 sass: ^1.38.0
sass-loader: ^10.0.0 sass-loader: ^10.0.0
typescript: ~4.6.3 typescript: ~4.6.3
uuid: ^8.3.2
vite: ^2.9.5 vite: ^2.9.5
vitest: ^0.9.3 vitest: ^0.9.3
vue: ^3.2.33 vue: ^3.2.33