From cbcb0afa7d1fa72f775b5a31784f3b1b12e72602 Mon Sep 17 00:00:00 2001
From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com>
Date: Sun, 13 Oct 2024 17:09:29 +0200
Subject: [PATCH 01/85] Development: Fix exam assessment e2e tests failing
(#9462)
---
src/test/playwright/e2e/exam/ExamAssessment.spec.ts | 2 +-
src/test/playwright/e2e/exam/ExamDateVerification.spec.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/test/playwright/e2e/exam/ExamAssessment.spec.ts b/src/test/playwright/e2e/exam/ExamAssessment.spec.ts
index 3bfee439d546..4cd492e86daa 100644
--- a/src/test/playwright/e2e/exam/ExamAssessment.spec.ts
+++ b/src/test/playwright/e2e/exam/ExamAssessment.spec.ts
@@ -332,7 +332,6 @@ export async function prepareExam(course: Course, end: dayjs.Dayjs, exerciseType
gracePeriod: 10,
};
exam = await examAPIRequests.createExam(examConfig);
- await examAPIRequests.registerStudentForExam(exam, studentOne);
let additionalData = {};
switch (exerciseType) {
case ExerciseType.PROGRAMMING:
@@ -347,6 +346,7 @@ export async function prepareExam(course: Course, end: dayjs.Dayjs, exerciseType
}
const exercise = await examExerciseGroupCreation.addGroupWithExercise(exam, exerciseType, additionalData);
+ await examAPIRequests.registerStudentForExam(exam, studentOne);
await examAPIRequests.generateMissingIndividualExams(exam);
await examAPIRequests.prepareExerciseStartForExam(exam);
exercise.additionalData = additionalData;
diff --git a/src/test/playwright/e2e/exam/ExamDateVerification.spec.ts b/src/test/playwright/e2e/exam/ExamDateVerification.spec.ts
index bf3f981640b0..48d80de67065 100644
--- a/src/test/playwright/e2e/exam/ExamDateVerification.spec.ts
+++ b/src/test/playwright/e2e/exam/ExamDateVerification.spec.ts
@@ -68,9 +68,9 @@ test.describe('Exam date verification', () => {
endDate: dayjs().add(3, 'days'),
};
const exam = await examAPIRequests.createExam(examConfig);
- await examAPIRequests.registerStudentForExam(exam, studentOne);
const exerciseGroup = await examAPIRequests.addExerciseGroupForExam(exam);
const exercise = await exerciseAPIRequests.createTextExercise({ exerciseGroup });
+ await examAPIRequests.registerStudentForExam(exam, studentOne);
await examAPIRequests.generateMissingIndividualExams(exam);
await examAPIRequests.prepareExerciseStartForExam(exam);
await login(studentOne);
@@ -105,9 +105,9 @@ test.describe('Exam date verification', () => {
endDate: examEnd,
};
const exam = await examAPIRequests.createExam(examConfig);
- await examAPIRequests.registerStudentForExam(exam, studentOne);
const exerciseGroup = await examAPIRequests.addExerciseGroupForExam(exam);
const exercise = await exerciseAPIRequests.createTextExercise({ exerciseGroup });
+ await examAPIRequests.registerStudentForExam(exam, studentOne);
await examAPIRequests.generateMissingIndividualExams(exam);
await examAPIRequests.prepareExerciseStartForExam(exam);
await login(studentOne);
From f01378426daaac15443a1844409f2cc8ba773d68 Mon Sep 17 00:00:00 2001
From: Paul Rangger <48455539+PaRangger@users.noreply.github.com>
Date: Sun, 13 Oct 2024 17:23:34 +0200
Subject: [PATCH 02/85] Communication: Fix user interface reload on channel
selection (#9464)
---
.../course-conversations.component.ts | 8 +-------
.../conversation-messages.component.html | 3 ++-
.../conversation-messages.component.scss | 4 ++++
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts b/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts
index 159818511aa8..2d56484f8a46 100644
--- a/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts
+++ b/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts
@@ -156,11 +156,11 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
this.subscribeToIsCodeOfConductAccepted();
this.subscribeToIsCodeOfConductPresented();
this.subscribeToConversationsOfUser();
- this.subscribeToLoading();
this.updateQueryParameters();
this.prepareSidebarData();
this.metisConversationService.checkIsCodeOfConductAccepted(this.course!);
this.isServiceSetUp = true;
+ this.isLoading = false;
}
});
@@ -224,12 +224,6 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
});
}
- private subscribeToLoading() {
- this.metisConversationService.isLoading$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((isLoading: boolean) => {
- this.isLoading = isLoading;
- });
- }
-
acceptCodeOfConduct() {
if (this.course) {
this.metisConversationService.acceptCodeOfConduct(this.course);
diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.html b/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.html
index 373d7b8d9694..f2931fa40d90 100644
--- a/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.html
+++ b/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.html
@@ -67,8 +67,9 @@
diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss b/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss
index 01f09f46008e..d1beda749f53 100644
--- a/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss
+++ b/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss
@@ -59,4 +59,8 @@
padding-top: 100px;
padding-bottom: 100px;
}
+
+ .conversation-messages-message-list.is-fetching-posts {
+ display: none;
+ }
}
From e072169c67c82f338a20ae76c7abb0e7f352eca6 Mon Sep 17 00:00:00 2001
From: Patrik Zander <38403547+pzdr7@users.noreply.github.com>
Date: Sun, 13 Oct 2024 17:26:27 +0200
Subject: [PATCH 03/85] Programming exercises: Add custom themes for the Monaco
editor (#9463)
---
.../model/themes/editor-colors.interface.ts | 86 +++++++++++++++++++
...nguage-token-style-definition.interface.ts | 27 ++++++
.../model/themes/monaco-dark.theme.ts | 47 ++++++++++
.../model/themes/monaco-editor-theme.model.ts | 66 ++++++++++++++
.../model/themes/monaco-light.theme.ts | 46 ++++++++++
.../monaco-theme-definition.interface.ts | 9 ++
.../monaco-editor.component.scss | 5 ++
.../monaco-editor/monaco-editor.service.ts | 33 +++++--
.../monaco-editor/monaco-editor-theme.spec.ts | 67 +++++++++++++++
.../monaco-editor.service.spec.ts | 8 +-
10 files changed, 384 insertions(+), 10 deletions(-)
create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/editor-colors.interface.ts
create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/language-token-style-definition.interface.ts
create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/monaco-dark.theme.ts
create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts
create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/monaco-light.theme.ts
create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/monaco-theme-definition.interface.ts
create mode 100644 src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-theme.spec.ts
diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/editor-colors.interface.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/editor-colors.interface.ts
new file mode 100644
index 000000000000..a89139f285ee
--- /dev/null
+++ b/src/main/webapp/app/shared/monaco-editor/model/themes/editor-colors.interface.ts
@@ -0,0 +1,86 @@
+/**
+ * Interface for the colors of the editor.
+ * See https://code.visualstudio.com/api/references/theme-color
+ * All colors must be in the format '#RRGGBB' or '#RRGGBBAA'.
+ */
+export interface EditorColors {
+ /**
+ * The background color of the editor.
+ */
+ backgroundColor?: string;
+ /**
+ * The default color of all text in the editor, not including syntax highlighting.
+ */
+ foregroundColor?: string;
+ /**
+ * Colors for line numbers in the editor.
+ */
+ lineNumbers?: {
+ /**
+ * The color of the line numbers.
+ */
+ foregroundColor?: string;
+ /**
+ * The color of the line number of the line that the cursor is on.
+ */
+ activeForegroundColor?: string;
+ /**
+ * The color of the line numbers for dimmed lines. This is used for the final newline of the code.
+ */
+ dimmedForegroundColor?: string;
+ };
+ /**
+ * Colors for the active line highlight in the editor.
+ */
+ lineHighlight?: {
+ /**
+ * The color used as the background color for the cursor's current line.
+ */
+ backgroundColor?: string;
+ /**
+ * The color used for the border of the cursor's current line.
+ */
+ borderColor?: string;
+ };
+ /**
+ * Colors for the diff editor.
+ */
+ diff?: {
+ /**
+ * The background color for inserted lines in the diff editor.
+ */
+ insertedLineBackgroundColor?: string;
+ /**
+ * The background color for inserted text in the diff editor.
+ * This will overlap with the `insertedLineBackgroundColor`.
+ */
+ insertedTextBackgroundColor?: string;
+ /**
+ * The background color for removed lines in the diff editor.
+ */
+ removedTextBackgroundColor?: string;
+ /**
+ * The background color for removed text in the diff editor.
+ * This will overlap with the `removedLineBackgroundColor`.
+ */
+ removedLineBackgroundColor?: string;
+ /**
+ * The color used for the diagonal fill in the diff editor.
+ * This is used when the diff editor pads the length of the files to align the lines of the original and modified files.
+ */
+ diagonalFillColor?: string;
+ /**
+ * Colors for the diff editor gutter. This is the area to the left of the editor that shows the line numbers.
+ */
+ gutter?: {
+ /**
+ * The background color for inserted lines in the diff editor gutter.
+ */
+ insertedLineBackgroundColor?: string;
+ /**
+ * The background color for removed lines in the diff editor gutter.
+ */
+ removedLineBackgroundColor?: string;
+ };
+ };
+}
diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/language-token-style-definition.interface.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/language-token-style-definition.interface.ts
new file mode 100644
index 000000000000..d811b868ad58
--- /dev/null
+++ b/src/main/webapp/app/shared/monaco-editor/model/themes/language-token-style-definition.interface.ts
@@ -0,0 +1,27 @@
+/**
+ * Interface for the style of a token in a language.
+ * The editor applies these styles to the tokens in the specified language (or all languages), e.g. identifiers, keywords, etc.
+ */
+export interface LanguageTokenStyleDefinition {
+ /**
+ * The token to style, e.g. identifier
+ */
+ token: string;
+ /**
+ * The language ID for which the token style should be applied.
+ * If not specified, the style is applied to all languages.
+ */
+ languageId?: string;
+ /**
+ * The color of the text that should be applied to the token.
+ */
+ foregroundColor?: string;
+ /**
+ * The background color that should be applied to the token.
+ */
+ backgroundColor?: string;
+ /**
+ * The font style that should be applied to the token.
+ */
+ fontStyle?: 'italic' | 'bold' | 'underline';
+}
diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-dark.theme.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-dark.theme.ts
new file mode 100644
index 000000000000..24dc4969284e
--- /dev/null
+++ b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-dark.theme.ts
@@ -0,0 +1,47 @@
+import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';
+
+export const MONACO_DARK_THEME_DEFINITION: MonacoThemeDefinition = {
+ id: 'custom-dark',
+ baseTheme: 'vs-dark',
+ tokenStyles: [
+ {
+ token: 'keyword',
+ foregroundColor: '#ff7b72',
+ },
+ {
+ token: 'comment',
+ foregroundColor: '#9198a1',
+ },
+ {
+ token: 'string',
+ foregroundColor: '#a5d6ff',
+ },
+ {
+ token: 'number',
+ foregroundColor: '#79c0ff',
+ },
+ ],
+ editorColors: {
+ backgroundColor: '#181a18',
+ lineHighlight: {
+ borderColor: '#00000000',
+ backgroundColor: '#282a2e',
+ },
+ lineNumbers: {
+ foregroundColor: '#ffffff',
+ activeForegroundColor: '#ffffff',
+ dimmedForegroundColor: '#ffffff',
+ },
+ diff: {
+ insertedLineBackgroundColor: '#2ea04326',
+ insertedTextBackgroundColor: '#2ea04326',
+ removedLineBackgroundColor: '#f8514926',
+ removedTextBackgroundColor: '#f8514946',
+ diagonalFillColor: '#00000000',
+ gutter: {
+ insertedLineBackgroundColor: '#3fb9504d',
+ removedLineBackgroundColor: '#f851494d',
+ },
+ },
+ },
+};
diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts
new file mode 100644
index 000000000000..c9f34f06b3de
--- /dev/null
+++ b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts
@@ -0,0 +1,66 @@
+import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';
+import * as monaco from 'monaco-editor';
+
+export class MonacoEditorTheme {
+ constructor(private readonly themeDefinition: MonacoThemeDefinition) {}
+
+ getId(): string {
+ return this.themeDefinition.id;
+ }
+
+ /**
+ * Creates a new record without any entries that have a value of `undefined`.
+ * @param record The record whose keys to filter.
+ * @returns The new record, only containing keys with defined values.
+ * @private
+ */
+ private getRecordWithoutUndefinedEntries(record: Record): Record {
+ const result: Record = {};
+ for (const [key, value] of Object.entries(record)) {
+ if (value !== undefined) {
+ result[key] = value;
+ }
+ }
+ return result;
+ }
+
+ register(): void {
+ const colorDefinitions = this.themeDefinition.editorColors;
+ // The color keys are available here: https://code.visualstudio.com/api/references/theme-color
+ const colors = {
+ 'editor.background': colorDefinitions.backgroundColor,
+ 'editor.foreground': colorDefinitions.foregroundColor,
+ 'editorLineNumber.foreground': colorDefinitions.lineNumbers?.foregroundColor,
+ 'editorLineNumber.activeForeground': colorDefinitions.lineNumbers?.activeForegroundColor,
+ 'editorLineNumber.dimmedForeground': colorDefinitions.lineNumbers?.dimmedForegroundColor,
+ 'editor.lineHighlightBackground': colorDefinitions.lineHighlight?.backgroundColor,
+ 'editor.lineHighlightBorder': colorDefinitions.lineHighlight?.borderColor,
+ 'diffEditor.insertedLineBackground': colorDefinitions.diff?.insertedLineBackgroundColor,
+ 'diffEditor.insertedTextBackground': colorDefinitions.diff?.insertedTextBackgroundColor,
+ 'diffEditor.removedTextBackground': colorDefinitions.diff?.removedTextBackgroundColor,
+ 'diffEditor.removedLineBackground': colorDefinitions.diff?.removedLineBackgroundColor,
+ 'diffEditor.diagonalFill': colorDefinitions.diff?.diagonalFillColor,
+ 'diffEditorGutter.insertedLineBackground': colorDefinitions.diff?.gutter?.insertedLineBackgroundColor,
+ 'diffEditorGutter.removedLineBackground': colorDefinitions.diff?.gutter?.removedLineBackgroundColor,
+ };
+
+ const tokenStyleDefinitions = this.themeDefinition.tokenStyles;
+ const rules = tokenStyleDefinitions.map((tokenDefinition) => {
+ // Language-specific tokens have the key `token.languageId`, e.g. keyword.custom-md
+ return {
+ token: `${tokenDefinition.token}${tokenDefinition.languageId ? '.' + tokenDefinition.languageId : ''}`,
+ foreground: tokenDefinition.foregroundColor,
+ background: tokenDefinition.backgroundColor,
+ fontStyle: tokenDefinition.fontStyle,
+ };
+ });
+
+ // We cannot pass undefined colors to Monaco, so we filter them out to preserve the default values.
+ monaco.editor.defineTheme(this.getId(), {
+ base: this.themeDefinition.baseTheme,
+ inherit: true,
+ rules: rules,
+ colors: this.getRecordWithoutUndefinedEntries(colors),
+ });
+ }
+}
diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-light.theme.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-light.theme.ts
new file mode 100644
index 000000000000..53d265ab5766
--- /dev/null
+++ b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-light.theme.ts
@@ -0,0 +1,46 @@
+import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';
+
+export const MONACO_LIGHT_THEME_DEFINITION: MonacoThemeDefinition = {
+ id: 'custom-light',
+ baseTheme: 'vs',
+ tokenStyles: [
+ {
+ token: 'keyword',
+ foregroundColor: '#cf222e',
+ },
+ {
+ token: 'comment',
+ foregroundColor: '#59636e',
+ },
+ {
+ token: 'string',
+ foregroundColor: '#0a3069',
+ },
+ {
+ token: 'number',
+ foregroundColor: '#0550ae',
+ },
+ ],
+ editorColors: {
+ lineHighlight: {
+ borderColor: '#00000000',
+ backgroundColor: '#e8e8e8',
+ },
+ lineNumbers: {
+ foregroundColor: '#000000',
+ activeForegroundColor: '#000000',
+ dimmedForegroundColor: '#000000',
+ },
+ diff: {
+ insertedLineBackgroundColor: '#dafbe1e6',
+ insertedTextBackgroundColor: '#aceebbe6',
+ removedLineBackgroundColor: '#ffebe9ef',
+ removedTextBackgroundColor: '#ff818250',
+ diagonalFillColor: '#00000000',
+ gutter: {
+ insertedLineBackgroundColor: '#d1f8d9',
+ removedLineBackgroundColor: '#ffcecb',
+ },
+ },
+ },
+};
diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-theme-definition.interface.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-theme-definition.interface.ts
new file mode 100644
index 000000000000..0de6563077a1
--- /dev/null
+++ b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-theme-definition.interface.ts
@@ -0,0 +1,9 @@
+import { LanguageTokenStyleDefinition } from 'app/shared/monaco-editor/model/themes/language-token-style-definition.interface';
+import { EditorColors } from 'app/shared/monaco-editor/model/themes/editor-colors.interface';
+
+export interface MonacoThemeDefinition {
+ id: string;
+ baseTheme: 'vs' | 'vs-dark' | 'hc-light' | 'hc-black';
+ tokenStyles: LanguageTokenStyleDefinition[];
+ editorColors: EditorColors;
+}
diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.scss b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.scss
index 96f11784298f..6886fcfcc0dc 100644
--- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.scss
+++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.scss
@@ -3,6 +3,11 @@
.monaco-editor-container {
width: 100%;
height: 100%;
+
+ .monaco-editor {
+ // Disables the focus border around the editor.
+ outline: none;
+ }
}
/*
diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
index b16cb7cf6b18..c83734732223 100644
--- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
+++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
@@ -3,6 +3,9 @@ import * as monaco from 'monaco-editor';
import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language';
import { Theme, ThemeService } from 'app/core/theme/theme.service';
import { toSignal } from '@angular/core/rxjs-interop';
+import { MONACO_LIGHT_THEME_DEFINITION } from 'app/shared/monaco-editor/model/themes/monaco-light.theme';
+import { MonacoEditorTheme } from 'app/shared/monaco-editor/model/themes/monaco-editor-theme.model';
+import { MONACO_DARK_THEME_DEFINITION } from 'app/shared/monaco-editor/model/themes/monaco-dark.theme';
/**
* Service providing shared functionality for the Monaco editor.
@@ -11,29 +14,41 @@ import { toSignal } from '@angular/core/rxjs-interop';
*/
@Injectable({ providedIn: 'root' })
export class MonacoEditorService {
- static readonly LIGHT_THEME_ID = 'vs';
- static readonly DARK_THEME_ID = 'vs-dark';
-
private readonly themeService: ThemeService = inject(ThemeService);
private readonly currentTheme = toSignal(this.themeService.getCurrentThemeObservable(), { requireSync: true });
+ private lightTheme: MonacoEditorTheme;
+ private darkTheme: MonacoEditorTheme;
+
constructor() {
- monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID });
- monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG);
- monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE);
+ this.registerCustomThemes();
+ this.registerCustomMarkdownLanguage();
effect(() => {
this.applyTheme(this.currentTheme());
});
}
+ private registerCustomThemes(): void {
+ this.lightTheme = new MonacoEditorTheme(MONACO_LIGHT_THEME_DEFINITION);
+ this.darkTheme = new MonacoEditorTheme(MONACO_DARK_THEME_DEFINITION);
+ this.lightTheme.register();
+ this.darkTheme.register();
+ }
+
+ private registerCustomMarkdownLanguage(): void {
+ monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID });
+ monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG);
+ monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE);
+ }
+
/**
* Applies the given theme to the Monaco editor.
* @param artemisTheme The theme to apply.
* @private
*/
private applyTheme(artemisTheme: Theme): void {
- monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? MonacoEditorService.LIGHT_THEME_ID : MonacoEditorService.DARK_THEME_ID);
+ monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? this.lightTheme.getId() : this.darkTheme.getId());
}
/**
@@ -79,6 +94,10 @@ export class MonacoEditorService {
hideUnchangedRegions: {
enabled: true,
},
+ guides: {
+ indentation: false,
+ },
+ renderLineHighlight: 'none',
fontSize: 12,
});
}
diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-theme.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-theme.spec.ts
new file mode 100644
index 000000000000..da4a35cbdf18
--- /dev/null
+++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-theme.spec.ts
@@ -0,0 +1,67 @@
+import * as monaco from 'monaco-editor';
+import { EditorColors } from 'app/shared/monaco-editor/model/themes/editor-colors.interface';
+import { LanguageTokenStyleDefinition } from 'app/shared/monaco-editor/model/themes/language-token-style-definition.interface';
+import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';
+import { MonacoEditorTheme } from 'app/shared/monaco-editor/model/themes/monaco-editor-theme.model';
+
+describe('MonacoEditorTheme', () => {
+ const colorDefinitions: EditorColors = {
+ backgroundColor: '#181a18',
+ foregroundColor: '#ffffff',
+ diff: {
+ insertedLineBackgroundColor: '#2ea04326',
+ insertedTextBackgroundColor: '#2ea04326',
+ removedLineBackgroundColor: undefined, // Explicit undefined to test that it is removed before being passed to Monaco
+ removedTextBackgroundColor: undefined,
+ },
+ };
+
+ const tokenStyleDefinitions: LanguageTokenStyleDefinition[] = [
+ {
+ token: 'keyword',
+ foregroundColor: '#ff7b72',
+ },
+ {
+ token: 'keyword',
+ languageId: 'custom-language-id',
+ foregroundColor: '#ffffff',
+ },
+ ];
+
+ const themeDefinition: MonacoThemeDefinition = {
+ id: 'test-theme',
+ baseTheme: 'vs',
+ tokenStyles: tokenStyleDefinitions,
+ editorColors: colorDefinitions,
+ };
+
+ it('should correctly register a theme', () => {
+ const theme = new MonacoEditorTheme(themeDefinition);
+ const defineThemeSpy = jest.spyOn(monaco.editor, 'defineTheme');
+ theme.register();
+ expect(defineThemeSpy).toHaveBeenCalledExactlyOnceWith('test-theme', {
+ base: 'vs',
+ inherit: true,
+ rules: [
+ {
+ token: 'keyword',
+ foreground: '#ff7b72',
+ background: undefined,
+ fontStyle: undefined,
+ },
+ {
+ token: 'keyword.custom-language-id',
+ foreground: '#ffffff',
+ background: undefined,
+ fontStyle: undefined,
+ },
+ ],
+ colors: {
+ 'editor.background': '#181a18',
+ 'editor.foreground': '#ffffff',
+ 'diffEditor.insertedLineBackground': '#2ea04326',
+ 'diffEditor.insertedTextBackground': '#2ea04326',
+ },
+ });
+ });
+});
diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts
index 82ffd4e2b8b3..1a5a59e69436 100644
--- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts
+++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts
@@ -6,6 +6,8 @@ import { ArtemisTestModule } from '../../../test.module';
import { CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language';
import { BehaviorSubject } from 'rxjs';
import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer';
+import { MONACO_LIGHT_THEME_DEFINITION } from 'app/shared/monaco-editor/model/themes/monaco-light.theme';
+import { MONACO_DARK_THEME_DEFINITION } from 'app/shared/monaco-editor/model/themes/monaco-dark.theme';
describe('MonacoEditorService', () => {
let monacoEditorService: MonacoEditorService;
@@ -40,17 +42,17 @@ describe('MonacoEditorService', () => {
it('should correctly handle themes', () => {
// Initialization: The editor should be in light mode since that is what we initialized the themeSubject with
- expect(setThemeSpy).toHaveBeenCalledExactlyOnceWith(MonacoEditorService.LIGHT_THEME_ID);
+ expect(setThemeSpy).toHaveBeenCalledExactlyOnceWith(MONACO_LIGHT_THEME_DEFINITION.id);
// Switch to dark theme
themeSubject.next(Theme.DARK);
TestBed.flushEffects();
expect(setThemeSpy).toHaveBeenCalledTimes(2);
- expect(setThemeSpy).toHaveBeenNthCalledWith(2, MonacoEditorService.DARK_THEME_ID);
+ expect(setThemeSpy).toHaveBeenNthCalledWith(2, MONACO_DARK_THEME_DEFINITION.id);
// Switch back to light theme
themeSubject.next(Theme.LIGHT);
TestBed.flushEffects();
expect(setThemeSpy).toHaveBeenCalledTimes(3);
- expect(setThemeSpy).toHaveBeenNthCalledWith(3, MonacoEditorService.LIGHT_THEME_ID);
+ expect(setThemeSpy).toHaveBeenNthCalledWith(3, MONACO_LIGHT_THEME_DEFINITION.id);
});
it.each([
From ca163670a56b8ad29f478b18497cfd57cdb95e5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christoph=20Kn=C3=B6dlseder?=
<53149143+chrisknedl@users.noreply.github.com>
Date: Sun, 13 Oct 2024 20:34:55 +0200
Subject: [PATCH 04/85] Assessment: Remove unnecessary whitespace in result
date (#9465)
---
.../webapp/app/exercises/shared/result/result.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/app/exercises/shared/result/result.component.html b/src/main/webapp/app/exercises/shared/result/result.component.html
index 911320c183df..65c4be9ca9de 100644
--- a/src/main/webapp/app/exercises/shared/result/result.component.html
+++ b/src/main/webapp/app/exercises/shared/result/result.component.html
@@ -61,7 +61,7 @@
}
@if (!isInSidebarCard) {
- ({{ result!.completionDate | artemisTimeAgo }} )
+ ({{ result!.completionDate | artemisTimeAgo }})
}
@if (hasBuildArtifact() && participation.type === ParticipationType.PROGRAMMING) {
From 6549075fade341f3b890c684f74e8b6bbcd9b4ea Mon Sep 17 00:00:00 2001
From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com>
Date: Sun, 13 Oct 2024 20:35:54 +0200
Subject: [PATCH 05/85] Development: Fix exam results overview e2e test (#9472)
---
src/test/playwright/e2e/exam/ExamResults.spec.ts | 2 +-
src/test/playwright/support/requests/ExamAPIRequests.ts | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/test/playwright/e2e/exam/ExamResults.spec.ts b/src/test/playwright/e2e/exam/ExamResults.spec.ts
index 874a49e23089..23168d2be415 100644
--- a/src/test/playwright/e2e/exam/ExamResults.spec.ts
+++ b/src/test/playwright/e2e/exam/ExamResults.spec.ts
@@ -82,7 +82,7 @@ test.describe('Exam Results', () => {
}
exercise = await examExerciseGroupCreation.addGroupWithExercise(exam, testCase.exerciseType, additionalData);
await examAPIRequests.registerStudentForExam(exam, studentOne);
- const studentExams = await examAPIRequests.generateMissingIndividualExams(exam);
+ const studentExams = await examAPIRequests.getAllStudentExams(exam);
studentExam = studentExams[0];
await examAPIRequests.prepareExerciseStartForExam(exam);
});
diff --git a/src/test/playwright/support/requests/ExamAPIRequests.ts b/src/test/playwright/support/requests/ExamAPIRequests.ts
index 3e5e18a04a7d..34ffccb9a8fe 100644
--- a/src/test/playwright/support/requests/ExamAPIRequests.ts
+++ b/src/test/playwright/support/requests/ExamAPIRequests.ts
@@ -165,6 +165,15 @@ export class ExamAPIRequests {
return await response.json();
}
+ /**
+ * Get all student-exams of an exam
+ * @param exam the exam for which the student-exams are fetched
+ */
+ async getAllStudentExams(exam: Exam) {
+ const response = await this.page.request.get(`${COURSE_BASE}/${exam.course!.id}/exams/${exam.id}/student-exams`);
+ return await response.json();
+ }
+
/**
* Prepares individual exercises for exam start
* @param exam the exam for which the exercises are prepared
From ba666c44fabadee95cddbfcfdca88e6c611c5f26 Mon Sep 17 00:00:00 2001
From: Tim Cremer <65229601+cremertim@users.noreply.github.com>
Date: Sun, 13 Oct 2024 22:27:58 +0200
Subject: [PATCH 06/85] Communication: Add default message to empty FAQ view
(#9467)
---
.../app/overview/course-faq/course-faq.component.html | 6 ++++++
.../webapp/app/overview/course-faq/course-faq.component.ts | 3 ++-
src/main/webapp/i18n/de/faq.json | 4 +++-
src/main/webapp/i18n/en/faq.json | 4 +++-
4 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/main/webapp/app/overview/course-faq/course-faq.component.html b/src/main/webapp/app/overview/course-faq/course-faq.component.html
index 115efb80cc1c..57e8599419e6 100644
--- a/src/main/webapp/app/overview/course-faq/course-faq.component.html
+++ b/src/main/webapp/app/overview/course-faq/course-faq.component.html
@@ -27,9 +27,15 @@
+ @if (faqs.length === 0) {
+
+ }
@for (faq of this.filteredFaqs; track faq) {
}
+ @if (filteredFaqs.length === 0 && faqs.length > 0) {
+
+ }
diff --git a/src/main/webapp/app/overview/course-faq/course-faq.component.ts b/src/main/webapp/app/overview/course-faq/course-faq.component.ts
index 03fd207fda21..db5a91e2c3d7 100644
--- a/src/main/webapp/app/overview/course-faq/course-faq.component.ts
+++ b/src/main/webapp/app/overview/course-faq/course-faq.component.ts
@@ -16,6 +16,7 @@ import { loadCourseFaqCategories } from 'app/faq/faq.utils';
import { CustomExerciseCategoryBadgeComponent } from 'app/shared/exercise-categories/custom-exercise-category-badge/custom-exercise-category-badge.component';
import { onError } from 'app/shared/util/global.utils';
import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component';
+import { ArtemisMarkdownModule } from 'app/shared/markdown.module';
@Component({
selector: 'jhi-course-faq',
@@ -23,7 +24,7 @@ import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.co
styleUrls: ['../course-overview.scss', 'course-faq.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: true,
- imports: [ArtemisSharedComponentModule, ArtemisSharedModule, CourseFaqAccordionComponent, CustomExerciseCategoryBadgeComponent, SearchFilterComponent],
+ imports: [ArtemisSharedComponentModule, ArtemisSharedModule, CourseFaqAccordionComponent, CustomExerciseCategoryBadgeComponent, SearchFilterComponent, ArtemisMarkdownModule],
})
export class CourseFaqComponent implements OnInit, OnDestroy {
private ngUnsubscribe = new Subject();
diff --git a/src/main/webapp/i18n/de/faq.json b/src/main/webapp/i18n/de/faq.json
index 0cb07d298310..987b093de2c7 100644
--- a/src/main/webapp/i18n/de/faq.json
+++ b/src/main/webapp/i18n/de/faq.json
@@ -20,7 +20,9 @@
"questionAnswer": "Antwort auf die Frage",
"categories": "Kategorien"
},
- "course": "Kurs"
+ "course": "Kurs",
+ "noExisting": "Momentan existiert für diesen Kurs noch kein FAQ.",
+ "noMatching": "Es gibt kein FAQ, das die Kombination aus Filter- und Suchkriterien erfüllt."
}
}
}
diff --git a/src/main/webapp/i18n/en/faq.json b/src/main/webapp/i18n/en/faq.json
index 1a158eb52c40..3fd403409d2b 100644
--- a/src/main/webapp/i18n/en/faq.json
+++ b/src/main/webapp/i18n/en/faq.json
@@ -20,7 +20,9 @@
"questionAnswer": "Question answer",
"categories": "Categories"
},
- "course": "Course"
+ "course": "Course",
+ "noExisting": "Currently, there is no FAQ available for this course.",
+ "noMatching": "There is no FAQ that matches the combination of filter and search criteria."
}
}
}
From 45a18f8b33a3418aca85b592ad9683cafc38a111 Mon Sep 17 00:00:00 2001
From: Stephan Krusche
Date: Sun, 13 Oct 2024 23:21:55 +0200
Subject: [PATCH 07/85] Programming exercises: Do not always show the request
feedback button in the online code editor (#9475)
---
.../request-feedback-button.component.html | 2 +-
.../request-feedback-button.component.ts | 4 ++--
.../request-feedback-button.component.spec.ts | 16 ++++++++--------
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html
index 6d6addcc2b84..69310708cac3 100644
--- a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html
+++ b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html
@@ -1,4 +1,4 @@
-@if (!isExamExercise) {
+@if (!isExamExercise && requestFeedbackEnabled) {
@if (athenaEnabled) {
@if (exercise().type === ExerciseType.TEXT) {