version 1.2

Reviewed-on: jlusiardi/retrotool_dev#28
Co-authored-by: Joachim Lusiardi <joachim@lusiardi.de>
Co-committed-by: Joachim Lusiardi <joachim@lusiardi.de>
This commit is contained in:
Joachim Lusiardi 2025-07-28 19:45:54 +02:00 committed by jlusiardi
parent 95a97182e3
commit 24213c4022
7 changed files with 375 additions and 64 deletions

23
app.py
View File

@ -28,6 +28,7 @@ def prepareRetroData(retroData, participantId, ttl=0):
"extId": retroData["extId"],
"moods": retroData["moods"],
"participants": len(retroData["participants"]),
"participants_ready": len(retroData["participants_ready"]),
"good": [foo(x) for x in retroData["good"]],
"bad": [foo(x) for x in retroData["bad"]],
"todo": [foo(x) for x in retroData["todo"]],
@ -68,6 +69,7 @@ def createRetro():
"good": [],
"bad": [],
"todo": [],
"participants_ready": [],
}
redis_client.set(f"retro_{ext_id}", json.dumps(newRetro))
redis_client.set(f"retrotimer_{ext_id}", "timer", ex=duration)
@ -146,7 +148,7 @@ def handle_join(data):
def handle_mood_update(retroId, participantId, mood):
print(f"{participantId} changed mood to {mood}")
retro = json.loads(redis_client.get(f"retro_{retroId}"))
retro["moods"][participantId] = mood
retro["moods"][participantId] = min(10, max(1,mood))
redis_client.set(f"retro_{retroId}", json.dumps(retro))
join_room(retroId)
@ -420,6 +422,25 @@ def handle_remark_edited(retroId, participantId, commentId, remarkId, text):
)
@socketio.on("participant_ready")
def handle_remark_edited(retroId, participantId):
print(f"{participantId} is ready")
retro = json.loads(redis_client.get(f"retro_{retroId}"))
retro["participants_ready"].append(participantId)
retro["participants_ready"] = list(set(retro["participants_ready"]))
redis_client.set(f"retro_{retroId}", json.dumps(retro))
join_room(retroId)
ttl = redis_client.ttl(f"retrotimer_{retroId}")
emit(
"transmit_retro",
json.dumps(prepareRetroData(retro, participantId, ttl)),
to=retroId,
)
#
if __name__ == "__main__":
socketio.run(app, debug=True)

45
package-lock.json generated
View File

@ -1,15 +1,28 @@
{
"name": "retrotool",
"name": "retrotool_dev",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"@types/bootstrap": "^5.2.10",
"@types/socket.io-client": "3.0.0",
"bootstrap": "5.3.7",
"socket.io-client": "4.8.1",
"typescript": "^5.8.3"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@ -17,6 +30,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/bootstrap": {
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/socket.io-client": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-3.0.0.tgz",
@ -28,6 +51,26 @@
"socket.io-client": "*"
}
},
"node_modules/bootstrap": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
"integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",

View File

@ -2,6 +2,8 @@
"devDependencies": {
"@types/socket.io-client": "3.0.0",
"socket.io-client": "4.8.1",
"@types/bootstrap": "^5.2.10",
"bootstrap": "5.3.7",
"typescript": "^5.8.3"
}
}

View File

@ -1,4 +1,5 @@
#!/bin/sh
npm install
./node_modules/.bin/tsc
python3 app.py

View File

@ -25,13 +25,15 @@
<script type="importmap">
{
"imports": {
"socket.io-client": "https://cdn.socket.io/4.8.1/socket.io.esm.min.js"
"socket.io-client": "https://cdn.socket.io/4.8.1/socket.io.esm.min.js",
"@popperjs/core": "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js",
"bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.esm.min.js"
}
}
</script>
<script type="module">
import { init_starter } from "/static/retro.js";
init_starter();
init_starter("{{ url_for('createRetro') }}");
</script>
</body>

View File

@ -17,7 +17,7 @@
<div class="container text-center">
<div class="row">
<div class="col">
<h1>Retro "<span id="title"></span>" mit <span id="numberOfParticipants">?</span> Teilnehmenden</h1>
<h1>Retro "<span id="title"></span>" mit <span id="numberOfParticipants">?</span> Teilnehmenden (<span id="numberOfReadyParticipants">?</span> sind fertig)</h1>
</div>
</div>
<div class="row">
@ -27,7 +27,7 @@
<div class="card-body">
Teile folgenden link: <input id="shareLink" type="text" class="form-control" id="exampleFormControlInput1"
value="" />
<button type="button" class="btn btn-primary"
<button type="button" class="btn btn-outline-primary"
onclick="var copyTextarea = document.getElementById('shareLink');copyTextarea.focus();copyTextarea.select();document.execCommand('copy')">
<i class="bi bi-clipboard-plus"></i>
</button>
@ -48,14 +48,19 @@
<div class="card" style="height: 100%;">
<h5 class="card-header">Werkzeuge</h5>
<div class="card-body" style="align-content: center;">
<button type="button" class="btn btn-primary" id="exportButton" data-bs-toggle="modal"
<button type="button" class="btn btn-outline-primary" id="exportButton" data-bs-toggle="modal"
data-bs-target="#exportBackdrop">
<i class="bi bi-download"></i>
</button>
<button id="ReadyButton" type="button" class="btn btn-outline-primary">🏁</button>
<button id="help" type="button" class="btn btn-outline-primary" data-bs-toggle="modal"
data-bs-target="#helpBackdrop">
<i class="bi bi-patch-question"></i>
</button>
</div>
</div>
<div class="modal fade modal-xl" id="exportBackdrop" data-bs-backdrop="static" data-bs-keyboard="false"
<div class="modal fade modal-xl" id="exportBackdrop" data-bs-backdrop="static" data-bs-keyboard="true"
tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -72,53 +77,133 @@
})]);
}
</script>
<button type="button" class="btn btn-primary" onclick="foo()">
<button type="button" class="btn btn-outline-primary" onclick="foo()">
<i class="bi bi-clipboard-plus"></i>
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<div class="modal fade modal-xl" id="helpBackdrop" data-bs-backdrop="static" data-bs-keyboard="true"
tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Hilfe</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="text-align: left;">
<h2>Tastaturkürzel</h2>
überall ausser bei Textfeldern:
<ul>
<li><kbd>h</kbd>: Zeigt diese Hilfe an</li>
<li><kbd>+</kbd>: Stimmungswert um 1 Punkt anheben</li>
<li><kbd>-</kbd>: Stimmungswert um 1 Punkt absenken</li>
<li><kbd>g</kbd>: Lege guten Punkt an</li>
<li><kbd>s</kbd>: Lege schlechten Punkt an</li>
<li><kbd>t</kbd>: Lege ToDo an</li>
<li><kbd>r</kbd>: Markiere dich als "Fertig"</li>
<li><kbd>e</kbd>: Exportiere die Retro</li>
</ul>
In Textfeldern:
<ul>
<li><kbd>alt</kbd>+<kbd>enter</kbd></li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-3">
<div class="card">
<h5 class="card-header">Gut gelaufen</h5>
<div class="card-body">
<textarea class="form-control" id="positivTextarea" rows="2"></textarea>
<button type="submit" class="btn btn-primary mb-3" id="positivButton">
<div class="card-header" style="font-size: 1.25rem;">
Gut gelaufen
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#createGoodModal" style="margin-bottom: 0px !important;">
<i class="bi bi-file-earmark-plus"></i>
</button>
</div>
<div class="modal fade" id="createGoodModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="createGoodModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="createGoodModalLabel">Neuer positiver Punkt</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control" id="positivTextarea" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="positivButton" type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Anlegen</button>
</div>
</div>
</div>
</div>
</div>
<div id="goods" class="mb-3">
</div>
</div>
<div class="col-3">
<div class="card">
<h5 class="card-header">Schlecht gelaufen</h5>
<div class="card-body">
<textarea class="form-control" id="negativTextarea" rows="2"></textarea>
<button type="submit" class="btn btn-primary mb-3" id="negativButton"><i
class="bi bi-file-earmark-plus"></i></button>
<div class="card-header" style="font-size: 1.25rem;">
Schlecht gelaufen
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#createBadModal" style="margin-bottom: 0px !important;">
<i class="bi bi-file-earmark-plus"></i></button>
</div>
<div class="modal fade" id="createBadModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="createBadModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="createBadModalLabel">Neuer negativer Punkt</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control" id="negativTextarea" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="negativButton" type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Anlegen</button>
</div>
</div>
</div>
</div>
</div>
<div id="bads" class="mb-3">
</div>
</div>
<div class="col-3">
<div class="card">
<h5 class="card-header">ToDos</h5>
<div class="card-body">
<textarea class="form-control" id="todoTextarea" rows="2"></textarea>
<button type="submit" class="btn btn-primary mb-3" id="todoButton"><i
class="bi bi-file-earmark-plus"></i></button>
<div class="card-header" style="font-size: 1.25rem;">
ToDos
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#createTodoModal" style="margin-bottom: 0px !important;">
<i class="bi bi-file-earmark-plus"></i></button>
</div>
<div class="modal fade" id="createTodoModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="createTodoModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="createTodoModalLabel">Neues ToDo anlegen</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control" id="todoTextarea" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="todoButton" type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Anlegen</button>
</div>
</div>
</div>
</div>
</div>
<div id="todos" class="mb-3">
</div>
@ -139,7 +224,9 @@
<script type="importmap">
{
"imports": {
"socket.io-client": "https://cdn.socket.io/4.8.1/socket.io.esm.min.js"
"socket.io-client": "https://cdn.socket.io/4.8.1/socket.io.esm.min.js",
"@popperjs/core": "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js",
"bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.esm.min.js"
}
}
</script>

View File

@ -1,4 +1,5 @@
import { io, Socket } from "socket.io-client";
import { Modal } from "bootstrap";
interface ServerToClientEvents {
transmit_retro: (participantId: string, mood: number) => void;
@ -14,9 +15,10 @@ interface ClientToServerEvents {
comment_edited: (retroId: string, participantId: string, commentId: string, text: string) => void;
thumb_up: (retroId: string, participantId: string, commentId: string) => void;
thumb_down: (retroId: string, participantId: string, commentId: string) => void;
remark_added: (retroId: string, participantId: string, commentId: string, text: string) => void;
remark_added: (retroId: string, participantId: string, commentId: string, text: string) => void;
remark_removed: (retroId: string, participantId: string, commentId: string, remarkId: string) => void;
remark_edited: (retroId: string, participantId: string, commentId: string, remarkId: string, text: string) => void;
participant_ready: (retroId: string, participantId: string) => void;
}
class Remark {
@ -55,6 +57,7 @@ class Comment {
class Retro {
extId: string;
participants: [];
participants_ready: number;
moods: Map<string, number>;
good: Array<Comment>;
bad: Array<Comment>;
@ -66,6 +69,7 @@ class Retro {
this.title = data["title"];
this.remainingTime = data["remainingTime"];
this.extId = data["extId"];
this.participants_ready = data["participants_ready"];
this.participants = data["participants"];
this.moods = new Map();
for (let k in (data["moods"] as object)) {
@ -125,15 +129,16 @@ function displayRemarks(targetElement: HTMLElement, comment: Comment): string {
targetElement.innerHTML = "";
comment.remarks.forEach(function (remark: Remark) {
var deleteButtonId = `${remark.extId}_deleteButton`;
var editButtonId = `${remark.extId}_editButton`;
var editButtonId = `${remark.extId}Button`;
var textareaId = `${remark.extId}Textarea`;
targetElement.innerHTML += `
<div class="card">
<h5 class="card-header">
<div class="row">
<div class="col-3">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#editBd_${remark.extId}"><i class="bi bi-pencil-square"></i></button>
<button id="${deleteButtonId}" type="button" class="btn btn-danger btn-sm"><i class="bi bi-trash"></i></button>
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#editBd_${remark.extId}"><i class="bi bi-pencil-square"></i></button>
<button id="${deleteButtonId}" type="button" class="btn btn-outline-danger btn-sm"><i class="bi bi-trash"></i></button>
</div>
</div>
<div class="col-9">
@ -144,7 +149,7 @@ function displayRemarks(targetElement: HTMLElement, comment: Comment): string {
<p class="card-text">${remark.text}</p>
</div>
</div>
<div class="modal fade" id="editBd_${remark.extId}" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal fade" id="editBd_${remark.extId}" data-bs-backdrop="static" data-bs-keyboard="true" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@ -152,11 +157,11 @@ function displayRemarks(targetElement: HTMLElement, comment: Comment): string {
<button id="${editButtonId}_cancel2" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control" id="editTa_${remark.extId}" rows="2">${remark.text}</textarea>
<textarea class="form-control" id="${textareaId}" rows="2">${remark.text}</textarea>
</div>
<div class="modal-footer">
<button id="${editButtonId}_cancel" type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="${editButtonId}" type="button" class="btn btn-primary" data-bs-dismiss="modal">Speichern</button>
<button id="${editButtonId}_cancel" type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="${editButtonId}" type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Speichern</button>
</div>
</div>
</div>
@ -165,19 +170,23 @@ function displayRemarks(targetElement: HTMLElement, comment: Comment): string {
});
comment.remarks.forEach(function (remark: Remark) {
var deleteButtonId = `${remark.extId}_deleteButton`;
var editButtonId = `${remark.extId}_editButton`;
var editButtonId = `${remark.extId}Button`;
var textareaId = `${remark.extId}Textarea`;
document.getElementById(deleteButtonId).onclick = function () {
socket.emit('remark_removed', retroId, participantId, comment.extId, remark.extId);
};
document.getElementById(editButtonId).addEventListener("click", function () {
var newValue = (document.getElementById(`editTa_${remark.extId}`) as HTMLTextAreaElement).value;
var newValue = (document.getElementById(textareaId) as HTMLTextAreaElement).value;
socket.emit('remark_edited', retroId, participantId, comment.extId, remark.extId, newValue);
});
document.getElementById(editButtonId + "_cancel").addEventListener("click", function () {
(document.getElementById(`editTa_${remark.extId}`) as HTMLTextAreaElement).value = remark.text;
(document.getElementById(textareaId) as HTMLTextAreaElement).value = remark.text;
});
document.getElementById(editButtonId + "_cancel2").addEventListener("click", function () {
(document.getElementById(`editTa_${remark.extId}`) as HTMLTextAreaElement).value = remark.text;
(document.getElementById(textareaId) as HTMLTextAreaElement).value = remark.text;
});
document.getElementById(`editBd_${remark.extId}`).addEventListener('shown.bs.modal', function () {
document.getElementById(textareaId).focus()
});
});
return tmp;
@ -188,35 +197,36 @@ function create_or_update(comment: Comment, type: string) {
var cardContentId = `${cardId}_content`;
var cardCommentsId = `${cardId}_comments`;
var deleteButtonId = `${cardId}_deleteButton`;
var editButtonId = `${cardId}_editButton`;
var editButtonId = `${cardId}Button`;
var textareaId = `${cardId}Textarea`;
var thumbUpButtonId = `${cardId}_thumbUpButton`;
var thumbDownButtonId = `${cardId}_thumbDownButton`;
var thumbUpCounterId = `${cardId}_thumbUpCounter`;
var thumbDownCounterId = `${cardId}_thumbDownCounter`;
var addRemarkButtonId = `${cardId}_remarkButton`;
var addRemarkTextareaId = `${cardId}_remarkTextarea`;
var addRemarkButtonId = `${cardId}_remarkButton`;
var addRemarkTextareaId = `${cardId}_remarkTextarea`;
var buttons = "";
var modal = "";
if (comment.canEdit()) {
buttons = `
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#editBd_${comment.extId}"><i class="bi bi-pencil-square"></i></button>
<button id="${deleteButtonId}" type="button" class="btn btn-danger btn-sm"><i class="bi bi-trash"></i></button>
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#editBd_${comment.extId}"><i class="bi bi-pencil-square"></i></button>
<button id="${deleteButtonId}" type="button" class="btn btn-outline-danger btn-sm"><i class="bi bi-trash"></i></button>
</div>
`
modal = `<div class="modal fade" id="editBd_${comment.extId}" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
modal = `<div class="modal fade" id="editBd_${comment.extId}" data-bs-backdrop="static" data-bs-keyboard="true" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Eintrag bearbeiten</h1>
<button id="${editButtonId}_cancel2" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button id="${editButtonId}_cancel2" type="button" class="btn-outline-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control" id="editTa_${comment.extId}" rows="2">${comment.text}</textarea>
<textarea class="form-control" id="${textareaId}" rows="2">${comment.text}</textarea>
</div>
<div class="modal-footer">
<button id="${editButtonId}_cancel" type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="${editButtonId}" type="button" class="btn btn-primary" data-bs-dismiss="modal">Speichern</button>
<button id="${editButtonId}_cancel" type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="${editButtonId}" type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Speichern</button>
</div>
</div>
</div>
@ -227,21 +237,21 @@ function create_or_update(comment: Comment, type: string) {
<div id="${cardId}" class="card">
<h5 class="card-header">
<div class="row">
<div class="col-3">
<div class="col-4">
${buttons}
</div>
<div class="col-4">
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addRemarkBd_${comment.extId}">
<div class="col-2">
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addRemarkBd_${comment.extId}">
<i class="bi bi-chat-left-text"></i>
</button>
</div>
<div class="col-5">
<div class="col-6">
<div class="btn-group" role="group">
<button id="${thumbUpButtonId}" type="button" class="btn btn-success btn-sm">
<button id="${thumbUpButtonId}" type="button" class="btn btn-outline-success btn-sm">
<span id="${thumbUpCounterId}">${comment.up}</span>
<i class="bi bi-hand-thumbs-up"></i>
</button>
<button id="${thumbDownButtonId}" type="button" class="btn btn-success btn-sm">
<button id="${thumbDownButtonId}" type="button" class="btn btn-outline-success btn-sm">
<span id="${thumbDownCounterId}">${comment.down}</span>
<i class="bi bi-hand-thumbs-down"></i>
</button>
@ -256,19 +266,19 @@ function create_or_update(comment: Comment, type: string) {
</div>
</div>
</div>
<div class="modal fade" id="addRemarkBd_${comment.extId}" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal fade" id="addRemarkBd_${comment.extId}" data-bs-backdrop="static" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Bemerkung hinzufügen</h1>
<button id="${addRemarkButtonId}_cancel2" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button id="${addRemarkButtonId}_cancel2" type="button" class="btn-outline-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control" id="${addRemarkTextareaId}" rows="2"></textarea>
</div>
<div class="modal-footer">
<button id="${addRemarkButtonId}_cancel" type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="${addRemarkButtonId}" type="button" class="btn btn-primary" data-bs-dismiss="modal">Speichern</button>
<button id="${addRemarkButtonId}_cancel" type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button id="${addRemarkButtonId}" type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Speichern</button>
</div>
</div>
</div>
@ -293,14 +303,17 @@ function create_or_update(comment: Comment, type: string) {
socket.emit('comment_removed', retroId, participantId, comment.extId);
});
document.getElementById(editButtonId).addEventListener("click", function () {
var newValue = (document.getElementById(`editTa_${comment.extId}`) as HTMLTextAreaElement).value;
var newValue = (document.getElementById(textareaId) as HTMLTextAreaElement).value;
socket.emit('comment_edited', retroId, participantId, comment.extId, newValue);
});
document.getElementById(editButtonId + "_cancel").addEventListener("click", function () {
(document.getElementById(`editTa_${comment.extId}`) as HTMLTextAreaElement).value = comment.text;
(document.getElementById(textareaId) as HTMLTextAreaElement).value = comment.text;
});
document.getElementById(editButtonId + "_cancel2").addEventListener("click", function () {
(document.getElementById(`editTa_${comment.extId}`) as HTMLTextAreaElement).value = comment.text;
(document.getElementById(textareaId) as HTMLTextAreaElement).value = comment.text;
});
document.getElementById(`editBd_${comment.extId}`).addEventListener('shown.bs.modal', function () {
document.getElementById(textareaId).focus()
});
}
document.getElementById(thumbUpButtonId).addEventListener("click", function () {
@ -315,9 +328,11 @@ function create_or_update(comment: Comment, type: string) {
if (remarkTextarea.value.length > 0) {
socket.emit('remark_added', retroId, participantId, comment.extId, remarkTextarea.value);
remarkTextarea.value = "";
}
}
};
document.getElementById(`addRemarkBd_${comment.extId}`).addEventListener('shown.bs.modal', function () {
document.getElementById(addRemarkTextareaId).focus()
});
}
document.getElementById(cardContentId).innerHTML = comment.text;
document.getElementById(thumbUpCounterId).innerHTML = comment.up.toString();
@ -338,9 +353,11 @@ function calc_time(seconds: number): string {
}
function update_retro(retro: Retro, own_participant_id: string) {
console.log(retro);
// render number of participants
(document.getElementById('title') as HTMLParagraphElement).textContent = retro.title;
(document.getElementById('numberOfParticipants') as HTMLParagraphElement).textContent = retro.participants.toString();
(document.getElementById('numberOfReadyParticipants') as HTMLParagraphElement).textContent = retro.participants_ready.toString();
(document.getElementById('timer') as HTMLDivElement).textContent = calc_time(Math.max(retro.remainingTime, 0));
(document.getElementById('timer') as HTMLDivElement).setAttribute("val", retro.remainingTime.toString());
var shareLink = window.location.href;
@ -413,6 +430,79 @@ export function init_retro(_retroId: string, _participantId: string) {
"retroId": retroId
}
});
document.onkeydown = function (ev: KeyboardEvent) {
//@ts-ignore
var nodeName: string = ev.target.nodeName;
//@ts-ignore
var targetId: string = ev.target.id;
if(nodeName == "TEXTAREA" && ev.altKey) {
ev.stopPropagation();
ev.preventDefault();
return false;
}
}
document.onkeyup = function (ev: KeyboardEvent) {
// console.log(ev)
//@ts-ignore
var nodeName: string = ev.target.nodeName;
//@ts-ignore
var targetId: string = ev.target.id;
switch (nodeName) {
case "TEXTAREA":
switch (ev.key) {
case "Enter":
if (ev.altKey) {
ev.stopPropagation();
ev.preventDefault();
var buttonId = targetId.replace("Textarea", "Button");
document.getElementById(buttonId).click();
}
break;
default:
break;
}
break;
default:
switch (ev.key) {
case "r":
ev.preventDefault();
document.getElementById("ReadyButton").click();
break;
case "+":
ev.preventDefault();
socket.emit('mood_change', retroId, participantId, Number.parseInt(rangeInput.value) + 1);
break;
case "-":
ev.preventDefault();
socket.emit('mood_change', retroId, participantId, Number.parseInt(rangeInput.value) - 1);
break;
case "e":
ev.preventDefault();
document.getElementById("exportButton").click();
break;
case "g":
ev.preventDefault();
(new Modal("#createGoodModal", {})).toggle();
break;
case "h":
ev.preventDefault();
(new Modal("#helpBackdrop", {})).toggle();
break;
case "s":
ev.preventDefault();
(new Modal("#createBadModal", {})).toggle();
break;
case "t":
ev.preventDefault();
(new Modal("#createTodoModal", {})).toggle();
break;
default:
break;
}
break;
}
}
const exportButton = document.getElementById('exportButton')! as HTMLButtonElement;
exportButton.addEventListener('click', function (this: HTMLButtonElement) {
@ -465,6 +555,57 @@ export function init_retro(_retroId: string, _participantId: string) {
JSON.stringify(retro);
})
// const exportButton = document.getElementById('exportButton')! as HTMLButtonElement;
// exportButton.addEventListener('click', function (this: HTMLButtonElement) {
// function showComments(comments: Comment[]): string {
// var exportText = ``;
// if (comments.length > 0) {
// exportText = `<ul>`;
// for (let index = 0; index < comments.length; index++) {
// const comment = comments[index];
// exportText += `<li>${comment.text} (${comment.up} 👍/ ${comment.down} 👎)`;
// if (comment.remarks.length > 0) {
// exportText += `<ul>`;
// for (let remarkIndex = 0; remarkIndex < comment.remarks.length; remarkIndex++) {
// const remark = comment.remarks[remarkIndex];
// exportText += `<li>${remark.text}</li>`
// }
// exportText += `</ul>`;
// }
// exportText += `</li>`
// }
// exportText += `</ul>`;
// } else {
// exportText = "Es wurden keine Punkte eingebracht."
// }
// return exportText;
// }
// const exportDiv = document.getElementById("exportDivId")! as HTMLDivElement;
// var exportText = `<h1>Zusammenfassung für '${retro.title}'</h1>`;
// exportText += `<h2>Gut gelaufen</h2>`;
// exportText += showComments(retro.good);
// exportText += `<h2>Schlecht gelaufen</h2>`;
// exportText += showComments(retro.bad);
// exportText += `<h2>Todos</h2>`;
// exportText += showComments(retro.todo);
// exportText += `<h2>Teamstimmung</h2>`;
// exportText += `<div>`;
// if (retro.moods.size > 0) {
// var sum = 0;
// var values = "";
// retro.moods.forEach(function name(value: number) {
// sum += value;
// })
// var moods: number[] = Array.from(retro.moods.values());
// exportText += `Durchschnittliche Stimmung: ${sum / retro.moods.size} (Einzelne Werte: ${moods.join(", ")})`;
// } else {
// exportText += `Durchschnittliche Stimmung kann aufgrund fehlender Werte nicht berechnet werden`;
// }
// exportText += `</div>`;
// exportDiv.innerHTML = exportText;
// JSON.stringify(retro);
// })
// handle positiv button clicks to submit stuff positivButton
const positivButton = document.getElementById('positivButton')! as HTMLButtonElement;
positivButton.addEventListener('click', function (this: HTMLButtonElement) {
@ -474,6 +615,15 @@ export function init_retro(_retroId: string, _participantId: string) {
positivTextarea.value = "";
}
})
document.getElementById("createGoodModal").addEventListener('shown.bs.modal', function () {
document.getElementById("positivTextarea").focus()
});
document.getElementById("createBadModal").addEventListener('shown.bs.modal', function () {
document.getElementById("negativTextarea").focus()
});
document.getElementById("createTodoModal").addEventListener('shown.bs.modal', function () {
document.getElementById("todoTextarea").focus()
});
const negativButton = document.getElementById('negativButton')! as HTMLButtonElement;
negativButton.addEventListener('click', function (this: HTMLButtonElement) {
@ -493,6 +643,11 @@ export function init_retro(_retroId: string, _participantId: string) {
}
})
const readyButton = document.getElementById('ReadyButton')! as HTMLButtonElement;
readyButton.onclick=function (this: HTMLButtonElement) {
socket.emit('participant_ready', retroId, participantId);
};
// handle the mood slider
const rangeInput = document.getElementById('moodRange')! as HTMLInputElement;
const rangeOutput = document.getElementById('moodValue')!;
@ -518,7 +673,7 @@ export function init_retro(_retroId: string, _participantId: string) {
}, 1000)
}
export function init_starter() {
export function init_starter(url: string) {
const link = document.getElementById('starter')! as HTMLLinkElement;
link.addEventListener("click", function () {
const duration_input = document.getElementById('duration')! as HTMLInputElement;
@ -526,7 +681,7 @@ export function init_starter() {
const title_input = document.getElementById('title')! as HTMLInputElement;
const title = title_input.value;
const link = document.getElementById('starter')! as HTMLLinkElement;
link.href = link.href + "?duration=" + (duration * 60) + "&title=" + title;
link.href = url + "?duration=" + (duration * 60) + "&title=" + title;
return false;
});
}