From dada9a62817ecc37df6b48fd9e188d9f71277f83 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Tue, 31 Mar 2026 14:08:42 +0200 Subject: [PATCH 1/4] The student results view is now outsourced into a plugin --- src/main/resources/css/site/results.css | 35 ------------------- .../resources/html/admin/student-results.html | 18 ---------- .../html/teacher/student-results.html | 6 ---- src/main/resources/html/user/results.html | 4 --- .../resources/js/teacher/build_results.js | 17 --------- src/main/resources/js/user/build_results.js | 8 ----- .../meta/navigation/navigation_elements.json | 17 +-------- src/main/resources/meta/paths/get_paths.json | 28 --------------- .../resources/meta/templates/templates.json | 4 --- .../templates/html/student_results.html | 23 ------------ 10 files changed, 1 insertion(+), 159 deletions(-) delete mode 100644 src/main/resources/css/site/results.css delete mode 100644 src/main/resources/html/admin/student-results.html delete mode 100644 src/main/resources/html/teacher/student-results.html delete mode 100644 src/main/resources/html/user/results.html delete mode 100644 src/main/resources/js/teacher/build_results.js delete mode 100644 src/main/resources/js/user/build_results.js delete mode 100644 src/main/resources/templates/html/student_results.html diff --git a/src/main/resources/css/site/results.css b/src/main/resources/css/site/results.css deleted file mode 100644 index 2c9de93..0000000 --- a/src/main/resources/css/site/results.css +++ /dev/null @@ -1,35 +0,0 @@ -.bar-chart { margin-bottom: 40px; } -.bar-container { display: flex; align-items: flex-end; height: 60px; margin-top: 10px; } -.bar-task { - height: 40px; - margin-right: 2px; - border-radius: 4px 4px 0 0; - border: 1px solid #ccc; - position: relative; - box-sizing: border-box; -} -.bar-task.level1 { background: #f5e9d4; } /* beige */ -.bar-task.level2 { background: #b3d1ff; } /* blue */ -.bar-task.level3 { background: #fff9b3; } /* yellow */ -.bar-task.special { background: #ffdddd;} /* pink */ -.bar-task.hatched { - background-image: repeating-linear-gradient( - 45deg, - rgba(0,0,0,0.1) 0, rgba(0,0,0,0.1) 4px, - transparent 4px, transparent 8px - ); -} -.bar-task.completed { opacity: 1; } -.bar-task.selected:not(.completed) { opacity: 0.7; } -.bar-task:not(.completed):not(.selected) { opacity: 0.3; } -.grade-scale { display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 0.9em; } -.grade-label { font-weight: bold; } -.grade-current, .grade-prediction { margin-top: 8px; } -.bar-task-label { - position: absolute; - bottom: 100%; left: 50%; - transform: translateX(-50%); - font-size: 0.8em; - white-space: nowrap; - margin-bottom: 2px; -} \ No newline at end of file diff --git a/src/main/resources/html/admin/student-results.html b/src/main/resources/html/admin/student-results.html deleted file mode 100644 index 1001f46..0000000 --- a/src/main/resources/html/admin/student-results.html +++ /dev/null @@ -1,18 +0,0 @@ -%[student_results;nav=!FOLLOWS] -%[admin_student_other_nav] -
- - -
- \ No newline at end of file diff --git a/src/main/resources/html/teacher/student-results.html b/src/main/resources/html/teacher/student-results.html deleted file mode 100644 index 1090f9f..0000000 --- a/src/main/resources/html/teacher/student-results.html +++ /dev/null @@ -1,6 +0,0 @@ -%[student_results;nav=!FOLLOWS] -%[teacher_student_other_nav] -
- - -
\ No newline at end of file diff --git a/src/main/resources/html/user/results.html b/src/main/resources/html/user/results.html deleted file mode 100644 index 72b7f22..0000000 --- a/src/main/resources/html/user/results.html +++ /dev/null @@ -1,4 +0,0 @@ -%[student_results;nav= - !FOLLOWS -] -%[student_nav] \ No newline at end of file diff --git a/src/main/resources/js/teacher/build_results.js b/src/main/resources/js/teacher/build_results.js deleted file mode 100644 index 38a6ec6..0000000 --- a/src/main/resources/js/teacher/build_results.js +++ /dev/null @@ -1,17 +0,0 @@ -let studentId = Number(sessionStorage.getItem('selectedStudentId')); -let settings; - -document.addEventListener('DOMContentLoaded', async () => { - // Load student data - const studentData = await fetchStudentData(studentId); - - loadStudentResultView(studentData); - document.getElementById('studentId').value = studentId; - document.getElementById('download-student-results-csv').addEventListener('submit', async function(e) { - e.preventDefault(); - const form = e.target; - const studentId = Number(form['studentId'].value); - - postDataAndDownload('/student-results-csv', JSON.stringify({ studentId }), 'schueler_ergebnisse_' + studentId + '.csv'); - }); -}); \ No newline at end of file diff --git a/src/main/resources/js/user/build_results.js b/src/main/resources/js/user/build_results.js deleted file mode 100644 index cd6300c..0000000 --- a/src/main/resources/js/user/build_results.js +++ /dev/null @@ -1,8 +0,0 @@ -let settings; - -document.addEventListener('DOMContentLoaded', async () => { - // Load student data (reuse endpoint from dashboard) - const studentData = await fetchMyData(); - - loadStudentResultView(studentData); -}); \ No newline at end of file diff --git a/src/main/resources/meta/navigation/navigation_elements.json b/src/main/resources/meta/navigation/navigation_elements.json index 14dbbb1..530ef9c 100644 --- a/src/main/resources/meta/navigation/navigation_elements.json +++ b/src/main/resources/meta/navigation/navigation_elements.json @@ -40,12 +40,7 @@ "path": "/dashboard", "label": "Zurück zum Admin-Dashboard" }, - - { - "type": "ADMIN_STUDENT", - "path": "/student-results", - "label": "Ergebnisse anzeigen" - }, + { "type": "ADMIN_STUDENT", "path": "/teacher", @@ -122,11 +117,6 @@ "label": "Zurück zum Lehrer-Dashboard" }, - { - "type": "TEACHER_STUDENT", - "path": "/student-results", - "label": "Ergebnisse anzeigen" - }, { "type": "TEACHER_STUDENT", "path": "/dashboard", @@ -144,11 +134,6 @@ "label": "Zurück zum Lehrer-Dashboard" }, - { - "type": "STUDENT_DASHBOARD", - "path": "/results", - "label": "Meine Ergebnisse anzeigen" - }, { "type": "STUDENT_DASHBOARD", "path": "/partner_search", diff --git a/src/main/resources/meta/paths/get_paths.json b/src/main/resources/meta/paths/get_paths.json index ff8c2a5..8250252 100644 --- a/src/main/resources/meta/paths/get_paths.json +++ b/src/main/resources/meta/paths/get_paths.json @@ -85,13 +85,6 @@ "context": "html", "access_level": "user" }, - "/results": { - "type": "GET", - "handler_type":"TemplatingFileRequestHandler", - "namespaces": ["user"], - "context": "html", - "access_level": "student" - }, "/partner_search": { "type": "GET", "handler_type":"TemplatingFileRequestHandler", @@ -107,13 +100,6 @@ "context": "html", "access_level": "teacher" }, - "/student-results": { - "type": "GET", - "handler_type": "TemplatingFileRequestHandler", - "namespaces": ["teacher", "admin"], - "context": "html", - "access_level": "teacher" - }, "/plugins": { "type": "GET", @@ -244,13 +230,6 @@ "context": "js", "access_level": "user" }, - "/build_results.js": { - "type": "GET", - "handler_type": "FileRequestHandler", - "namespaces": ["user", "teacher"], - "context": "js", - "access_level": "user" - }, "/build_partner_search.js": { "type": "GET", "handler_type": "FileRequestHandler", @@ -309,13 +288,6 @@ "context": "css", "access_level": "public" }, - "/results.css": { - "type": "GET", - "handler_type": "FileRequestHandler", - "namespaces": ["site"], - "context": "css", - "access_level": "public" - }, "/favicon.ico": { "type": "GET", "handler_type": "FileRequestHandler", diff --git a/src/main/resources/meta/templates/templates.json b/src/main/resources/meta/templates/templates.json index b39d226..69d6494 100644 --- a/src/main/resources/meta/templates/templates.json +++ b/src/main/resources/meta/templates/templates.json @@ -23,10 +23,6 @@ "type": "HTMLFileTemplate", "path": "student_dashboard" }, - "student_results": { - "type": "HTMLFileTemplate", - "path": "student_results" - }, "subject_info": { "type": "HTMLFileTemplate", "path": "subject_info" diff --git a/src/main/resources/templates/html/student_results.html b/src/main/resources/templates/html/student_results.html deleted file mode 100644 index 661ab57..0000000 --- a/src/main/resources/templates/html/student_results.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Ergebnisse – Lernsystem - - - - - -
-
-

Ergebnisse für

-
-
- - -
- - \ No newline at end of file From 3b78d10cef61b2b74071aefef12b78589bb6d50a Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Tue, 31 Mar 2026 15:11:33 +0200 Subject: [PATCH 2/4] Better resource handling for plugins Now using separate ClassLoader for resources --- .../de/igslandstuhl/database/Application.java | 4 +++- .../database/plugins/PluginLoader.java | 21 ++++++++++++------- .../plugins/PluginResourceProvider.java | 11 ++++++---- .../database/plugins/PreLoadedPlugin.java | 3 ++- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/Application.java b/src/main/java/de/igslandstuhl/database/Application.java index b91688f..d84ca56 100644 --- a/src/main/java/de/igslandstuhl/database/Application.java +++ b/src/main/java/de/igslandstuhl/database/Application.java @@ -98,7 +98,9 @@ public Topic[] readFile(String file) throws SerializationException, SQLException public static void main(String[] args) throws Exception { instance = new Application(args); - + + PluginLoader.getInstance().preloadPlugins(); + if (!getInstance().suppressCmd()) { Command.registerCommands(); CommandLineUtils.setup(); diff --git a/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java b/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java index a87e3e0..85bf0a3 100644 --- a/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java +++ b/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java @@ -16,7 +16,6 @@ import org.yaml.snakeyaml.Yaml; import de.igslandstuhl.database.Registry; -import de.igslandstuhl.database.plugins.config.BoolSetting; public class PluginLoader { private final List pluginInfos = new ArrayList<>(); @@ -42,11 +41,16 @@ private Map loadYaml(URLClassLoader classLoader) { } public PreLoadedPlugin loadPluginFromJar(File jarFile) { URLClassLoader classLoader; + URLClassLoader resourceLoader; try { classLoader = new URLClassLoader( new URL[]{jarFile.toURI().toURL()}, getClass().getClassLoader() ); + resourceLoader = new URLClassLoader( + new URL[]{jarFile.toURI().toURL()}, + null + ); } catch (MalformedURLException e) { e.printStackTrace(); return null; @@ -85,7 +89,7 @@ public PreLoadedPlugin loadPluginFromJar(File jarFile) { throw new IllegalStateException("Main class does not extend Plugin"); } - return new PreLoadedPlugin(new PluginDescription(id, name, description, mainClassName, depends), clazz, classLoader); + return new PreLoadedPlugin(new PluginDescription(id, name, description, mainClassName, depends), clazz, classLoader, resourceLoader); } catch (Exception e) { e.printStackTrace(); @@ -115,7 +119,7 @@ public void load(PreLoadedPlugin preload) { e.printStackTrace(); } } - public void loadAllPlugins(File folder) { + public void preloadAllPlugins(File folder) { File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar")); if (jars == null) return; @@ -137,6 +141,8 @@ public void loadAllPlugins(File folder) { } PluginSort.sortPlugins(plugins).forEach((p) -> pluginInfos.add(p)); + } + public void loadAllPlugins(File folder) { pluginInfos.forEach(this::load); } public void enablePlugins() { @@ -159,6 +165,7 @@ public void unloadPlugins() { try { p.classLoader().close(); + p.resourceLoader().close(); } catch (IOException e) { throw new RuntimeException("Problem while unloading", e); } @@ -174,12 +181,10 @@ private void registerPlugin(Plugin plugin) { } Registry.pluginRegistry().register(plugin.getId(), plugin); } + public void preloadPlugins() { + preloadAllPlugins(new File("plugins")); + } public void registerPlugins() { - registerPlugin(new Plugin.DummyModule("result_view", "Student Results View", "The view displaying the student's current progress and prognoses for the final result", List.of( - new BoolSetting("show_prognosis", "Show Prognosis", "Whether to display the prognosis for the final result", true), - new BoolSetting("show_current_progress", "Show Current", "Whether to display the current progress to the subject (in percent)", true), - new BoolSetting("show_current_grade", "Show Currently Achieved Grade", "Whether to display the grade the student would achieve when they decide to immediately stop working", false) - ))); loadAllPlugins(new File("plugins")); } } diff --git a/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java b/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java index 7b6c2b6..77f46fc 100644 --- a/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java +++ b/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java @@ -23,7 +23,7 @@ public InputStream open(ResourceLocation location) { String path = location.context() + "/" + location.namespace() + "/" + location.resource(); for (PreLoadedPlugin module : PluginLoader.getInstance().getPluginInfos()) { - ClassLoader cl = module.classLoader(); + ClassLoader cl = module.resourceLoader(); InputStream stream = cl.getResourceAsStream(path); if (stream != null) { @@ -40,7 +40,7 @@ public List openAll(ResourceLocation location) { List inputStreams = new ArrayList<>(); for (PreLoadedPlugin module : PluginLoader.getInstance().getPluginInfos()) { - ClassLoader cl = module.classLoader(); + ClassLoader cl = module.resourceLoader(); InputStream stream = cl.getResourceAsStream(path); if (stream != null) { @@ -58,7 +58,7 @@ public Collection list(Pattern pattern) { final Path virtualRoot = Paths.get("").toAbsolutePath().normalize(); for (PreLoadedPlugin module : PluginLoader.getInstance().getPluginInfos()) { - try (ZipFile zip = new ZipFile(new File(module.classLoader().getURLs()[0].toURI()))) { + try (ZipFile zip = new ZipFile(new File(module.resourceLoader().getURLs()[0].toURI()))) { Enumeration entries = zip.entries(); @@ -69,7 +69,10 @@ public Collection list(Pattern pattern) { String name = entry.getName(); - if (!CoreResourceProvider.isSafeZipEntryName(name, virtualRoot)) + if (!CoreResourceProvider.isSafeZipEntryName(name, virtualRoot)) { + continue; + } + if (pattern.matcher(name).matches()) { ResourceLocation loc = ResourceLocation.fromPath(name); if (loc != null) result.add(loc); diff --git a/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java b/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java index 0f794a0..dcb82df 100644 --- a/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java +++ b/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java @@ -5,7 +5,8 @@ record PreLoadedPlugin ( PluginDescription description, Class clazz, - URLClassLoader classLoader + URLClassLoader classLoader, + URLClassLoader resourceLoader ) { } \ No newline at end of file From 3d934132d94a71e179342b291f4c8a6238bb339b Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Tue, 31 Mar 2026 15:22:19 +0200 Subject: [PATCH 3/4] New get request handler type: MergingFileRequestHandler this is for merging js and css files among plugins --- .../webserver/handlers/GetRequestHandler.java | 5 ++++ .../webserver/responses/GetResponse.java | 28 ++++++++++++++----- src/main/resources/meta/paths/get_paths.json | 4 +-- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/GetRequestHandler.java b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/GetRequestHandler.java index fc35cab..ff9e130 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/GetRequestHandler.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/GetRequestHandler.java @@ -51,6 +51,10 @@ public static GetResponse handleTemplatingFileRequest(GetRequest request) { String user = getUser(request).getUsername(); return GetResponse.getResource(request, request.toResourceLocation(user), user, true); } + public static GetResponse handleMergingFileRequest(GetRequest request) { + String user = getUser(request).getUsername(); + return GetResponse.getResource(request, request.toResourceLocation(user), user, false, true); + } public static GetResponse handleSQLRequest(GetRequest request) { String user = getUser(request).getUsername(); return GetResponse.getResource(request, request.toResourceLocation(user), user, false); @@ -68,6 +72,7 @@ public final void registerHandlers() { ThrowingFunction handlerFunction = switch (webPath.handlerType()) { case "FileRequestHandler" -> GetRequestHandler::handleFileRequest; case "TemplatingFileRequestHandler" -> GetRequestHandler::handleTemplatingFileRequest; + case "MergingFileRequestHandler" -> GetRequestHandler::handleMergingFileRequest; case "SQLRequestHandler" -> GetRequestHandler::handleSQLRequest; case "PluginRequestHandler" -> GetRequestHandler::handlePluginRequest; default -> throw new IllegalArgumentException("Unknown handler type: " + webPath.handlerType()); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java index 327a575..71d10da 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java @@ -23,28 +23,28 @@ public class GetResponse implements HttpResponse { * @return the GetResponse object */ public static GetResponse notFound(HttpRequest request) { - return new GetResponse(request, Status.NOT_FOUND, new ResourceLocation("html", "errors", "404.html"), ContentType.HTML, "", false); + return new GetResponse(request, Status.NOT_FOUND, new ResourceLocation("html", "errors", "404.html"), ContentType.HTML, "", false, false); } /** * Returns a response for a GET request that resulted in an internal error. * @return the GetResponse object */ public static GetResponse internalServerError(HttpRequest request) { - return new GetResponse(request, Status.INTERNAL_SERVER_ERROR, new ResourceLocation("html", "errors", "500.html"), ContentType.HTML, "", false); + return new GetResponse(request, Status.INTERNAL_SERVER_ERROR, new ResourceLocation("html", "errors", "500.html"), ContentType.HTML, "", false, false); } /** * Returns a response for a GET request the user has no access to. * @return the HttpRequest object */ public static GetResponse forbidden(HttpRequest request) { - return new GetResponse(request, Status.FORBIDDEN, new ResourceLocation("html", "errors", "403.html"), ContentType.HTML, "", false); + return new GetResponse(request, Status.FORBIDDEN, new ResourceLocation("html", "errors", "403.html"), ContentType.HTML, "", false, false); } /** * Returns a response for a GET request the user must be logged in for. * @return the GetResponse object */ public static GetResponse unauthorized(HttpRequest request) { - return new GetResponse(request, Status.UNAUTHORIZED, new ResourceLocation("html", "errors", "401.html"), ContentType.HTML, "", false); + return new GetResponse(request, Status.UNAUTHORIZED, new ResourceLocation("html", "errors", "401.html"), ContentType.HTML, "", false, false); } /** * Returns a response for a GET request the user must be logged in for. @@ -59,6 +59,7 @@ public static GetResponse unauthorized(String message, HttpRequest request) { new ResourceLocation("html", "errors", "401.html"), ContentType.HTML, message, + false, false ); } @@ -75,6 +76,7 @@ public static GetResponse tooManyRequests(String message, HttpRequest request) { new ResourceLocation("html", "errors", "429.html"), ContentType.HTML, message, + false, false ); } @@ -115,6 +117,7 @@ public static GetResponse tooManyRequests(HttpRequest request) { private final HttpRequest request; private final boolean isTemplating; + private final boolean isMerging; /** * Creates a new GetResponse with the given parameters. @@ -125,13 +128,14 @@ public static GetResponse tooManyRequests(HttpRequest request) { * @param user the user who made the request * @param isTemplating whether the resource is a templated resource */ - public GetResponse(HttpRequest request, Status status, ResourceLocation resourceLocation, ContentType contentType, String user, boolean isTemplating) { + public GetResponse(HttpRequest request, Status status, ResourceLocation resourceLocation, ContentType contentType, String user, boolean isTemplating, boolean isMerging) { this.status = status; this.resourceLocation = resourceLocation; this.contentType = contentType; this.user = user; this.request = request; this.isTemplating = isTemplating; + this.isMerging = isMerging; } /** * Returns a response for a GET request for the given resource. @@ -143,9 +147,15 @@ public static GetResponse getResource(HttpRequest request, ResourceLocation reso return getResource(request, resourceLocation, user, isTemplating, request.getPath()); } public static GetResponse getResource(HttpRequest request, ResourceLocation resourceLocation, String user, boolean isTemplating, String path) { + return getResource(request, resourceLocation, user, isTemplating, false, path); + } + public static GetResponse getResource(HttpRequest request, ResourceLocation resourceLocation, String user, boolean isTemplating, boolean isMerging) { + return getResource(request, resourceLocation, user, isTemplating, isMerging, request.getPath()); + } + public static GetResponse getResource(HttpRequest request, ResourceLocation resourceLocation, String user, boolean isTemplating, boolean isMerging, String path) { try { if (AccessManager.getInstance().hasAccess(user, path)) { - return new GetResponse(request, Status.OK, resourceLocation, ContentType.ofResourceLocation(resourceLocation), user, isTemplating); + return new GetResponse(request, Status.OK, resourceLocation, ContentType.ofResourceLocation(resourceLocation), user, isTemplating, isMerging); } else { return unauthorized(request); } @@ -175,7 +185,11 @@ public void respond(PrintStream out) { String resource = ""; if (resourceLocation != null) { if (!resourceLocation.isVirtual()) { - resource = Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); + if (!isMerging) { + resource = Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); + } else { + resource = Server.getInstance().getResourceManager().readCodeMerged(resourceLocation); + } } else { if (resourceLocation.namespace().equals("plugin")) { resource = PluginRequestHandler.getPluginResource(resourceLocation.resource()); diff --git a/src/main/resources/meta/paths/get_paths.json b/src/main/resources/meta/paths/get_paths.json index 8250252..137e12a 100644 --- a/src/main/resources/meta/paths/get_paths.json +++ b/src/main/resources/meta/paths/get_paths.json @@ -217,7 +217,7 @@ "/student-database.js": { "type": "GET", - "handler_type": "FileRequestHandler", + "handler_type": "MergingFileRequestHandler", "namespaces": ["site"], "context": "js", "access_level": "public" @@ -283,7 +283,7 @@ "/style.css": { "type": "GET", - "handler_type": "FileRequestHandler", + "handler_type": "MergingFileRequestHandler", "namespaces": ["site"], "context": "css", "access_level": "public" From f2045305233047918186049280630328471eeb00 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Tue, 31 Mar 2026 15:27:54 +0200 Subject: [PATCH 4/4] Also excluded the js library parts of the results plugin from the main student-database.js --- .../resources/js/site/student-database.js | 133 ------------------ 1 file changed, 133 deletions(-) diff --git a/src/main/resources/js/site/student-database.js b/src/main/resources/js/site/student-database.js index c3fe14b..ec439b1 100644 --- a/src/main/resources/js/site/student-database.js +++ b/src/main/resources/js/site/student-database.js @@ -499,123 +499,6 @@ function setStudentInfo(studentData) { const graduationLevels = ["Neustarter", "Starter", "Durchstarter", "Lernprofi"]; document.getElementById('student-graduation').textContent = graduationLevels[studentData.graduationLevel]; } -function getGrade(progress) { - if (progress >= 0.85) return 1; - if (progress >= 0.70) return 2; - if (progress >= 0.55) return 3; - if (progress >= 0.40) return 4; - if (progress >= 0.20) return 5; - return 6; -} -function getGradeLabel(grade) { - return [ - "", - "1 (Sehr gut)", - "2 (Gut)", - "3 (Befriedigend)", - "4 (Ausreichend)", - "5 (Mangelhaft)", - "6 (Ungenügend)" - ][grade]; -} -function getTaskColorClass(level) { - if (level === 1) return "level1"; - if (level === 2) return "level2"; - if (level === 3) return "level3"; - return "special"; -} -function createGradeScale() { - const scale = document.createElement('div'); - scale.className = 'grade-scale'; - [6,5,4,3,2,1].forEach(grade => { - const label = document.createElement('span'); - label.className = 'grade-label'; - label.textContent = getGradeLabel(grade); - scale.appendChild(label); - }); - return scale; -} - -function createBarChart(subject, subjectName, studentData, settings) { - const chart = document.createElement('div'); - chart.className = 'bar-chart'; - - const title = document.createElement('h3'); - title.textContent = subjectName; - chart.appendChild(title); - - // Grade scale - chart.appendChild(createGradeScale()); - - // Get all tasks for this subject (from completed, selected, and topic.tasks) - const completed = studentData.completedTasks.filter(t => (t.topic && t.topic.subject && t.topic.subject.name === subject.name) || (!t.topic && t.subject && t.subject == subject.name)); - const selected = studentData.selectedTasks.filter(t => t.topic && t.topic.id && t.topic.subject && t.topic.subject.name === subject.name); - // For demo: If you have all tasks for the subject, fetch them here. Otherwise, use completed+selected as all tasks. - let allTasks = [...completed, ...selected]; - console.log(`Creating bar chart for subject: ${subjectName}`, subject, { - completed: completed.length, - selected: selected.length, - allTasks: allTasks.length - }); - // Remove duplicates by id - allTasks = allTasks.filter((task, idx, arr) => arr.findIndex(t => t.id === task.id && t.name === task.name) === idx); - - // Sort by completion: completed first (by completion order if available), then selected, then others - allTasks.sort((a, b) => { - const aCompleted = completed.some(t => t.id === a.id); - const bCompleted = completed.some(t => t.id === b.id); - if (aCompleted && !bCompleted) return -1; - if (!aCompleted && bCompleted) return 1; - // Optionally: sort by task.number or id - return (a.number || 0) - (b.number || 0); - }); - - // Bar container - const bar = document.createElement('div'); - bar.className = 'bar-container'; - - allTasks.forEach(task => { - const div = document.createElement('div'); - div.className = 'bar-task ' + getTaskColorClass(task.niveau); - if (completed.some(t => t.id === task.id && t.name === task.name)) { - div.classList.add('completed'); - } else if (selected.some(t => t.id === task.id && t.name === task.name)) { - div.classList.add('selected', 'hatched'); - } - div.style.width = (100 * task.ratio) + '%'; - // Optional: show task number or name - const label = document.createElement('span'); - label.className = 'bar-task-label'; - label.textContent = task.number || ''; - div.appendChild(label); - bar.appendChild(div); - }); - - chart.appendChild(bar); - - // Progress and grade info - const progress = studentData.currentProgress && studentData.currentProgress[subjectName] - ? studentData.currentProgress[subjectName].progress : 0; - const predicted = studentData.predictedProgress && studentData.predictedProgress[subjectName] - ? studentData.predictedProgress[subjectName].predictedProgress : 0; - - const grade = settings.show_current_grade ? getGrade(progress) : 0; - const predictedGrade = getGrade(predicted); - - if (!settings || settings.show_current_progress) { - const gradeInfo = document.createElement('div'); - gradeInfo.className = 'grade-current'; - gradeInfo.innerHTML = `Aktueller Fortschritt: ${getGradeLabel(grade)} (${Math.round(progress * 100)}%)`; - chart.appendChild(gradeInfo); - } - - const predInfo = document.createElement('div'); - predInfo.className = 'grade-prediction'; - predInfo.innerHTML = `Prognose für Jahresende: ${getGradeLabel(predictedGrade)} (${Math.round(predicted * 100)}%)`; - chart.appendChild(predInfo); - - return chart; -} function createPanel(header, bodyContent, loadCallback) { const panel = document.createElement('div'); panel.className = 'panel'; @@ -812,22 +695,6 @@ function loadStudentDashboard(studentData, subjects, teacherPerms) { // Show stu subjectList.appendChild(panel); }); } -async function loadStudentResultView(studentData) { - const config = await fetchPluginConfig('result_view'); - - document.getElementById('student-name').textContent = `${studentData.firstName} ${studentData.lastName}`; - - // Get all subjects from progress keys - const subjectNames = Object.keys(studentData.currentProgress || {}); - // If you have subject objects, map them here; else, use names as fallback - // For demo: create fake subject objects - const subjects = subjectNames.map(name => ({ id: name, name })); - - const charts = document.getElementById('charts'); - subjects.forEach(subject => { - charts.appendChild(createBarChart(subject, subject.name, studentData, config.values)); - }); -} let plugin_panels = {} function loadPluginSection(pluginKey) { return createPanel(pluginKey, document.createElement("div"), async (header, body) => {