Compare commits

...

11 commits

Author SHA1 Message Date
Maximilian Kratz 3907bbb213 Merge branch 'main' into feature/github-ci 2024-04-21 14:15:57 +02:00
Alex Andres b9701a91d4
presenter: disable focus for tabbed panes 2024-04-20 15:24:58 +02:00
Alex Andres 6fc39584f2
presenter: fixed missing default access link in publisher api 2024-04-20 15:06:32 +02:00
Alex Andres 3017e56d38
Deps/update (#901)
* build(deps-dev): bump express in /lect-player-web/src/main/frontend (#888)

Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump webpack-dev-middleware (#887)

Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump follow-redirects (#886)

Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump org.apache.tomcat.embed:tomcat-embed-websocket (#895)

Bumps org.apache.tomcat.embed:tomcat-embed-websocket from 10.1.10 to 10.1.19.

---
updated-dependencies:
- dependency-name: org.apache.tomcat.embed:tomcat-embed-websocket
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-20 14:27:39 +02:00
Alex Andres 3670addb1b
Merge pull request #890 from lectureStudio/feature/next
presenter: added Bachelorpraktikum chat extension
2024-04-20 13:38:39 +02:00
Alex Andres 159f46a59c
presenter: fixed slide overlay detection for some slide sets #889 2024-04-18 17:19:20 +02:00
Alex Andres 9bfe08f8f3
presenter: fixed opening whiteboard via toolbar #894 2024-04-18 13:40:02 +02:00
Alex Andres 5381908205
presenter: minor translation fixes 2024-04-18 13:32:11 +02:00
Alex Andres af1ed754ee
presenter: fixed showing external window when the screen does not exist 2024-04-18 13:29:33 +02:00
Alex Andres 9a31df9413
presenter: fixed edited chat message to slide conversion 2024-04-18 12:42:21 +02:00
Alex Andres af82092ccb
presenter: added Bachelorpraktikum chat extension 2024-04-07 20:19:43 +02:00
48 changed files with 1071 additions and 221 deletions

View file

@ -28,7 +28,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;
import org.lecturestudio.core.geometry.Dimension2D;
import org.lecturestudio.core.geometry.Rectangle2D;
@ -758,8 +757,8 @@ public class Document {
pages.clear();
int pageCount = pdfDocument.getPageCount();
String[] prevSplitPageText = new String[2];
String[] splitPageText;
List<String> lastPageTextLines = List.of();
for (int number = 0; number < pageCount; number++) {
Page page = new Page(this, number);
@ -772,26 +771,24 @@ public class Document {
}
}
splitPageText = page.getPageText().split("\n");
List<String> pageTextLines = pdfDocument.getPageTextLines(number, 2);
if (splitPageText.length >= 2 && prevSplitPageText.length >= 2 &&
Stream.of(prevSplitPageText[0], prevSplitPageText[1], splitPageText[0], splitPageText[1])
.allMatch(Objects::nonNull)) {
if (prevSplitPageText[0].equals(splitPageText[0]) && prevSplitPageText[1].equals(splitPageText[1])) {
page.setOverlay(true);
List<String> finalLastPageTextLines = lastPageTextLines;
List<String> differences = pageTextLines.stream()
.filter(element -> !finalLastPageTextLines.contains(element)).toList();
if (number > 0) {
Page prevPage = pages.get(number - 1);
prevPage.setOverlay(true);
}
}
else {
prevSplitPageText = splitPageText;
// The pages have equal content, thus mark them as overlay pages.
if (differences.isEmpty()) {
page.setOverlay(true);
if (number > 0) {
Page prevPage = pages.get(number - 1);
prevPage.setOverlay(true);
}
}
else {
prevSplitPageText = splitPageText;
}
lastPageTextLines = pageTextLines;
pages.add(page);
}

View file

@ -130,6 +130,16 @@ public interface DocumentAdapter {
*/
String getPageText(int pageNumber) throws IOException;
/**
* Get the text of the page as a list where each list entry represents a text line in the page.
*
* @param pageNumber The page number.
* @param maxLines The maximum number of lines to retrieve.
*
* @return the text of the page as a list of text lines in the page.
*/
List<String> getPageTextLines(int pageNumber, int maxLines);
/**
* Get the word bounds of the page that has the specified page number.
*

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.core.pdf;
public interface DocumentPlatformQueue {
void runInPlatformQueue(Runnable runnable);
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.core.pdf;
import java.util.concurrent.LinkedBlockingQueue;
public class DocumentWorker {
private final DocumentPlatformQueue eventQueue;
protected LinkedBlockingQueue<DocumentWorkerTask> queue;
protected boolean alive;
protected Thread thread;
public DocumentWorker(DocumentPlatformQueue eventQueue) {
this.eventQueue = eventQueue;
queue = new LinkedBlockingQueue<>();
thread = new Thread(this::run);
}
public void start() {
alive = true;
thread.start();
}
public void stop() {
alive = false;
thread.interrupt();
}
public void add(DocumentWorkerTask task) {
try {
queue.put(task);
}
catch (InterruptedException x) {
// Ignore
}
}
public void run() {
while (alive) {
final DocumentWorkerTask task;
try {
task = queue.take();
}
catch (InterruptedException x) {
break;
}
try {
task.runInBackground();
eventQueue.runInPlatformQueue(task);
}
catch (final Throwable t) {
eventQueue.runInPlatformQueue(() -> task.exception(t));
}
}
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.lecturestudio.core.pdf;
public abstract class DocumentWorkerTask implements Runnable {
/**
* This method will be executed on the background thread.
*/
public void runInBackground() {
}
/**
* This method will be executed on the UI thread if work() did not throw any exception.
*/
public void run() {
}
/**
* This method will be executed on the UI thread if work() throws an exception.
*
* @param t The throwable containing the error.
*/
public void exception(final Throwable t) {
}
}

View file

@ -317,6 +317,18 @@ public class PdfDocument {
return muPDFDocument.getPageText(pageNumber);
}
/**
* Get the text of the page as a list where each list entry represents a text line in the page.
*
* @param pageNumber The page number.
* @param maxLines The maximum number of lines to retrieve.
*
* @return the text of the page as a list of text lines in the page.
*/
public List<String> getPageTextLines(int pageNumber, int maxLines) {
return muPDFDocument.getPageTextLines(pageNumber, maxLines);
}
/**
* Get the word bounds of the page that has the specified page number.
*

View file

@ -41,14 +41,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.io.BitConverter;
@ -201,7 +196,6 @@ public class MuPDFDocument implements DocumentAdapter {
Page page = getPage(pageNumber);
SimpleTextWalker textWalker = new SimpleTextWalker(page.getBounds());
StructuredText structuredText = displayList.toStructuredText();
structuredText.walk(textWalker);
@ -496,6 +490,62 @@ public class MuPDFDocument implements DocumentAdapter {
}
}
@Override
public List<String> getPageTextLines(int pageNumber, int maxLines) {
synchronized (mutex) {
List<String> lines = new ArrayList<>();
DisplayList displayList = getDisplayList(pageNumber);
StructuredText structuredText = displayList.toStructuredText();
// Sort page text blocks, so that the page text is chronologically ordered (top-down).
var sorted = Arrays.stream(structuredText.getBlocks())
.sorted((o1, o2) -> o1.bbox.y0 < o2.bbox.y0 ? -1 : 1)
.toList();
// Last text block y-position.
double lastY0 = Double.MAX_VALUE;
double lastY1 = Double.MAX_VALUE;
// Read text lines from each block.
for (var block : sorted) {
// Check if the blocks intersect each other.
boolean intersects = (block.bbox.y0 >= lastY0 && block.bbox.y0 <= lastY1)
|| (block.bbox.y1 >= lastY0 && block.bbox.y1 <= lastY1);
if (block.lines.length > 0) {
StructuredText.TextLine textLine = block.lines[0];
if (textLine.chars.length > 0) {
// Convert individual chars into lines of strings.
var text = Arrays.stream(textLine.chars)
.map(textChar -> Character.toString(textChar.c))
.collect(Collectors.joining(""));
// If the blocks intersect, merge the text into a single line.
if (intersects) {
// Get last line text.
int index = lines.size() - 1;
String lastLine = lines.get(index) + text;
lines.set(index, lastLine);
}
else {
lines.add(text);
}
if (lines.size() >= maxLines) {
break;
}
}
}
lastY0 = block.bbox.y0;
lastY1 = block.bbox.y1;
}
return lines;
}
}
/**
* Get the display list of the {@link PageEntry} that is mapped to the specified page number.
*

View file

@ -605,6 +605,11 @@ public class PDFBoxDocument implements DocumentAdapter {
return shapes;
}
@Override
public List<String> getPageTextLines(int pageNumber, int maxLines) {
return List.of();
}
private void loadOutline(PDDocumentCatalog catalog, PDOutlineItem item,
DocumentOutlineItem outline) {
if (isNull(item)) {

View file

@ -125,7 +125,7 @@ public class DocumentService {
final File file = new File(nonNull(templatePath) ? templatePath : "");
return CompletableFuture.supplyAsync(() -> {
// Search for a opened whiteboard.
// Search for an opened whiteboard.
Document whiteboard = documents.getFirstWhiteboard();
// If there isn't any whiteboard, then create one.

View file

@ -22,6 +22,9 @@ button.download = Herunterladen
button.update = Aktualisieren
button.record = Aufzeichnen
label.reply = antwortet
label.edited = Bearbeitet
file.description.pdf = PDF-Folien
file.description.recording = Vorlesungsaufzeichnung
file.description.wav = Wave Sound

View file

@ -22,6 +22,9 @@ button.download = Download
button.update = Update
button.record = Record
label.reply = replies
label.edited = Edited
file.description.pdf = PDF Slides
file.description.recording = Lecture Recording
file.description.wav = Wave Sound

View file

@ -2129,43 +2129,6 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dev": true,
"requires": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
}
}
},
"bonjour-service": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz",
@ -2595,12 +2558,6 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
"cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"dev": true
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -3161,17 +3118,17 @@
}
},
"express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@ -3205,6 +3162,32 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"dev": true
},
"body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
}
},
"cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -3219,6 +3202,18 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
}
}
},
@ -3345,9 +3340,9 @@
}
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true
},
"for-each": {
@ -4993,18 +4988,6 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true
},
"raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dev": true,
"requires": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -6336,9 +6319,9 @@
}
},
"webpack-dev-middleware": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
"integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
"integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"requires": {
"colorette": "^2.0.10",
@ -6376,15 +6359,15 @@
"dev": true
},
"schema-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
"integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.8.0",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.0.0"
"ajv-keywords": "^5.1.0"
}
}
}

View file

@ -21,6 +21,7 @@ package org.lecturestudio.presenter.api.context;
import static java.util.Objects.isNull;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -68,6 +69,8 @@ public class PresenterContext extends ApplicationContext {
private final ObservableList<MessengerMessage> messengerMessages = new ObservableArrayList<>();
private final List<MessengerMessage> allReceivedMessengerMessages = new ArrayList<>();
private final IntegerProperty messageCount = new IntegerProperty();
private final ObservableList<SpeechRequestMessage> speechRequests = new ObservableArrayList<>();
@ -187,6 +190,10 @@ public class PresenterContext extends ApplicationContext {
return messengerMessages;
}
public List<MessengerMessage> getAllReceivedMessengerMessages() {
return allReceivedMessengerMessages;
}
public List<SpeechRequestMessage> getSpeechRequests() {
return speechRequests;
}

View file

@ -1,6 +1,7 @@
package org.lecturestudio.presenter.api.event;
public class ExternalMessagesViewEvent extends ExternalViewEvent {
public ExternalMessagesViewEvent(boolean enabled) {
super(enabled);
}

View file

@ -120,10 +120,10 @@ public abstract class CefStreamPresenter<T extends View> extends Presenter<T> {
// Open the running course url.
StreamConfiguration streamConfig = ctx.getConfiguration().getStreamConfig();
long courseId = ctx.getCourse().getId();
String accessLink = ctx.getCourse().getDefaultAccessLink();
String serverName = streamConfig.getServerName();
String serverUrl = String.format("https://%s", serverName);
String courseApiUrl = String.format("%s/course/api/%d", serverUrl, courseId);
String courseApiUrl = String.format("%s/course/api/%s", serverUrl, accessLink);
browser = client.createBrowser(courseApiUrl, false, false);
}

View file

@ -95,9 +95,11 @@ import org.lecturestudio.presenter.api.service.ScreenSourceService;
import org.lecturestudio.presenter.api.service.StreamService;
import org.lecturestudio.presenter.api.handler.shutdown.SaveDocumentsHandler;
import org.lecturestudio.presenter.api.handler.shutdown.SaveRecordingHandler;
import org.lecturestudio.presenter.api.service.WebService;
import org.lecturestudio.presenter.api.util.ScreenDocumentCreator;
import org.lecturestudio.presenter.api.view.MainView;
import org.lecturestudio.web.api.exception.StreamMediaException;
import org.lecturestudio.web.api.message.StopStreamEnvironmentMessage;
public class MainPresenter extends org.lecturestudio.core.presenter.MainPresenter<MainView> implements ViewHandler {
@ -131,6 +133,8 @@ public class MainPresenter extends org.lecturestudio.core.presenter.MainPresente
private final ScreenShareService screenShareService;
private final WebService webService;
private SlidesPresenter slidesPresenter;
/** The waiting notification. */
@ -147,7 +151,8 @@ public class MainPresenter extends org.lecturestudio.core.presenter.MainPresente
BookmarkService bookmarkService,
RecordingService recordingService,
StreamService streamService,
ScreenShareService screenShareService) {
ScreenShareService screenShareService,
WebService webService) {
super(context, view);
this.audioSystemProvider = audioSystemProvider;
@ -160,6 +165,7 @@ public class MainPresenter extends org.lecturestudio.core.presenter.MainPresente
this.streamService = streamService;
this.screenSourceService = new ScreenSourceService();
this.screenShareService = screenShareService;
this.webService = webService;
this.viewMap = new ObservableHashMap<>();
this.shortcutMap = new HashMap<>();
this.contexts = new ArrayList<>();
@ -336,6 +342,7 @@ public class MainPresenter extends org.lecturestudio.core.presenter.MainPresente
if (state == ExecutableState.Stopped) {
PresenterContext presenterContext = (PresenterContext) context;
presenterContext.getMessengerMessages().clear();
presenterContext.getAllReceivedMessengerMessages().clear();
// destroyHandler(MessengerWindowPresenter.class);
}
@ -455,6 +462,33 @@ public class MainPresenter extends org.lecturestudio.core.presenter.MainPresente
}
}
@Subscribe
public void onEvent(StopStreamEnvironmentMessage event) {
//This method tries to force the stop of all stream components (stream, messenger, quiz) even if they might be inactive from this side
final String initiator = String.format("%s %s", event.getFirstName(), event.getFamilyName());
try {
webService.stopQuiz();
}
catch (Exception exception) {
logException(exception, "Stop quiz failed");
}
try {
webService.stopMessenger();
}
catch (Exception exception) {
logException(exception, "Stop messenger failed");
}
((PresenterContext) context).setStreamStarted(false);
context.showNotification(NotificationType.WARNING,
"stream.environment.stopped.by.message.title",
"stream.environment.stopped.by.message",
initiator);
}
@Override
public void addShutdownHandler(ShutdownHandler handler) {
requireNonNull(handler, "ShutdownHandler must not be null.");

View file

@ -28,9 +28,11 @@ import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.presenter.Presenter;
import org.lecturestudio.core.view.ViewLayer;
import org.lecturestudio.presenter.api.context.PresenterContext;
import org.lecturestudio.presenter.api.view.MessengerWindow;
import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.message.SpeechRequestMessage;
import org.lecturestudio.web.api.message.util.MessageUtil;
public class MessengerWindowPresenter extends Presenter<MessengerWindow> {
@ -81,6 +83,26 @@ public class MessengerWindowPresenter extends Presenter<MessengerWindow> {
requireNonNull(message);
view.setMessengerMessage(message);
if(message.isDeleted()) {
view.removeMessengerMessage(message.getMessageId());
return;
}
if(message.isEdited()) {
view.setModifiedMessengerMessage(message);
return;
}
if(MessageUtil.isReply(message)) {
final MessengerMessage messageToReplyTo = MessageUtil.findMessageToReplyTo(
((PresenterContext) context).getAllReceivedMessengerMessages(),
message);
view.setMessengerMessageAsReply(message, messageToReplyTo);
}
else {
view.setMessengerMessage(message);
}
}
@Subscribe

View file

@ -124,6 +124,7 @@ import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.message.SpeechBaseMessage;
import org.lecturestudio.web.api.message.SpeechCancelMessage;
import org.lecturestudio.web.api.message.SpeechRequestMessage;
import org.lecturestudio.web.api.message.util.MessageUtil;
import org.lecturestudio.web.api.model.Message;
import org.lecturestudio.web.api.model.ScreenSource;
import org.lecturestudio.web.api.service.ServiceParameters;
@ -166,6 +167,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
private final RecordingService recordingService;
private final UserPrivilegeService userPrivilegeService;
private StylusHandler stylusHandler;
private PageEditedListener pageEditedListener;
@ -184,8 +187,6 @@ public class SlidesPresenter extends Presenter<SlidesView> {
private SelectionIdleTimer idleTimer;
private final UserPrivilegeService userPrivilegeService;
@Inject
SlidesPresenter(ApplicationContext context, SlidesView view,
@ -197,10 +198,10 @@ public class SlidesPresenter extends Presenter<SlidesView> {
DocumentService documentService,
DocumentRecorder documentRecorder,
RecordingService recordingService,
UserPrivilegeService userPrivilegeService,
WebService webService,
WebServiceInfo webServiceInfo,
WebRtcStreamService streamService,
UserPrivilegeService userPrivilegeService) {
WebRtcStreamService streamService) {
super(context, view);
this.viewFactory = viewFactory;
@ -211,6 +212,7 @@ public class SlidesPresenter extends Presenter<SlidesView> {
this.bookmarkService = bookmarkService;
this.documentService = documentService;
this.recordingService = recordingService;
this.userPrivilegeService = userPrivilegeService;
this.webService = webService;
this.webServiceInfo = webServiceInfo;
this.streamService = streamService;
@ -219,7 +221,6 @@ public class SlidesPresenter extends Presenter<SlidesView> {
this.pageObjectRegistry = new PageObjectRegistry();
this.documentChangeListener = new DocumentChangeHandler();
this.screenViewContext = new ScreenPresentationViewContext();
this.userPrivilegeService = userPrivilegeService;
}
@Subscribe
@ -356,9 +357,32 @@ public class SlidesPresenter extends Presenter<SlidesView> {
requireNonNull(message);
PresenterContext presenterContext = (PresenterContext) context;
presenterContext.getMessengerMessages().add(message);
view.setMessengerMessage(message);
if (message.isDeleted()) {
onDiscardMessage(message);
view.removeMessengerMessage(message.getMessageId());
return;
}
if (message.isEdited()) {
view.setModifiedMessengerMessage(message);
MessageUtil.updateOutdatedMessage(presenterContext.getMessengerMessages(), message);
MessageUtil.updateOutdatedMessage(presenterContext.getAllReceivedMessengerMessages(), message);
return;
}
presenterContext.getMessengerMessages().add(message);
presenterContext.getAllReceivedMessengerMessages().add(message);
if (MessageUtil.isReply(message)) {
final MessengerMessage messageToReplyTo = MessageUtil.findMessageToReplyTo(
((PresenterContext) context).getAllReceivedMessengerMessages(),
message);
view.setMessengerMessageAsReply(message, messageToReplyTo);
}
else {
view.setMessengerMessage(message);
}
}
@Subscribe
@ -1467,7 +1491,9 @@ public class SlidesPresenter extends Presenter<SlidesView> {
return;
}
action.accept(config.isEnabled(), checkIfScreenInList(list, config.getScreen()));
checkScreenExists(config);
action.accept(config.isEnabled(), true);
}
});
}
@ -1502,9 +1528,9 @@ public class SlidesPresenter extends Presenter<SlidesView> {
}
private void showExternalScreen(ExternalWindowConfiguration config, BiConsumer<Boolean, Boolean> action) {
final ObservableList<Screen> screens = presentationController.getScreens();
checkScreenExists(config);
action.accept(config.isEnabled(), checkIfScreenInList(screens, config.getScreen()));
action.accept(config.isEnabled(), true);
}
private void hideExternalScreens() {
@ -1513,6 +1539,14 @@ public class SlidesPresenter extends Presenter<SlidesView> {
view.hideExternalSpeech();
}
private void checkScreenExists(ExternalWindowConfiguration config) {
final ObservableList<Screen> screens = presentationController.getScreens();
if (!checkIfScreenInList(screens, config.getScreen())) {
config.setScreen(null);
}
}
private void viewShowExternalMessages(boolean persistent) {
final ExternalWindowConfiguration config = getExternalMessagesConfig();
@ -1520,6 +1554,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
config.setEnabled(true);
}
checkScreenExists(config);
view.showExternalMessages(config.getScreen(), config.getPosition(), config.getSize());
}
@ -1540,6 +1576,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
config.setEnabled(true);
}
checkScreenExists(config);
view.showExternalParticipants(config.getScreen(), config.getPosition(), config.getSize());
}
@ -1560,6 +1598,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
config.setEnabled(true);
}
checkScreenExists(config);
view.showExternalSlidePreview(config.getScreen(), config.getPosition(), config.getSize());
}
@ -1580,6 +1620,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
config.setEnabled(true);
}
checkScreenExists(config);
view.showExternalSpeech(config.getScreen(), config.getPosition(), config.getSize());
}
@ -1600,6 +1642,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
config.setEnabled(true);
}
checkScreenExists(config);
view.showExternalNotes(config.getScreen(), config.getPosition(), config.getSize());
}
@ -1620,6 +1664,8 @@ public class SlidesPresenter extends Presenter<SlidesView> {
config.setEnabled(true);
}
checkScreenExists(config);
view.showExternalSlideNotes(config.getScreen(), config.getPosition(), config.getSize());
}
@ -1637,7 +1683,7 @@ public class SlidesPresenter extends Presenter<SlidesView> {
if (screen == null) {
return true;
}
return screens.stream().anyMatch(scrn -> scrn.equals(screen));
return screens.stream().anyMatch(s -> s.equals(screen));
}
private void recordPage(Page page) {

View file

@ -310,7 +310,7 @@ public class ToolbarPresenter extends Presenter<ToolbarView> {
String template = config.getTemplateConfig()
.getWhiteboardTemplateConfig().getTemplatePath();
documentService.openWhiteboard(template).join();
documentService.openWhiteboard(template);
}
else {
documentService.selectDocument(documents.getLastNonWhiteboard());

View file

@ -38,6 +38,7 @@ import org.lecturestudio.presenter.api.event.MessengerStateEvent;
import org.lecturestudio.presenter.api.event.QuizStateEvent;
import org.lecturestudio.presenter.api.net.LocalBroadcaster;
import org.lecturestudio.web.api.client.TokenProvider;
import org.lecturestudio.web.api.message.StopStreamEnvironmentMessage;
import org.lecturestudio.web.api.message.CoursePresenceMessage;
import org.lecturestudio.web.api.message.MessageTransport;
import org.lecturestudio.web.api.message.SpeechBaseMessage;
@ -364,6 +365,9 @@ public class WebService extends ExecutableBase {
messageTransport.addListener(SpeechBaseMessage.class, message -> {
context.getEventBus().post(message);
});
messageTransport.addListener(StopStreamEnvironmentMessage.class, message -> {
context.getEventBus().post(message);
});
}
}

View file

@ -27,6 +27,12 @@ public interface MessengerWindow extends View {
void setMessengerMessage(MessengerMessage message);
void setMessengerMessageAsReply(MessengerMessage message, MessengerMessage messageToReplyTo);
void setModifiedMessengerMessage(MessengerMessage modifiedMessage);
void removeMessengerMessage(String messageId);
void setSpeechRequestMessage(SpeechRequestMessage message);
void setTextSize(double size);

View file

@ -102,6 +102,12 @@ public interface SlidesView extends View {
void setMessengerMessage(MessengerMessage message);
void removeMessengerMessage(String messageId);
void setMessengerMessageAsReply(MessengerMessage message, MessengerMessage messageToReplyTo);
void setModifiedMessengerMessage(MessengerMessage modifiedMessage);
void addSpeechRequest(SpeechBaseMessage message);
void removeSpeechRequest(SpeechBaseMessage message);

View file

@ -1,5 +1,5 @@
audio.device.connected = Audiogerät verbunden
audio.device.disconnected = Audiogerät getrennt
audio.device.connected = Audioger\u00e4t verbunden
audio.device.disconnected = Audioger\u00e4t getrennt
bookmark.assign.warning = Lesezeichen konnte nicht erstellt werden
bookmark.delete.error = Lesezeichen konnte nicht gel\u00f6scht werden
@ -16,7 +16,7 @@ document.save.lecture = Vorlesung
error = Fehler
generic.error = Bitte kontaktieren Sie Ihren Systemadministrator. Vollst\u00e4ndige Details finden Sie im Anwendungsprotokoll.
please.wait = Einen Moment bitte...
please.wait = Einen Moment bitte ...
messenger.starting = Der Messenger wird gestartet
messenger.start.error = Messenger konnte nicht gestartet werden
@ -76,6 +76,7 @@ text.message = Nachricht
text.message.time = Datum / Uhrzeit
text.messages = Textnachrichten
text.message.me = Ich
text.message.me.dative = Mir
text.message.privately = Privat
text.message.recipient = {0} an {1}
text.message.to.me = mich
@ -144,4 +145,7 @@ shortcut.color.one = Farbe 1
shortcut.color.two = Farbe 2
shortcut.color.three = Farbe 3
shortcut.color.four = Farbe 4
shortcut.color.five = Farbe 5
shortcut.color.five = Farbe 5
stream.environment.stopped.by.message.title = Stream-Umgebung stoppen
stream.environment.stopped.by.message = Stoppen der Stream-Umgebung bestehend aus Quiz, Messenger und Stream wurde initiiert. Initiator: {0}

View file

@ -16,7 +16,7 @@ document.save.lecture = Lecture
error = Error
generic.error = Please contact your system administrator. For complete details, see the application log.
please.wait = One moment please...
please.wait = One moment please ...
messenger.starting = Starting Messenger
messenger.start.error = Messenger could not be started
@ -76,6 +76,7 @@ text.message = Message
text.message.time = Date / Time
text.messages = Text Messages
text.message.me = Me
text.message.dative = Me
text.message.privately = Privately
text.message.recipient = {0} to {1}
text.message.to.me = me
@ -144,4 +145,7 @@ shortcut.color.one = Color 1
shortcut.color.two = Color 2
shortcut.color.three = Color 3
shortcut.color.four = Color 4
shortcut.color.five = Color 5
shortcut.color.five = Color 5
stream.environment.stopped.by.message.title = Stream-environment stopped
stream.environment.stopped.by.message = Stop of Stream-environment consisting of quiz, messenger and stream was initiated. Initiator: {0}

View file

@ -65,7 +65,11 @@ import org.lecturestudio.presenter.api.context.PresenterContext;
import org.lecturestudio.presenter.api.input.Shortcut;
import org.lecturestudio.presenter.api.net.LocalBroadcaster;
import org.lecturestudio.presenter.api.recording.FileLectureRecorder;
import org.lecturestudio.presenter.api.service.*;
import org.lecturestudio.presenter.api.service.BookmarkService;
import org.lecturestudio.presenter.api.service.RecordingService;
import org.lecturestudio.presenter.api.service.WebRtcStreamService;
import org.lecturestudio.presenter.api.service.WebService;
import org.lecturestudio.presenter.api.service.WebServiceInfo;
import org.lecturestudio.presenter.api.view.MainView;
import org.lecturestudio.presenter.api.view.QuitRecordingView;
import org.lecturestudio.presenter.api.view.RestoreRecordingView;
@ -196,7 +200,7 @@ class MainPresenterTest extends PresenterTest {
public <T> T getInstance(Class<T> cls) {
if (cls == SlidesPresenter.class) {
ToolController toolController = new ToolController(context, documentService);
return (T) new SlidesPresenter(context, createProxy(SlidesView.class), null, toolController, presentationController, null, bookmarkService, documentService, documentRecorder, recordingService, webService, webServiceInfo, streamService, null);
return (T) new SlidesPresenter(context, createProxy(SlidesView.class), null, toolController, presentationController, null, bookmarkService, documentService, documentRecorder, recordingService, null, webService, webServiceInfo, streamService);
}
else if (cls == SettingsPresenter.class) {
return (T) new SettingsPresenter(context, createProxy(SettingsView.class));
@ -232,7 +236,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.showView(testView, ViewLayer.Content);
}
@ -270,7 +274,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.display(new TestPresenter(context, new TestView()));
@ -321,7 +325,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.display(new TestPresenter(context, new TestView()));
@ -367,7 +371,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.display(testPresenter);
presenter.destroy(testPresenter);
@ -414,7 +418,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.display(testPresenter);
presenter.destroy(testPresenter);
@ -436,7 +440,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.setFullscreen(true);
@ -474,7 +478,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
view.shownAction.execute();
@ -496,7 +500,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.addShutdownHandler(new ShutdownHandler() {
@ -538,7 +542,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
presenter.display(new TestPresenter(context, new TestView()));
@ -565,7 +569,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
recordingService.start();
@ -601,7 +605,7 @@ class MainPresenterTest extends PresenterTest {
MainPresenter presenter = new MainPresenter(context, view,
audioSystemProvider, presentationController, null, viewFactory,
documentService, bookmarkService, recordingService, null, null);
documentService, bookmarkService, recordingService, null, null, null);
presenter.initialize();
ToolController toolController = new ToolController(context, documentService);

View file

@ -106,6 +106,21 @@ class MessengerWindowPresenterTest extends PresenterTest {
}
@Override
public void setMessengerMessageAsReply(MessengerMessage message, MessengerMessage messageToReplyTo) {
}
@Override
public void setModifiedMessengerMessage(MessengerMessage modifiedMessage) {
}
@Override
public void removeMessengerMessage(String messageId) {
}
@Override
public void setSpeechRequestMessage(SpeechRequestMessage message) {

View file

@ -24,6 +24,7 @@ import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.swing.components.MessagePanel;
import org.lecturestudio.swing.components.NotesPanel;
import org.lecturestudio.web.api.message.UserMessage;
import org.lecturestudio.web.api.message.util.MessageUtil;
import org.lecturestudio.web.api.model.UserInfo;
public class ViewUtil {
@ -43,21 +44,7 @@ public class ViewUtil {
*/
public static <T extends MessagePanel> T createMessageView(Class<T> c,
UserInfo userInfo, UserMessage message, Dictionary dict) {
String myId = userInfo.getUserId();
boolean byMe = Objects.equals(message.getUserId(), myId);
String sender;
if (byMe) {
sender = dict.get("text.message.me");
}
else {
String nameFull = message.getFirstName() + " " + message.getFamilyName();
String[] nameParts = nameFull.split(" ");
String firstName = nameParts.length > 0 ? nameParts[0] : "";
String lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : "";
sender = String.format("%s %s", firstName, lastName);
}
String sender = MessageUtil.evaluateSender(message, userInfo, dict);
try {
T view = c.getDeclaredConstructor(Dictionary.class)
@ -71,6 +58,7 @@ public class ViewUtil {
throw new RuntimeException(e);
}
}
/**
* Creates a concrete NotesPanel specified by the provided class
* parameter.
@ -91,5 +79,4 @@ public class ViewUtil {
throw new RuntimeException(e);
}
}
}

View file

@ -22,6 +22,7 @@ import java.awt.Component;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Optional;
import javax.inject.Inject;
import javax.swing.JFrame;
@ -31,13 +32,14 @@ import org.lecturestudio.core.view.Action;
import org.lecturestudio.presenter.api.service.UserPrivilegeService;
import org.lecturestudio.presenter.api.view.MessengerWindow;
import org.lecturestudio.presenter.swing.utils.ViewUtil;
import org.lecturestudio.swing.components.MessageAsReplyView;
import org.lecturestudio.swing.components.MessageView;
import org.lecturestudio.swing.components.SpeechRequestView;
import org.lecturestudio.swing.util.SwingUtils;
import org.lecturestudio.swing.view.SwingView;
import org.lecturestudio.web.api.message.MessengerDirectMessage;
import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.message.SpeechRequestMessage;
import org.lecturestudio.web.api.message.util.MessageUtil;
import org.lecturestudio.web.api.model.UserInfo;
@SwingView(name = "messenger-window")
@ -65,23 +67,11 @@ public class SwingMessengerWindow extends JFrame implements MessengerWindow {
UserInfo userInfo = userPrivilegeService.getUserInfo();
MessageView messageView = ViewUtil.createMessageView(MessageView.class, userInfo, message, dict);
messageView.setMessage(message.getMessage().getText());
messageView.setMessage(message, userInfo);
messageView.setOnDiscard(() -> {
removeMessageView(messageView);
});
if (message instanceof MessengerDirectMessage) {
MessengerDirectMessage directMessage = (MessengerDirectMessage) message;
String recipient = directMessage.getRecipientId();
if (recipient.equals("organisers")) {
messageView.setPrivateText(dict.get("text.message.to.organisators"));
}
else {
messageView.setPrivateText(dict.get("text.message.privately"));
}
}
messageView.pack();
messageViewContainer.add(messageView);
@ -89,6 +79,49 @@ public class SwingMessengerWindow extends JFrame implements MessengerWindow {
});
}
@Override
public void setMessengerMessageAsReply(MessengerMessage message, MessengerMessage messageToReplyTo) {
SwingUtils.invoke(() -> {
UserInfo userInfo = userPrivilegeService.getUserInfo();
MessageAsReplyView messageView = ViewUtil.createMessageView(MessageAsReplyView.class, userInfo, message, dict);
messageView.setMessage(message, userInfo);
final String userToReplyTo = MessageUtil.evaluateSenderOfMessageToReplyTo(messageToReplyTo, userInfo, dict);
messageView.setUserToReplyTo(userToReplyTo);
messageView.setOnDiscard(() -> {
removeMessageView(messageView);
});
messageView.pack();
messageViewContainer.add(messageView);
messageViewContainer.revalidate();
});
}
@Override
public void setModifiedMessengerMessage(MessengerMessage modifiedMessage) {
SwingUtils.invoke(() -> {
final Optional<MessageView> toModify = findCorrespondingMessageView(modifiedMessage.getMessageId());
if (toModify.isEmpty()) {
return;
}
UserInfo userInfo = userPrivilegeService.getUserInfo();
toModify.get().setMessage(modifiedMessage, userInfo);
toModify.get().setIsEdited();
});
}
@Override
public void removeMessengerMessage(String messageId) {
removeMessageView(messageId);
}
@Override
public void setSpeechRequestMessage(SpeechRequestMessage message) {
SwingUtils.invoke(() -> {
@ -148,4 +181,28 @@ public class SwingMessengerWindow extends JFrame implements MessengerWindow {
}
}
}
private void removeMessageView(final String messageId) {
for(Component component : messageViewContainer.getComponents()) {
if((component instanceof MessageView messageView) &&
messageView.getMessageId().equals(messageId)) {
messageViewContainer.remove(component);
messageViewContainer.validate();
messageViewContainer.repaint();
return;
}
}
}
private Optional<MessageView> findCorrespondingMessageView(final String messageId) {
for (Component component : messageViewContainer.getComponents()) {
if ((component instanceof MessageView messageView) &&
messageView.getMessageId().equals(messageId)) {
return Optional.of(messageView);
}
}
return Optional.empty();
}
}

View file

@ -50,12 +50,12 @@ import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.IntSupplier;
@ -87,6 +87,7 @@ import org.lecturestudio.presenter.swing.utils.ViewUtil;
import org.lecturestudio.stylus.awt.AwtStylusManager;
import org.lecturestudio.swing.components.AdaptiveTabbedPane;
import org.lecturestudio.swing.components.ExternalFrame;
import org.lecturestudio.swing.components.MessageAsReplyView;
import org.lecturestudio.swing.components.MessagePanel;
import org.lecturestudio.swing.components.MessageView;
import org.lecturestudio.swing.components.NotesView;
@ -113,9 +114,9 @@ import org.lecturestudio.swing.view.SwingView;
import org.lecturestudio.swing.view.ViewPostConstruct;
import org.lecturestudio.web.api.event.PeerStateEvent;
import org.lecturestudio.web.api.event.RemoteVideoFrameEvent;
import org.lecturestudio.web.api.message.MessengerDirectMessage;
import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.message.SpeechBaseMessage;
import org.lecturestudio.web.api.message.util.MessageUtil;
import org.lecturestudio.web.api.model.UserInfo;
import org.lecturestudio.web.api.stream.model.CourseParticipant;
@ -744,10 +745,37 @@ public class SwingSlidesView extends JPanel implements SlidesView {
public void setMessengerMessage(MessengerMessage message) {
SwingUtils.invoke(() -> {
UserInfo userInfo = userPrivilegeService.getUserInfo();
String myId = userInfo.getUserId();
MessageView messageView = ViewUtil.createMessageView(MessageView.class, userInfo, message, dict);
messageView.setMessage(message.getMessage().getText());
messageView.setMessage(message, userInfo);
messageView.setOnDiscard(() -> {
executeAction(discardMessageAction, messageView.getMessage());
removeMessageView(messageView);
});
messageView.setOnCreateSlide(() -> {
createMessageSlideAction.execute(messageView.getMessage());
removeMessageView(messageView);
});
messageView.pack();
addMessageView(messageView);
});
}
@Override
public void setMessengerMessageAsReply(MessengerMessage message, MessengerMessage messageToReplyTo) {
SwingUtils.invoke(() -> {
UserInfo userInfo = userPrivilegeService.getUserInfo();
MessageAsReplyView messageView = ViewUtil.createMessageView(MessageAsReplyView.class, userInfo, message, dict);
messageView.setMessage(message, userInfo);
final String userToReplyTo = MessageUtil.evaluateSenderOfMessageToReplyTo(messageToReplyTo, userInfo, dict);
messageView.setUserToReplyTo(userToReplyTo);
messageView.setOnDiscard(() -> {
executeAction(discardMessageAction, message);
@ -759,32 +787,37 @@ public class SwingSlidesView extends JPanel implements SlidesView {
removeMessageView(messageView);
});
if (message instanceof MessengerDirectMessage directMessage) {
String recipientId = directMessage.getRecipientId();
boolean byMe = Objects.equals(message.getUserId(), myId);
boolean toMe = Objects.equals(recipientId, myId);
boolean toOrganisers = Objects.equals(recipientId, "organisers");
String sender = byMe
? dict.get("text.message.me")
: String.format("%s %s", message.getFirstName(), message.getFamilyName());
String recipient = toMe
? dict.get("text.message.to.me")
: toOrganisers
? dict.get("text.message.to.organisators.short")
: String.format("%s %s", directMessage.getRecipientFirstName(), directMessage.getRecipientFamilyName());
messageView.setUserName(MessageFormat.format(dict.get("text.message.recipient"), sender, ""));
messageView.setPrivateText(recipient);
}
messageView.pack();
addMessageView(messageView);
});
}
@Override
public void setModifiedMessengerMessage(MessengerMessage modifiedMessage) {
SwingUtils.invoke(() -> {
final Optional<MessageView> toModify = findCorrespondingMessageView(modifiedMessage.getMessageId());
if (toModify.isEmpty()){
return;
}
UserInfo userInfo = userPrivilegeService.getUserInfo();
toModify.get().setMessage(modifiedMessage, userInfo);
toModify.get().setIsEdited();
});
}
@Override
public void removeMessengerMessage(String messageId) {
final Optional<MessageView> toRemove = findCorrespondingMessageView(messageId);
if(toRemove.isEmpty()) return;
removeMessageView(toRemove.get());
}
@Override
public void addSpeechRequest(SpeechBaseMessage message) {
SwingUtils.invoke(() -> {
@ -2411,6 +2444,16 @@ public class SwingSlidesView extends JPanel implements SlidesView {
}
}
private Optional<MessageView> findCorrespondingMessageView(final String messageId) {
for(Component component : messageViewContainer.getComponents()) {
if((component instanceof MessageView messageView) &&
messageView.getMessageId().equals(messageId)) {
return Optional.of(messageView);
}
}
return Optional.empty();
}
/**
* Removes all notes from the View
*/

View file

@ -17,6 +17,7 @@ import java.util.List;
import java.util.stream.IntStream;
public class AdaptiveTabbedPane extends JComponent {
public static final int TAB_SIZE_OFFSET = UIScale.scale(1);
private AdaptiveTabType defaultTabType = AdaptiveTabType.NORMAL;
@ -44,8 +45,12 @@ public class AdaptiveTabbedPane extends JComponent {
public AdaptiveTabbedPane(int tabPlacement) {
setBorder(new EmptyBorder(0, 0, 0, 0));
setLayout(new BorderLayout());
tabbedPane.setFocusable(false);
tabbedPane.setTabPlacement(tabPlacement);
add(tabbedPane);
tabbedPane.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {

View file

@ -0,0 +1,49 @@
package org.lecturestudio.swing.components;
import org.lecturestudio.core.app.dictionary.Dictionary;
import java.awt.Font;
import javax.swing.JLabel;
import javax.swing.Box;
public class MessageAsReplyView extends MessageView {
private JLabel userToReplyToLabel;
private JLabel replyToLabel;
public MessageAsReplyView(Dictionary dict) {
super(dict);
}
public void setUserToReplyTo(final String userToReplyTo) {
userToReplyToLabel.setText(userToReplyTo);
}
@Override
protected Box createUserPanel() {
final Box userPanel = Box.createHorizontalBox();
userPanel.setOpaque(false);
userPanel.add(userLabel);
userPanel.add(Box.createHorizontalStrut(20));
userPanel.add(replyToLabel);
userPanel.add(Box.createHorizontalStrut(20));
userPanel.add(userToReplyToLabel);
userPanel.add(privateLabel);
userPanel.add(Box.createHorizontalGlue());
return userPanel;
}
@Override
protected void initComponents() {
super.initComponents();
userToReplyToLabel = new JLabel();
replyToLabel = new JLabel(dict.get("label.reply"));
replyToLabel.setFont(replyToLabel.getFont().deriveFont(Font.ITALIC));
}
}

View file

@ -49,6 +49,8 @@ public abstract class MessagePanel extends JPanel {
protected JLabel privateLabel;
protected JLabel editedLabel;
abstract protected void createContent(JPanel content);
@ -118,6 +120,11 @@ public abstract class MessagePanel extends JPanel {
privateLabel.setForeground(Color.RED);
privateLabel.setVisible(false);
editedLabel = new JLabel();
editedLabel.setText(dict.get("label.edited"));
editedLabel.setFont(editedLabel.getFont().deriveFont(Font.BOLD));
editedLabel.setVisible(false);
createContent(content);
add(content);

View file

@ -23,6 +23,8 @@ import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.text.MessageFormat;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.Box;
@ -34,22 +36,80 @@ import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.swing.AwtResourceLoader;
import org.lecturestudio.swing.util.SwingUtils;
import org.lecturestudio.web.api.message.MessengerDirectMessage;
import org.lecturestudio.web.api.message.MessengerDirectMessageAsReply;
import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.model.UserInfo;
public class MessageView extends MessagePanel {
private MessengerMessage message;
private JButton discardButton;
private JTextArea textArea;
private JButton createSlideButton;
private String messageId;
protected JTextArea textArea;
public MessageView(Dictionary dict) {
super(dict);
}
public void setMessage(String message) {
textArea.setText(message);
public MessengerMessage getMessage() {
return message;
}
public void setMessage(MessengerMessage message, UserInfo userInfo) {
this.message = message;
textArea.setText(message.getMessage().getText());
messageId = message.getMessageId();
if (message instanceof MessengerDirectMessage directMessage) {
String myId = userInfo.getUserId();
String recipientId = directMessage.getRecipientId();
boolean byMe = Objects.equals(message.getUserId(), myId);
boolean toMe = Objects.equals(recipientId, myId);
boolean toOrganisers = Objects.equals(recipientId, "organisers");
String sender = byMe
? dict.get("text.message.me")
: String.format("%s %s", message.getFirstName(), message.getFamilyName());
String recipient = toMe
? dict.get("text.message.to.me")
: toOrganisers
? dict.get("text.message.to.organisators.short")
: String.format("%s %s", directMessage.getRecipientFirstName(), directMessage.getRecipientFamilyName());
setUserName(MessageFormat.format(dict.get("text.message.recipient"), sender, ""));
setPrivateText(recipient);
}
if (message instanceof MessengerDirectMessageAsReply directMessageAsReply) {
String myId = userInfo.getUserId();
String recipientId = directMessageAsReply.getRecipientId();
boolean byMe = Objects.equals(message.getUserId(), myId);
boolean toMe = Objects.equals(recipientId, myId);
boolean toOrganisers = Objects.equals(recipientId, "organisers");
String sender = byMe
? dict.get("text.message.me")
: String.format("%s %s", message.getFirstName(), message.getFamilyName());
String recipient = toMe
? dict.get("text.message.to.me")
: toOrganisers
? dict.get("text.message.to.organisators.short")
: String.format("%s %s", directMessageAsReply.getRecipientFirstName(), directMessageAsReply.getRecipientFamilyName());
setUserName(MessageFormat.format(dict.get("text.message.recipient"), sender, ""));
setPrivateText(recipient);
}
}
public void setPrivateText(String text) {
@ -65,29 +125,32 @@ public class MessageView extends MessagePanel {
SwingUtils.bindAction(createSlideButton, action);
}
public String getMessageId() {
return messageId;
}
public void setIsEdited() {
editedLabel.setVisible(true);
}
@Override
protected void createContent(JPanel content) {
discardButton = new JButton(AwtResourceLoader.getIcon("message-check.svg", 18));
discardButton.setToolTipText(dict.get("button.processed"));
createSlideButton = new JButton(AwtResourceLoader.getIcon("message-slide.svg", 18));
createSlideButton.setToolTipText(dict.get("button.create.slide"));
initComponents();
Box userPanel = Box.createHorizontalBox();
userPanel.setOpaque(false);
userPanel.add(userLabel);
userPanel.add(privateLabel);
userPanel.add(Box.createHorizontalGlue());
Box userPanel = createUserPanel();
Box timePanel = Box.createHorizontalBox();
timePanel.setOpaque(false);
timePanel.add(timeLabel);
timePanel.add(Box.createHorizontalGlue());
Box timeEditedPanel = Box.createHorizontalBox();
timeEditedPanel.setOpaque(false);
timeEditedPanel.add(timeLabel);
timeEditedPanel.add(Box.createHorizontalStrut(10));
timeEditedPanel.add(editedLabel);
timeEditedPanel.add(Box.createHorizontalGlue());
Box userTimePanel = Box.createVerticalBox();
userTimePanel.setBorder(BorderFactory.createEmptyBorder());
userTimePanel.setOpaque(false);
userTimePanel.add(userPanel);
userTimePanel.add(timePanel);
userTimePanel.add(timeEditedPanel);
Box controlPanel = Box.createHorizontalBox();
controlPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
@ -133,4 +196,23 @@ public class MessageView extends MessagePanel {
}
});
}
protected Box createUserPanel() {
final Box userPanel = Box.createHorizontalBox();
userPanel.setOpaque(false);
userPanel.add(userLabel);
userPanel.add(privateLabel);
userPanel.add(Box.createHorizontalGlue());
return userPanel;
}
protected void initComponents() {
discardButton = new JButton(AwtResourceLoader.getIcon("message-check.svg", 18));
createSlideButton = new JButton(AwtResourceLoader.getIcon("message-slide.svg", 18));
discardButton.setToolTipText(dict.get("button.processed"));
createSlideButton.setToolTipText(dict.get("button.create.slide"));
}
}

View file

@ -62,13 +62,13 @@ public class ParticipantList extends JPanel {
@Inject
public ParticipantList(ResourceBundle bundle) {
super();
actionMap = new HashMap<>();
setLayout(new BorderLayout());
setFocusable(false);
setIgnoreRepaint(true);
listModel = new SortedListModel();
actionMap = new HashMap<>();
popupMenuBanItem = new JMenuItem("Ban");
popupMenuBanItem.addActionListener(e -> {

View file

@ -455,7 +455,8 @@ public class ThumbPanel extends JPanel {
if (selected) {
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, getWidth(), getHeight());
}else if(page.isOverlay()){
}
else if (page.isOverlay()) {
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, getWidth(), getHeight());
}

View file

@ -175,7 +175,7 @@
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>10.1.10</version>
<version>10.1.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>

View file

@ -27,7 +27,9 @@ import javax.json.JsonValue;
import javax.json.bind.adapter.JsonbAdapter;
import org.lecturestudio.web.api.message.MessengerDirectMessage;
import org.lecturestudio.web.api.message.MessengerDirectMessageAsReply;
import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.message.MessengerMessageAsReply;
import org.lecturestudio.web.api.model.Message;
public class MessengerMessageAdapter implements JsonbAdapter<MessengerMessage, JsonObject> {
@ -43,12 +45,20 @@ public class MessengerMessageAdapter implements JsonbAdapter<MessengerMessage, J
builder.add("date", message.getDate().toString());
builder.add("messageId", message.getMessageId());
if(message instanceof MessengerMessageAsReply messageAsReply) {
builder.add("msgIdToReplyTo", messageAsReply.getMsgIdToReplyTo());
}
if (message instanceof MessengerDirectMessage directMessage) {
builder.add("recipientId", directMessage.getRecipientId());
builder.add("recipientFirstName", directMessage.getRecipientFirstName());
builder.add("recipientFamilyName", directMessage.getRecipientFamilyName());
}
if(message instanceof MessengerDirectMessageAsReply directMessageAsReply) {
builder.add("msgIdToReplyTo", directMessageAsReply.getMsgIdToReplyTo());
}
return builder.build();
}
@ -58,21 +68,45 @@ public class MessengerMessageAdapter implements JsonbAdapter<MessengerMessage, J
MessengerMessage message;
if (type.equals("MessengerDirectMessage")) {
MessengerDirectMessage directMessage = new MessengerDirectMessage();
directMessage.setRecipientId(jsonObject.getString("recipientId"));
switch (type) {
case "MessengerDirectMessage" -> {
MessengerDirectMessage directMessage = new MessengerDirectMessage();
if (!jsonObject.isNull("recipientFirstName")) {
directMessage.setRecipientFirstName(jsonObject.getString("recipientFirstName"));
}
if (!jsonObject.isNull("recipientFamilyName")) {
directMessage.setRecipientFamilyName(jsonObject.getString("recipientFamilyName"));
directMessage.setRecipientId(jsonObject.getString("recipientId"));
if (!jsonObject.isNull("recipientFirstName")) {
directMessage.setRecipientFirstName(jsonObject.getString("recipientFirstName"));
}
if (!jsonObject.isNull("recipientFamilyName")) {
directMessage.setRecipientFamilyName(jsonObject.getString("recipientFamilyName"));
}
message = directMessage;
}
case "MessengerDirectMessageAsReply" -> {
MessengerDirectMessageAsReply directMessageAsReply = new MessengerDirectMessageAsReply();
message = directMessage;
}
else {
message = new MessengerMessage();
directMessageAsReply.setRecipientId(jsonObject.getString("recipientId"));
if (!jsonObject.isNull("recipientFirstName")) {
directMessageAsReply.setRecipientFirstName(jsonObject.getString("recipientFirstName"));
}
if (!jsonObject.isNull("recipientFamilyName")) {
directMessageAsReply.setRecipientFamilyName(jsonObject.getString("recipientFamilyName"));
}
if (!jsonObject.isNull("messageIdToReplyTo")) {
directMessageAsReply.setMsgIdToReplyTo(jsonObject.getString("msgIdToReplyTo"));
}
message = directMessageAsReply;
}
case "MessengerMessageAsReply" -> {
MessengerMessageAsReply messageAsReply = new MessengerMessageAsReply();
if (!jsonObject.isNull("msgIdToReplyTo")) {
messageAsReply.setMsgIdToReplyTo(jsonObject.getString("msgIdToReplyTo"));
}
message = messageAsReply;
}
default -> message = new MessengerMessage();
}
if (jsonObject.get("text").getValueType() != JsonValue.ValueType.NULL) {
@ -96,6 +130,12 @@ public class MessengerMessageAdapter implements JsonbAdapter<MessengerMessage, J
if (jsonObject.get("messageId").getValueType() != JsonValue.ValueType.NULL) {
message.setMessageId(jsonObject.getString("messageId"));
}
if (jsonObject.get("deleted").getValueType() != JsonValue.ValueType.NULL) {
message.setDeleted(jsonObject.getBoolean("deleted"));
}
if (jsonObject.get("edited").getValueType() != JsonValue.ValueType.NULL) {
message.setEdited(jsonObject.getBoolean("edited"));
}
return message;
}

View file

@ -0,0 +1,45 @@
package org.lecturestudio.web.api.data.bind;
import static java.util.Objects.nonNull;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.bind.adapter.JsonbAdapter;
import org.lecturestudio.web.api.message.StopStreamEnvironmentMessage;
import java.time.ZonedDateTime;
public class StopStreamEnvironmentMessageAdapter implements JsonbAdapter<StopStreamEnvironmentMessage, JsonObject> {
@Override
public JsonObject adaptToJson(StopStreamEnvironmentMessage stopStreamEnvironmentMessage) throws Exception {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add("type", stopStreamEnvironmentMessage.getClass().getSimpleName());
if (nonNull(stopStreamEnvironmentMessage.getMessageId())) {
builder.add("messageId", stopStreamEnvironmentMessage.getMessageId());
}
return builder.build();
}
@Override
public org.lecturestudio.web.api.message.StopStreamEnvironmentMessage adaptFromJson(JsonObject jsonObject) throws Exception {
String typeStr = jsonObject.getString("type");
String className = StopStreamEnvironmentMessage.class.getPackageName() + "." + typeStr;
Class<?> cls = Class.forName(className);
StopStreamEnvironmentMessage message = (StopStreamEnvironmentMessage) cls.getConstructor().newInstance();
message.setMessageId(jsonObject.getString("messageId"));
message.setDate(ZonedDateTime.parse(jsonObject.getString("time")));
message.setUserId(jsonObject.getString("userId"));
message.setFirstName(jsonObject.getString("firstName"));
message.setFamilyName(jsonObject.getString("familyName"));
return message;
}
}

View file

@ -0,0 +1,14 @@
package org.lecturestudio.web.api.message;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class MessengerDirectMessageAsReply extends MessengerDirectMessage {
private String msgIdToReplyTo;
}

View file

@ -31,4 +31,7 @@ public class MessengerMessage extends UserMessage {
private Message message;
private boolean deleted;
private boolean edited;
}

View file

@ -0,0 +1,14 @@
package org.lecturestudio.web.api.message;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class MessengerMessageAsReply extends MessengerMessage {
private String msgIdToReplyTo;
}

View file

@ -0,0 +1,5 @@
package org.lecturestudio.web.api.message;
public class StopStreamEnvironmentMessage extends UserMessage {
}

View file

@ -33,4 +33,12 @@ public abstract class WebMessage {
private ZonedDateTime date;
@Override
public boolean equals(final Object obj) {
if(!(obj instanceof WebMessage webMessage)) return false;
return this == webMessage || this.messageId.equals(webMessage.getMessageId());
}
}

View file

@ -96,7 +96,8 @@ public class WebSocketStompTransport extends ExecutableBase implements MessageTr
new CoursePresenceMessageAdapter(),
new MessengerMessageAdapter(),
new QuizAnswerMessageAdapter(),
new SpeechMessageAdapter()
new SpeechMessageAdapter(),
new StopStreamEnvironmentMessageAdapter()
);
jsonb = JsonbBuilder.create(jsonbConfig);
@ -255,6 +256,9 @@ public class WebSocketStompTransport extends ExecutableBase implements MessageTr
subscribe(stompSession, "/topic/course/event/{id}/presence");
subscribe(stompSession, "/topic/course/{id}/chat");
subscribe(stompSession, "/topic/course/{id}/chat/deletion");
subscribe(stompSession, "/topic/course/{id}/chat/edit");
subscribe(stompSession, "/topic/course/{id}/stopStreamEnvironment");
subscribe(stompSession, "/user/queue/course/{id}/chat");
subscribe(stompSession, "/user/queue/course/{id}/presence");
subscribe(stompSession, "/user/queue/course/{id}/speech");

View file

@ -0,0 +1,72 @@
package org.lecturestudio.web.api.message.util;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.web.api.message.MessengerDirectMessageAsReply;
import org.lecturestudio.web.api.message.MessengerMessage;
import org.lecturestudio.web.api.message.MessengerMessageAsReply;
import org.lecturestudio.web.api.message.UserMessage;
import org.lecturestudio.web.api.model.UserInfo;
import java.util.List;
import java.util.Objects;
public class MessageUtil {
public static boolean isReply(final UserMessage message) {
return message instanceof MessengerMessageAsReply || message instanceof MessengerDirectMessageAsReply;
}
public static String evaluateSender(final UserMessage message, final UserInfo userInfo, final Dictionary dictionary) {
final boolean byMe = sentByMe(message, userInfo);
if (byMe) return dictionary.get("text.message.me");
else {
String nameFull = message.getFirstName() + " " + message.getFamilyName();
String[] nameParts = nameFull.split(" ");
String firstName = nameParts.length > 0 ? nameParts[0] : "";
String lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : "";
return String.format("%s %s", firstName, lastName);
}
}
public static String evaluateSenderOfMessageToReplyTo(final UserMessage messageToReplyTo, final UserInfo userInfo, final Dictionary dictionary) {
final boolean byMe = sentByMe(messageToReplyTo, userInfo);
if(byMe) return dictionary.get("text.message.me.dative");
return evaluateSender(messageToReplyTo, userInfo, dictionary);
}
public static MessengerMessage findMessageToReplyTo(final List<MessengerMessage> messages, final MessengerMessage message) {
if(message instanceof MessengerMessageAsReply messageAsReply) {
return messages.stream()
.filter(messengerMessage -> messengerMessage.getMessageId().equals(messageAsReply.getMsgIdToReplyTo()))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Given ID of message to reply to couldn't be found"));
}
if(message instanceof MessengerDirectMessageAsReply directMessageAsReply) {
return messages.stream()
.filter(messengerMessage -> messengerMessage.getMessageId().equals(directMessageAsReply.getMsgIdToReplyTo()))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Given ID of message to reply to couldn't be found"));
}
return null;
}
public static void updateOutdatedMessage(final List<MessengerMessage> messages, final MessengerMessage newMessage) {
for (MessengerMessage message : messages) {
if (message.getMessageId().equals(newMessage.getMessageId())) {
messages.remove(message);
messages.add(newMessage);
}
}
}
private static boolean sentByMe(final UserMessage message, final UserInfo userInfo) {
final String myId = userInfo.getUserId();
return Objects.equals(message.getUserId(), myId);
}
}

View file

@ -39,6 +39,14 @@ public class Message extends ServiceModel {
this.text = text;
}
public boolean isReply() {
return false;
}
public String getMsgIdToReplyTo() {
return null;
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + text;

View file

@ -0,0 +1,33 @@
package org.lecturestudio.web.api.model;
public class MessageAsReply extends Message {
private String msgIdToReplyTo;
public MessageAsReply() {
this(null);
}
public MessageAsReply(String msgIdToReplyTo) {
setMsgIdToReplyTo(msgIdToReplyTo);
}
public String getMsgIdToReplyTo() {
return msgIdToReplyTo;
}
public void setMsgIdToReplyTo(final String msgIdToReplyTo) {
this.msgIdToReplyTo = msgIdToReplyTo;
}
@Override
public boolean isReply() {
return true;
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + msgIdToReplyTo;
}
}

View file

@ -25,6 +25,8 @@ public class Course {
private Long id;
private String defaultAccessLink;
private String roomId;
private String title;
@ -36,6 +38,10 @@ public class Course {
return id;
}
public String getDefaultAccessLink() {
return defaultAccessLink;
}
public String getRoomId() {
return roomId;
}