android app progress + polls plugin

This commit is contained in:
2023-09-16 11:43:42 +02:00
parent 196cc5aed9
commit 364f1d4c38
15 changed files with 669 additions and 9 deletions

View File

@@ -0,0 +1,47 @@
#popup, #popup-data {
display: none;
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba( 0, 0, 0, 0.5 );
}
.popup-positioning {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.submit {
margin-top: 2%;
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
background-size: 300px;
padding: 10px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 3s;
font-size: 75%;
color: white;
}
.submit:hover {
background-size: 200%;
background-position: -100%;
}
.popup-main {
border: none;
border-radius: 20px;
padding: 5%;
background-color: rgb(34, 34, 34);
color: white;
max-width: 70%;
max-height: 70%;
overflow-y: scroll;
}

View File

@@ -0,0 +1,57 @@
html {
height: 98%;
}
body {
font-family: monospace;
height: 100%;
width: 100%;
background-color: lightgray;
}
.title-area {
font-size: 1000%;
margin: 0;
padding: 0;
}
.title-area h1 {
margin: 0;
padding: 0;
}
.content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.small {
font-style: italic;
font-size: 80%;
}
.main {
width: 70%;
height: 40%;
font-size: 150%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form {
width: 70%;
}
.textarea {
width: 100%;
padding: 2%;
border-radius: 10px;
resize: vertical;
font-size: 90%;
}

View File

@@ -0,0 +1 @@
{"test":{"1":{"title":"test","comment":"test","id":1,"count":3}}}

View File

@@ -0,0 +1 @@
{"test":{"allowAdding":true,"display":"test","id":"test","comment":"test2"}}

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=7">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Settings - Polls :: libreevent-plugin</title>
<link rel="stylesheet" href="/polls/css/style.css">
<link rel="stylesheet" href="/polls/css/popup.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<style>
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24
}
.voting-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}
.voting {
border-radius: 500px;
border: 1px black solid;
font-size: 150%;
cursor: pointer;
}
.voting-counter {
margin: 0;
font-size: 150%;
margin-left: 10px;
margin-right: 10px;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.content {
justify-content: flex-start;
}
.input {
width: 30vw;
padding: 20px;
border-radius: 20px;
border: none;
margin-bottom: 1vh;
margin-top: 5px;
}
.poll {
border: black 2px solid;
padding: 1% 10%;
}
.selected {
background-color: green;
}
</style>
</head>
<body>
<div class="content" id="app">
<h1>Polls plugin - Settings</h1>
<div style="margin-bottom: 1%;">
<a href="/admin/plugins">Back to the admin panel</a><br>
<button @click="addPoll();">Add new poll</button>
</div>
<div v-for="poll in polls" class="poll">
<h3>{{ poll.display }}</h3>
<p>{{ poll.comment }}</p>
<button @click="editPoll( poll.id )">Edit</button>
</div>
<div id="popup">
<div class="popup-positioning">
<div class="popup-main">
<form id="popup-message">
<h2 style="font-size: 200%;">{{ operation }} poll</h2>
<label for="title">Poll title</label><br>
<input type="text" v-model="newPoll.display" name="title" id="title" class="input"><br>
<label for="id">Poll id</label><br>
<input type="text" v-model="newPoll.id" name="id" id="id" class="input"><br>
<label for="comment">Comments</label><br>
<textarea type="text" v-model="newPoll.comment" name="comment" id="comment" class="input" rows="5"></textarea><br>
<label for="id">Allow adding suggestions</label>
<input type="checkbox" v-model="newPoll.allowAdding" name="allowAdding" id="allowAdding"><br>
</form>
<button @click="save()" class="submit">Save</button>
<button @click="closePopup()" class="submit">Cancel</button>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="/polls/js/settings.js"></script>
</body>
</html>

View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=7">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Polls :: libreevent-plugin</title>
<link rel="stylesheet" href="/polls/css/style.css">
<link rel="stylesheet" href="/polls/css/popup.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<style>
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24
}
.voting-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}
.voting {
border-radius: 500px;
border: 1px black solid;
font-size: 150%;
cursor: pointer;
}
.voting-counter {
margin: 0;
font-size: 150%;
margin-left: 10px;
margin-right: 10px;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.content {
justify-content: flex-start;
}
.input {
width: 30vw;
padding: 20px;
border-radius: 20px;
border: none;
margin-bottom: 1vh;
margin-top: 5px;
}
.entry {
border: black 2px solid;
padding: 1% 10%;
}
.selected {
background-color: green;
}
</style>
</head>
<body>
<div class="content" id="app">
<div v-if="votingDetails.display">
<h1>Voting on {{ votingDetails.display ?? 'untitled' }}</h1>
<p v-if="votingDetails.description">{{ votingDetails.description }}</p>
<div style="margin-bottom: 1%;" v-if="votingDetails.allowAdding">
<button onclick="location.href = '/'">Back to website</button>
<button @click="addSuggestion();">Add suggestion</button>
</div>
<div v-for="entry in entries" class="entry">
<h3>{{ entry.title }}</h3>
<p>{{ entry.comment }}</p>
<div class="voting-wrapper">
<span class="material-symbols-outlined voting" @click="vote( 'up', entry.id )" :class="votedOn[ entry.id ] === 'up' ? 'selected' : ''">arrow_upward</span>
<p class="voting-counter">{{ entry.count ?? 0 }}</p>
<span class="material-symbols-outlined voting" @click="vote( 'down', entry.id )" :class="votedOn[ entry.id ] === 'down' ? 'selected' : ''">arrow_downward</span>
</div>
</div>
<div id="popup">
<div class="popup-positioning">
<div class="popup-main">
<form id="popup-message">
<h2 style="font-size: 200%;">Add new suggestion</h2>
<label for="title">Suggestion title</label><br>
<input type="text" v-model="newSuggestion.title" name="title" id="title" class="input"><br>
<label for="title">Comments</label><br>
<textarea type="text" v-model="newSuggestion.comment" name="comment" id="comment" class="input" rows="5"></textarea><br>
</form>
<button @click="save()" class="submit">Add</button>
<button @click="closePopup()" class="submit">Cancel</button>
</div>
</div>
</div>
</div>
<div v-else style="display: flex; justify-content: center; align-items: center; height: 100vh; font-size: 110%;">
<h1>This poll does not exist!</h1>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="/polls/js/voting.js"></script>
</body>
</html>

View File

@@ -0,0 +1,63 @@
const { createApp } = Vue;
createApp( {
data() {
return {
polls: {},
newPoll: {},
operation: 'Add new',
};
},
methods: {
getData() {
fetch( '/admin/plugins/polls/getData' ).then( response => {
response.json().then( data => {
this.polls = data;
this.newPoll = {};
} );
} );
},
save() {
if ( this.newPoll.comment && this.newPoll.display && this.newPoll.id ) {
this.polls[ this.newPoll.id ] = this.newPoll;
let fetchOptions = {
method: 'post',
body: JSON.stringify( this.polls ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/admin/plugins/polls/save', fetchOptions ).then( response => {
if ( response.status !== 200 ) {
alert( 'there was an error updating' );
}
} );
this.closePopup();
this.getData();
} else {
alert( 'Not all required fields are filled out!' );
}
},
closePopup() {
$( '#popup' ).fadeOut( 500 );
$( 'body' ).removeClass( 'menuOpen' );
this.getData();
},
addPoll () {
this.newPoll = { 'allowAdding': true };
this.operation = 'Add new';
$( '#popup' ).fadeIn( 500 );
$( 'body' ).addClass( 'menuOpen' );
},
editPoll ( pollID ) {
this.operation = 'Edit';
this.newPoll = this.polls[ pollID ];
$( '#popup' ).fadeIn( 500 );
$( 'body' ).addClass( 'menuOpen' );
}
},
mounted() {
this.getData();
}
} ).mount( '#app' );

View File

@@ -0,0 +1,91 @@
const { createApp } = Vue;
createApp( {
data() {
return {
entries: {},
newSuggestion: {},
votingDetails: {},
votedOn: {},
};
},
methods: {
getData() {
fetch( '/polls/get/' + location.pathname.substring( 7 ) ).then( response => {
response.json().then( data => {
this.entries = data;
} );
} );
fetch( '/polls/getDetails/' + location.pathname.substring( 7 ) ).then( response => {
response.json().then( data => {
this.votingDetails = data;
} );
} );
this.votedOn = JSON.parse( localStorage.getItem( 'itemsVotedOn' ) ?? '{}' );
},
save() {
if ( this.newSuggestion.comment && this.newSuggestion.title ) {
let fetchOptions = {
method: 'post',
body: JSON.stringify( this.newSuggestion ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/polls/add/' + location.pathname.substring( 7 ), fetchOptions ).then( response => {
if ( response.status !== 200 ) {
alert( 'there was an error updating' );
}
} );
this.closePopup();
this.getData();
} else {
alert( 'Not all required fields are filled out!' );
}
},
vote( type, suggestionID ) {
let voteType = type;
let didDeactivate = false;
if ( this.votedOn[ suggestionID ] === type ) {
didDeactivate = true;
if ( type === 'up' ) {
voteType = 'down';
} else {
voteType = 'up';
}
} else if ( this.votedOn[ suggestionID ] ) {
return;
}
let fetchOptions = {
method: 'post',
body: JSON.stringify( { 'voteType': voteType, 'id': suggestionID } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
},
};
fetch( '/polls/vote/' + location.pathname.substring( 7 ), fetchOptions ).then( response => {
if ( response.status !== 200 ) {
alert( 'there was an error updating' );
} else {
this.votedOn[ suggestionID ] = didDeactivate ? undefined : voteType;
localStorage.setItem( 'itemsVotedOn', JSON.stringify( this.votedOn ) );
this.getData();
}
} );
},
closePopup() {
$( '#popup' ).fadeOut( 500 );
$( 'body' ).removeClass( 'menuOpen' );
this.getData();
},
addSuggestion () {
$( '#popup' ).fadeIn( 500 );
$( 'body' ).addClass( 'menuOpen' );
}
},
mounted() {
this.getData();
}
} ).mount( '#app' );

View File

@@ -6,8 +6,8 @@
"pluginWebsite": "https://libreevent.janishutz.com/plugins/polls",
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/polls",
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/poll",
"settingsURL": "/admin/plugins/polls/settings",
"mainPluginURL": "/polls",
"settingsURL": "/admin/plugins/polls",
"mainPluginURL": "/voting",
"logo": "",
"version": "1.0.0"
}

View File

@@ -7,6 +7,99 @@
*
*/
module.exports = ( app, settings ) => {
const path = require( 'path' );
const fs = require( 'fs' );
const bodyParser = require( 'body-parser' );
module.exports = ( app ) => {
app.get( '/polls/:vote', ( req, res ) => {
res.sendFile( path.join( __dirname + '/html/voting.html' ) );
} );
app.get( '/polls/css/:file', ( req, res ) => {
res.sendFile( path.join( __dirname + '/css/' + req.params.file ) );
} );
app.get( '/polls/js/:file', ( req, res ) => {
res.sendFile( path.join( __dirname + '/js/' + req.params.file ) );
} );
app.get( '/polls/getDetails/:vote', ( req, res ) => {
fs.readFile( path.join( __dirname + '/data/votingSettings.json' ), ( error, filedata ) => {
res.send( JSON.parse( filedata )[ req.params.vote ] ?? {} );
} );
} );
app.get( '/polls/get/:vote', ( req, res ) => {
fs.readFile( path.join( __dirname + '/data/voting.json' ), ( error, filedata ) => {
res.send( JSON.parse( filedata )[ req.params.vote ] ?? {} );
} );
} );
app.post( '/polls/vote/:vote/', bodyParser.json(), ( req, res ) => {
// up / down-voting
fs.readFile( path.join( __dirname + '/data/voting.json' ), ( error, filedata ) => {
let json = JSON.parse( filedata );
if ( json[ req.params.vote ] ) {
if ( req.body.voteType === 'up' ) {
json[ req.params.vote ][ req.body.id ].count += 1;
} else if ( req.body.voteType === 'down' ) {
json[ req.params.vote ][ req.body.id ].count -= 1;
}
fs.writeFile( path.join( __dirname + '/data/voting.json' ), JSON.stringify( json ), ( err ) => {
if ( err ) res.status( 500 ).send( 'ERR_VOTING' );
res.send( 'ok' );
} );
} else {
res.status( 404 ).send( 'ok' );
}
} );
} );
app.post( '/polls/add/:vote', bodyParser.json(), ( req, res ) => {
let data = req.body;
if ( data.title && data.comment ) {
fs.readFile( path.join( __dirname + '/data/voting.json' ), ( error, filedata ) => {
let file = JSON.parse( filedata );
if ( !file[ req.params.vote ] ) {
file[ req.params.vote ] = {};
}
const id = parseInt( Object.keys( file[ req.params.vote ] )[ Object.keys( file[ req.params.vote ] ).length - 1 ] ?? 0 ) + 1;
file[ req.params.vote ][ id ] = data;
file[ req.params.vote ][ id ][ 'id' ] = id;
file[ req.params.vote ][ id ][ 'count' ] = 1;
fs.writeFile( path.join( __dirname + '/data/voting.json' ), JSON.stringify( file ), ( error ) => {
if ( error ) console.error( 'failed to write data', file );
res.send( 'ok' );
} );
} );
} else {
res.status( 400 ).send( 'incomplete' );
}
} );
app.get( '/admin/plugins/polls', ( req, res ) => {
if ( req.session.loggedInAdmin ) {
res.sendFile( path.join( __dirname + '/html/settings.html' ) );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.get( '/admin/plugins/polls/getData', ( req, res ) => {
if ( req.session.loggedInAdmin ) {
res.sendFile( path.join( __dirname + '/data/votingSettings.json' ) );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
app.post( '/admin/plugins/polls/save', bodyParser.json(), ( req, res ) => {
if ( req.session.loggedInAdmin ) {
fs.writeFileSync( path.join( __dirname + '/data/votingSettings.json' ), JSON.stringify( req.body ) );
res.send( 'ok' );
} else {
res.status( 403 ).send( 'unauthorized' );
}
} );
};