Added default PFP; Added PFP upload functionality; Show pfp in Navbar

This commit is contained in:
EisiBaer 2023-09-23 16:01:51 +02:00
parent fea9a0d0d2
commit 53edb8c7da
19 changed files with 570 additions and 60 deletions

View File

@ -1,4 +1,3 @@
const GameModel = require("../models/GameModel");
const PlayerModel = require("../models/PlayerModel");
/**

View File

@ -1,9 +1,10 @@
const bcrypt = require("bcryptjs");
const fs = require("node:fs/promises");
const UserModel = require("../models/UserModel");
const BoardModel = require("../models/BoardModel");
exports.listUsers = (req, res) => {
exports.listUsers = () => {
return new Promise((resolve, reject) => {
UserModel.find({}, "-password")
.then((users) => {
@ -141,3 +142,30 @@ exports.addBoardToUser = ( board ) => {
})
});
};
exports.updateProfilePicture = ( userId, pfpFilename ) => {
return new Promise( ( resolve, reject ) => {
UserModel.findByIdAndUpdate( userId, { pfpFilename: pfpFilename } )
.then( ( userBefore ) => {
if( userBefore === null ){
let userNotFoundError = new Error(`No user found in session"`);
userNotFoundError.name = "NotFoundError";
throw userNotFoundError;
}
if( userBefore.pfpFilename !== null ){
return fs.rm( "public/uploads/" + userBefore.pfpFilename );
} else {
return undefined;
}
})
.then( () => {
return UserModel.findById( userId , {}, { new: true });
})
.then( ( user ) => {
resolve( user );
})
.catch( ( err ) => {
reject( err );
})
});
};

View File

@ -11,6 +11,7 @@ const UserSchema = new Schema({
},
password: { type: String, required: true, },
boards: [{ type: Schema.Types.ObjectId, ref: "Board" }],
pfpFilename: [{ type: String, required: false, default: null }],
});
// Virtual for player's URL

View File

@ -60,6 +60,8 @@ const fileFilterFn = function( req, file, cb ){
return;
}
if( req.body.board ){
let board = JSON.parse( req.body.board );
boardController.isBoardFromUser( board.boardId, req.session.user )
@ -75,6 +77,9 @@ const fileFilterFn = function( req, file, cb ){
cb( null, true );
}
});
} else {
cb( null, true );
}
}
@ -84,7 +89,7 @@ router.get("/", (req, res)=>{
if( req.session.user !== undefined ){
userController.findUser( req.session.user )
.then( user => {
res.send({success: true, user: { username: user.username } } );
res.send({success: true, user: { username: user.username, pfpFilename: user.pfpFilename } } );
})
.catch( err => {
console.debug(err);
@ -124,6 +129,7 @@ router.post("/login", (req, res)=>{
res.send({success: true, user: { username: req.session.user } } );
})
.catch( ( err ) => {
console.error( err );
res.send({success:false, error: "Error with logging you in" });
})
}else{
@ -240,19 +246,45 @@ router.get("/boards/:id", (req, res) => {
});
router.get("/pfp/:filename", (req, res) => {
console.log("Getting pfp: ", req.params.filename);
let options = {
root: 'public/uploads',
dotfiles: 'deny',
headers: {
'Access-Control-Allow-Origin': '*',
}
}
res.sendFile( req.params.filename, options );
});
router.post("/pfp", upload.single( "pfp" ), (req, res) => {
if( req.session.user === undefined ){
res.send( { success: false, error: "Not logged in!" } );
} else {
let imageFile = null;
if( !req.file ){
console.error( "No file attached" );
return;
throw new Error("No file attached to be saved" );
}
imageFile = req.file;
let imageFile = req.file;
return userController.updateProfilePicture( req.session.user, imageFile.filename )
.then( ( user ) => {
res.send( { success: true, newProfilePicture: user.profilePicture } );
res.send( { success: true, newProfilePicture: user.pfpFilename } );
})
.catch( ( err ) => {
res.send( { success: false, error: err } );
});
}
});
router.delete("/pfp", (req, res) => {
if( req.session.user === undefined ){
res.send( { success: false, error: "Not logged in!" } );
} else {
return userController.updateProfilePicture( req.session.user, null )
.then( ( _user ) => {
res.send( { success: true, } );
})
.catch( ( err ) => {
res.send( { success: false, error: err } );

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="PFP_Bear.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="true"
inkscape:zoom="0.7071068"
inkscape:cx="48.790367"
inkscape:cy="195.16147"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<ellipse
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:2.94695;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="ellipse9"
ry="8.7389574"
rx="7.3490396"
cy="0.21687971"
cx="-74.58239"
transform="matrix(-0.88593468,-0.46381003,-0.43331387,0.90124308,0,0)" />
<ellipse
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:2.94695;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8"
ry="8.7389574"
rx="7.3490396"
cy="46.607407"
cx="15.560421"
transform="matrix(0.88593467,-0.46381004,0.43331386,0.90124309,0,0)" />
<path
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:3.20808;stroke-dasharray:none;stroke-opacity:1"
d="M 0.09642571,100.42076 C 7.8465924,67.218518 20.053978,42.128065 50.000563,42.128065 c 29.949567,0 42.15361,25.090329 49.904141,58.294115"
id="path1"
sodipodi:nodetypes="ccc" />
<circle
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
id="path2"
cx="50"
cy="51.3937"
r="22.843529" />
<path
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
d="m 45.498078,55.013425 c 0.503992,-1.440484 2.975715,-1.436799 4.501822,-1.436799 1.526107,0 3.997459,-0.0036 4.501822,1.436799 0.75684,2.161377 -2.209427,5.387564 -4.499483,5.388153 -2.291078,5.89e-4 -5.260782,-3.225617 -4.504161,-5.388153 z"
id="path5"
sodipodi:nodetypes="sssss" />
<path
style="display:inline;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 49.983098,60.437125 c 0,3.055652 -0.219217,3.50138 -1.243542,4.092774 -1.678595,0.969137 -3.486678,-0.345521 -4.177109,-0.744142"
id="path6"
sodipodi:nodetypes="ccc" />
<path
style="display:inline;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 49.952982,60.437125 c 0,3.055652 0.219217,3.50138 1.243542,4.092774 1.678595,0.969137 3.486678,-0.345521 4.177109,-0.744142"
id="path7"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 56.5,44.887112 3.181598,-3.181598 3.180744,3.180745"
id="path9" />
<path
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 36.5,44.887112 3.181598,-3.181598 3.180744,3.180745"
id="path10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="PFP_BearHead.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="true"
inkscape:zoom="0.7071068"
inkscape:cx="48.790367"
inkscape:cy="195.16147"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" />
<defs
id="defs1" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="translate(0.01884618,-0.22140619)">
<ellipse
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:4.42699;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="ellipse9"
ry="13.127905"
rx="11.039931"
cy="-10.383468"
cx="-78.467972"
transform="matrix(-0.88593467,-0.46381003,-0.43331387,0.90124309,0,0)" />
<ellipse
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:4.42699;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8"
ry="13.127905"
rx="11.039931"
cy="36.007061"
cx="11.674847"
transform="matrix(0.88593467,-0.46381003,0.43331387,0.90124309,0,0)" />
<circle
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:4.50669;stroke-dasharray:none;stroke-opacity:1"
id="path2"
cx="50.009468"
cy="51.98246"
r="34.316185" />
<path
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:4.50669;stroke-dasharray:none;stroke-opacity:1"
d="m 43.246556,57.420114 c 0.75711,-2.163936 4.470201,-2.158401 6.762761,-2.158401 2.292561,0 6.005093,-0.0054 6.762762,2.158401 1.136946,3.246879 -3.319062,8.093347 -6.759248,8.094232 -3.441721,8.75e-4 -7.902893,-4.845611 -6.766275,-8.094232 z"
id="path5"
sodipodi:nodetypes="sssss" />
<path
style="display:inline;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:4.50669;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 49.984077,65.567745 c 0,4.590286 -0.329314,5.259871 -1.868084,6.148279 -2.521632,1.455864 -5.237785,-0.51905 -6.274968,-1.11787"
id="path6"
sodipodi:nodetypes="ccc" />
<path
style="display:inline;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:4.50669;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 49.938836,65.567745 c 0,4.590286 0.329314,5.259871 1.868083,6.148279 2.521632,1.455864 5.237784,-0.51905 6.274969,-1.11787"
id="path7"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:4.50669;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 59.773948,42.208084 4.779484,-4.779484 4.778202,4.778204"
id="path9" />
<path
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:4.50669;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 29.729393,42.208084 4.779485,-4.779484 4.778201,4.778204"
id="path10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="PFP_draft1.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="true"
inkscape:zoom="0.3535534"
inkscape:cx="-465.27625"
inkscape:cy="96.16652"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
d="M 8.2680968e-4,99.998029 C 7.7658401,71.018605 19.996612,49.119274 50.000563,49.119274 c 30.00694,0 42.234362,21.899223 49.999737,50.879996"
id="path1"
sodipodi:nodetypes="ccc" />
<circle
style="fill:#e86a92;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
id="path2"
cx="50"
cy="31.188124"
r="22.843529" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -87,6 +87,8 @@ $modal-content-bg: $dark-blue;
$font-family-base: "Urbanist";
$enable-caret: false;
// 2. Include any default variable overrides here
@ -189,3 +191,7 @@ $utilities: map-merge(
.image-contain{
object-fit: contain;
}
.pfp{
object-fit: cover;
}

View File

@ -1,11 +1,17 @@
<script setup>
import { ref } from 'vue';
import { useUserStore } from "@/stores/UserStore";
import GenericMultiButtonModal from '@/components/views/GenericMultiButtonModal.vue';
import { openModal } from "@/services/util";
import ProfilePicture from '@/components/blocks/ProfilePicture.vue';
const emit = defineEmits(["profilePictureChanged"]);
let uploadedFileUrl = ref(null);
let uploadedFileObj = ref(null);
let uploadingInProcess = ref(false);
let imageInput = ref(null);
const userStore = useUserStore();
function newImageUploaded( event ){
let files = event.target.files || event.dataTransfer.files;
@ -16,14 +22,43 @@ function newImageUploaded( event ){
data: files[0],
url: URL.createObjectURL( files[0] ),
};
uploadedFileUrl.value = fileObj;
uploadedFileObj.value = fileObj;
}
function revealAnswer(){
emit("profilePictureChanged");
function saveProfilePicture(){
if( uploadedFileObj.value === null || uploadingInProcess.value === true ){
return;
}
uploadingInProcess.value = true;
userStore.saveProfilePicture( uploadedFileObj.value.data )
.finally( () => {
uploadingInProcess.value = false;
resetUploadedFileObj();
})
}
revealAnswer();
function deleteProfilePicture(){
openModal('confirmDeletePfpModal');
}
function resetUploadedFileObj(){
uploadedFileObj.value = null;
imageInput.value.value = null;
}
function handleModalButtonClick( buttonIndex ){
switch( buttonIndex ){
case 0:
userStore.deleteProfilePicture()
.finally( () => {
uploadedFileObj.value = null;
})
break;
case 1:
default:
return;
}
}
</script>
@ -36,23 +71,51 @@ revealAnswer();
</div>
</div>
<div class="row">
<div class="col-2">
<div class="ratio ratio-16x9">
<img src="" alt="Current Profile Picture">
</div>
</div>
<div class="col">
<div class="row">
<div class="col">
<img src="" alt="Standard Profile Picture">
<div class="col-auto">
<ProfilePicture
:srcOverride="(uploadedFileObj !== null ? uploadedFileObj.url : null )"
:isPreview="uploadedFileObj !== null"
@previewDiscarded="resetUploadedFileObj()"
/>
</div>
<div class="col-auto border-start">
<div class="h-100 d-flex flex-column justify-content-evenly">
<div>
<label class="form-label fs-5" for="question-image">Upload new profile picture</label>
<input ref="imageInput" class="form-control bg-dark-blue" type="file" name="question-image" id="question-image" @change="newImageUploaded" accept="image/*">
</div>
<div>
<label class="form-label fs-4 mt-3" for="question-image">Upload new profile picture</label>
<input class="form-control bg-dark-blue" type="file" name="question-image" id="question-image" @change="newImageUploaded( questionIndex, $event )" accept="image/*">
<button class="btn btn-pink-accent-primary me-3" :disabled="uploadedFileObj === null" @click="saveProfilePicture">
<font-awesome-icon v-if="uploadingInProcess" icon="fa-solid fa-spinner" spin/>
<font-awesome-icon v-else icon="fa-solid fa-floppy-disk" />
Save
</button>
<button class="btn btn-danger" :disabled="userStore.pfpFilename === null" @click="deleteProfilePicture">
<font-awesome-icon icon="fa-solid fa-floppy-disk" />
Delete Current Profile Picture
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped></style>
<GenericMultiButtonModal
:id="'confirmDeletePfpModal'"
:hasTitle="true"
:title="'Are you sure?'"
:modalText="'Are you sure you want to delete your current profile picture?'"
:buttonList="[
{
text: 'Yes, delete!',
emitsEvent: 'discardClicked',
bgColorClass: 'btn-danger',
},
{
text: 'Cancel',
bgColorClass: 'btn-pink-accent-primary',
},
]"
@buttonClicked="handleModalButtonClick"
/>
</template>

View File

@ -0,0 +1,72 @@
<script setup>
import { computed } from 'vue';
import { useUserStore } from "@/stores/UserStore"
const props = defineProps({
srcOverride: {
type: [String, null],
default: null,
required: false,
},
sizingClasses: {
type: Array,
default: () => ["pfp-sizing"],
required: false,
},
isPreview: {
type: Boolean,
default: false,
required: false,
}
});
const emit = defineEmits(["previewDiscarded"]);
const userStore = useUserStore();
let protocol = ('https:' == document.location.protocol ? 'https://' : 'http://');
let hostname = window.location.hostname;
if( window.location.hostname.includes("localhost" ) ){
hostname += ':3000';
}
const API_URL = `${protocol}${hostname}/api`;
const pfpSrc = computed( () => {
if( props.srcOverride !== null ){
return props.srcOverride;
} else if( userStore.pfpFilename === null ){
return "/src/webapp/assets/images/PFP_BearHead.svg";
} else {
return `${API_URL}/user/pfp/${userStore.pfpFilename}`;
}
})
</script>
<template>
<div>
<div class="border border-1 border-white rounded-3 overflow-hidden">
<img :src="pfpSrc" alt="Profile Picture of the user" class="pfp" :class="sizingClasses" />
<div v-show="props.isPreview" class="position-relative">
<span class="position-absolute bottom-0 end-0 bg-black bg-opacity-50 px-1 rounded-2">
Preview
<font-awesome-icon icon="fa-solid fa-rotate-left" size="sm" @click="emit('previewDiscarded')" title="Discard uploaded image" class="pointer"/>
</span>
</div>
</div>
</div>
</template>
<style scoped>
.pfp-sizing{
height: 10rem;
width: 10rem;
max-height: 100vh;
max-width: 100vw;
}
.pfp-sizing-navbar{
height: 2em;
width: 2em;
}
</style>

View File

@ -1,5 +1,6 @@
<script setup>
import { useUserStore } from '@/stores/UserStore';
import ProfilePicture from '@/components/blocks/ProfilePicture.vue';
const userStore = useUserStore();
@ -67,7 +68,13 @@ function logoutButtonClicked(_event){
<div v-if="userStore.loggedIn">
<div class="dropdown text-center">
<a class="dropdown-toggle text-decoration-none" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ userStore.username }}
<div class="d-flex align-items-center justify-content-around">
<font-awesome-icon icon="fa-solid fa-angle-down" size="sm" class="me-2"/>
<span class="me-1">{{ userStore.username }}</span>
<ProfilePicture
:sizingClasses="['pfp-sizing-navbar']"
/>
</div>
</a>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark bg-dark-blue">
<li>
@ -96,7 +103,13 @@ function logoutButtonClicked(_event){
<div v-if="userStore.loggedIn">
<div class="dropdown">
<a class="dropdown-toggle text-decoration-none" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ userStore.username }}
<div class="d-flex align-items-center justify-content-around">
<font-awesome-icon icon="fa-solid fa-angle-down" size="sm" class="me-2"/>
<span class="me-1">{{ userStore.username }}</span>
<ProfilePicture
:sizingClasses="['pfp-sizing-navbar']"
/>
</div>
</a>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark bg-dark-blue">
<li>
@ -127,4 +140,8 @@ function logoutButtonClicked(_event){
.nav-logo{
height: 3.75em;
}
.pfp-sizing-navbar{
height: 2em;
width: 2em;
}
</style>

View File

@ -1,7 +1,7 @@
<script setup>
import { onMounted, ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Modal } from "bootstrap";
import { openModal } from "@/services/util";
import BoardEntryEditView from '@/components/views/BoardEntryEditView.vue';
import CategoryEditView from '@/components/views/CategoryEditView.vue';
@ -192,13 +192,6 @@ function exitCreatePage(){
router.push("/profile");
}
//Maybe extract
function openModal( modalId ){
let modalElement = document.getElementById( modalId );
let modalInstance = Modal.getOrCreateInstance( modalElement );
modalInstance.show();
}
function toggleBottomView(){
showingBottomView.value = !showingBottomView.value;
}
@ -328,7 +321,6 @@ if( route.params.boardId !== undefined ){
:buttonList="[
{
text: 'Yes, discard!',
emitsEvent: 'discardClicked',
bgColorClass: 'btn-danger',
},
{

View File

@ -20,12 +20,10 @@ const props = defineProps({
return [
{
text: "Ok",
emitsEvent: "b1Clicked",
bgColorClass: "btn-pink-accent-primary",
},
{
text: "Cancel",
emitsEvent: "b2Clicked",
bgColorClass: "btn-outline-danger",
},
];

View File

@ -4,7 +4,8 @@ import { createPinia } from "pinia";
import { FontAwesomeIcon, FontAwesomeLayers } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faDragon, faRightToBracket, faUsers, faUserPlus, faSpinner, faPlusSquare, faBorderAll, faPen, faTrash, faAngleDown, faAngleUp,
faPlus, faMinus, faAngleRight, faSquare, faPlay, faCircleExclamation, faSquareCheck, faSquareMinus, faHandPointer } from "@fortawesome/free-solid-svg-icons";
faPlus, faMinus, faAngleRight, faSquare, faPlay, faCircleExclamation, faSquareCheck, faSquareMinus, faHandPointer, faFloppyDisk,
faEye, faRotateLeft } from "@fortawesome/free-solid-svg-icons";
import { faCircleUser, faSquarePlus } from "@fortawesome/free-regular-svg-icons";
@ -40,6 +41,9 @@ library.add({
faPlay,
faCircleExclamation,
faHandPointer,
faFloppyDisk,
faEye,
faRotateLeft,
});

View File

@ -101,4 +101,22 @@ export default class UserService{
);
}
saveNewProfilePicture( pfpFormData ){
return axios.post(
API_URL + "/user/pfp",
pfpFormData,
{
withCredentials: true,
}
);
}
deleteProfilePicture( ){
return axios.delete(
API_URL + "/user/pfp",
{
withCredentials: true,
}
);
}
}

View File

@ -1,7 +1,10 @@
import { Modal } from "bootstrap";
import BoardEntry from "@/models/BoardEntry";
import Category from "@/models/Category";
import Board from "@/models/Board";
export function boardResponseToBoardModel( boardResponse ){
let categories = [];
@ -29,3 +32,9 @@ export function boardResponseToBoardModel( boardResponse ){
return new Board( boardResponse._id, boardResponse.name, categories );
}
export function openModal( modalId ){
let modalElement = document.getElementById( modalId );
let modalInstance = Modal.getOrCreateInstance( modalElement );
modalInstance.show();
}

View File

@ -247,7 +247,7 @@ export const useGameStore = defineStore('game', {
this.websocketConnection.onerror = ( _event ) => {
console.error("Websocket Error");
};
this.websocketConnection.onclose = ( event ) => {
this.websocketConnection.onclose = ( _event ) => {
clearInterval( this.keepAliveInterval );
this.keepAliveInterval = undefined;
const userStore = useUserStore();
@ -317,7 +317,7 @@ export const useGameStore = defineStore('game', {
this.players = data.payload.players;
}
});
this.addSocketListener("payloadIncomplete", ( data ) => {
this.addSocketListener("payloadIncomplete", ( _data ) => {
console.error("Invalid or Incomplete Payload!");
});
}

View File

@ -9,6 +9,7 @@ export const useUserStore = defineStore('user', {
loggedIn: false,
username: "",
admin: false,
pfpFilename: null,
initialUserPromise: new Promise( (resolve, reject ) => {
uService.getUserFromSession()
.then( res => {
@ -43,6 +44,7 @@ export const useUserStore = defineStore('user', {
setUser( user ){
this.loggedIn = true;
this.username = user.username;
this.pfpFilename = user.pfpFilename;
},
resetInitialUserDataPromise(){
this.initialUserPromise = new Promise( (resolve, reject ) => {
@ -59,6 +61,39 @@ export const useUserStore = defineStore('user', {
reject( err );
});
});
},
saveProfilePicture( imageData ){
return new Promise( ( resolve, reject ) => {
let formData = new FormData();
formData.append( "pfp", imageData );
this.userService.saveNewProfilePicture( formData )
.then( ( response ) => {
this.pfpFilename = response.data.newProfilePicture;
resolve();
})
.catch( ( error ) => {
console.error( error );
reject();
});
});
},
deleteProfilePicture(){
return new Promise( ( resolve, reject ) => {
this.userService.deleteProfilePicture()
.then( ( response ) => {
if( response.data.success ){
this.pfpFilename = null;
resolve();
} else {
console.warn( "Profile picture could not be deleted" );
reject();
}
})
.catch( ( error ) => {
console.error( error );
reject();
});
});
},
},
})