Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor javascript statesystem #4530

Merged
merged 38 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
83051f7
Replace annotation state by litState
jorg-vr Mar 31, 2023
b4efd9d
Create improved custom state system
jorg-vr Apr 3, 2023
1d004e3
Remove lit-elemenet-state
jorg-vr Apr 3, 2023
1885f2b
Refactor coursestate
jorg-vr Apr 3, 2023
33a8edd
Fix tests
jorg-vr Apr 3, 2023
4f0a88a
Update evaluationState
jorg-vr Apr 4, 2023
5577358
Refactor exercise state
jorg-vr Apr 4, 2023
701ddc9
refactor MachineAnnotation state
jorg-vr Apr 4, 2023
fe5021d
Shorten names
jorg-vr Apr 4, 2023
68b343f
Fix tests
jorg-vr Apr 4, 2023
a56c810
Refactor savedAnnotationState
jorg-vr Apr 4, 2023
d2714ce
Refactor searchquery
jorg-vr Apr 4, 2023
444df46
Fix sortquery test
jorg-vr Apr 4, 2023
fd17251
Do't return coverage when testing on command line
jorg-vr Apr 4, 2023
f6d6885
Fix searchfield tests
jorg-vr Apr 4, 2023
2420373
Fix javascript tests
jorg-vr Apr 4, 2023
59f97ab
Fix tests
jorg-vr Apr 4, 2023
14bddbc
Refactor submissionstate
jorg-vr Apr 5, 2023
907d1a3
Refactor userannotations
jorg-vr Apr 5, 2023
ee12cf4
Refactor userstae
jorg-vr Apr 5, 2023
8151f0e
Delete old state mixin
jorg-vr Apr 5, 2023
af2ad27
Improve typing
jorg-vr Apr 5, 2023
e071969
Fix tests
jorg-vr Apr 5, 2023
47a6211
Fix lit promise warnings
jorg-vr Apr 5, 2023
0985fc1
Avoid rerendering of threads
jorg-vr Apr 5, 2023
a8ef1d7
Fix tests
jorg-vr Apr 5, 2023
aa2a3b6
Add unit tests for state
jorg-vr Apr 5, 2023
c4e090c
Test staterecorder
jorg-vr Apr 5, 2023
1979ef6
Add tests for statecontroller
jorg-vr Apr 5, 2023
0ef9cdf
Simplify statecontroller test
jorg-vr Apr 5, 2023
f4500ec
Add comments and credits
jorg-vr Apr 5, 2023
c92e135
Apply comments
jorg-vr Apr 7, 2023
767f800
Remove unused import
jorg-vr Apr 7, 2023
8eca9b3
Fix linting
jorg-vr Apr 7, 2023
959d434
Add tests that check if page history keeps working as expected
jorg-vr Apr 11, 2023
0b0c7e3
Fix tests
jorg-vr Apr 11, 2023
8bcbf14
Merge branch 'develop' into chore/replace-state-system
jorg-vr Apr 11, 2023
953eea0
Fix test
jorg-vr Apr 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions app/assets/javascripts/code_listing.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { CodeListingRow } from "components/annotations/code_listing_row";
import { render } from "lit";
import { fetchUserAnnotations } from "state/UserAnnotations";
import { MachineAnnotationData, setMachineAnnotations } from "state/MachineAnnotations";
import { setCourseId } from "state/Courses";
import { setExerciseId } from "state/Exercises";
import { addPermission, setUserId } from "state/Users";
import { getSubmissionId, setCode, setSubmissionId } from "state/Submissions";
import { setQuestionMode } from "state/Annotations";
import { userAnnotationState } from "state/UserAnnotations";
import { MachineAnnotationData, machineAnnotationState } from "state/MachineAnnotations";
import { courseState } from "state/Courses";
import { userState } from "state/Users";
import { submissionState } from "state/Submissions";
import "components/annotations/annotation_options";
import "components/annotations/annotations_count_badge";
import { annotationState } from "state/Annotations";
import { exerciseState } from "state/Exercises";

const MARKING_CLASS = "marked";

function initAnnotations(submissionId: number, courseId: number, exerciseId: number, userId: number, code: string, codeLines: number, questionMode = false): void {
setCode(code);
setCourseId(courseId);
setExerciseId(exerciseId);
setUserId(userId);
setSubmissionId(submissionId);
setQuestionMode(questionMode);
submissionState.code = code;
courseState.id = courseId;
exerciseState.id = exerciseId;
userState.id = userId;
submissionState.id = submissionId;
annotationState.isQuestionMode = questionMode;

const table = document.querySelector<HTMLTableElement>("table.code-listing");
const rows = table.querySelectorAll("tr");
Expand All @@ -34,15 +34,15 @@ function initAnnotations(submissionId: number, courseId: number, exerciseId: num
}

function addMachineAnnotations(data: MachineAnnotationData[]): void {
setMachineAnnotations(data);
machineAnnotationState.setMachineAnnotations(data);
}

function initAnnotateButtons(): void {
addPermission("annotation.create");
userState.addPermission("annotation.create");
}

function loadUserAnnotations(): void {
fetchUserAnnotations(getSubmissionId());
userAnnotationState.fetch(submissionState.id);
}


Expand Down
27 changes: 7 additions & 20 deletions app/assets/javascripts/components/annotations/annotation_form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { watchMixin } from "components/meta/watch_mixin";
import { createRef, Ref, ref } from "lit/directives/ref.js";
import "components/saved_annotations/saved_annotation_input";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { getCourseId } from "state/Courses";
import { stateMixin } from "state/StateMixin";
import { isQuestionMode } from "state/Annotations";
import { annotationState } from "state/Annotations";

// Min and max of the annotation text is defined in the annotation model.
const maxLength = 10_000;
Expand All @@ -28,7 +26,7 @@ const maxLength = 10_000;
* @fires submit - if the users presses the submit button, detail contains {text: string, savedAnnotationId: string}
*/
@customElement("d-annotation-form")
export class AnnotationForm extends stateMixin(watchMixin(ShadowlessLitElement)) {
export class AnnotationForm extends watchMixin(ShadowlessLitElement) {
@property({ type: String, attribute: "annotation-text" })
annotationText: string;
@property({ type: String, attribute: "saved-annotation-id" })
Expand Down Expand Up @@ -61,19 +59,8 @@ export class AnnotationForm extends stateMixin(watchMixin(ShadowlessLitElement))
}
};

state = ["getCourseId"/* REMOVE AFTER CLOSED BETA */, "getQuestionMode"];

/* REMOVE AFTER CLOSED BETA */
get courseId(): number {
return getCourseId();
}

get isQuestionMode(): boolean {
return isQuestionMode();
}

get type(): string {
return this.isQuestionMode ? "user_question" : "user_annotation";
return annotationState.isQuestionMode ? "user_question" : "user_annotation";
}

get rows(): number {
Expand Down Expand Up @@ -154,7 +141,7 @@ export class AnnotationForm extends stateMixin(watchMixin(ShadowlessLitElement))
render(): TemplateResult {
return html`
<form class="annotation-submission form">
${this.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse(this.courseId) ? "" : html`
${annotationState.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse() ? "" : html`
<d-saved-annotation-input
name="saved_annotation_id"
class="saved-annotation-input"
Expand All @@ -164,7 +151,7 @@ export class AnnotationForm extends stateMixin(watchMixin(ShadowlessLitElement))
></d-saved-annotation-input>
`}
<div class="field form-group">
${this.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse(this.courseId) ? "" : html`
${annotationState.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse() ? "" : html`
<label class="form-label" for="annotation-text">
${I18n.t("js.user_annotation.fields.annotation_text")}
</label>
Expand All @@ -183,15 +170,15 @@ export class AnnotationForm extends stateMixin(watchMixin(ShadowlessLitElement))
></textarea>
<div class="clearfix annotation-help-block">
<span class='help-block'>${unsafeHTML(I18n.t("js.user_annotation.help"))}</span>
${this.isQuestionMode ? html`
${annotationState.isQuestionMode ? html`
<span class='help-block'>${unsafeHTML(I18n.t("js.user_annotation.help_student"))}</span>
` : ""}
<span class="help-block float-end">
<span class="used-characters">${I18n.formatNumber(this._annotationText.length)}</span> / ${I18n.formatNumber(maxLength)}
</span>
</div>
</div>
${this.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse(this.courseId) ? "" : html`
${annotationState.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse() ? "" : html`
<div class="field form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" @click="${() => this.toggleSaveAnnotation()}" id="check-save-annotation">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { customElement, property } from "lit/decorators.js";
import { ShadowlessLitElement } from "components/meta/shadowless_lit_element";
import { html, TemplateResult } from "lit";
import { stateMixin } from "state/StateMixin";
import { isQuestionMode } from "state/Annotations";
import "components/annotations/annotations_toggles";
import "components/annotations/hidden_annotations_dot";
import { i18nMixin } from "components/meta/i18n_mixin";
import { hasPermission } from "state/Users";
import { userState } from "state/Users";
import { annotationState } from "state/Annotations";


/**
Expand All @@ -16,22 +15,16 @@ import { hasPermission } from "state/Users";
* @element d-annotation-options
*/
@customElement("d-annotation-options")
export class AnnotationOptions extends i18nMixin(stateMixin(ShadowlessLitElement)) {
export class AnnotationOptions extends i18nMixin(ShadowlessLitElement) {
@property({ state: true })
showForm = false;

state = ["getQuestionMode", "hasPermission"];

get isQuestionMode(): boolean {
return isQuestionMode();
}

get canCreateAnnotation(): boolean {
return hasPermission("annotation.create");
return userState.hasPermission("annotation.create");
}

get addAnnotationTitle(): string {
return this.isQuestionMode ?
return annotationState.isQuestionMode ?
I18n.t("js.annotations.options.add_global_question") :
I18n.t("js.annotations.options.add_global_annotation");
}
Expand Down
44 changes: 16 additions & 28 deletions app/assets/javascripts/components/annotations/annotations_cell.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { customElement, property } from "lit/decorators.js";
import { ShadowlessLitElement } from "components/meta/shadowless_lit_element";
import { html, TemplateResult } from "lit";
import {
createUserAnnotation,
getUserAnnotationsByLine,
UserAnnotationData,
UserAnnotationFormData
} from "state/UserAnnotations";
import { getEvaluationId } from "state/Evaluations";
import { isQuestionMode, isAnnotationVisible } from "state/Annotations";
import { getSubmissionId } from "state/Submissions";
import { getMachineAnnotationsByLine, MachineAnnotationData } from "state/MachineAnnotations";
import { UserAnnotationFormData, userAnnotationState } from "state/UserAnnotations";
import { annotationState } from "state/Annotations";
import { submissionState } from "state/Submissions";
import { MachineAnnotationData, machineAnnotationState } from "state/MachineAnnotations";
import "components/annotations/machine_annotation";
import "components/annotations/user_annotation";
import "components/annotations/annotation_form";
import "components/annotations/thread";
import { stateMixin } from "state/StateMixin";
import { AnnotationForm } from "components/annotations/annotation_form";
import { createRef, Ref, ref } from "lit/directives/ref.js";
import { evaluationState } from "state/Evaluations";

/**
* This component represents a cell that groups all annotations for a specific line.
Expand All @@ -31,40 +25,34 @@ import { createRef, Ref, ref } from "lit/directives/ref.js";
* @fires close-form - if the form should be closed
*/
@customElement("d-annotations-cell")
export class AnnotationsCell extends stateMixin(ShadowlessLitElement) {
export class AnnotationsCell extends ShadowlessLitElement {
@property({ type: Boolean, attribute: "show-form" })
showForm: boolean;
@property({ type: Number })
row: number;

annotationFormRef: Ref<AnnotationForm> = createRef();

state = ["getUserAnnotations", "getMachineAnnotations", "isAnnotationVisible", "getQuestionMode"];

get machineAnnotations(): MachineAnnotationData[] {
return getMachineAnnotationsByLine(this.row);
}

get userAnnotations(): UserAnnotationData[] {
return getUserAnnotationsByLine(this.row);
return machineAnnotationState.byLine.get(this.row) || [];
}

get isQuestionMode(): boolean {
return isQuestionMode();
get userAnnotationIds(): number[] {
return userAnnotationState.rootIdsByLine.get(this.row) || [];
}


async createAnnotation(e: CustomEvent): Promise<void> {
const annotationData: UserAnnotationFormData = {
"annotation_text": e.detail.text,
"line_nr": this.row,
"evaluation_id": getEvaluationId(),
"evaluation_id": evaluationState.id,
"saved_annotation_id": e.detail.savedAnnotationId || undefined,
};

try {
const mode = isQuestionMode() ? "question" : "annotation";
await createUserAnnotation(annotationData, getSubmissionId(), mode, e.detail.saveAnnotation, e.detail.savedAnnotationTitle);
const mode = annotationState.isQuestionMode ? "question" : "annotation";
await userAnnotationState.create(annotationData, submissionState.id, mode, e.detail.saveAnnotation, e.detail.savedAnnotationTitle);
this.closeForm();
} catch (err) {
this.annotationFormRef.value.hasErrors = true;
Expand All @@ -74,7 +62,7 @@ export class AnnotationsCell extends stateMixin(ShadowlessLitElement) {

getVisibleMachineAnnotationsOfType(type: string): TemplateResult[] {
return this.machineAnnotations
.filter(isAnnotationVisible)
.filter(a => annotationState.isVisible(a))
.filter(a => a.type === type).map(a => html`
<d-machine-annotation .data=${a}></d-machine-annotation>
`);
Expand All @@ -89,7 +77,7 @@ export class AnnotationsCell extends stateMixin(ShadowlessLitElement) {
return html`
<div class="annotation-cell">
${this.showForm ? html`
<div class="annotation ${this.isQuestionMode ? "question" : "user" }">
<div class="annotation ${annotationState.isQuestionMode ? "question" : "user" }">
<d-annotation-form @submit=${e => this.createAnnotation(e)}
@cancel=${() => this.closeForm()}
${ref(this.annotationFormRef)}
Expand All @@ -101,8 +89,8 @@ export class AnnotationsCell extends stateMixin(ShadowlessLitElement) {
${this.getVisibleMachineAnnotationsOfType("error")}
</div>
<div class="annotation-group-conversation">
${this.userAnnotations.filter(isAnnotationVisible).map(a => html`
<d-thread .data=${a}></d-thread>
${this.userAnnotationIds.map(a => html`
<d-thread .rootId=${a}></d-thread>
`)}
</div>
<div class="annotation-group-warning">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { customElement } from "lit/decorators.js";
import { stateMixin } from "state/StateMixin";
import { ShadowlessLitElement } from "components/meta/shadowless_lit_element";
import { getUserAnnotationsCount } from "state/UserAnnotations";
import { getMachineAnnotationsCount } from "state/MachineAnnotations";
import { userAnnotationState } from "state/UserAnnotations";
import { html, TemplateResult } from "lit";
import { machineAnnotationState } from "state/MachineAnnotations";

/**
* This component represents a badge that shows the total number of annotations.
*
* @element d-annotations-count-badge
*/
@customElement("d-annotations-count-badge")
export class AnnotationsCountBadge extends stateMixin(ShadowlessLitElement) {
state = ["getUserAnnotationsCount", "getMachineAnnotationsCount"];

export class AnnotationsCountBadge extends ShadowlessLitElement {
get annotationsCount(): number {
return getUserAnnotationsCount() + getMachineAnnotationsCount();
return userAnnotationState.count + machineAnnotationState.count;
}

render(): TemplateResult {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { customElement } from "lit/decorators.js";
import { ShadowlessLitElement } from "components/meta/shadowless_lit_element";
import { stateMixin } from "state/StateMixin";
import { html, TemplateResult, PropertyValues } from "lit";
import { AnnotationVisibilityOptions, getAnnotationVisibility, setAnnotationVisibility } from "state/Annotations";
import { annotationState } from "state/Annotations";
import { i18nMixin } from "components/meta/i18n_mixin";
import { initTooltips } from "util.js";

Expand All @@ -13,46 +12,40 @@ import { initTooltips } from "util.js";
* @element d-annotations-toggles
*/
@customElement("d-annotations-toggles")
export class AnnotationsToggles extends i18nMixin(stateMixin(ShadowlessLitElement)) {
state = ["getAnnotationVisibility"];

export class AnnotationsToggles extends i18nMixin(ShadowlessLitElement) {
protected update(changedProperties: PropertyValues): void {
super.update(changedProperties);
initTooltips(this);
}

get annotationVisibility(): AnnotationVisibilityOptions {
return getAnnotationVisibility();
}

protected render(): TemplateResult {
return html`
<span class="diff-switch-buttons switch-buttons">
<span>${I18n.t("js.annotations.toggles.title")}</span>
<div class="btn-group btn-toggle" role="group" aria-label="${I18n.t("js.annotations.toggles.title")}" data-bs-toggle="buttons">
<button class="btn annotation-toggle ${this.annotationVisibility === "all" ? "active" : ""}"
<button class="btn annotation-toggle ${annotationState.visibility === "all" ? "active" : ""}"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-trigger="hover"
title="${I18n.t("js.annotations.toggles.show_all")}"
@click=${() => setAnnotationVisibility("all")}
@click=${() => annotationState.visibility = "all"}
>
<i class="mdi mdi-comment-multiple-outline"></i>
</button>
<button class="btn annotation-toggle ${this.annotationVisibility === "important" ? "active" : ""}"
<button class="btn annotation-toggle ${annotationState.visibility === "important" ? "active" : ""}"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-trigger="hover"
title="${I18n.t("js.annotations.toggles.show_errors")}"
@click=${() => setAnnotationVisibility("important")}>
@click=${() => annotationState.visibility = "important"}>
<i class="mdi mdi-comment-alert-outline"></i>
</button>
<button class="btn annotation-toggle ${this.annotationVisibility === "none" ? "active" : ""}"
<button class="btn annotation-toggle ${annotationState.visibility === "none" ? "active" : ""}"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-trigger="hover"
title="${I18n.t("js.annotations.toggles.hide_all")}"
@click=${() => setAnnotationVisibility("none")}>
@click=${() => annotationState.visibility = "none"}>
<i class="mdi mdi-comment-remove-outline"></i>
</button>
</div>
Expand Down
Loading