Skip to content

Commit

Permalink
Experimental: signal-based feedback widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
pzdr7 committed Oct 16, 2024
1 parent 370769e commit 666d451
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
<div class="card">
<jhi-code-editor-header [showTabSizeSelector]="false" [fileName]="selectedFile()" [isLoading]="loadingCount() > 0" />
<div class="card-body card-body-monaco">
@if (selectedFile()) {
@for (feedback of filterFeedbackForSelectedFile(feedbackInternal()); track feedback.id) {
<!-- actual, stored feedback -->
<jhi-code-editor-tutor-assessment-inline-feedback
class="monaco-hidden-element"
[selectedFile]="selectedFile()!"
[codeLine]="Feedback.getReferenceLine(feedback)!"
[feedback]="feedback"
[readOnly]="readOnlyManualFeedback()"
[highlightDifferences]="highlightDifferences()"
[course]="course()"
(onUpdateFeedback)="updateFeedback($event)"
(onDeleteFeedback)="deleteFeedback($event)"
(onCancelFeedback)="cancelFeedback($event)"
/>
<div class="d-none">
@if (selectedFile()) {
@for (feedback of filterFeedbackForSelectedFile(feedbackInternal()); track feedback.id) {
<!-- actual, stored feedback -->
<jhi-code-editor-tutor-assessment-inline-feedback
class="monaco-hidden-element"
[selectedFile]="selectedFile()!"
[codeLine]="Feedback.getReferenceLine(feedback)!"
[feedback]="feedback"
[readOnly]="readOnlyManualFeedback()"
[highlightDifferences]="highlightDifferences()"
[course]="course()"
(onUpdateFeedback)="updateFeedback($event)"
(onDeleteFeedback)="deleteFeedback($event)"
(onCancelFeedback)="cancelFeedback($event)"
/>
}
<!-- new, unsaved feedback -->
@for (line of newFeedbackLines(); track line) {
<jhi-code-editor-tutor-assessment-inline-feedback
class="monaco-hidden-element"
[selectedFile]="selectedFile()!"
[codeLine]="line"
[feedback]="undefined"
[highlightDifferences]="highlightDifferences()"
[readOnly]="readOnlyManualFeedback()"
[course]="course()"
(onUpdateFeedback)="updateFeedback($event)"
(onDeleteFeedback)="deleteFeedback($event)"
(onCancelFeedback)="cancelFeedback($event)"
/>
}
<!-- feedback suggestions -->
@for (suggestion of filterFeedbackForSelectedFile(feedbackSuggestionsInternal()); track suggestion.id) {
<jhi-code-editor-tutor-assessment-inline-feedback-suggestion
[codeLine]="Feedback.getReferenceLine(suggestion)!"
[feedback]="suggestion"
[course]="course()"
(onAcceptSuggestion)="acceptSuggestion($event)"
(onDiscardSuggestion)="discardSuggestion($event)"
/>
}
}
<!-- new, unsaved feedback -->
@for (line of newFeedbackLines(); track line) {
<jhi-code-editor-tutor-assessment-inline-feedback
class="monaco-hidden-element"
[selectedFile]="selectedFile()!"
[codeLine]="line"
[feedback]="undefined"
[highlightDifferences]="highlightDifferences()"
[readOnly]="readOnlyManualFeedback()"
[course]="course()"
(onUpdateFeedback)="updateFeedback($event)"
(onDeleteFeedback)="deleteFeedback($event)"
(onCancelFeedback)="cancelFeedback($event)"
/>
}
<!-- feedback suggestions -->
@for (suggestion of filterFeedbackForSelectedFile(feedbackSuggestionsInternal()); track suggestion.id) {
<jhi-code-editor-tutor-assessment-inline-feedback-suggestion
[codeLine]="Feedback.getReferenceLine(suggestion)!"
[feedback]="suggestion"
[course]="course()"
(onAcceptSuggestion)="acceptSuggestion($event)"
(onDiscardSuggestion)="discardSuggestion($event)"
/>
}
}
</div>
<jhi-monaco-editor
(textChanged)="onFileTextChanged($event)"
[hidden]="!selectedFile() || loadingCount() > 0 || binaryFileSelected()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export class CodeEditorMonacoComponent implements OnChanges {
readonly feedbackInternal = signal<Feedback[]>([]);
readonly feedbackSuggestionsInternal = signal<Feedback[]>([]);

readonly lineNumberToFocus = signal<number | undefined>(undefined);

annotationsArray: Array<Annotation> = [];

constructor() {
Expand All @@ -129,6 +131,20 @@ export class CodeEditorMonacoComponent implements OnChanges {
const annotations = this.buildAnnotations();
untracked(() => this.setBuildAnnotations(annotations));
});

effect(() => {
this.inlineFeedbackSuggestionComponents();
this.inlineFeedbackComponents();
this.renderFeedbackWidgets(this.lineNumberToFocus());
});

effect(() => {
// Upon initialization, the editor is not yet available. We need to wait for it to be initialized before we can render feedback widgets.
untracked(() => {
this.changeDetectorRef.detectChanges();
setTimeout(() => this.renderFeedbackWidgets(), 0);
});
});
}

async ngOnChanges(changes: SimpleChanges): Promise<void> {
Expand All @@ -143,7 +159,7 @@ export class CodeEditorMonacoComponent implements OnChanges {
await this.selectFileInEditor(this.selectedFile());
this.setBuildAnnotations(this.annotationsArray);
this.newFeedbackLines.set([]);
this.renderFeedbackWidgets();
// this.renderFeedbackWidgets();
if (this.isTutorAssessment() && !this.readOnlyManualFeedback()) {
this.setupAddFeedbackButton();
}
Expand All @@ -152,7 +168,6 @@ export class CodeEditorMonacoComponent implements OnChanges {

if (changes.feedbacks) {
this.newFeedbackLines.set([]);
this.renderFeedbackWidgets();
}

this.editor().layout();
Expand Down Expand Up @@ -228,8 +243,9 @@ export class CodeEditorMonacoComponent implements OnChanges {
// TODO for a follow-up: in the future, there might be multiple feedback items on the same line.
const lineNumberZeroBased = lineNumber - 1;
if (!this.getInlineFeedbackNode(lineNumberZeroBased)) {
this.newFeedbackLines().push(lineNumberZeroBased);
this.renderFeedbackWidgets(lineNumberZeroBased);
this.lineNumberToFocus.set(lineNumberZeroBased);
this.newFeedbackLines.set([...this.newFeedbackLines(), lineNumberZeroBased]);
// this.renderFeedbackWidgets(lineNumberZeroBased);
}
}

Expand All @@ -245,10 +261,10 @@ export class CodeEditorMonacoComponent implements OnChanges {
this.feedbackInternal()[existingFeedbackIndex] = feedback;
} else {
// New feedback -> save as actual feedback.
this.feedbackInternal().push(feedback);
this.feedbackInternal.set([...this.feedbackInternal(), feedback]);
this.newFeedbackLines.set(this.newFeedbackLines().filter((l) => l !== line));
}
this.renderFeedbackWidgets();
// this.renderFeedbackWidgets();
this.onUpdateFeedback.emit(this.feedbackInternal());
}

Expand All @@ -260,7 +276,7 @@ export class CodeEditorMonacoComponent implements OnChanges {
// We only have to remove new feedback.
if (this.newFeedbackLines().includes(line)) {
this.newFeedbackLines.set(this.newFeedbackLines().filter((l) => l !== line));
this.renderFeedbackWidgets();
// this.renderFeedbackWidgets();
}
}

Expand All @@ -271,7 +287,7 @@ export class CodeEditorMonacoComponent implements OnChanges {
deleteFeedback(feedback: Feedback) {
this.feedbackInternal.set(this.feedbackInternal().filter((f) => !Feedback.areIdentical(f, feedback)));
this.onUpdateFeedback.emit(this.feedbackInternal());
this.renderFeedbackWidgets();
// this.renderFeedbackWidgets();
}

/**
Expand All @@ -291,7 +307,7 @@ export class CodeEditorMonacoComponent implements OnChanges {
*/
discardSuggestion(feedback: Feedback): void {
this.feedbackSuggestionsInternal.set(this.feedbackSuggestionsInternal().filter((f) => f !== feedback));
this.renderFeedbackWidgets();
// this.renderFeedbackWidgets();
this.onDiscardSuggestion.emit(feedback);
}

Expand All @@ -302,24 +318,26 @@ export class CodeEditorMonacoComponent implements OnChanges {
*/
protected renderFeedbackWidgets(lineOfWidgetToFocus?: number) {
// Since the feedback widgets rely on the DOM nodes of each feedback item, Angular needs to re-render each node, hence the timeout.
this.changeDetectorRef.detectChanges();
/*this.changeDetectorRef.detectChanges();
setTimeout(() => {
this.editor().disposeWidgets();
for (const feedback of this.filterFeedbackForSelectedFile([...this.feedbackInternal(), ...this.feedbackSuggestionsInternal()])) {
this.addLineWidgetWithFeedback(feedback);
}
// New, unsaved feedback has no associated object yet.
for (const line of this.newFeedbackLines()) {
const feedbackNode = this.getInlineFeedbackNodeOrElseThrow(line);
this.editor().addLineWidget(line + 1, 'feedback-new-' + line, feedbackNode);
}
}, 0);*/
this.editor().disposeWidgets();
for (const feedback of this.filterFeedbackForSelectedFile([...this.feedbackInternal(), ...this.feedbackSuggestionsInternal()])) {
this.addLineWidgetWithFeedback(feedback);
}

// Focus the text area of the widget on the specified line if available.
if (lineOfWidgetToFocus !== undefined) {
this.getInlineFeedbackNode(lineOfWidgetToFocus)?.querySelector<HTMLTextAreaElement>('#feedback-textarea')?.focus();
}
}, 0);
// New, unsaved feedback has no associated object yet.
for (const line of this.newFeedbackLines()) {
const feedbackNode = this.getInlineFeedbackNodeOrElseThrow(line);
this.editor().addLineWidget(line + 1, 'feedback-new-' + line, feedbackNode);
}

// Focus the text area of the widget on the specified line if available.
if (lineOfWidgetToFocus !== undefined) {
this.getInlineFeedbackNode(lineOfWidgetToFocus)?.querySelector<HTMLTextAreaElement>('#feedback-textarea')?.focus();
this.lineNumberToFocus.set(undefined);
}
}

/**
Expand Down

0 comments on commit 666d451

Please sign in to comment.