mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 05:14:23 +00:00
android app progress + polls plugin
This commit is contained in:
@@ -38,6 +38,7 @@ dependencies {
|
|||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("com.google.android.material:material:1.8.0")
|
implementation("com.google.android.material:material:1.8.0")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
|
implementation("com.journeyapps:zxing-android-embedded:4.2.0")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
|||||||
@@ -22,5 +22,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="true" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.janishutz.libreevent
|
package com.janishutz.libreevent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
@@ -9,5 +10,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
val loginButton = findViewById<Button>(R.id.loginButton)
|
||||||
|
loginButton.setOnClickListener {
|
||||||
|
login()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun login() {
|
||||||
|
val switchIntent = Intent(this, ScanActivity::class.java)
|
||||||
|
startActivity(switchIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,67 @@
|
|||||||
package com.janishutz.libreevent
|
package com.janishutz.libreevent
|
||||||
|
|
||||||
class ScannerActivity {
|
import android.Manifest
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import com.journeyapps.barcodescanner.CaptureManager
|
||||||
|
import com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||||
|
|
||||||
|
class ScanActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var barcodeView: DecoratedBarcodeView
|
||||||
|
private lateinit var captureManager: CaptureManager
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_scanner)
|
||||||
|
|
||||||
|
barcodeView = findViewById(R.id.barcodeScannerView)
|
||||||
|
|
||||||
|
// Check for camera permission and request if not granted
|
||||||
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST)
|
||||||
|
} else {
|
||||||
|
setupScanner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupScanner() {
|
||||||
|
captureManager = CaptureManager(this, barcodeView)
|
||||||
|
|
||||||
|
captureManager.initializeFromIntent(intent, null)
|
||||||
|
captureManager.decode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
captureManager.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
captureManager.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass savedInstanceState to onSaveInstanceState
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
captureManager.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == CAMERA_PERMISSION_REQUEST) {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
setupScanner()
|
||||||
|
} else {
|
||||||
|
// Handle permission denied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CAMERA_PERMISSION_REQUEST = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||||
|
android:id="@+id/barcodeScannerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
47
src/server/backend/plugins/others/poll/css/popup.css
Normal file
47
src/server/backend/plugins/others/poll/css/popup.css
Normal 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;
|
||||||
|
}
|
||||||
57
src/server/backend/plugins/others/poll/css/style.css
Normal file
57
src/server/backend/plugins/others/poll/css/style.css
Normal 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%;
|
||||||
|
}
|
||||||
1
src/server/backend/plugins/others/poll/data/voting.json
Normal file
1
src/server/backend/plugins/others/poll/data/voting.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"test":{"1":{"title":"test","comment":"test","id":1,"count":3}}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"test":{"allowAdding":true,"display":"test","id":"test","comment":"test2"}}
|
||||||
108
src/server/backend/plugins/others/poll/html/settings.html
Normal file
108
src/server/backend/plugins/others/poll/html/settings.html
Normal 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>
|
||||||
114
src/server/backend/plugins/others/poll/html/voting.html
Normal file
114
src/server/backend/plugins/others/poll/html/voting.html
Normal 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>
|
||||||
63
src/server/backend/plugins/others/poll/js/settings.js
Normal file
63
src/server/backend/plugins/others/poll/js/settings.js
Normal 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' );
|
||||||
91
src/server/backend/plugins/others/poll/js/voting.js
Normal file
91
src/server/backend/plugins/others/poll/js/voting.js
Normal 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' );
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
"pluginWebsite": "https://libreevent.janishutz.com/plugins/polls",
|
"pluginWebsite": "https://libreevent.janishutz.com/plugins/polls",
|
||||||
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/polls",
|
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/polls",
|
||||||
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/poll",
|
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/poll",
|
||||||
"settingsURL": "/admin/plugins/polls/settings",
|
"settingsURL": "/admin/plugins/polls",
|
||||||
"mainPluginURL": "/polls",
|
"mainPluginURL": "/voting",
|
||||||
"logo": "",
|
"logo": "",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
@@ -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' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user