Added default PFP; Added PFP upload functionality; Show pfp in Navbar
This commit is contained in:
parent
fea9a0d0d2
commit
53edb8c7da
|
|
@ -1,4 +1,3 @@
|
|||
const GameModel = require("../models/GameModel");
|
||||
const PlayerModel = require("../models/PlayerModel");
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
})
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -60,21 +60,26 @@ const fileFilterFn = function( req, file, cb ){
|
|||
return;
|
||||
}
|
||||
|
||||
let board = JSON.parse( req.body.board );
|
||||
if( req.body.board ){
|
||||
|
||||
boardController.isBoardFromUser( board.boardId, req.session.user )
|
||||
.then( ( isFromUser ) => {
|
||||
if( !isFromUser && board.boardId !== undefined ){
|
||||
cb( new Error( "The associated board is not the users" ) );
|
||||
} else {
|
||||
if( req.session.user === undefined ){
|
||||
cb( new Error( "Only logged in Users can upload pictures" ) );
|
||||
return;
|
||||
let board = JSON.parse( req.body.board );
|
||||
|
||||
boardController.isBoardFromUser( board.boardId, req.session.user )
|
||||
.then( ( isFromUser ) => {
|
||||
if( !isFromUser && board.boardId !== undefined ){
|
||||
cb( new Error( "The associated board is not the users" ) );
|
||||
} else {
|
||||
if( req.session.user === undefined ){
|
||||
cb( new Error( "Only logged in Users can upload pictures" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
cb( null, true );
|
||||
}
|
||||
|
||||
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 } );
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ app.use(helmet(
|
|||
"media-src": ["'self'", "data:"],
|
||||
"style-src-elem": ["'self'", "'unsafe-inline'"],
|
||||
"connect-src": ["'self'", "ws:"],
|
||||
"img-src": ["'self'", "blob:", "data:"],
|
||||
"img-src": ["'self'", "blob:", "data:"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 class="col-auto">
|
||||
<ProfilePicture
|
||||
:srcOverride="(uploadedFileObj !== null ? uploadedFileObj.url : null )"
|
||||
:isPreview="uploadedFileObj !== null"
|
||||
@previewDiscarded="resetUploadedFileObj()"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="" alt="Standard Profile Picture">
|
||||
<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>
|
||||
<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>
|
||||
<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/*">
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -247,10 +247,10 @@ 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();
|
||||
const userStore = useUserStore();
|
||||
userStore.resetInitialUserDataPromise();
|
||||
userStore.initialUserPromise
|
||||
.then( ( userData ) => {
|
||||
|
|
@ -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!");
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
Loading…
Reference in New Issue