diff --git a/angular.json b/angular.json index bc784ccfabb..cea5388000c 100644 --- a/angular.json +++ b/angular.json @@ -15,7 +15,7 @@ "angular", "fabric", "dom-autoscroller", - "drawing-tool", + "@wise-community/drawing-tool", "jquery", "rxjs/internal/BehaviorSubject", "canvg", diff --git a/package-lock.json b/package-lock.json index d32290971ae..f1be6226c9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,11 +26,14 @@ "@stomp/rx-stomp": "^1.1.4", "@stomp/stompjs": "^5.4.4", "@tinymce/tinymce-angular": "^7.0.0", + "@wise-community/angular-password-strength-meter": "^7.0.1", + "@wise-community/drawing-tool": "^2.3.0-pre.1", + "@zxcvbn-ts/core": "^2.2.1", + "@zxcvbn-ts/language-en": "^2.1.0", "canvg": "^2.0.0", "compute-covariance": "^1.0.1", "core-js": "^3.22.0", "dom-autoscroller": "^2.3.4", - "drawing-tool": "^2.1.2", "eventemitter2": "^5.0.1", "fabric": "3.6.3", "file-saver": "^2.0.5", @@ -7659,6 +7662,40 @@ } } }, + "node_modules/@wise-community/angular-password-strength-meter": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@wise-community/angular-password-strength-meter/-/angular-password-strength-meter-7.0.1.tgz", + "integrity": "sha512-IHtrUU1uj+aWOwxb4wXAFjgYRw6m8hoRDkvfdqHmBVC0jnc8Qc7ESILmpoPwq5z5Zf/8h5KcpITrgjfyRoW/6g==", + "dependencies": { + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0", + "@zxcvbn-ts/core": "^2.0.1", + "@zxcvbn-ts/language-en": "^2.0.1" + } + }, + "node_modules/@wise-community/drawing-tool": { + "version": "2.3.0-pre.1", + "resolved": "https://registry.npmjs.org/@wise-community/drawing-tool/-/drawing-tool-2.3.0-pre.1.tgz", + "integrity": "sha512-hnGQX06NoKVvoNZlSAxDzfwbdkWApttvr4Yn4KGagSJCbBi0czQR2DHJ5GZbYXiOtJBYzIs5iaSpirNOg2xe7g==", + "dependencies": { + "eventemitter2": "~0.4.14", + "fabric": "3.6.3", + "hammerjs": "~2.0.4", + "query-string": "^4.3.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "jquery": ">= 2.1.3" + } + }, + "node_modules/@wise-community/drawing-tool/node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -7677,6 +7714,19 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "node_modules/@zxcvbn-ts/core": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@zxcvbn-ts/core/-/core-2.2.1.tgz", + "integrity": "sha512-Cg1JyRpCDIF+Dh3nauqygmmCYxogNVZDxSn+9PgkPD1HZ2QiJe4elruVJrGmYRS7muGmZ1hNJq8ySQdPv6GHaw==", + "dependencies": { + "fastest-levenshtein": "1.0.16" + } + }, + "node_modules/@zxcvbn-ts/language-en": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@zxcvbn-ts/language-en/-/language-en-2.1.0.tgz", + "integrity": "sha512-I3n4AAbArjPAZtwCrk9MQnSrcj5+9rq8sic2rUU44fP5QaR17Vk8zDt61+R9dnP9ZRsj09aAUYML4Ash05qZjQ==" + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -11335,26 +11385,6 @@ "node": ">=8" } }, - "node_modules/drawing-tool": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/drawing-tool/-/drawing-tool-2.2.0.tgz", - "integrity": "sha512-ohvxdZHB899eCKSdAF4k9un9ymMDYeKvV+Sb9HTT8whcb8k7ZTwdKKPh6dNpKNTT3QUWLwEsDlLnd5tpqz85Xw==", - "dependencies": { - "eventemitter2": "~0.4.14", - "fabric": "3.6.3", - "hammerjs": "~2.0.4", - "query-string": "^4.3.2", - "uuid": "^8.3.2" - }, - "peerDependencies": { - "jquery": ">= 2.1.3" - } - }, - "node_modules/drawing-tool/node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==" - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -12878,6 +12908,14 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", @@ -28489,9 +28527,9 @@ } }, "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tslint": { "version": "6.1.3", diff --git a/package.json b/package.json index 8388896d324..ea8a4ed8721 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,14 @@ "@stomp/rx-stomp": "^1.1.4", "@stomp/stompjs": "^5.4.4", "@tinymce/tinymce-angular": "^7.0.0", + "@wise-community/drawing-tool": "^2.3.0-pre.1", + "@zxcvbn-ts/core": "^2.2.1", + "@zxcvbn-ts/language-en": "^2.1.0", + "@wise-community/angular-password-strength-meter": "^7.0.1", "canvg": "^2.0.0", "compute-covariance": "^1.0.1", "core-js": "^3.22.0", "dom-autoscroller": "^2.3.4", - "drawing-tool": "^2.1.2", "eventemitter2": "^5.0.1", "fabric": "3.6.3", "file-saver": "^2.0.5", diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 1cfe37a3ea5..d188b2229e5 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -11,6 +11,10 @@ app-header { z-index: 2; } +mat-sidenav-container, mat-sidenav-content { + overflow: unset; +} + .to-top { position: fixed; bottom: 20px; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 347fd654226..1f225ddc9fa 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -61,7 +61,8 @@ export function initialize( RecaptchaV3Module, RouterModule.forRoot([], { scrollPositionRestoration: 'enabled', - anchorScrolling: 'enabled' + anchorScrolling: 'enabled', + onSameUrlNavigation: 'reload' }) ], providers: [ diff --git a/src/app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html b/src/app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html index 1dcf5932e25..67fd0d3b55a 100644 --- a/src/app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html +++ b/src/app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html @@ -9,7 +9,7 @@
Choose the step(s) that you want to import, then select Next.
i18n-matTooltip matTooltipPosition="above" > - visibility + preview

i18n-matTooltip matTooltipPosition="above" > - visibility + preview
diff --git a/src/app/common/password-helper.ts b/src/app/common/password-helper.ts new file mode 100644 index 00000000000..5fd523c9819 --- /dev/null +++ b/src/app/common/password-helper.ts @@ -0,0 +1,27 @@ +import { FormGroup } from '@angular/forms'; +import { NewPasswordAndConfirmComponent } from '../password/new-password-and-confirm/new-password-and-confirm.component'; +import { PasswordErrors } from '../domain/password/password-errors'; + +export function changePasswordError( + error: PasswordErrors, + incorrectPasswordFormGroup: FormGroup, + invalidPasswordFormGroup: FormGroup, + previousPasswordFieldName: string +): void { + switch (error.messageCode) { + case 'incorrectPassword': + incorrectPasswordFormGroup + .get(previousPasswordFieldName) + .setErrors({ incorrectPassword: true }); + break; + case 'invalidPassword': + injectPasswordErrors(invalidPasswordFormGroup, error); + break; + } +} + +export function injectPasswordErrors(formGroup: FormGroup, passwordErrors: PasswordErrors): void { + formGroup + .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) + .setErrors(passwordErrors); +} diff --git a/src/app/domain/password/password-errors.ts b/src/app/domain/password/password-errors.ts new file mode 100644 index 00000000000..babac015eb3 --- /dev/null +++ b/src/app/domain/password/password-errors.ts @@ -0,0 +1,12 @@ +export class PasswordErrors { + messageCode: string = 'invalidPassword'; + missingLetter: boolean; + missingNumber: boolean; + tooShort: boolean; + + constructor(missingLetter: boolean, missingNumber: boolean, tooShort: boolean) { + this.missingLetter = missingLetter; + this.missingNumber = missingNumber; + this.tooShort = tooShort; + } +} diff --git a/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.spec.ts b/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.spec.ts index a4e3e2e5d9e..415af495791 100644 --- a/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.spec.ts +++ b/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.spec.ts @@ -8,6 +8,7 @@ import { StudentService } from '../../../student/student.service'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; import { PasswordModule } from '../../../password/password.module'; +import { PasswordRequirementComponent } from '../../../password/password-requirement/password-requirement.component'; export class MockStudentService { changePassword( @@ -26,8 +27,6 @@ export class MockStudentService { } } -const PASSWORD = 'Abcd1234'; - describe('ForgotStudentPasswordChangeComponent', () => { let component: ForgotStudentPasswordChangeComponent; let fixture: ComponentFixture; @@ -65,8 +64,9 @@ describe('ForgotStudentPasswordChangeComponent', () => { }); it('should enable the submit button when the password fields are filled in', () => { - component.changePasswordFormGroup.controls['newPassword'].setValue(PASSWORD); - component.changePasswordFormGroup.controls['confirmNewPassword'].setValue(PASSWORD); + const password = PasswordRequirementComponent.VALID_PASSWORD; + component.changePasswordFormGroup.controls['newPassword'].setValue(password); + component.changePasswordFormGroup.controls['confirmNewPassword'].setValue(password); fixture.detectChanges(); const submitButton = getSubmitButton(); expect(submitButton.disabled).toBe(false); diff --git a/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.ts b/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.ts index f767d7a43c9..9de2edba322 100644 --- a/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.ts +++ b/src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.ts @@ -4,6 +4,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { StudentService } from '../../../student/student.service'; import { finalize } from 'rxjs/operators'; import { NewPasswordAndConfirmComponent } from '../../../password/new-password-and-confirm/new-password-and-confirm.component'; +import { injectPasswordErrors } from '../../../common/password-helper'; +import { PasswordErrors } from '../../../domain/password/password-errors'; @Component({ selector: 'forgot-student-password-change', @@ -62,20 +64,10 @@ export class ForgotStudentPasswordChangeComponent implements OnInit { this.goToSuccessPage(); } - private changePasswordError(error: any): void { - const formError: any = {}; + private changePasswordError(error: PasswordErrors): void { switch (error.messageCode) { - case 'invalidPasswordLength': - formError.minlength = true; - this.changePasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - case 'invalidPasswordPattern': - formError.pattern = true; - this.changePasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); + case 'invalidPassword': + injectPasswordErrors(this.changePasswordFormGroup, error); break; default: this.setErrorOccurredMessage(); diff --git a/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.spec.ts b/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.spec.ts index 7b6ae754a50..b0d07c868c3 100644 --- a/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.spec.ts +++ b/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.spec.ts @@ -9,6 +9,7 @@ import { PasswordModule } from '../../../password/password.module'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatCardModule } from '@angular/material/card'; import { MatDividerModule } from '@angular/material/divider'; +import { PasswordRequirementComponent } from '../../../password/password-requirement/password-requirement.component'; export class MockTeacherService { changePassword( @@ -50,8 +51,8 @@ describe('ForgotTeacherPasswordChangeComponent', () => { MatCardModule, MatDividerModule, PasswordModule, - RouterTestingModule, - ReactiveFormsModule + ReactiveFormsModule, + RouterTestingModule ], providers: [{ provide: TeacherService, useClass: MockTeacherService }], schemas: [] @@ -100,7 +101,7 @@ describe('ForgotTeacherPasswordChangeComponent', () => { const navigateSpy = spyOn(router, 'navigate'); component.username = 'SpongebobSquarepants'; component.verificationCode = '123456'; - const newPassword = 'Abcd1234'; + const newPassword = PasswordRequirementComponent.VALID_PASSWORD; component.changePasswordFormGroup.controls['newPassword'].setValue(newPassword); component.changePasswordFormGroup.controls['confirmNewPassword'].setValue(newPassword); component.submit(); diff --git a/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts b/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts index 15350471392..cde29eefb8b 100644 --- a/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts +++ b/src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts @@ -4,6 +4,8 @@ import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; import { TeacherService } from '../../../teacher/teacher.service'; import { finalize } from 'rxjs/operators'; import { NewPasswordAndConfirmComponent } from '../../../password/new-password-and-confirm/new-password-and-confirm.component'; +import { injectPasswordErrors } from '../../../common/password-helper'; +import { PasswordErrors } from '../../../domain/password/password-errors'; @Component({ selector: 'app-forgot-teacher-password-change', @@ -67,8 +69,7 @@ export class ForgotTeacherPasswordChangeComponent implements OnInit { this.goToSuccessPage(); } - private changePasswordError(error: any): void { - const formError: any = {}; + private changePasswordError(error: PasswordErrors): void { switch (error.messageCode) { case 'tooManyVerificationCodeAttempts': this.setTooManyVerificationCodeAttemptsMessage(); @@ -85,23 +86,13 @@ export class ForgotTeacherPasswordChangeComponent implements OnInit { case 'verificationCodeIncorrect': this.setVerificationCodeIncorrectMessage(); break; - case 'invalidPasswordLength': - formError.minlength = true; - this.changePasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - case 'invalidPasswordPattern': - formError.pattern = true; - this.changePasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); + case 'invalidPassword': + injectPasswordErrors(this.changePasswordFormGroup, error); break; case 'passwordDoesNotMatch': - formError.passwordDoesNotMatch = true; this.changePasswordFormGroup .get(NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); + .setErrors({ passwordDoesNotMatch: true }); break; default: this.setErrorOccurredMessage(); diff --git a/src/app/help/faq/faq.component.ts b/src/app/help/faq/faq.component.ts new file mode 100644 index 00000000000..2a22fa0fa4e --- /dev/null +++ b/src/app/help/faq/faq.component.ts @@ -0,0 +1,23 @@ +import { Directive, OnInit } from '@angular/core'; +import { ConfigService } from '../../services/config.service'; +import { filter } from 'rxjs'; + +@Directive() +export abstract class FaqComponent implements OnInit { + protected contextPath: string; + + constructor(private configService: ConfigService) { + this.configService + .getConfig() + .pipe(filter((config) => config != null)) + .subscribe((config) => { + this.contextPath = config.contextPath; + }); + } + + ngOnInit(): void {} + + ngAfterViewInit(): void { + document.getElementsByTagName('app-help')[0]?.scrollIntoView(); + } +} diff --git a/src/app/help/getting-started/getting-started.component.html b/src/app/help/faq/getting-started/getting-started.component.html similarity index 89% rename from src/app/help/getting-started/getting-started.component.html rename to src/app/help/faq/getting-started/getting-started.component.html index 17ecdff5176..02a5eec5979 100644 --- a/src/app/help/getting-started/getting-started.component.html +++ b/src/app/help/faq/getting-started/getting-started.component.html @@ -1,17 +1,17 @@
-

- infoGetting Started +

+ infoGetting Started

-
+

Creating an Account

-

+

All users must create their own account. Teachers must create a teacher account and students must create a student account. Users can create an account by going to the WISE Home Page and clicking on the Register link at the upper right of the screen.

-

Here are instructions on how to create a teacher account.

+

Here are instructions on how to create a teacher account.

  1. Go to the WISE Home Page.
  2. @@ -27,13 +27,13 @@

    Creating an Account

    There may be a number added at the end of your username if someone has the same name as you.
-
+

Using a WISE Unit

-

+

To use a WISE unit with your class, you must first choose a unit to use and then set up a "Run" of that unit.

-

Here are instructions on how to set up a Run.

+

Here are instructions on how to set up a Run.

  1. Sign in to WISE with your teacher account.
  2. Click the "Unit Library" tab.
  3. diff --git a/src/app/help/getting-started/getting-started.component.spec.ts b/src/app/help/faq/getting-started/getting-started.component.spec.ts similarity index 70% rename from src/app/help/getting-started/getting-started.component.spec.ts rename to src/app/help/faq/getting-started/getting-started.component.spec.ts index dc0f0e7e974..a1d74d99b39 100644 --- a/src/app/help/getting-started/getting-started.component.spec.ts +++ b/src/app/help/faq/getting-started/getting-started.component.spec.ts @@ -2,8 +2,8 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { GettingStartedComponent } from './getting-started.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ConfigService } from '../../services/config.service'; -import { Config } from '../../domain/config'; +import { ConfigService } from '../../../services/config.service'; +import { Config } from '../../../domain/config'; import { Observable } from 'rxjs'; export class MockConfigService { @@ -24,13 +24,15 @@ describe('GettingStartedComponent', () => { let component: GettingStartedComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [GettingStartedComponent], - providers: [{ provide: ConfigService, useClass: MockConfigService }], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [GettingStartedComponent], + providers: [{ provide: ConfigService, useClass: MockConfigService }], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(GettingStartedComponent); diff --git a/src/app/help/faq/getting-started/getting-started.component.ts b/src/app/help/faq/getting-started/getting-started.component.ts new file mode 100644 index 00000000000..7c9dff486aa --- /dev/null +++ b/src/app/help/faq/getting-started/getting-started.component.ts @@ -0,0 +1,9 @@ +import { Component, OnInit } from '@angular/core'; +import { ConfigService } from '../../../services/config.service'; +import { FaqComponent } from '../faq.component'; + +@Component({ + selector: 'app-getting-started', + templateUrl: './getting-started.component.html' +}) +export class GettingStartedComponent extends FaqComponent {} diff --git a/src/app/help/student-faq/student-faq.component.html b/src/app/help/faq/student-faq/student-faq.component.html similarity index 87% rename from src/app/help/student-faq/student-faq.component.html rename to src/app/help/faq/student-faq/student-faq.component.html index cec1cb8efe5..a267f8fe598 100644 --- a/src/app/help/student-faq/student-faq.component.html +++ b/src/app/help/faq/student-faq/student-faq.component.html @@ -1,17 +1,19 @@ -
    -

    - faceStudent Frequently Asked Questions +
    +

    + faceStudent Frequently Asked Questions

    -
    -

    Table of Contents

    -

    - General Questions -

    -

    - Technical Questions -

    -
    -

    General Questions

    + +

    Table of Contents

    + + +

    General Questions

    How do I create an account?

    1. Go to the Register page.
    2. @@ -49,14 +51,14 @@

      How do I start working on a project?

    3. Choose your period.
    4. Click "Add".
    5. The run will be added to your list of runs.
    6. -
    7. Click "Launch" to open the project.
    8. +
    9. Click "Launch" to open the project.

    I chose the wrong period, how can I change it?

    • Only your teacher can change your period. Please ask them for assistance.
    -
    -

    Technical Questions

    + +

    Technical Questions

    The WISE web site won't load on my web browser. What do I do?

    • @@ -76,7 +78,7 @@

      What if I have trouble logging in?

    Can I use WISE in another language?

    - Yes! +

    Yes!

    1. Sign into WISE with your student account.
    2. diff --git a/src/app/help/student-faq/student-faq.component.spec.ts b/src/app/help/faq/student-faq/student-faq.component.spec.ts similarity index 70% rename from src/app/help/student-faq/student-faq.component.spec.ts rename to src/app/help/faq/student-faq/student-faq.component.spec.ts index 47c07104675..510f802e99b 100644 --- a/src/app/help/student-faq/student-faq.component.spec.ts +++ b/src/app/help/faq/student-faq/student-faq.component.spec.ts @@ -2,9 +2,9 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { StudentFaqComponent } from './student-faq.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ConfigService } from '../../services/config.service'; +import { ConfigService } from '../../../services/config.service'; import { Observable } from 'rxjs'; -import { Config } from '../../domain/config'; +import { Config } from '../../../domain/config'; export class MockConfigService { getConfig(): Observable { @@ -24,13 +24,15 @@ describe('StudentFaqComponent', () => { let component: StudentFaqComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [StudentFaqComponent], - providers: [{ provide: ConfigService, useClass: MockConfigService }], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [StudentFaqComponent], + providers: [{ provide: ConfigService, useClass: MockConfigService }], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(StudentFaqComponent); diff --git a/src/app/help/faq/student-faq/student-faq.component.ts b/src/app/help/faq/student-faq/student-faq.component.ts new file mode 100644 index 00000000000..de943d33c5c --- /dev/null +++ b/src/app/help/faq/student-faq/student-faq.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; +import { FaqComponent } from '../faq.component'; + +@Component({ + selector: 'app-student-faq', + templateUrl: './student-faq.component.html' +}) +export class StudentFaqComponent extends FaqComponent {} diff --git a/src/app/help/teacher-faq/teacher-faq.component.html b/src/app/help/faq/teacher-faq/teacher-faq.component.html similarity index 90% rename from src/app/help/teacher-faq/teacher-faq.component.html rename to src/app/help/faq/teacher-faq/teacher-faq.component.html index a7f99a7f86a..eaa8fabcf63 100644 --- a/src/app/help/teacher-faq/teacher-faq.component.html +++ b/src/app/help/faq/teacher-faq/teacher-faq.component.html @@ -1,34 +1,31 @@
      -

      - schoolTeacher Frequently Asked Questions +

      + schoolTeacher Frequently Asked Questions

      -
      +

      Table of Contents

      -

      - General Questions -

      -

      - Student Management -

      -

      - Project Management -

      -

      - Assessment of Student Work -

      -

      - Real Time Classroom Monitor -

      -

      - Technical Questions -

      -
      -

      General Questions

      + + +

      General Questions

      How do I create an account?

      1. Go to the Register page.
      2. @@ -53,7 +50,7 @@

        How do I use a unit with my class?

        Choose the number of students per team. Choosing 1-3 will allow students to work together in a team. -
      3. +
      4. Choose the start date. Students will not be able to use the project until this date.
      5. Click "Create Run".
      6. @@ -63,10 +60,10 @@

        How do I use a unit with my class?

        Students will need to use this access code to work on the run you created.
      -
      -

      Student Management

      + +

      Student Management

      Should I register my students for WISE or have them do it themselves?

      -

      +

      WISE makes student registration simple and intuitive -- direct your students to the register page by having them go to the WISE home page and clicking the "Register" link at the upper right. They should be able to register in 10 minutes or less. However, you can also opt to @@ -128,14 +125,14 @@

      How do I change a student team after they've started a project run?

    3. - Warning 1: + Warning 1: If you move Student A into an established team, student A loses all of their current work and inherits the current work of the established team.
    4. - Warning 2: + Warning 2: If you move Student A into a newly created (blank) team, student A will lose all of their work.How do I change the period for a student?

  4. Choose a different period.
  5. Click "Save Changes".
  6. - Warning: + Warning: If you move a student to a different period, they will lose all of their work.
-

+

I do not remember my teacher access code that students need to create their account for my new students.

@@ -172,8 +169,8 @@

  • Find the run in your Teacher Home Page.
  • The Access Code will be displayed below the run title.
  • -
    -

    Project Management

    + +

    Project Management

    When should I set up my project run?

    -
    -

    Assessment of Student Work

    + +

    Assessment of Student Work

    How do I review and grade student work?

    -

    How do I find time to grade all of the student work?

    +

    How do I find time to grade all of the student work?

    -
    -

    Real Time Classroom Monitor

    + +

    Real Time Classroom Monitor

    What is the Real Time Classroom Monitor?

    Does the Real Time Classroom Monitor work on a tablet like the iPad?

    Can I use the Real Time Classroom Monitor to pause student screens?

    -
    -

    Technical Questions

    + +

    Technical Questions

    The WISE web site won't load on my web browser. What do I do?

    Can I use WISE in another language?

    - Yes! +

    Yes!

    1. Sign into WISE with your teacher account.
    2. diff --git a/src/app/help/teacher-faq/teacher-faq.component.spec.ts b/src/app/help/faq/teacher-faq/teacher-faq.component.spec.ts similarity index 70% rename from src/app/help/teacher-faq/teacher-faq.component.spec.ts rename to src/app/help/faq/teacher-faq/teacher-faq.component.spec.ts index c3003da94c2..83dfa30c6c4 100644 --- a/src/app/help/teacher-faq/teacher-faq.component.spec.ts +++ b/src/app/help/faq/teacher-faq/teacher-faq.component.spec.ts @@ -2,9 +2,9 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { TeacherFaqComponent } from './teacher-faq.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ConfigService } from '../../services/config.service'; +import { ConfigService } from '../../../services/config.service'; import { Observable } from 'rxjs'; -import { Config } from '../../domain/config'; +import { Config } from '../../../domain/config'; export class MockConfigService { getConfig(): Observable { @@ -24,13 +24,15 @@ describe('TeacherFaqComponent', () => { let component: TeacherFaqComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [TeacherFaqComponent], - providers: [{ provide: ConfigService, useClass: MockConfigService }], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [TeacherFaqComponent], + providers: [{ provide: ConfigService, useClass: MockConfigService }], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(TeacherFaqComponent); diff --git a/src/app/help/faq/teacher-faq/teacher-faq.component.ts b/src/app/help/faq/teacher-faq/teacher-faq.component.ts new file mode 100644 index 00000000000..19ecd9b9ced --- /dev/null +++ b/src/app/help/faq/teacher-faq/teacher-faq.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; +import { FaqComponent } from '../faq.component'; + +@Component({ + selector: 'app-teacher-faq', + templateUrl: './teacher-faq.component.html' +}) +export class TeacherFaqComponent extends FaqComponent {} diff --git a/src/app/help/getting-started/getting-started.component.scss b/src/app/help/getting-started/getting-started.component.scss deleted file mode 100644 index 6e01b342645..00000000000 --- a/src/app/help/getting-started/getting-started.component.scss +++ /dev/null @@ -1,8 +0,0 @@ - -.title-icon { - margin-right: 8px; -} - -.indented-text { - margin-left: 24px; -} diff --git a/src/app/help/getting-started/getting-started.component.ts b/src/app/help/getting-started/getting-started.component.ts deleted file mode 100644 index c2af5ccd982..00000000000 --- a/src/app/help/getting-started/getting-started.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ConfigService } from '../../services/config.service'; - -@Component({ - selector: 'app-getting-started', - templateUrl: './getting-started.component.html', - styleUrls: ['./getting-started.component.scss'] -}) -export class GettingStartedComponent implements OnInit { - contextPath: string; - - constructor(private configService: ConfigService) { - this.configService.getConfig().subscribe((config) => { - if (config != null) { - this.contextPath = config.contextPath; - } - }); - } - - ngOnInit() {} - - ngAfterViewInit() { - const appHelpElements = document.getElementsByTagName('app-help'); - if (appHelpElements.length > 0) { - appHelpElements[0].scrollIntoView(); - } - } -} diff --git a/src/app/help/help-routing.module.ts b/src/app/help/help-routing.module.ts index 2424f0c9b7c..7ddfc43d57b 100644 --- a/src/app/help/help-routing.module.ts +++ b/src/app/help/help-routing.module.ts @@ -2,9 +2,9 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HelpComponent } from './help.component'; import { HelpHomeComponent } from './help-home/help-home.component'; -import { GettingStartedComponent } from './getting-started/getting-started.component'; -import { TeacherFaqComponent } from './teacher-faq/teacher-faq.component'; -import { StudentFaqComponent } from './student-faq/student-faq.component'; +import { GettingStartedComponent } from './faq/getting-started/getting-started.component'; +import { TeacherFaqComponent } from './faq/teacher-faq/teacher-faq.component'; +import { StudentFaqComponent } from './faq/student-faq/student-faq.component'; const helpRoutes: Routes = [ { diff --git a/src/app/help/help.module.ts b/src/app/help/help.module.ts index 13dfb611982..6c70bf443ce 100644 --- a/src/app/help/help.module.ts +++ b/src/app/help/help.module.ts @@ -3,13 +3,14 @@ import { CommonModule } from '@angular/common'; import { HelpComponent } from './help.component'; import { HelpRoutingModule } from './help-routing.module'; import { SharedModule } from '../modules/shared/shared.module'; -import { GettingStartedComponent } from './getting-started/getting-started.component'; -import { TeacherFaqComponent } from './teacher-faq/teacher-faq.component'; -import { StudentFaqComponent } from './student-faq/student-faq.component'; +import { GettingStartedComponent } from './faq/getting-started/getting-started.component'; +import { TeacherFaqComponent } from './faq/teacher-faq/teacher-faq.component'; +import { StudentFaqComponent } from './faq/student-faq/student-faq.component'; import { HelpHomeComponent } from './help-home/help-home.component'; +import { MatDividerModule } from '@angular/material/divider'; @NgModule({ - imports: [CommonModule, HelpRoutingModule, SharedModule], + imports: [CommonModule, HelpRoutingModule, MatDividerModule, SharedModule], declarations: [ HelpComponent, GettingStartedComponent, diff --git a/src/app/help/student-faq/student-faq.component.scss b/src/app/help/student-faq/student-faq.component.scss deleted file mode 100644 index 4d5559364f4..00000000000 --- a/src/app/help/student-faq/student-faq.component.scss +++ /dev/null @@ -1,16 +0,0 @@ - -.title-icon { - margin-right: 8px; -} - -.link { - cursor: pointer; -} - -.indented-text { - margin-left: 24px; -} - -.warning { - color: red; -} diff --git a/src/app/help/student-faq/student-faq.component.ts b/src/app/help/student-faq/student-faq.component.ts deleted file mode 100644 index ef648921a4a..00000000000 --- a/src/app/help/student-faq/student-faq.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ConfigService } from '../../services/config.service'; - -@Component({ - selector: 'app-student-faq', - templateUrl: './student-faq.component.html', - styleUrls: ['./student-faq.component.scss'] -}) -export class StudentFaqComponent implements OnInit { - contextPath: string; - - constructor(private configService: ConfigService) { - this.configService.getConfig().subscribe((config) => { - if (config != null) { - this.contextPath = config.contextPath; - } - }); - } - - ngOnInit() {} - - ngAfterViewInit() { - const appHelpElements = document.getElementsByTagName('app-help'); - if (appHelpElements.length > 0) { - appHelpElements[0].scrollIntoView(); - } - } - - scrollTo(id) { - document.getElementById(id).scrollIntoView(); - } -} diff --git a/src/app/help/teacher-faq/teacher-faq.component.scss b/src/app/help/teacher-faq/teacher-faq.component.scss deleted file mode 100644 index 4d5559364f4..00000000000 --- a/src/app/help/teacher-faq/teacher-faq.component.scss +++ /dev/null @@ -1,16 +0,0 @@ - -.title-icon { - margin-right: 8px; -} - -.link { - cursor: pointer; -} - -.indented-text { - margin-left: 24px; -} - -.warning { - color: red; -} diff --git a/src/app/help/teacher-faq/teacher-faq.component.ts b/src/app/help/teacher-faq/teacher-faq.component.ts deleted file mode 100644 index 2d751768007..00000000000 --- a/src/app/help/teacher-faq/teacher-faq.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ConfigService } from '../../services/config.service'; - -@Component({ - selector: 'app-teacher-faq', - templateUrl: './teacher-faq.component.html', - styleUrls: ['./teacher-faq.component.scss'] -}) -export class TeacherFaqComponent implements OnInit { - contextPath: string; - - constructor(private configService: ConfigService) { - this.configService.getConfig().subscribe((config) => { - if (config != null) { - this.contextPath = config.contextPath; - } - }); - } - - ngOnInit() {} - - ngAfterViewInit() { - const appHelpElements = document.getElementsByTagName('app-help'); - if (appHelpElements.length > 0) { - appHelpElements[0].scrollIntoView(); - } - } - - scrollTo(id) { - document.getElementById(id).scrollIntoView(); - } -} diff --git a/src/app/modules/library/library-project-details/library-project-details.component.html b/src/app/modules/library/library-project-details/library-project-details.component.html index d1eee0628ab..daa84fd4955 100644 --- a/src/app/modules/library/library-project-details/library-project-details.component.html +++ b/src/app/modules/library/library-project-details/library-project-details.component.html @@ -140,6 +140,6 @@ >
    diff --git a/src/app/modules/shared/edit-password/edit-password.component.spec.ts b/src/app/modules/shared/edit-password/edit-password.component.spec.ts index 1b6c6eececb..e671f168d3d 100644 --- a/src/app/modules/shared/edit-password/edit-password.component.spec.ts +++ b/src/app/modules/shared/edit-password/edit-password.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EditPasswordComponent } from './edit-password.component'; import { UserService } from '../../../services/user.service'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subscriber } from 'rxjs'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ReactiveFormsModule } from '@angular/forms'; import { NO_ERRORS_SCHEMA } from '@angular/core'; @@ -10,13 +10,15 @@ import { By } from '@angular/platform-browser'; import { User } from '../../../domain/user'; import { MatDialogModule } from '@angular/material/dialog'; import { PasswordModule } from '../../../password/password.module'; +import { MatIconModule } from '@angular/material/icon'; +import { PasswordErrors } from '../../../domain/password/password-errors'; +import { PasswordRequirementComponent } from '../../../password/password-requirement/password-requirement.component'; const CORRECT_OLD_PASSWORD = 'correctOldPassword123'; const INCORRECT_OLD_PASSWORD = 'incorrectOldPassword123'; -const INVALID_PASSWORD_TOO_SHORT = 'Abcd123'; -const INVALID_PASSWORD_PATTERN = 'abcd1234'; -const NEW_PASSWORD_1 = 'Abcd1111'; -const NEW_PASSWORD_2 = 'Abcd2222'; +const INVALID_PASSWORD = PasswordRequirementComponent.INVALID_PASSWORD_TOO_SHORT; +const NEW_PASSWORD_1 = PasswordRequirementComponent.VALID_PASSWORD; +const NEW_PASSWORD_2 = PasswordRequirementComponent.VALID_PASSWORD + '!'; export class MockUserService { getUser(): BehaviorSubject { @@ -54,6 +56,7 @@ describe('EditPasswordComponent', () => { imports: [ BrowserAnimationsModule, MatDialogModule, + MatIconModule, MatSnackBarModule, PasswordModule, ReactiveFormsModule @@ -70,121 +73,126 @@ describe('EditPasswordComponent', () => { validForm_enableSubmitButton(); passwordMismatch_disableSubmitButtonAndInvalidateForm(); oldPasswordIncorrect_disableSubmitButtonAndShowError(); - saveChanges_newPasswordTooShort_ShowError(); saveChanges_newPasswordPatternInvalid_ShowError(); formSubmit_disableSubmitButton(); notGoogleUser_showUnlinkOption(); unlinkGoogleButtonClick_showDialog(); - invalidPasswordTooShort_showError(); - invalidPasswordPattern_showError(); + invalidPassword_showError(); }); function initialState_disableSubmitButton() { - it('should disable submit button and invalidate form on initial state', () => { - expect(component.changePasswordFormGroup.valid).toBeFalsy(); - expectSubmitButtonDisabled(); + describe('the form is first loaded', () => { + it('disables the submit button', () => { + expect(component.changePasswordFormGroup.valid).toBeFalsy(); + expectSubmitButtonDisabled(); + }); }); } function validForm_enableSubmitButton() { - it('should enable submit button when form is valid', () => { - setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1); - expectSubmitButtonEnabled(); - expect(component.changePasswordFormGroup.valid).toBeTruthy(); + describe('the form is valid', () => { + it('enables the submit button', () => { + setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1); + expectSubmitButtonEnabled(); + expect(component.changePasswordFormGroup.valid).toBeTruthy(); + }); }); } function passwordMismatch_disableSubmitButtonAndInvalidateForm() { - it(`should disable submit button and invalidate form when new password and confirm new password - fields do not match`, () => { - setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_2); - expectSubmitButtonDisabled(); - expect(component.changePasswordFormGroup.valid).toBeFalsy(); + describe('new password and confirm new password do not match', () => { + it(`disables the submit button`, () => { + setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_2); + expect(component.changePasswordFormGroup.valid).toBeFalsy(); + expectSubmitButtonDisabled(); + }); }); } function oldPasswordIncorrect_disableSubmitButtonAndShowError() { - it(`should disable submit button and set incorrectPassword error when old password is - incorrect`, async () => { - spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( - generateObservableResponse('incorrectPassword', false) - ); - setPasswords(INCORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1); - submitForm(); - expectSubmitButtonDisabled(); - expect(component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword')).toBe( - true - ); + describe('server returns response that says old password is incorrect', () => { + it('shows the incorrect password error and disables the submit button', async () => { + spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( + generateErrorObservable('incorrectPassword') + ); + setPasswords(INCORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1); + submitForm(); + expect( + component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword') + ).toBe(true); + expectSubmitButtonDisabled(); + }); }); } -function saveChanges_newPasswordTooShort_ShowError() { - it(`should set minlength error when changePassword response returns new password is not long - enough error`, async () => { - spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( - generateObservableResponse('invalidPasswordLength', false) - ); - setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_TOO_SHORT, INVALID_PASSWORD_TOO_SHORT); - component.saveChanges(); - expect(component.newPasswordFormGroup.get('newPassword').getError('minlength')).toBe(true); +function saveChanges_newPasswordPatternInvalid_ShowError() { + describe('server returns response that says new password is not valid', () => { + it('shows the new password error messages and disables the submit button', async () => { + const passwordErrors = new PasswordErrors(false, false, true); + spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( + generateErrorObservable(passwordErrors) + ); + setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD, INVALID_PASSWORD); + component.saveChanges(); + expectPasswordErrors(false, false, true); + expectSubmitButtonDisabled(); + }); }); } -function saveChanges_newPasswordPatternInvalid_ShowError() { - it(`should set pattern error when changePassword response returns new password is not valid - pattern error`, async () => { - spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( - generateObservableResponse('invalidPasswordPattern', false) - ); - setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_PATTERN, INVALID_PASSWORD_PATTERN); - component.saveChanges(); - expect(component.newPasswordFormGroup.get('newPassword').getError('pattern')).toBe(true); - }); +function expectPasswordErrors( + missingLetter: boolean, + missingNumber: boolean, + tooShort: boolean +): void { + expect(component.newPasswordFormGroup.get('newPassword').getError('missingLetter')).toBe( + missingLetter + ); + expect(component.newPasswordFormGroup.get('newPassword').getError('missingNumber')).toBe( + missingNumber + ); + expect(component.newPasswordFormGroup.get('newPassword').getError('tooShort')).toBe(tooShort); } function formSubmit_disableSubmitButton() { - it('should disable submit button when form is successfully submitted', async () => { - spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( - generateObservableResponse('passwordChanged', true) - ); - setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1); - submitForm(); - expectSubmitButtonDisabled(); + describe('form is successfully submitted', () => { + it('disables the submit button', async () => { + spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue( + generateSuccessObservable('passwordChanged') + ); + setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1); + submitForm(); + expectSubmitButtonDisabled(); + }); }); } function notGoogleUser_showUnlinkOption() { - it('should hide show option to unlink google account if the user is not a google user', () => { - expect(getUnlinkGoogleAccountButton()).toBeNull(); + describe('user is not a google user', () => { + it('hides the option to unlink google acccount', () => { + expect(getUnlinkGoogleAccountButton()).toBeNull(); + }); }); } function unlinkGoogleButtonClick_showDialog() { - it('clicking on unlink google account link should open a dialog', () => { - const dialogSpy = spyOn(component.dialog, 'open'); - setGoogleUser(); - getUnlinkGoogleAccountButton().click(); - expect(dialogSpy).toHaveBeenCalled(); - }); -} - -function invalidPasswordTooShort_showError() { - it(`should disable submit button and set min length error when new password is too - short`, async () => { - setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_TOO_SHORT, INVALID_PASSWORD_TOO_SHORT); - expectSubmitButtonDisabled(); - expect(component.newPasswordFormGroup.get('newPassword').getError('minlength')).toBeTruthy(); - expect(component.newPasswordFormGroup.get('newPassword').getError('pattern')).toBeFalsy(); + describe('user is a google user and clicks unlink google account button', () => { + it('opens dialog', () => { + const dialogSpy = spyOn(component.dialog, 'open'); + setGoogleUser(); + getUnlinkGoogleAccountButton().click(); + expect(dialogSpy).toHaveBeenCalled(); + }); }); } -function invalidPasswordPattern_showError() { - it(`should disable submit button and set pattern error when new password does not satisfy the - pattern requirements`, async () => { - setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_PATTERN, INVALID_PASSWORD_PATTERN); - expectSubmitButtonDisabled(); - expect(component.newPasswordFormGroup.get('newPassword').getError('minlength')).toBeFalsy(); - expect(component.newPasswordFormGroup.get('newPassword').getError('pattern')).toBeTruthy(); +function invalidPassword_showError() { + describe('new password does not satisfy the requirements', () => { + it('shows password errors and disables submit button', async () => { + setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD, INVALID_PASSWORD); + expect(component.newPasswordFormGroup.get('newPassword').errors).not.toBeNull(); + expectSubmitButtonDisabled(); + }); }); } @@ -213,16 +221,27 @@ function setPasswords(oldPass: string, newPass: string, newPassConfirm: string) fixture.detectChanges(); } -function generateObservableResponse(messageCode: string, isSuccess: boolean): Observable { - if (isSuccess) { - return new Observable((observer) => { - observer.next({ messageCode: messageCode }); - observer.complete(); - }); - } else { - return new Observable((observer) => { - observer.error({ error: { messageCode: messageCode } }); - observer.complete(); - }); - } +function generateSuccessObservable(arg: string | any): Observable { + return generateResponseObservable(arg, true); +} + +function generateSuccessResponseValue(arg: string | any): any { + return typeof arg === 'string' ? { messageCode: arg } : arg; +} + +function generateErrorObservable(arg: string | any): Observable { + return generateResponseObservable(arg, false); +} + +function generateResponseObservable(arg: string | any, isSuccess: boolean): Observable { + return new Observable((observer: Subscriber) => { + isSuccess + ? observer.next(generateSuccessResponseValue(arg)) + : observer.error(generateErrorResponseValue(arg)); + observer.complete(); + }); +} + +function generateErrorResponseValue(arg: string | any): any { + return typeof arg === 'string' ? { error: { messageCode: arg } } : { error: arg }; } diff --git a/src/app/modules/shared/edit-password/edit-password.component.ts b/src/app/modules/shared/edit-password/edit-password.component.ts index 9264c09c292..8bbf0316e1d 100644 --- a/src/app/modules/shared/edit-password/edit-password.component.ts +++ b/src/app/modules/shared/edit-password/edit-password.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; @@ -6,13 +6,14 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { UserService } from '../../../services/user.service'; import { UnlinkGoogleAccountConfirmComponent } from '../unlink-google-account-confirm/unlink-google-account-confirm.component'; import { NewPasswordAndConfirmComponent } from '../../../password/new-password-and-confirm/new-password-and-confirm.component'; +import { changePasswordError } from '../../../common/password-helper'; @Component({ selector: 'app-edit-password', templateUrl: './edit-password.component.html', styleUrls: ['./edit-password.component.scss'] }) -export class EditPasswordComponent { +export class EditPasswordComponent implements OnInit { @ViewChild('changePasswordForm', { static: false }) changePasswordForm; isSaving: boolean = false; isGoogleUser: boolean = false; @@ -77,29 +78,16 @@ export class EditPasswordComponent { private changePasswordSuccess(): void { this.resetForm(); - this.snackBar.open($localize`Password changed.`); + this.snackBar.open($localize`Successfully changed password.`); } private changePasswordError(error: any): void { - const formError: any = {}; - switch (error.messageCode) { - case 'incorrectPassword': - formError.incorrectPassword = true; - this.changePasswordFormGroup.get('oldPassword').setErrors(formError); - break; - case 'invalidPasswordLength': - formError.minlength = true; - this.newPasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - case 'invalidPasswordPattern': - formError.pattern = true; - this.newPasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - } + changePasswordError( + error, + this.changePasswordFormGroup, + this.newPasswordFormGroup, + 'oldPassword' + ); } unlinkGoogleAccount(): void { diff --git a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts index f8fe636454e..23e50a439af 100644 --- a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts +++ b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts @@ -9,6 +9,7 @@ import { of } from 'rxjs'; import { PasswordModule } from '../../../password/password.module'; import { UserService } from '../../../services/user.service'; import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password.component'; +import { PasswordRequirementComponent } from '../../../password/password-requirement/password-requirement.component'; class MockUserService { unlinkGoogleUser(newPassword: string) { @@ -26,11 +27,11 @@ describe('UnlinkGoogleAccountPasswordComponent', () => { declarations: [UnlinkGoogleAccountPasswordComponent], imports: [ BrowserAnimationsModule, + MatDialogModule, MatFormFieldModule, MatInputModule, - ReactiveFormsModule, PasswordModule, - MatDialogModule + ReactiveFormsModule ], providers: [{ provide: UserService, useValue: userService }], schemas: [NO_ERRORS_SCHEMA] @@ -45,7 +46,7 @@ describe('UnlinkGoogleAccountPasswordComponent', () => { function formSubmit_callUserServiceUnlinkGoogleUserFunction() { it('should call UserService.UnlinkGoogleUserFunction when form is submitted', () => { const unlinkFunctionSpy = spyOn(userService, 'unlinkGoogleUser').and.returnValue(of({})); - const newPassword = 'Abcd1234'; + const newPassword = PasswordRequirementComponent.VALID_PASSWORD; component.newPasswordFormGroup.setValue({ newPassword: newPassword, confirmNewPassword: newPassword diff --git a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts index 3bd16b6704c..4e55902151c 100644 --- a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts +++ b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts @@ -4,6 +4,7 @@ import { MatDialog } from '@angular/material/dialog'; import { NewPasswordAndConfirmComponent } from '../../../password/new-password-and-confirm/new-password-and-confirm.component'; import { UserService } from '../../../services/user.service'; import { UnlinkGoogleAccountSuccessComponent } from '../unlink-google-account-success/unlink-google-account-success.component'; +import { injectPasswordErrors } from '../../../common/password-helper'; @Component({ styleUrls: ['./unlink-google-account-password.component.scss'], @@ -51,20 +52,8 @@ export class UnlinkGoogleAccountPasswordComponent { private error(error: any): void { this.isSaving = false; - const formError: any = {}; - switch (error.messageCode) { - case 'invalidPasswordLength': - formError.minlength = true; - this.newPasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - case 'invalidPasswordPattern': - formError.pattern = true; - this.newPasswordFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; + if (error.messageCode === 'invalidPassword') { + injectPasswordErrors(this.newPasswordFormGroup, error); } } } diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html index 2178d65eee1..0836004faba 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html @@ -1,25 +1,55 @@ -

    - +

    + {{ passwordLabel }} - + Password required - - Password must be at least 8 characters - - - Password must have at least one lowercase, one uppercase, and one number character - -

    + + +
    +
    Password Strength:
    + +
    Very Weak
    +
    Weak
    +
    Good
    +
    Strong
    +
    Very Strong
    +
    +
    +
    +
    Your password needs to:
    + +
    +
    +

    {{ confirmPasswordLabel }} @@ -30,10 +60,11 @@ [formControlName]="confirmNewPasswordFormControlName" required /> - - Confirm Password required - - + Password does not match diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss index e69de29bb2d..6b2ca22f231 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss @@ -0,0 +1,15 @@ +.password-requirements-menu { + &.mat-mdc-menu-panel { + width: 300px; + padding: 0 16px; + max-width: none; + } +} + +.password-requirements-list { + margin: 16px 0; +} + +.password-requirements-label { + text-align: start; +} diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts index 8f7ad5f950b..658444bb3fc 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts @@ -1,119 +1,145 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AbstractControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NewPasswordAndConfirmComponent } from './new-password-and-confirm.component'; +import { MatIconModule } from '@angular/material/icon'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { NewPasswordAndConfirmHarness } from './new-password-and-confirm.harness'; +import { PasswordModule } from '../password.module'; +import { PasswordErrors } from '../../domain/password/password-errors'; +import { PasswordRequirementComponent } from '../password-requirement/password-requirement.component'; +import { MatMenuModule } from '@angular/material/menu'; +import { HarnessLoader } from '@angular/cdk/testing'; let component: NewPasswordAndConfirmComponent; let fixture: ComponentFixture; +let newPasswordAndConfirmHarness: NewPasswordAndConfirmHarness; +let rootLoader: HarnessLoader; describe('NewPasswordAndConfirmComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [NewPasswordAndConfirmComponent], - imports: [BrowserAnimationsModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule] + imports: [ + BrowserAnimationsModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatMenuModule, + PasswordModule, + ReactiveFormsModule + ] }).compileComponents(); - fixture = TestBed.createComponent(NewPasswordAndConfirmComponent); component = fixture.componentInstance; component.formGroup = new FormGroup({}); fixture.detectChanges(); + newPasswordAndConfirmHarness = await TestbedHarnessEnvironment.harnessForFixture( + fixture, + NewPasswordAndConfirmHarness + ); + rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture); }); - passwordValidation(); + newPasswordValidation(); confirmPasswordValidation(); }); -function passwordValidation() { - it('should show password required error', () => { - setPasswordAndExpectError('', 'required'); - }); - - it('should show password length error', () => { - setPasswordAndExpectError('1234567', 'minlength'); - }); +function newPasswordValidation() { + passwordIsMissing(); + passwordIsValid(); + passwordErrorCases(); +} - it('should show password pattern error', () => { - setPasswordAndExpectError('1234567', 'pattern'); +function passwordIsMissing(): void { + describe('password is missing', () => { + it('shows password required error', async () => { + await newPasswordAndConfirmHarness.setNewPassword(''); + expect(await newPasswordAndConfirmHarness.isNewPasswordRequiredErrorDisplayed()).toBeTrue(); + }); }); +} - it('should not show any error when password is valid', () => { - setNewPasswordValue('Abcd1234'); - expect(getNewPasswordFormControl().errors).toBeNull(); +function passwordIsValid(): void { + describe('password is valid', () => { + describe('password contains letters and numbers', () => { + it('does not show any error', async () => { + await setPasswordAndExpectNoErrors(PasswordRequirementComponent.VALID_PASSWORD); + }); + }); + describe('password contains letters, numbers, and symbols', () => { + it('does not show any error', async () => { + await setPasswordAndExpectNoErrors(PasswordRequirementComponent.VALID_PASSWORD + '!$'); + }); + }); }); } -function setPasswordAndExpectError(password: string, errorName: string): void { - setNewPasswordValue(password); - expect(newPasswordHasError(errorName)).toBeTrue(); +async function setPasswordAndExpectNoErrors(password: string): Promise { + await newPasswordAndConfirmHarness.setNewPassword(password); + expect(await newPasswordAndConfirmHarness.isNewPasswordRequiredErrorDisplayed()).toBeFalse(); } -function setNewPasswordValue(value: string): void { - setFormControlValue( - component, - NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME, - value - ); +function passwordErrorCases(): void { + const errorCases = [ + { + descriptionText: 'missing a letter', + password: PasswordRequirementComponent.INVALID_PASSWORD_MISSING_LETTER, + expectedErrors: new PasswordErrors(true, false, false) + }, + { + descriptionText: 'missing a number', + password: PasswordRequirementComponent.INVALID_PASSWORD_MISSING_NUMBER, + expectedErrors: new PasswordErrors(false, true, false) + }, + { + descriptionText: 'too short', + password: PasswordRequirementComponent.INVALID_PASSWORD_TOO_SHORT, + expectedErrors: new PasswordErrors(false, false, true) + } + ]; + errorCases.forEach(({ descriptionText, password, expectedErrors }) => { + describe(`password is ${descriptionText}`, () => { + beforeEach(async () => { + await newPasswordAndConfirmHarness.setNewPassword(password); + }); + it(`shows password is ${descriptionText} message`, async () => { + await checkPasswordRequirements(expectedErrors); + }); + }); + }); } -function newPasswordHasError(errorName: string): boolean { - return hasError( - component, - NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME, - errorName +async function checkPasswordRequirements(passwordErrors: PasswordErrors): Promise { + expect(await newPasswordAndConfirmHarness.isMissingLetter(rootLoader)).toBe( + passwordErrors.missingLetter ); -} - -function getNewPasswordFormControl(): AbstractControl { - return getFormControl(component, NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME); + expect(await newPasswordAndConfirmHarness.isMissingNumber(rootLoader)).toBe( + passwordErrors.missingNumber + ); + expect(await newPasswordAndConfirmHarness.isTooShort(rootLoader)).toBe(passwordErrors.tooShort); } function confirmPasswordValidation() { - it('should show password does not match error', () => { - setNewPasswordValue('a'); - setConfirmNewPasswordValue('b'); - component.formGroup.markAsTouched(); - expect(confirmNewPasswordHasError('passwordDoesNotMatch')).toBeTrue(); + describe('passwords do not match', () => { + it('shows password does not match error', async () => { + await newPasswordAndConfirmHarness.setNewPassword('a'); + await newPasswordAndConfirmHarness.setConfirmNewPassword('b'); + expect( + await newPasswordAndConfirmHarness.isConfirmNewPasswordDoesNotMatchErrorDisplayed() + ).toBeTrue(); + }); }); - it('should not show any error when passwords match', () => { - setNewPasswordValue('a'); - setConfirmNewPasswordValue('a'); - expect(getConfirmNewPasswordFormControl().errors).toBeNull(); + describe('passwords match', () => { + it('does not show any error', async () => { + const password = PasswordRequirementComponent.VALID_PASSWORD; + await newPasswordAndConfirmHarness.setNewPassword(password); + await newPasswordAndConfirmHarness.setConfirmNewPassword(password); + expect( + await newPasswordAndConfirmHarness.isConfirmNewPasswordDoesNotMatchErrorDisplayed() + ).toBeFalse(); + }); }); } - -function setConfirmNewPasswordValue(value: string): void { - setFormControlValue( - component, - NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME, - value - ); -} - -function confirmNewPasswordHasError(errorName: string): boolean { - return hasError( - component, - NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME, - errorName - ); -} - -function getConfirmNewPasswordFormControl(): AbstractControl { - return getFormControl( - component, - NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME - ); -} - -function setFormControlValue(component: any, formControlName: string, value: string): void { - getFormControl(component, formControlName).setValue(value); -} - -function getFormControl(component: any, formControlName: string): AbstractControl { - return component.formGroup.controls[formControlName]; -} - -function hasError(component: any, formControlName: string, errorName: string): boolean { - return getFormControl(component, formControlName).hasError(errorName); -} diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts index e7f0977d21c..d9267d33bbb 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts @@ -1,34 +1,47 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { + AbstractControl, + FormControl, + FormGroup, + ValidationErrors, + ValidatorFn, + Validators +} from '@angular/forms'; +import { MatMenuTrigger } from '@angular/material/menu'; @Component({ selector: 'new-password-and-confirm', templateUrl: './new-password-and-confirm.component.html', - styleUrls: ['./new-password-and-confirm.component.scss'] + styleUrls: ['./new-password-and-confirm.component.scss'], + encapsulation: ViewEncapsulation.None }) export class NewPasswordAndConfirmComponent implements OnInit { static readonly CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME: string = 'confirmNewPassword'; static readonly NEW_PASSWORD_FORM_CONTROL_NAME: string = 'newPassword'; PASSWORD_MIN_LENGTH: number = 8; - PASSWORD_PATTERN: string = '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]+$'; - confirmNewPasswordFormControl: FormControl; - confirmNewPasswordFormControlName: string = + protected confirmNewPasswordFormControl: FormControl; + protected confirmNewPasswordFormControlName: string = NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME; @Input() confirmPasswordLabel: string = $localize`Confirm New Password`; @Input() formGroup: FormGroup; - newPasswordFormControl: FormControl; - newPasswordFormControlName: string = + protected newPasswordFormControl: FormControl; + protected newPasswordFormControlName: string = NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME; @Input() passwordLabel: string = $localize`New Password`; + protected passwordRequirements: any[] = [ + { errorFieldName: 'missingLetter', text: $localize`include a letter` }, + { errorFieldName: 'missingNumber', text: $localize`include a number` }, + { errorFieldName: 'tooShort', text: $localize`be at least 8 characters long` } + ]; + protected passwordStrength: number = 0; constructor() {} ngOnInit(): void { this.newPasswordFormControl = new FormControl('', [ Validators.required, - Validators.minLength(this.PASSWORD_MIN_LENGTH), - Validators.pattern(this.PASSWORD_PATTERN) + this.createPasswordStrengthValidator() ]); this.formGroup.addControl(this.newPasswordFormControlName, this.newPasswordFormControl); this.confirmNewPasswordFormControl = new FormControl('', [Validators.required]); @@ -39,20 +52,51 @@ export class NewPasswordAndConfirmComponent implements OnInit { this.formGroup.addValidators(this.passwordMatchValidator); } - passwordMatchValidator(formGroup: FormGroup): ValidationErrors { + private createPasswordStrengthValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value ? control.value : ''; + const hasLetter = /[a-zA-Z]+/.test(value); + const hasNumber = /[0-9]+/.test(value); + const appropriateLength = value.length >= this.PASSWORD_MIN_LENGTH; + const passwordValid = hasLetter && hasNumber && appropriateLength; + return !passwordValid + ? { + missingLetter: !hasLetter, + missingNumber: !hasNumber, + tooShort: !appropriateLength + } + : null; + }; + } + + private passwordMatchValidator(formGroup: FormGroup): ValidationErrors { const password = formGroup.get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) .value; const confirmPassword = formGroup.get( NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME ).value; - if (password == confirmPassword) { - return null; - } else { - const error = { passwordDoesNotMatch: true }; - formGroup.controls[ - NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME - ].setErrors(error); - return error; - } + const error = password === confirmPassword ? null : { passwordDoesNotMatch: true }; + formGroup.controls[ + NewPasswordAndConfirmComponent.CONFIRM_NEW_PASSWORD_FORM_CONTROL_NAME + ].setErrors(error); + return error; + } + + protected passwordStrengthChange(value: number): void { + this.passwordStrength = value ? value : 0; + } + + onNewPasswordFocus(menuTrigger: MatMenuTrigger): void { + // This setTimeout is required because sometimes when the user clicks on the input, it will + // trigger a blur and then a focus which can lead to the menu not opening. This makes sure that + // if a blur and a focus occur right after each other, the openMenu() will be called after the + // blur is complete. + setTimeout(() => { + menuTrigger.openMenu(); + }); + } + + onNewPasswordBlur(menuTrigger: MatMenuTrigger): void { + menuTrigger.closeMenu(); } } diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts b/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts new file mode 100644 index 00000000000..8806b91ad0b --- /dev/null +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts @@ -0,0 +1,75 @@ +import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing'; +import { MatInputHarness } from '@angular/material/input/testing'; +import { MatErrorHarness } from '@angular/material/form-field/testing'; +import { PasswordRequirementHarness } from '../password-requirement/password-requirement.harness'; + +export class NewPasswordAndConfirmHarness extends ComponentHarness { + static hostSelector = 'new-password-and-confirm'; + + getNewPasswordInput = this.locatorFor( + MatInputHarness.with({ selector: 'input[name="newPassword"]' }) + ); + getConfirmNewPasswordInput = this.locatorFor( + MatInputHarness.with({ selector: 'input[name="confirmNewPassword"]' }) + ); + getNewPasswordRequiredError = this.locatorForOptional( + MatErrorHarness.with({ selector: '.new-password-required-error' }) + ); + getConfirmNewPasswordRequiredError = this.locatorForOptional( + MatErrorHarness.with({ selector: '.confirm-new-password-required-error' }) + ); + getConfirmNewPasswordDoesNotMatchError = this.locatorForOptional( + MatErrorHarness.with({ selector: '.confirm-new-password-does-not-match-error' }) + ); + getPasswordRequirements = this.locatorForAll(PasswordRequirementHarness); + + async isMissingLetter(rootLoader: HarnessLoader): Promise { + return this.isMissingRequirement(rootLoader, 'include a letter'); + } + + async isMissingNumber(rootLoader: HarnessLoader): Promise { + return this.isMissingRequirement(rootLoader, 'include a number'); + } + + async isTooShort(rootLoader: HarnessLoader): Promise { + return this.isMissingRequirement(rootLoader, 'be at least 8 characters long'); + } + + private async isMissingRequirement( + rootLoader: HarnessLoader, + requirement: string + ): Promise { + const passwordRequirement = await rootLoader.getHarness( + PasswordRequirementHarness.with({ text: requirement }) + ); + return await passwordRequirement.isFail(); + } + + async setNewPassword(value: string): Promise { + await this.setPassword(value, false); + } + + async setConfirmNewPassword(value: string): Promise { + await this.setPassword(value, true); + } + + async setPassword(value: string, isConfirm: boolean): Promise { + await this.setInputValue( + await (isConfirm ? this.getConfirmNewPasswordInput() : this.getNewPasswordInput()), + value + ); + } + + private async setInputValue(input: MatInputHarness, value: string): Promise { + await input.setValue(value); + await input.blur(); + } + + async isNewPasswordRequiredErrorDisplayed(): Promise { + return (await this.getNewPasswordRequiredError()) != null; + } + + async isConfirmNewPasswordDoesNotMatchErrorDisplayed(): Promise { + return (await this.getConfirmNewPasswordDoesNotMatchError()) != null; + } +} diff --git a/src/app/password/password-requirement/password-requirement.component.html b/src/app/password/password-requirement/password-requirement.component.html new file mode 100644 index 00000000000..2810ee2de7a --- /dev/null +++ b/src/app/password/password-requirement/password-requirement.component.html @@ -0,0 +1,31 @@ +

    +
    +
    + + close + done +
    {{ requirementText }}
    +
    diff --git a/src/app/password/password-requirement/password-requirement.component.scss b/src/app/password/password-requirement/password-requirement.component.scss new file mode 100644 index 00000000000..daf42fda433 --- /dev/null +++ b/src/app/password/password-requirement/password-requirement.component.scss @@ -0,0 +1,4 @@ +.pristine-container { + width: 24px; + height: 24px; +} diff --git a/src/app/password/password-requirement/password-requirement.component.spec.ts b/src/app/password/password-requirement/password-requirement.component.spec.ts new file mode 100644 index 00000000000..59fb9e19ea3 --- /dev/null +++ b/src/app/password/password-requirement/password-requirement.component.spec.ts @@ -0,0 +1,65 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { PasswordRequirementComponent } from './password-requirement.component'; +import { FormControl } from '@angular/forms'; +import { PasswordRequirementHarness } from './password-requirement.harness'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatIconModule } from '@angular/material/icon'; + +let component: PasswordRequirementComponent; +let fixture: ComponentFixture; +let passwordRequirementHarness: PasswordRequirementHarness; + +describe('PasswordRequirementComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PasswordRequirementComponent], + imports: [MatIconModule] + }).compileComponents(); + + fixture = TestBed.createComponent(PasswordRequirementComponent); + component = fixture.componentInstance; + component.passwordFormControl = new FormControl(); + fixture.detectChanges(); + passwordRequirementHarness = await TestbedHarnessEnvironment.harnessForFixture( + fixture, + PasswordRequirementHarness + ); + }); + + requirementIsPristine(); + requirementIsFailed(); + requirementIsPassed(); +}); + +function requirementIsPristine(): void { + describe('form control is pristine', () => { + it('shows no icon', async () => { + expect(await passwordRequirementHarness.isPristine()).toBeTrue(); + }); + }); +} + +function requirementIsFailed(): void { + describe('requirement is failed', () => { + beforeEach(() => { + component.errorFieldName = 'missingLetter'; + component.requirementText = 'include a letter'; + component.passwordFormControl.setErrors({ missingLetter: true }); + component.passwordFormControl.markAsDirty(); + }); + it('shows error icon', async () => { + expect(await passwordRequirementHarness.isFail()).toBeTrue(); + }); + }); +} + +function requirementIsPassed(): void { + describe('requirement is passed', () => { + beforeEach(() => { + component.passwordFormControl.markAsDirty(); + }); + it('shows success icon', async () => { + expect(await passwordRequirementHarness.isPass()).toBeTrue(); + }); + }); +} diff --git a/src/app/password/password-requirement/password-requirement.component.ts b/src/app/password/password-requirement/password-requirement.component.ts new file mode 100644 index 00000000000..ab84eda8648 --- /dev/null +++ b/src/app/password/password-requirement/password-requirement.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'password-requirement', + templateUrl: './password-requirement.component.html', + styleUrls: ['./password-requirement.component.scss'] +}) +export class PasswordRequirementComponent { + public static INVALID_PASSWORD_MISSING_LETTER = '12345678'; + public static INVALID_PASSWORD_MISSING_NUMBER = 'abcdefgh'; + public static INVALID_PASSWORD_TOO_SHORT = 'abcd123'; + public static VALID_PASSWORD = 'abcd1234'; + + @Input() errorFieldName: string; + @Input() passwordFormControl: FormControl; + @Input() requirementText: string; +} diff --git a/src/app/password/password-requirement/password-requirement.harness.ts b/src/app/password/password-requirement/password-requirement.harness.ts new file mode 100644 index 00000000000..1a0bf2fc523 --- /dev/null +++ b/src/app/password/password-requirement/password-requirement.harness.ts @@ -0,0 +1,53 @@ +import { + BaseHarnessFilters, + ComponentHarness, + HarnessPredicate, + TestElement +} from '@angular/cdk/testing'; + +interface PasswordRequirementHarnessFilters extends BaseHarnessFilters { + text?: string | RegExp; +} + +export class PasswordRequirementHarness extends ComponentHarness { + static hostSelector = 'password-requirement'; + + static with( + options: PasswordRequirementHarnessFilters + ): HarnessPredicate { + return new HarnessPredicate(PasswordRequirementHarness, options).addOption( + 'text', + options.text, + (harness, text) => { + return HarnessPredicate.stringMatches(harness.getText(), text); + } + ); + } + + async getText(): Promise { + const text = await this.locatorFor('.requirement-text')(); + return text.text(); + } + + async isPristine(): Promise { + const icon = await this.getIcon(); + return icon == null; + } + + async isFail(): Promise { + return this.iconHasText('close'); + } + + async isPass(): Promise { + return this.iconHasText('done'); + } + + private async iconHasText(text: string): Promise { + const icon = await this.getIcon(); + return (await icon.text()) === text; + } + + private async getIcon(): Promise { + return await this.locatorForOptional('.mat-icon')(); + } +} diff --git a/src/app/password/password.module.ts b/src/app/password/password.module.ts index 7ffe018e50d..9f3da55444f 100644 --- a/src/app/password/password.module.ts +++ b/src/app/password/password.module.ts @@ -5,6 +5,10 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { NewPasswordAndConfirmComponent } from './new-password-and-confirm/new-password-and-confirm.component'; +import { MatIconModule } from '@angular/material/icon'; +import { PasswordStrengthMeterModule } from 'angular-password-strength-meter'; +import { PasswordRequirementComponent } from './password-requirement/password-requirement.component'; +import { MatMenuModule } from '@angular/material/menu'; @NgModule({ imports: [ @@ -12,10 +16,13 @@ import { NewPasswordAndConfirmComponent } from './new-password-and-confirm/new-p FlexLayoutModule, FormsModule, MatFormFieldModule, + MatIconModule, MatInputModule, + MatMenuModule, + PasswordStrengthMeterModule.forRoot(), ReactiveFormsModule ], - declarations: [NewPasswordAndConfirmComponent], + declarations: [NewPasswordAndConfirmComponent, PasswordRequirementComponent], exports: [NewPasswordAndConfirmComponent] }) export class PasswordModule {} diff --git a/src/app/register/register-student-form/register-student-form.component.spec.ts b/src/app/register/register-student-form/register-student-form.component.spec.ts index f4ac3c01720..eeafb1e2c50 100644 --- a/src/app/register/register-student-form/register-student-form.component.spec.ts +++ b/src/app/register/register-student-form/register-student-form.component.spec.ts @@ -20,12 +20,13 @@ import { BrowserModule, By } from '@angular/platform-browser'; import { RecaptchaV3Module, ReCaptchaV3Service, RECAPTCHA_V3_SITE_KEY } from 'ng-recaptcha'; import { PasswordModule } from '../../password/password.module'; import { ConfigService } from '../../services/config.service'; +import { PasswordRequirementComponent } from '../../password/password-requirement/password-requirement.component'; let router: Router; let component: RegisterStudentFormComponent; let configService: ConfigService; let fixture: ComponentFixture; -const PASSWORD: string = 'Abcd1234'; +const PASSWORD: string = PasswordRequirementComponent.VALID_PASSWORD; let recaptchaV3Service: ReCaptchaV3Service; let studentService: StudentService; let snackBar: MatSnackBar; @@ -54,14 +55,14 @@ describe('RegisterStudentFormComponent', () => { declarations: [RegisterStudentFormComponent], imports: [ BrowserAnimationsModule, - PasswordModule, - RouterTestingModule, - ReactiveFormsModule, - MatSelectModule, + BrowserModule, MatInputModule, + MatSelectModule, MatSnackBarModule, - BrowserModule, - RecaptchaV3Module + PasswordModule, + ReactiveFormsModule, + RecaptchaV3Module, + RouterTestingModule ], providers: [ { provide: ConfigService, useClass: MockConfigService }, diff --git a/src/app/register/register-student-form/register-student-form.component.ts b/src/app/register/register-student-form/register-student-form.component.ts index 24f82ef2145..4aa2f5d2d51 100644 --- a/src/app/register/register-student-form/register-student-form.component.ts +++ b/src/app/register/register-student-form/register-student-form.component.ts @@ -112,7 +112,7 @@ export class RegisterStudentFormComponent extends RegisterUserFormComponent impl this.createAccountSuccess(response); }, (response: HttpErrorResponse) => { - this.createAccountError(response.error); + this.handleCreateAccountError(response.error, this.user); } ); } diff --git a/src/app/register/register-teacher-form/register-teacher-form.component.spec.ts b/src/app/register/register-teacher-form/register-teacher-form.component.spec.ts index 0a90d517488..44a57b1ccf9 100644 --- a/src/app/register/register-teacher-form/register-teacher-form.component.spec.ts +++ b/src/app/register/register-teacher-form/register-teacher-form.component.spec.ts @@ -21,6 +21,7 @@ import { By } from '@angular/platform-browser'; import { RecaptchaV3Module, ReCaptchaV3Service, RECAPTCHA_V3_SITE_KEY } from 'ng-recaptcha'; import { PasswordModule } from '../../password/password.module'; import { ConfigService } from '../../services/config.service'; +import { PasswordRequirementComponent } from '../../password/password-requirement/password-requirement.component'; class MockTeacherService { registerTeacherAccount() {} @@ -35,7 +36,7 @@ class MockConfigService { let component: RegisterTeacherFormComponent; let configService: ConfigService; let fixture: ComponentFixture; -const PASSWORD: string = 'Abcd1234'; +const PASSWORD: string = PasswordRequirementComponent.VALID_PASSWORD; let teacherService: TeacherService; let recaptchaV3Service: ReCaptchaV3Service; let router: Router; @@ -48,14 +49,14 @@ describe('RegisterTeacherFormComponent', () => { declarations: [RegisterTeacherFormComponent], imports: [ BrowserAnimationsModule, - RouterTestingModule, - ReactiveFormsModule, MatCheckboxModule, - MatSelectModule, MatInputModule, + MatSelectModule, MatSnackBarModule, + PasswordModule, + ReactiveFormsModule, RecaptchaV3Module, - PasswordModule + RouterTestingModule ], providers: [ { provide: ConfigService, useClass: MockConfigService }, diff --git a/src/app/register/register-teacher-form/register-teacher-form.component.ts b/src/app/register/register-teacher-form/register-teacher-form.component.ts index d096e990c02..68a747f537e 100644 --- a/src/app/register/register-teacher-form/register-teacher-form.component.ts +++ b/src/app/register/register-teacher-form/register-teacher-form.component.ts @@ -91,7 +91,7 @@ export class RegisterTeacherFormComponent extends RegisterUserFormComponent impl this.createAccountSuccess(response); }, (response: HttpErrorResponse) => { - this.createAccountError(response.error); + this.handleCreateAccountError(response.error, this.user); } ); } diff --git a/src/app/register/register-user-form/register-user-form.component.ts b/src/app/register/register-user-form/register-user-form.component.ts index 41645944e8b..0f75d5e51b3 100644 --- a/src/app/register/register-user-form/register-user-form.component.ts +++ b/src/app/register/register-user-form/register-user-form.component.ts @@ -1,7 +1,7 @@ -import { MatSnackBar } from '@angular/material/snack-bar'; -import { User } from '../../domain/user'; -import { NewPasswordAndConfirmComponent } from '../../password/new-password-and-confirm/new-password-and-confirm.component'; import { FormBuilder, FormGroup } from '@angular/forms'; +import { User } from '../../domain/user'; +import { injectPasswordErrors } from '../../common/password-helper'; +import { MatSnackBar } from '@angular/material/snack-bar'; export class RegisterUserFormComponent { protected NAME_REGEX = '^[a-zA-Z]+([ -]?[a-zA-Z]+)*$'; @@ -14,23 +14,13 @@ export class RegisterUserFormComponent { constructor(protected fb: FormBuilder, protected snackBar: MatSnackBar) {} - protected createAccountError(error: any): void { - const formError: any = {}; + handleCreateAccountError(error: any, userObject: User): void { switch (error.messageCode) { - case 'invalidPasswordLength': - formError.minlength = true; - this.passwordsFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - case 'invalidPasswordPattern': - formError.pattern = true; - this.passwordsFormGroup - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); + case 'invalidPassword': + injectPasswordErrors(this.passwordsFormGroup, error); break; case 'recaptchaResponseInvalid': - this.user['isRecaptchaInvalid'] = true; + userObject['isRecaptchaInvalid'] = true; break; default: this.snackBar.open(this.translateCreateAccountErrorMessageCode(error.messageCode)); diff --git a/src/app/services/projectService.spec.ts b/src/app/services/projectService.spec.ts index ebd04b06bed..f2b706d3501 100644 --- a/src/app/services/projectService.spec.ts +++ b/src/app/services/projectService.spec.ts @@ -43,7 +43,6 @@ describe('ProjectService', () => { shouldReplaceAssetPathsInHtmlComponentContent(); shouldNotReplaceAssetPathsInHtmlComponentContent(); shouldRetrieveProjectWhenConfigProjectURLIsValid(); - shouldNotRetrieveProjectWhenConfigProjectURLIsUndefined(); shouldGetDefaultThemePath(); shouldReturnTheStartNodeOfTheProject(); shouldReturnTheNodeByNodeId(); @@ -157,15 +156,6 @@ function shouldRetrieveProjectWhenConfigProjectURLIsValid() { }); } -function shouldNotRetrieveProjectWhenConfigProjectURLIsUndefined() { - it('should not retrieve project when Config.projectURL is undefined', () => { - spyOn(configService, 'getConfigParam').and.returnValue(null); - const project = service.retrieveProject(); - expect(configService.getConfigParam).toHaveBeenCalledWith('projectURL'); - expect(project).toBeNull(); - }); -} - function shouldGetDefaultThemePath() { it('should get default theme path', () => { spyOn(configService, 'getConfigParam').and.returnValue(wiseBaseURL); diff --git a/src/app/services/studentDataService.spec.ts b/src/app/services/studentDataService.spec.ts index 4cad90753b4..383d34699ec 100644 --- a/src/app/services/studentDataService.spec.ts +++ b/src/app/services/studentDataService.spec.ts @@ -31,7 +31,6 @@ describe('StudentDataService', () => { shouldEvaluateHasWorkCreatedAfterTimestampFalse(); shouldEvaluateHasWorkCreatedAfterTimestampTrue(); shouldGetBranchPathTakenEventsByNodeId(); - shouldGetNotebookItemsByNodeId(); shouldHandleSaveStudentWorkToServerSuccess(); shouldHandleSaveEventsToServerSuccess(); shouldHandleSaveAnnotationsToServerSuccess(); @@ -39,11 +38,9 @@ describe('StudentDataService', () => { shouldCheckIsComponentSubmitDirty(); shouldGetLatestComponentStateByNodeIdAndComponentId(); shouldGetLatestSubmitComponentState(); - shouldGetStudentWorkByStudentWorkId(); shouldGetComponentStatesByNodeId(); shouldGetComponentStatesByNodeIdAndComponentId(); shouldGetEventsByNodeId(); - shouldGetEventsByNodeIdAndComponentId(); shouldGetLatestNodeEnteredEventNodeIdWithExistingNode(); shouldCalculateCanVisitNode(); shouldGetNodeStatusByNodeId(); @@ -96,32 +93,6 @@ function shouldGetBranchPathTakenEventsByNodeId() { }); } -function shouldGetNotebookItemsByNodeId() { - it('should get notebook items by node id', () => { - const notebook = { - allItems: [ - { nodeId: 'node1' }, - { nodeId: 'node1' }, - { nodeId: 'node1' }, - { nodeId: 'node1' }, - { nodeId: 'node1' }, - { nodeId: 'node2' }, - { nodeId: 'node2' }, - { nodeId: 'node2' }, - { nodeId: 'node2' }, - { nodeId: 'node2' } - ] - }; - const notebookItems = service.getNotebookItemsByNodeId(notebook, 'node1'); - expect(notebookItems.length).toEqual(5); - expect(notebookItems[0].nodeId).toEqual('node1'); - expect(notebookItems[1].nodeId).toEqual('node1'); - expect(notebookItems[2].nodeId).toEqual('node1'); - expect(notebookItems[3].nodeId).toEqual('node1'); - expect(notebookItems[4].nodeId).toEqual('node1'); - }); -} - function shouldHandleSaveStudentWorkToServerSuccess() { xit('should handle save student work to server success', () => { service.studentData = { @@ -408,31 +379,6 @@ function shouldGetLatestSubmitComponentState() { }); } -function shouldGetStudentWorkByStudentWorkId() { - it('should get student work by student work id with an id that does not exist', () => { - service.studentData = { - componentStates: [ - createComponentState(1, 'node1', 'component1'), - createComponentState(2, 'node2', 'component2'), - createComponentState(3, 'node3', 'component3') - ] - }; - const componentState = service.getStudentWorkByStudentWorkId(4); - expect(componentState).toEqual(null); - }); - it('should get student work by student work id with an id that does exist', () => { - service.studentData = { - componentStates: [ - createComponentState(1, 'node1', 'component1'), - createComponentState(2, 'node1', 'component1'), - createComponentState(3, 'node1', 'component1') - ] - }; - const componentState = service.getStudentWorkByStudentWorkId(2); - expect(componentState.id).toEqual(2); - }); -} - function shouldGetComponentStatesByNodeId() { it('should get component states by node id', () => { service.studentData = { @@ -489,24 +435,6 @@ function shouldGetEventsByNodeId() { }); } -function shouldGetEventsByNodeIdAndComponentId() { - it('should get events by node id and component id', () => { - service.studentData = { - events: [ - createEvent(1, 'node1', 'component1'), - createEvent(2, 'node1', 'component2'), - createEvent(3, 'node2', 'component3'), - createEvent(4, 'node3', 'component4'), - createEvent(5, 'node1', 'component1') - ] - }; - const events = service.getEventsByNodeIdAndComponentId('node1', 'component1'); - expect(events.length).toEqual(2); - expect(events[0].id).toEqual(1); - expect(events[1].id).toEqual(5); - }); -} - function shouldGetLatestNodeEnteredEventNodeIdWithExistingNode() { it('should get latest node entered event node id with existing node', () => { service.studentData = { diff --git a/src/app/student/student-run-list-item/student-run-list-item.component.html b/src/app/student/student-run-list-item/student-run-list-item.component.html index 42ad4b7ca25..0eca7804b0d 100644 --- a/src/app/student/student-run-list-item/student-run-list-item.component.html +++ b/src/app/student/student-run-list-item/student-run-list-item.component.html @@ -64,7 +64,7 @@ play_circle_filled Launch diff --git a/src/app/teacher/authoring-routing.module.ts b/src/app/teacher/authoring-routing.module.ts index f3456f3414b..52a68784f44 100644 --- a/src/app/teacher/authoring-routing.module.ts +++ b/src/app/teacher/authoring-routing.module.ts @@ -38,6 +38,7 @@ import { ProjectAuthoringParentComponent } from '../../assets/wise5/authoringToo import { ChooseImportUnitComponent } from '../authoring-tool/import-step/choose-import-unit/choose-import-unit.component'; import { NodeAuthoringParentComponent } from '../../assets/wise5/authoringTool/node/node-authoring-parent/node-authoring-parent.component'; import { AddLessonChooseTemplateComponent } from '../../assets/wise5/authoringTool/addLesson/add-lesson-choose-template/add-lesson-choose-template.component'; +import { RecoveryAuthoringProjectResolver } from './recovery-authoring-project.resolver'; const routes: Routes = [ { @@ -46,8 +47,12 @@ const routes: Routes = [ resolve: { config: AuthoringConfigResolver }, children: [ { path: 'home', component: ProjectListComponent }, - { path: 'new-unit', component: AddProjectComponent }, + { + path: 'recovery/:unitId', + component: RecoveryAuthoringComponent, + resolve: { project: RecoveryAuthoringProjectResolver } + }, { path: 'unit/:unitId', component: ProjectAuthoringParentComponent, @@ -165,8 +170,7 @@ const routes: Routes = [ } ] }, - { path: 'notebook', component: NotebookAuthoringComponent }, - { path: 'recovery', component: RecoveryAuthoringComponent } + { path: 'notebook', component: NotebookAuthoringComponent } ] } ] diff --git a/src/app/teacher/recovery-authoring-project.resolver.ts b/src/app/teacher/recovery-authoring-project.resolver.ts new file mode 100644 index 00000000000..f2557b57635 --- /dev/null +++ b/src/app/teacher/recovery-authoring-project.resolver.ts @@ -0,0 +1,16 @@ +import { inject } from '@angular/core'; +import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from '@angular/router'; +import { Observable, switchMap } from 'rxjs'; +import { ProjectService } from '../../assets/wise5/services/projectService'; +import { ConfigService } from '../../assets/wise5/services/configService'; + +export const RecoveryAuthoringProjectResolver: ResolveFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + configService: ConfigService = inject(ConfigService), + projectService: ProjectService = inject(ProjectService) +): Observable => { + return configService + .retrieveConfig(`/api/author/config/${route.params['unitId']}`) + .pipe(switchMap(() => projectService.retrieveProjectWithoutParsing())); +}; diff --git a/src/app/teacher/run-menu/run-menu.component.html b/src/app/teacher/run-menu/run-menu.component.html index 4ae5f012639..97dea0111ea 100644 --- a/src/app/teacher/run-menu/run-menu.component.html +++ b/src/app/teacher/run-menu/run-menu.component.html @@ -10,7 +10,7 @@ Edit Settings - tv + preview Preview diff --git a/src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html b/src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html index 31ba970524a..a0dd1dddf2d 100644 --- a/src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html +++ b/src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html @@ -23,13 +23,11 @@
    Choose an assessment item:
    mat-raised-button color="primary" (click)="previewNode(item.node)" - aria-label="Preview Step" - i18n-aria-label matTooltip="Preview Step" matTooltipPosition="above" i18n-matTooltip > - visibility + preview diff --git a/src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html b/src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html index 82b660bc530..5c685989f35 100644 --- a/src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html +++ b/src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html @@ -1,16 +1,4 @@
    - diff --git a/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html b/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html index 96b713973e4..f368fbb5c07 100644 --- a/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html +++ b/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html @@ -22,14 +22,15 @@

    mat-icon-button *ngIf="runId" fxHide.xs - aria-label="Switch to Grading View" - i18n-aria-label matTooltip="Switch to Grading View" i18n-matTooltip (click)="switchToGradingView()" > assignment_turned_in +

    diff --git a/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.ts b/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.ts index cbcadd41914..f1daaeeda10 100644 --- a/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.ts +++ b/src/assets/wise5/authoringTool/components/top-bar/top-bar.component.ts @@ -53,6 +53,10 @@ export class TopBarComponent implements OnInit { return projectInfo; } + protected previewProject(): void { + window.open(`${this.configService.getConfigParam('previewProjectURL')}`); + } + protected showHelp(): void { window.open( 'https://docs.google.com/document/d/1G8lVtiUlGXLRAyFOvkEdadHYhJhJLW4aor9dol2VzeU', diff --git a/src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html b/src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html index 8661c552d0b..a25172e603f 100644 --- a/src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html +++ b/src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html @@ -63,7 +63,7 @@

    {{ importProject.metadata.title }}

    matTooltipPosition="above" i18n-matTooltip > - visibility + preview
    color="primary" *ngIf="importItem.node.type !== 'group'" (click)="previewImportNode(importItem.node)" - aria-label="Preview Step" - i18n-aria-label matTooltip="Preview Step" matTooltipPosition="above" i18n-matTooltip > - visibility + preview
    color="primary" *ngIf="importItem.node.type !== 'group'" (click)="previewImportNode(importItem.node)" - aria-label="Preview Component" - i18n-aria-label matTooltip="Preview Component" matTooltipPosition="above" i18n-matTooltip > - visibility + preview
    diff --git a/src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html b/src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html index 00eb1e69f58..4207b6534f6 100644 --- a/src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html +++ b/src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html @@ -28,14 +28,13 @@ *ngIf="!isGroupNode" mat-raised-button color="primary" - class="top-button" (click)="previewStepInNewWindow()" [disabled]="showEditTransitions" matTooltip="Preview Step" matTooltipPosition="above" i18n-matTooltip > - visibility + preview diff --git a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html index 2e4c634cce3..3433d7147cb 100644 --- a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html +++ b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html @@ -1,169 +1,137 @@ -
    -
    - - - - - - - - -
    +
    + + + + +
    - -
    -
    +
    + -
    - +
    + +

    + call_split - -

    -
    - -

    - call_split - block - block - message -

    -
    + block + block + message +

    - +
    Unused Lessons
    There are no Unused Lessons
    Unused Lessons branchPathStepHeader: isNodeInAnyBranchPath(inactiveNode.id) && !isGroupNode(inactiveNode.id) }" - class="projectItem" + class="pointer projectItem" + (click)="nodeClicked(inactiveNode.id)" > -
    - - -
    -
    + +
    -
    - - -
    -
    + +
    @@ -231,32 +191,29 @@
    Unused Steps
    -
    - - -
    -
    + +
    @@ -265,4 +222,3 @@
    Unused Steps
    -
    diff --git a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.scss b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.scss index 13e78e75400..43c973327e5 100644 --- a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.scss +++ b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.scss @@ -65,6 +65,7 @@ .projectItemTitleDiv { width: 100%; + font-weight: initial; } .multiLineTooltip { diff --git a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts index 046cfe4e048..cc2c3e88384 100644 --- a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts +++ b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { ConfigService } from '../../services/configService'; import { DeleteNodeService } from '../../services/deleteNodeService'; import { TeacherProjectService } from '../../services/teacherProjectService'; import { TeacherDataService } from '../../services/teacherDataService'; @@ -25,7 +24,6 @@ export class ProjectAuthoringComponent { private subscriptions: Subscription = new Subscription(); constructor( - private configService: ConfigService, private deleteNodeService: DeleteNodeService, private projectService: TeacherProjectService, private dataService: TeacherDataService, @@ -52,6 +50,7 @@ export class ProjectAuthoringComponent { private refreshProject(): void { this.projectService.parseProject(); this.items = this.projectService.getNodesInOrder(); + this.items.shift(); // remove the 'group0' master root node from consideration this.inactiveGroupNodes = this.projectService.getInactiveGroupNodes(); this.inactiveStepNodes = this.projectService.getInactiveStepNodes(); this.inactiveNodes = this.projectService.getInactiveNodes(); @@ -59,18 +58,6 @@ export class ProjectAuthoringComponent { this.unselectAllItems(); } - protected previewProject(): void { - window.open(`${this.configService.getConfigParam('previewProjectURL')}`); - } - - protected getNodePositionById(nodeId: string): string { - return this.projectService.getNodePositionById(nodeId); - } - - protected getNodeTitle(nodeId: string): string { - return this.projectService.getNodeTitle(nodeId); - } - protected isGroupNode(nodeId: string): boolean { return this.projectService.isGroupNode(nodeId); } @@ -149,18 +136,10 @@ export class ProjectAuthoringComponent { this.router.navigate([`/teacher/edit/unit/${this.projectId}/add-node/choose-template`]); } - protected goToAdvancedAuthoring(): void { - this.router.navigate([`/teacher/edit/unit/${this.projectId}/advanced`]); - } - protected isNodeInAnyBranchPath(nodeId: string): boolean { return this.projectService.isNodeInAnyBranchPath(nodeId); } - /** - * Temporarily highlight the new nodes to draw attention to them - * @param newNodes the new nodes to highlight - */ private temporarilyHighlightNewNodes(newNodes = []): void { if (newNodes.length > 0) { setTimeout(() => { @@ -204,7 +183,7 @@ export class ProjectAuthoringComponent { * The checkbox for a node was clicked. We do not allow selecting a mix of group and step nodes. * If any group nodes are selected, disable all step node checkboxes, and vise-versa. */ - protected selectNode(): void { + protected selectNode($event: Event): void { const checkedNodes = this.items .concat(Object.values(this.idToNode)) .filter((item) => item.checked); @@ -215,6 +194,7 @@ export class ProjectAuthoringComponent { this.groupNodeSelected = this.isGroupNode(checkedNodes[0].id); this.stepNodeSelected = !this.groupNodeSelected; } + $event.stopPropagation(); } protected isBranchPoint(nodeId: string): boolean { diff --git a/src/assets/wise5/authoringTool/project-list/project-list.component.html b/src/assets/wise5/authoringTool/project-list/project-list.component.html index 746baeb3a8c..394e50520a7 100644 --- a/src/assets/wise5/authoringTool/project-list/project-list.component.html +++ b/src/assets/wise5/authoringTool/project-list/project-list.component.html @@ -34,7 +34,7 @@ matTooltipPosition="left" i18n-matTooltip > - pageview + preview -
    - - -
    -
    -
    JSON Valid
    -
    JSON Invalid
    -
    -
    - {{ globalMessage.text }} {{ globalMessage.time | date: 'medium' }} +
    +

    Recovery View

    +
    +
    + + +
    +
    +
    JSON Valid
    +
    JSON Invalid
    +
    +
    + {{ globalMessage.text }} {{ globalMessage.time | date: 'medium' }} +
    -
    -
    - Warning: Modifying the JSON may break the project. Please make a backup copy of the JSON before - you modify it. -
    -
    -
    Potential Problems
    -
    -
    {{ badNode.nodeId }}
    -
    - This group references the node ID but the node does not exist: - {{ badNode.referencedIdsThatDoNotExist }} -
    -
    - This group references the same node ID multiple times: - {{ badNode.referencedIdsThatAreDuplicated }} +
    + Warning: Modifying the JSON may break the project. Please make a backup copy of the JSON before + you modify it. +
    +
    +
    Potential Problems
    +
    +
    {{ badNode.nodeId }}
    +
    + This group references the node ID but the node does not exist: + {{ badNode.referencedIdsThatDoNotExist }} +
    +
    + This group references the same node ID multiple times: + {{ badNode.referencedIdsThatAreDuplicated }} +
    +
    This node has a transition to null
    -
    This node has a transition to null
    -
    -
    - - Edit Unit JSON - - +
    + + Edit Unit JSON + + +
    diff --git a/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.scss b/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.scss index 35cd60020ae..99df6c2161a 100644 --- a/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.scss +++ b/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.scss @@ -1,15 +1,5 @@ -.main-box { - position: relative; - padding: 16px !important; -} - -.top-div { - background-color: white; - position: sticky; - top: 0px; - z-index: 2; - padding-top: 4px; - padding-bottom: 4px; +.body { + padding: 0px 32px; } .save-bar { @@ -24,6 +14,10 @@ color: red; } +.view-title { + margin-bottom: 20px; +} + .warning-div { margin-bottom: 20px; color: red; @@ -42,7 +36,7 @@ border: 1px solid red; border-radius: 8px; padding: 8px; - margin-bottom: 4px; + margin-bottom: 20px; } .node-id { diff --git a/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.spec.ts b/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.spec.ts index c48f5e0ac59..bb0d44698db 100644 --- a/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.spec.ts +++ b/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.spec.ts @@ -7,6 +7,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { StudentTeacherCommonServicesModule } from '../../../../app/student-teacher-common-services.module'; import { TeacherProjectService } from '../../services/teacherProjectService'; import { RecoveryAuthoringComponent } from './recovery-authoring.component'; +import { ActivatedRoute, RouterModule } from '@angular/router'; class MockTeacherProjectService { project = { @@ -46,9 +47,13 @@ describe('RecoveryAuthoringComponent', () => { HttpClientTestingModule, MatDialogModule, MatInputModule, + RouterModule, StudentTeacherCommonServicesModule ], - providers: [{ provide: TeacherProjectService, useClass: MockTeacherProjectService }] + providers: [ + { provide: ActivatedRoute, useValue: { snapshot: { paramMap: { get: () => '1' } } } }, + { provide: TeacherProjectService, useClass: MockTeacherProjectService } + ] }).compileComponents(); }); @@ -81,7 +86,7 @@ function detectJSONValidity() { function setJSONAndExpect(json: string, jsonIsValid: boolean, saveButtonEnabled: boolean) { setProjectJSONStringAndTriggerChange(json); - expect(component.jsonIsValid).toEqual(jsonIsValid); + expect(component.jsonValid).toEqual(jsonIsValid); expect(component.saveButtonEnabled).toEqual(saveButtonEnabled); } diff --git a/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.ts b/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.ts index 944ef7ad822..81dc973f4e4 100644 --- a/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.ts +++ b/src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.ts @@ -4,6 +4,7 @@ import { NotificationService } from '../../services/notificationService'; import { TeacherProjectService } from '../../services/teacherProjectService'; import { NodeRecoveryAnalysis } from '../../../../app/domain/nodeRecoveryAnalysis'; import { isValidJSONString } from '../../common/string/string'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'recovery-authoring', @@ -13,17 +14,20 @@ import { isValidJSONString } from '../../common/string/string'; export class RecoveryAuthoringComponent implements OnInit { badNodes: NodeRecoveryAnalysis[] = []; protected globalMessage: any; - jsonIsValid: boolean; + jsonValid: boolean; projectJSONString: string; saveButtonEnabled: boolean = false; private subscriptions: Subscription = new Subscription(); + protected unitId: string; constructor( + private route: ActivatedRoute, private notificationService: NotificationService, private projectService: TeacherProjectService ) {} ngOnInit(): void { + this.unitId = this.route.snapshot.paramMap.get('unitId'); this.projectJSONString = JSON.stringify(this.projectService.project, null, 4); this.checkProjectJSONValidity(); this.subscribeToGlobalMessage(); @@ -36,14 +40,14 @@ export class RecoveryAuthoringComponent implements OnInit { projectJSONChanged(): void { this.checkProjectJSONValidity(); - this.saveButtonEnabled = this.jsonIsValid; - if (this.jsonIsValid) { + this.saveButtonEnabled = this.jsonValid; + if (this.jsonValid) { this.checkNodes(); } } private checkProjectJSONValidity(): void { - this.jsonIsValid = isValidJSONString(this.projectJSONString); + this.jsonValid = isValidJSONString(this.projectJSONString); } private subscribeToGlobalMessage(): void { diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.spec.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.spec.ts index ef585fb7c1a..3fcb0b5ef97 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.spec.ts +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.spec.ts @@ -29,7 +29,7 @@ let fixture: ComponentFixture; let teacherService: TeacherService; let snackBar: MatSnackBar; let dialog: MatDialog; -let snackBarSpy, dialogSpy; +let snackBarOpenSpy, dialogCloseAllSpy; describe('ChangeStudentPasswordDialogComponent', () => { beforeEach(async () => { @@ -60,8 +60,8 @@ describe('ChangeStudentPasswordDialogComponent', () => { function changePassword() { describe('changePassword()', () => { beforeEach(() => { - snackBarSpy = spyOn(snackBar, 'open'); - dialogSpy = spyOn(dialog, 'closeAll'); + snackBarOpenSpy = spyOn(snackBar, 'open'); + dialogCloseAllSpy = spyOn(dialog, 'closeAll'); }); afterEach(() => { expect(component.isChangingPassword).toBeFalsy(); @@ -75,8 +75,8 @@ function changePassword_success_closeDialog() { it('should close dialog on success', () => { spyOn(teacherService, 'changeStudentPassword').and.returnValue(of({})); component.changePassword(); - expect(snackBarSpy).toHaveBeenCalled(); - expect(dialogSpy).toHaveBeenCalled(); + expect(snackBarOpenSpy).toHaveBeenCalled(); + expect(dialogCloseAllSpy).toHaveBeenCalled(); }); } @@ -84,10 +84,10 @@ function changePassword_failure_keepDialogOpen() { it('should keep dialog open on failure', () => { spyOn(teacherService, 'changeStudentPassword').and.returnValue( throwError({ - error: { messageCode: 'invalidPasswordLength' } + error: { messageCode: 'invalidPassword' } }) ); component.changePassword(); - expect(dialogSpy).not.toHaveBeenCalled(); + expect(dialogCloseAllSpy).not.toHaveBeenCalled(); }); } diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts index cab30dcc0b5..a4d4719e1f1 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts @@ -5,6 +5,7 @@ import { ConfigService } from '../../../../services/configService'; import { TeacherService } from '../../../../../../app/teacher/teacher.service'; import { MatSnackBar } from '@angular/material/snack-bar'; import { NewPasswordAndConfirmComponent } from '../../../../../../app/password/new-password-and-confirm/new-password-and-confirm.component'; +import { changePasswordError } from '../../../../../../app/common/password-helper'; @Component({ selector: 'app-change-student-password-dialog', @@ -74,25 +75,7 @@ export class ChangeStudentPasswordDialogComponent implements OnInit { } private changePasswordError(error: any): void { - const formError: any = {}; this.isChangingPassword = false; - switch (error.messageCode) { - case 'incorrectPassword': - formError.incorrectPassword = true; - this.changePasswordForm.get('teacherPassword').setErrors(formError); - break; - case 'invalidPasswordLength': - formError.minlength = true; - this.changePasswordForm - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - case 'invalidPasswordPattern': - formError.pattern = true; - this.changePasswordForm - .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME) - .setErrors(formError); - break; - } + changePasswordError(error, this.changePasswordForm, this.changePasswordForm, 'teacherPassword'); } } diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/nodeProgress/node-progress-view/node-progress-view.component.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/nodeProgress/node-progress-view/node-progress-view.component.ts index 8befdf7d0b6..750a95e4598 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/nodeProgress/node-progress-view/node-progress-view.component.ts +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/nodeProgress/node-progress-view/node-progress-view.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { TeacherProjectService } from '../../../../services/teacherProjectService'; import { TeacherDataService } from '../../../../services/teacherDataService'; import { Subscription } from 'rxjs'; @@ -20,6 +20,7 @@ export class NodeProgressViewComponent implements OnInit { private subscriptions: Subscription = new Subscription(); constructor( + private changeDetectorRef: ChangeDetectorRef, private configService: ConfigService, private dialog: MatDialog, private projectService: TeacherProjectService, @@ -40,6 +41,10 @@ export class NodeProgressViewComponent implements OnInit { } } + ngAfterViewChecked(): void { + this.changeDetectorRef.detectChanges(); + } + private subscribeToCurrentNodeChanged(): void { this.subscriptions.add( this.dataService.currentNodeChanged$.subscribe(({ currentNode }) => { diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/node-info/node-info.component.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/node-info/node-info.component.ts index adc9a24d0a2..3d392db86dd 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/node-info/node-info.component.ts +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/node-info/node-info.component.ts @@ -6,6 +6,7 @@ import { ComponentTypeService } from '../../../../services/componentTypeService' import { TeacherDataService } from '../../../../services/teacherDataService'; import { TeacherProjectService } from '../../../../services/teacherProjectService'; import { ComponentFactory } from '../../../../common/ComponentFactory'; +import { isMatchingPeriods } from '../../../../common/period/period'; @Component({ selector: 'node-info', @@ -49,7 +50,7 @@ export class NodeInfoComponent { component.hasScoresSummary = this.summaryService.isScoresSummaryAvailableForComponentType( component.type ); - component.hasScoreAnnotation = this.annotationService.isThereAnyScoreAnnotation( + component.hasScoreAnnotation = this.hasScoreAnnotation( this.nodeId, component.id, this.periodId @@ -66,6 +67,16 @@ export class NodeInfoComponent { } } + private hasScoreAnnotation(nodeId: string, componentId: string, periodId: number): boolean { + return this.annotationService + .getAnnotationsByNodeIdComponentId(nodeId, componentId) + .some( + (annotation) => + isMatchingPeriods(annotation.periodId, periodId) && + ['score', 'autoScore'].includes(annotation.type) + ); + } + private componentHasCorrectAnswer(component: any): boolean { const service = this.componentServiceLookupService.getService(component.type); return service.componentHasCorrectAnswer(component); diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html index 94437c05a49..e5bc3e2dbcd 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html @@ -33,13 +33,11 @@

    diff --git a/src/assets/wise5/classroomMonitor/dataExport/strategies/PeerChatComponentDataExportStrategy.ts b/src/assets/wise5/classroomMonitor/dataExport/strategies/PeerChatComponentDataExportStrategy.ts index ded1ac3fc58..98684ae76c4 100644 --- a/src/assets/wise5/classroomMonitor/dataExport/strategies/PeerChatComponentDataExportStrategy.ts +++ b/src/assets/wise5/classroomMonitor/dataExport/strategies/PeerChatComponentDataExportStrategy.ts @@ -25,7 +25,6 @@ export class PeerChatComponentDataExportStrategy extends AbstractDataExportStrat 'Component Part Number', 'Step Title', 'Component Type', - 'Component Prompt', 'Response' ]; @@ -52,55 +51,61 @@ export class PeerChatComponentDataExportStrategy extends AbstractDataExportStrat private generateComponentHeaderRow(component: any, columnNameToNumber: any): string[] { const headerRow = this.defaultColumnNames.map((columnName: string) => columnName); const componentStates = this.teacherDataService.getComponentStatesByComponentId(component.id); - this.insertPrePromptColumnIfNecessary(headerRow, component); - this.insertDynamicPromptColumnIfNecessary(headerRow, componentStates); - this.insertPostPromptColumnIfNecessary(headerRow, component); - this.insertQuestionColumnsIfNecessary(headerRow, componentStates); + this.insertPromptColumns(headerRow, component); + this.insertQuestionColumns(headerRow, component, componentStates); this.populateColumnNameMappings(headerRow, columnNameToNumber); return headerRow; } - private insertPrePromptColumnIfNecessary(headerRow: string[], component: any): void { + private insertPromptColumns(headerRow: string[], component: any): void { + if (!this.hasDynamicPrompt(component)) { + this.insertBeforeResponseColumn(headerRow, 'Prompt'); + } if (this.hasPrePrompt(component)) { - headerRow.splice(headerRow.indexOf('Response'), 0, 'Pre Prompt'); + this.insertBeforeResponseColumn(headerRow, 'Pre Prompt'); + } + if (this.hasDynamicPrompt(component)) { + this.insertBeforeResponseColumn(headerRow, 'Dynamic Prompt'); + } + if (this.hasPostPrompt(component)) { + this.insertBeforeResponseColumn(headerRow, 'Post Prompt'); } } - private insertDynamicPromptColumnIfNecessary(headerRow: string[], componentStates: any[]): void { - if (this.hasDynamicPrompt(componentStates)) { - headerRow.splice(headerRow.indexOf('Response'), 0, 'Dynamic Prompt'); + private insertQuestionColumns(headerRow: string[], component: any, componentStates: any[]): void { + const maxQuestions = this.getMaxQuestionBankCount(componentStates); + if (maxQuestions > 0) { + for (let q = 0; q < maxQuestions; q++) { + this.insertBeforeResponseColumn(headerRow, `Question ${q + 1}`); + } + } + if (this.isClickToUseEnabled(component)) { + this.insertBeforeResponseColumn(headerRow, 'Question Used'); } } - private insertPostPromptColumnIfNecessary(headerRow: string[], component: any): void { - if (this.hasPostPrompt(component)) { - headerRow.splice(headerRow.indexOf('Response'), 0, 'Post Prompt'); - } + private insertBeforeResponseColumn(headerRow: string[], columnName: string): void { + headerRow.splice(headerRow.indexOf('Response'), 0, columnName); } private hasPrePrompt(component: any): boolean { - const prePrompt = component.dynamicPrompt?.prePrompt; - return prePrompt != null && prePrompt !== ''; + return this.hasDynamicPrompt(component) && this.hasValue(component.dynamicPrompt?.prePrompt); } - private hasDynamicPrompt(componentStates: any[]): boolean { - return componentStates.some( - (componentState: any) => componentState.studentData.dynamicPrompt?.prompt != null - ); + private hasDynamicPrompt(component: any): boolean { + return component.dynamicPrompt?.enabled; } private hasPostPrompt(component: any): boolean { - const postPrompt = component.dynamicPrompt?.postPrompt; - return postPrompt != null && postPrompt !== ''; + return this.hasDynamicPrompt(component) && this.hasValue(component.dynamicPrompt?.postPrompt); } - private insertQuestionColumnsIfNecessary(headerRow: string[], componentStates: any[]): void { - const maxQuestions = this.getMaxQuestionBankCount(componentStates); - if (maxQuestions > 0) { - for (let q = 0; q < maxQuestions; q++) { - headerRow.splice(headerRow.indexOf('Response'), 0, `Question ${q + 1}`); - } - } + private hasValue(value: any): boolean { + return value != null && value !== ''; + } + + private isClickToUseEnabled(component: any): boolean { + return component.questionBank?.clickToUseEnabled; } private getMaxQuestionBankCount(componentStates: any[]): number { @@ -218,7 +223,7 @@ export class PeerChatComponentDataExportStrategy extends AbstractDataExportStrat this.projectService.getNodePositionAndTitle(nodeId) ); this.setColumnValue(row, columnNameToNumber, 'Component Type', component.type); - this.setColumnValue(row, columnNameToNumber, 'Component Prompt', component.prompt); + this.setColumnValue(row, columnNameToNumber, 'Prompt', component.prompt); } private setStudentWork( @@ -239,25 +244,53 @@ export class PeerChatComponentDataExportStrategy extends AbstractDataExportStrat 'Client Timestamp', millisecondsToDateTime(componentState.clientSaveTime) ); - this.setColumnValue(row, columnNameToNumber, 'Pre Prompt', component.dynamicPrompt?.prePrompt); - this.setColumnValue( - row, - columnNameToNumber, - 'Dynamic Prompt', - componentState.studentData.dynamicPrompt?.prompt - ); - this.setColumnValue( - row, - columnNameToNumber, - 'Post Prompt', - component.dynamicPrompt?.postPrompt - ); + this.setDynamicPrompts(row, columnNameToNumber, component, componentState); if (componentState.studentData.questionBank != null) { this.setQuestions(row, columnNameToNumber, componentState); } + if (this.isClickToUseEnabled(component)) { + this.setColumnValue( + row, + columnNameToNumber, + 'Question Used', + this.getQuestionText(component, componentState.studentData.questionId) + ); + } this.setColumnValue(row, columnNameToNumber, 'Response', componentState.studentData.response); } + private setDynamicPrompts( + row: any, + columnNameToNumber: any, + component: any, + componentState: any + ): void { + if (this.hasPrePrompt(component)) { + this.setColumnValue( + row, + columnNameToNumber, + 'Pre Prompt', + component.dynamicPrompt?.prePrompt + ); + } + if (this.hasDynamicPrompt(component)) { + this.setColumnValue( + row, + columnNameToNumber, + 'Dynamic Prompt', + componentState.studentData.dynamicPrompt?.prompt + ); + } + if (this.hasPostPrompt(component)) { + this.setColumnValue( + row, + columnNameToNumber, + 'Post Prompt', + component.dynamicPrompt?.postPrompt + ); + } + } + private setQuestions(row: any[], columnNameToNumber: any, componentState: any): void { let questionCounter = 1; for (const questionBank of componentState.studentData.questionBank) { @@ -272,6 +305,17 @@ export class PeerChatComponentDataExportStrategy extends AbstractDataExportStrat } } + private getQuestionText(component: any, questionId: string): string { + for (const rule of component.questionBank.rules) { + for (const question of rule.questions) { + if (question.id === questionId) { + return question.text; + } + } + } + return null; + } + private setColumnValue( row: any[], columnNameToNumber: any, diff --git a/src/assets/wise5/classroomMonitor/student-grading/student-grading.component.ts b/src/assets/wise5/classroomMonitor/student-grading/student-grading.component.ts index 9941423e2f5..76a2775b166 100644 --- a/src/assets/wise5/classroomMonitor/student-grading/student-grading.component.ts +++ b/src/assets/wise5/classroomMonitor/student-grading/student-grading.component.ts @@ -70,8 +70,7 @@ export class StudentGradingComponent implements OnInit { this.maxScore = maxScore ? maxScore : 0; this.totalScore = this.dataService.getTotalScoreByWorkgroupId(this.workgroupId); this.projectCompletion = this.classroomStatusService.getStudentProjectCompletion( - this.workgroupId, - true + this.workgroupId ); this.nodeIds = this.projectService.getFlattenedProjectAsNodeIds(); this.setNodesById(); @@ -134,7 +133,7 @@ export class StudentGradingComponent implements OnInit { private subscribeToCurrentWorkgroupChanged(): void { this.subscriptions.add( this.dataService.currentWorkgroupChanged$ - .pipe(filter((workgroup) => workgroup != null)) + .pipe(filter(({ currentWorkgroup }) => currentWorkgroup != null)) .subscribe(({ currentWorkgroup }) => { const workgroupId = currentWorkgroup.workgroupId; if (this.workgroupId !== workgroupId) { diff --git a/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts b/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts index fd40e0937ad..bbcd180f005 100644 --- a/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts +++ b/src/assets/wise5/classroomMonitor/student-progress/student-progress.component.ts @@ -53,7 +53,9 @@ export class StudentProgressComponent implements OnInit { private initializeStudents(): void { this.teams = []; - const workgroups = this.configService.getClassmateUserInfos(); + const workgroups = this.configService + .getClassmateUserInfos() + .filter((workgroup: any) => workgroup.workgroupId != null); for (const workgroup of workgroups) { const workgroupId = workgroup.workgroupId; const displayNames = this.configService.getDisplayUsernamesByWorkgroupId(workgroupId); @@ -71,7 +73,7 @@ export class StudentProgressComponent implements OnInit { private updateTeam(workgroupId: number): void { const location = this.getCurrentNodeForWorkgroupId(workgroupId); - const completion = this.getStudentProjectCompletion(workgroupId); + const completion = this.classroomStatusService.getStudentProjectCompletion(workgroupId); const score = this.getStudentTotalScore(workgroupId); let maxScore = this.classroomStatusService.getMaxScoreForWorkgroupId(workgroupId); maxScore = maxScore ? maxScore : 0; @@ -93,15 +95,6 @@ export class StudentProgressComponent implements OnInit { ); } - /** - * Get project completion data for the given workgroup (only include nodes with student work) - * @param workgroupId the workgroup id - * @return object with completed, total, and percent completed (integer between 0 and 100) - */ - private getStudentProjectCompletion(workgroupId: number): any { - return this.classroomStatusService.getStudentProjectCompletion(workgroupId, true); - } - private getStudentTotalScore(workgroupId: number): number { return this.dataService.getTotalScoreByWorkgroupId(workgroupId); } diff --git a/src/assets/wise5/common/ProjectCompletion.ts b/src/assets/wise5/common/ProjectCompletion.ts new file mode 100644 index 00000000000..6f17890dd54 --- /dev/null +++ b/src/assets/wise5/common/ProjectCompletion.ts @@ -0,0 +1,5 @@ +export class ProjectCompletion { + completedItems: number; + completionPct: number; + totalItems: number; +} diff --git a/src/assets/wise5/common/constraint/strategies/AddXNumberOfNotesOnThisStepConstraintStrategy.ts b/src/assets/wise5/common/constraint/strategies/AddXNumberOfNotesOnThisStepConstraintStrategy.ts index 66bc5e2fb5a..16c30f9344e 100644 --- a/src/assets/wise5/common/constraint/strategies/AddXNumberOfNotesOnThisStepConstraintStrategy.ts +++ b/src/assets/wise5/common/constraint/strategies/AddXNumberOfNotesOnThisStepConstraintStrategy.ts @@ -3,7 +3,7 @@ import { AbstractConstraintStrategy } from './AbstractConstraintStrategy'; export class AddXNumberOfNotesOnThisStepConstraintStrategy extends AbstractConstraintStrategy { evaluate(criteria: any): boolean { try { - const notebookItemsByNodeId = this.dataService.getNotebookItemsByNodeId( + const notebookItemsByNodeId = this.getNotebookItemsByNodeId( this.notebookService.getNotebookByWorkgroup(), criteria.params.nodeId ); @@ -11,4 +11,8 @@ export class AddXNumberOfNotesOnThisStepConstraintStrategy extends AbstractConst } catch (e) {} return false; } + + private getNotebookItemsByNodeId(notebook: any, nodeId: string): any[] { + return notebook.allItems.filter((item) => item.nodeId === nodeId); + } } diff --git a/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.html b/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.html index 0bc605789df..0086ecf9a9a 100644 --- a/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.html +++ b/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.html @@ -5,8 +5,8 @@ [response]="componentState" [numReplies]="componentState.replies.length" [mode]="'grading'" - (deleteButtonClicked)="deleteButtonClicked($event)" - (undoDeleteButtonClicked)="undoDeleteButtonClicked($event)" + (deleteButtonClicked)="hidePost($event)" + (undoDeleteButtonClicked)="showPost($event)" [isDisabled]="true" class="post" > diff --git a/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.ts b/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.ts index 80fcd6b7cd5..d311c5f3e87 100644 --- a/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.ts +++ b/src/assets/wise5/components/discussion/discussion-show-work/discussion-show-work.component.ts @@ -107,37 +107,8 @@ export class DiscussionShowWorkComponent extends ComponentShowWorkDirective { * the class from seeing the post. * @param componentState the student component state the teacher wants to delete. */ - deleteButtonClicked(componentState: any): void { - const toWorkgroupId = componentState.workgroupId; - const userInfo = this.ConfigService.getUserInfoByWorkgroupId(toWorkgroupId); - const periodId = userInfo.periodId; - const teacherUserInfo = this.ConfigService.getMyUserInfo(); - const fromWorkgroupId = teacherUserInfo.workgroupId; - const runId = this.ConfigService.getRunId(); - const nodeId = this.nodeId; - const componentId = this.componentId; - const studentWorkId = componentState.id; - const data = { - action: 'Delete' - }; - const annotation = this.AnnotationService.createInappropriateFlagAnnotation( - runId, - periodId, - nodeId, - componentId, - fromWorkgroupId, - toWorkgroupId, - studentWorkId, - data - ); - this.AnnotationService.saveAnnotation(annotation).then(() => { - const componentStates = this.TeacherDiscussionService.getPostsAssociatedWithComponentIdsAndWorkgroupId( - this.getGradingComponentIds(), - this.workgroupId - ); - const annotations = this.getInappropriateFlagAnnotationsByComponentStates(componentStates); - this.setClassResponses(componentStates, annotations); - }); + protected hidePost(componentState: any): void { + this.flagPost(componentState, 'Delete'); } /** @@ -146,7 +117,11 @@ export class DiscussionShowWorkComponent extends ComponentShowWorkDirective { * This will make the post visible to the students. * @param componentState the student component state the teacher wants to show again. */ - undoDeleteButtonClicked(componentState: any): any { + protected showPost(componentState: any): void { + this.flagPost(componentState, 'Undo Delete'); + } + + private flagPost(componentState: any, action: 'Delete' | 'Undo Delete'): void { const toWorkgroupId = componentState.workgroupId; const userInfo = this.ConfigService.getUserInfoByWorkgroupId(toWorkgroupId); const periodId = userInfo.periodId; @@ -157,7 +132,7 @@ export class DiscussionShowWorkComponent extends ComponentShowWorkDirective { const componentId = this.componentId; const studentWorkId = componentState.id; const data = { - action: 'Undo Delete' + action: action }; const annotation = this.AnnotationService.createInappropriateFlagAnnotation( runId, diff --git a/src/assets/wise5/components/draw/drawService.ts b/src/assets/wise5/components/draw/drawService.ts index 270221be4d6..534e2222063 100644 --- a/src/assets/wise5/components/draw/drawService.ts +++ b/src/assets/wise5/components/draw/drawService.ts @@ -5,7 +5,7 @@ import * as fabric from 'fabric'; window['fabric'] = fabric.fabric; import * as EventEmitter2 from 'eventemitter2'; window['EventEmitter2'] = EventEmitter2; -import DrawingTool from 'drawing-tool/dist/drawing-tool'; +import DrawingTool from '@wise-community/drawing-tool/dist/drawing-tool'; import { ComponentService } from '../componentService'; import { StudentAssetService } from '../../services/studentAssetService'; import { Injectable } from '@angular/core'; diff --git a/src/assets/wise5/services/annotationService.ts b/src/assets/wise5/services/annotationService.ts index d14691f6b0b..06987e54ea7 100644 --- a/src/assets/wise5/services/annotationService.ts +++ b/src/assets/wise5/services/annotationService.ts @@ -5,7 +5,6 @@ import { ProjectService } from './projectService'; import { ConfigService } from './configService'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, Subject } from 'rxjs'; -import { isMatchingPeriods } from '../common/period/period'; import { generateRandomKey } from '../common/string/string'; import { Annotation } from '../common/Annotation'; @@ -28,17 +27,14 @@ export class AnnotationService { return this.annotations; } - /** - * Get the annotation with the specified id, or null if not found - * @param annotationId - */ - getAnnotationById(annotationId) { - for (let annotation of this.annotations) { - if (annotation.id === annotationId) { - return annotation; - } - } - return null; + getAnnotationById(annotationId: number): Annotation { + return this.annotations.find((annotation) => annotation.id === annotationId) || null; + } + + getAnnotationsByNodeIdComponentId(nodeId: string, componentId: string): Annotation[] { + return this.annotations.filter( + (annotation) => annotation.nodeId === nodeId && annotation.componentId === componentId + ); } /** @@ -440,28 +436,15 @@ export class AnnotationService { * @return object containing the component's latest score and comment annotations */ getLatestComponentAnnotations( - nodeId, - componentId, - workgroupId, + nodeId: string, + componentId: string, + workgroupId: number, scoreType: 'score' | 'autoScore' | 'any' = 'any', - commentType = null - ) { - let latestScoreAnnotation = this.getLatestScoreAnnotation( - nodeId, - componentId, - workgroupId, - scoreType - ); - let latestCommentAnnotation = this.getLatestCommentAnnotation( - nodeId, - componentId, - workgroupId, - commentType - ); - + commentType: 'comment' | 'autoComment' | 'any' = 'any' + ): any { return { - score: latestScoreAnnotation, - comment: latestCommentAnnotation + score: this.getLatestScoreAnnotation(nodeId, componentId, workgroupId, scoreType), + comment: this.getLatestCommentAnnotation(nodeId, componentId, workgroupId, commentType) }; } @@ -537,13 +520,10 @@ export class AnnotationService { scoreType: 'score' | 'autoScore' | 'any' = 'any' ): Annotation { return ( - this.getAnnotations() + this.getAnnotationsByNodeIdComponentId(nodeId, componentId) .filter( (annotation) => - annotation.nodeId == nodeId && - annotation.componentId == componentId && - annotation.toWorkgroupId == workgroupId && - this.matchesScoreType(annotation, scoreType) + annotation.toWorkgroupId == workgroupId && this.matchesScoreType(annotation, scoreType) ) .at(-1) || null ); @@ -559,74 +539,31 @@ export class AnnotationService { ); } - isThereAnyScoreAnnotation(nodeId, componentId, periodId) { - const annotations = this.getAnnotations(); - for (const annotation of annotations) { - if ( - annotation.nodeId === nodeId && - annotation.componentId === componentId && - isMatchingPeriods(annotation.periodId, periodId) && - this.isScoreOrAutoScore(annotation) - ) { - return true; - } - } - return false; + getLatestCommentAnnotation( + nodeId: string, + componentId: string, + workgroupId: number, + commentType: 'comment' | 'autoComment' | 'any' = 'any' + ): Annotation { + return ( + this.getAnnotationsByNodeIdComponentId(nodeId, componentId) + .filter( + (annotation) => + annotation.toWorkgroupId == workgroupId && + this.matchesCommentType(annotation, commentType) + ) + .at(-1) || null + ); } - /** - * Get the latest comment annotation - * @param nodeId the node id - * @param componentId the component id - * @param workgroupId the workgroup id - * @param commentType (optional) the type of comment - * e.g. - * 'autoComment' for auto graded comment - * 'comment' for teacher graded comment - * 'any' for auto graded comment or teacher graded comment - * @returns the latest comment annotation - */ - getLatestCommentAnnotation(nodeId, componentId, workgroupId, commentType) { - let annotation = null; - const annotations = this.getAnnotations(); - - if (commentType == null) { - commentType = 'any'; - } - - for (let a = annotations.length - 1; a >= 0; a--) { - const tempAnnotation = annotations[a]; - if (tempAnnotation != null) { - let acceptAnnotation = false; - const tempNodeId = tempAnnotation.nodeId; - const tempComponentId = tempAnnotation.componentId; - const tempToWorkgroupId = tempAnnotation.toWorkgroupId; - const tempAnnotationType = tempAnnotation.type; - - if ( - nodeId == tempNodeId && - componentId == tempComponentId && - workgroupId == tempToWorkgroupId - ) { - if ( - commentType === 'any' && - (tempAnnotationType === 'autoComment' || tempAnnotationType === 'comment') - ) { - acceptAnnotation = true; - } else if (commentType === 'autoComment' && tempAnnotationType === 'autoComment') { - acceptAnnotation = true; - } else if (commentType === 'comment' && tempAnnotationType === 'comment') { - acceptAnnotation = true; - } - - if (acceptAnnotation) { - annotation = tempAnnotation; - break; - } - } - } - } - return annotation; + private matchesCommentType( + annotation: Annotation, + commentType: 'comment' | 'autoComment' | 'any' + ): boolean { + return ( + (commentType === 'any' && ['autoComment', 'comment'].includes(annotation.type)) || + annotation.type === commentType + ); } getScoreValueFromScoreAnnotation(scoreAnnotation: any): number { @@ -684,21 +621,14 @@ export class AnnotationService { * @param type the type of annotation * @return the latest annotation for the given student work and annotation type */ - getLatestAnnotationByStudentWorkIdAndType(studentWorkId, type) { - for (let a = this.annotations.length - 1; a >= 0; a--) { - const annotation = this.annotations[a]; - - if (annotation != null) { - if (studentWorkId == annotation.studentWorkId && type == annotation.type) { - /* - * we have found an annotation with the given student work - * id and annotation type - */ - return annotation; - } - } - } - return null; + getLatestAnnotationByStudentWorkIdAndType(studentWorkId: number, type: string): Annotation { + return ( + this.annotations + .filter( + (annotation) => annotation.studentWorkId === studentWorkId && annotation.type === type + ) + .at(-1) || null + ); } /** @@ -706,71 +636,8 @@ export class AnnotationService { * @param studentWorkId the student work id * @return array of annotations for the given student work */ - getAnnotationsByStudentWorkId(studentWorkId) { - let annotations = []; - for (let annotation of this.annotations) { - if (annotation && studentWorkId == annotation.studentWorkId) { - annotations.push(annotation); - } - } - return annotations; - } - - getAverageAutoScore(nodeId, componentId, periodId = -1, type = null) { - let totalScoreSoFar = 0; - let annotationsCounted = 0; - for (let annotation of this.getAllLatestScoreAnnotations(nodeId, componentId, periodId)) { - if ( - annotation.nodeId === nodeId && - annotation.componentId === componentId && - (periodId === -1 || annotation.periodId === periodId) - ) { - let score = null; - if (type != null) { - score = this.getSubScore(annotation, type); - } else { - score = this.getScoreFromAnnotation(annotation); - } - if (score != null) { - totalScoreSoFar += score; - annotationsCounted++; - } - } - } - return totalScoreSoFar / annotationsCounted; - } - - getAllLatestScoreAnnotations(nodeId, componentId, periodId) { - const workgroupIdsFound = {}; - const latestScoreAnnotations = []; - for (let a = this.annotations.length - 1; a >= 0; a--) { - const annotation = this.annotations[a]; - const workgroupId = annotation.toWorkgroupId; - if ( - workgroupIdsFound[workgroupId] == null && - nodeId === annotation.nodeId && - componentId === annotation.componentId && - (periodId === -1 || periodId === annotation.periodId) && - ('score' === annotation.type || 'autoScore' === annotation.type) - ) { - workgroupIdsFound[workgroupId] = annotation; - latestScoreAnnotations.push(annotation); - } - } - return latestScoreAnnotations; - } - - getScoreFromAnnotation(annotation) { - return annotation.data.value; - } - - getSubScore(annotation, type) { - for (let score of annotation.data.scores) { - if (score.id === type) { - return score.score; - } - } - return null; + getAnnotationsByStudentWorkId(studentWorkId: number): Annotation[] { + return this.annotations.filter((annotation) => annotation.studentWorkId === studentWorkId); } broadcastAnnotationSavedToServer(annotation: Annotation): void { diff --git a/src/assets/wise5/services/classroomStatusService.ts b/src/assets/wise5/services/classroomStatusService.ts index 186462e2544..8d7a6eba37c 100644 --- a/src/assets/wise5/services/classroomStatusService.ts +++ b/src/assets/wise5/services/classroomStatusService.ts @@ -4,6 +4,8 @@ import { AnnotationService } from './annotationService'; import { ConfigService } from './configService'; import { ProjectService } from './projectService'; import { Observable, Subject, tap } from 'rxjs'; +import { NodeProgress } from '../common/NodeProgress'; +import { ProjectCompletion } from '../common/ProjectCompletion'; @Injectable() export class ClassroomStatusService { @@ -64,16 +66,11 @@ export class ClassroomStatusService { } getStudentStatusForWorkgroupId(workgroupId: number): any { - const studentStatuses = this.getStudentStatuses(); - for (let tempStudentStatus of studentStatuses) { - if (tempStudentStatus != null) { - const tempWorkgroupId = tempStudentStatus.workgroupId; - if (workgroupId === tempWorkgroupId) { - return tempStudentStatus; - } - } - } - return null; + return ( + this.getStudentStatuses().find( + (studentStatus) => studentStatus.workgroupId === workgroupId + ) || null + ); } hasStudentStatus(workgroupId: number): boolean { @@ -96,79 +93,25 @@ export class ClassroomStatusService { } } - /** - * Get the student project completion data by workgroup id - * @param workgroupId the workgroup id - * @param excludeNonWorkNodes boolean whether to exclude nodes without - * @returns object with completed, total, and percent completed (integer - * between 0 and 100) - */ - getStudentProjectCompletion(workgroupId, excludeNonWorkNodes) { - let completion = { - totalItems: 0, - completedItems: 0, - completionPct: 0 - }; - - // get the student status for the workgroup - let studentStatus = this.getStudentStatusForWorkgroupId(workgroupId); - + getStudentProjectCompletion(workgroupId: number): ProjectCompletion { + let completion: ProjectCompletion = new ProjectCompletion(); + const studentStatus = this.getStudentStatusForWorkgroupId(workgroupId); if (studentStatus) { - let projectCompletion = studentStatus.projectCompletion; - - if (projectCompletion) { - if (excludeNonWorkNodes) { - // we're only looking for completion of nodes with work - let completionPctWithWork = projectCompletion.completionPctWithWork; - - if (completionPctWithWork) { - completion.totalItems = projectCompletion.totalItemsWithWork; - completion.completedItems = projectCompletion.completedItemsWithWork; - completion.completionPct = projectCompletion.completionPctWithWork; - } else { - /* - * we have a legacy projectCompletion object that only includes information for all nodes - * so we need to calculate manually - */ - completion = this.getNodeCompletion('group0', -1, workgroupId, true); - } - } else { - completion = projectCompletion; - } + const projectCompletion = studentStatus.projectCompletion; + const completionPctWithWork = projectCompletion.completionPctWithWork; + if (completionPctWithWork) { + completion.totalItems = projectCompletion.totalItemsWithWork; + completion.completedItems = projectCompletion.completedItemsWithWork; + completion.completionPct = projectCompletion.completionPctWithWork; + } else { + // we have a legacy projectCompletion object that only includes information for all nodes so + // we need to calculate completion manually + completion = this.getNodeCompletion('group0', -1, workgroupId, true); } } return completion; } - /** - * Get the workgroups on a node in the given period - * @param nodeId the node id - * @param periodId the period id. pass in -1 to select all periods. - * @returns an array of workgroup ids on a node in a period - */ - getWorkgroupIdsOnNode(nodeId, periodId) { - let workgroupIds = []; - let studentStatuses = this.studentStatuses; - for (let studentStatus of studentStatuses) { - if (studentStatus != null) { - if (periodId === -1 || periodId === studentStatus.periodId) { - let currentNodeId = studentStatus.currentNodeId; - if (nodeId === currentNodeId) { - workgroupIds.push(studentStatus.workgroupId); - } else if (this.projectService.isGroupNode(nodeId)) { - let currentNode = this.projectService.getNodeById(currentNodeId); - let group = this.projectService.getNodeById(nodeId); - - if (this.projectService.isNodeDescendentOfGroup(currentNode, group)) { - workgroupIds.push(studentStatus.workgroupId); - } - } - } - } - } - return workgroupIds; - } - /** * Get node completion info for the given parameters * @param nodeId the node id diff --git a/src/assets/wise5/services/milestoneReportService.ts b/src/assets/wise5/services/milestoneReportService.ts index 041c129ef53..56ebe394454 100644 --- a/src/assets/wise5/services/milestoneReportService.ts +++ b/src/assets/wise5/services/milestoneReportService.ts @@ -2,6 +2,8 @@ import { Injectable } from '@angular/core'; import { AnnotationService } from './annotationService'; import { ProjectService } from './projectService'; import { MilestoneCriteriaEvaluator } from '../classroomMonitor/milestones/milestoneCriteriaEvaluator'; +import { Annotation } from '../common/Annotation'; +import { isMatchingPeriods } from '../common/period/period'; @Injectable() export class MilestoneReportService { @@ -72,11 +74,7 @@ export class MilestoneReportService { reportSettings: any ) { const aggregate = {}; - const scoreAnnotations = this.annotationService.getAllLatestScoreAnnotations( - nodeId, - componentId, - periodId - ); + const scoreAnnotations = this.getAllLatestScoreAnnotations(nodeId, componentId, periodId); for (const scoreAnnotation of scoreAnnotations) { if (scoreAnnotation.type === 'autoScore') { this.addDataToAggregate(aggregate, scoreAnnotation, reportSettings); @@ -100,6 +98,29 @@ export class MilestoneReportService { return aggregate; } + private getAllLatestScoreAnnotations( + nodeId: string, + componentId: string, + periodId: number + ): Annotation[] { + return this.annotationService + .getAnnotationsByNodeIdComponentId(nodeId, componentId) + .filter( + (annotation) => + isMatchingPeriods(annotation.periodId, periodId) && + ['autoScore', 'score'].includes(annotation.type) + ) + .reduceRight( + (latestAnnotations, annotation) => + latestAnnotations.some( + (latestAnnotation) => latestAnnotation.toWorkgroupId === annotation.toWorkgroupId + ) + ? latestAnnotations + : latestAnnotations.concat(annotation), + [] + ); + } + private mergeAutoScoreAndTeacherScore( autoScoreAnnotation: any, teacherScoreAnnotation: any, diff --git a/src/assets/wise5/services/nodeService.ts b/src/assets/wise5/services/nodeService.ts index 477521006c7..ab6dacc0a50 100644 --- a/src/assets/wise5/services/nodeService.ts +++ b/src/assets/wise5/services/nodeService.ts @@ -88,33 +88,18 @@ export class NodeService { * Get the previous node in the project sequence * @param currentId (optional) */ - getPrevNodeId(currentId?) { + getPrevNodeId(currentId?: string): string { let prevNodeId = null; - let currentNodeId = null; - const mode = this.ConfigService.getMode(); - if (currentId) { - currentNodeId = currentId; - } else { - let currentNode = null; - currentNode = this.DataService.getCurrentNode(); - if (currentNode) { - currentNodeId = currentNode.id; - } - } + const currentNodeId = currentId ?? this.DataService.getCurrentNodeId(); if (currentNodeId) { - if (['classroomMonitor', 'author'].includes(mode)) { - let currentNodeOrder = this.ProjectService.getNodeOrderById(currentNodeId); + if (['author', 'classroomMonitor'].includes(this.ConfigService.getMode())) { + const currentNodeOrder = this.ProjectService.getNodeOrderById(currentNodeId); if (currentNodeOrder) { - let prevNodeOrder = currentNodeOrder - 1; - let prevId = this.ProjectService.getNodeIdByOrder(prevNodeOrder); + const prevId = this.ProjectService.getNodeIdByOrder(currentNodeOrder - 1); if (prevId) { - if (this.ProjectService.isApplicationNode(prevId)) { - // node is a step, so set it as the next node - prevNodeId = prevId; - } else if (this.ProjectService.isGroupNode(prevId)) { - // node is an activity, so get next nodeId - prevNodeId = this.getPrevNodeId(prevId); - } + prevNodeId = this.ProjectService.isApplicationNode(prevId) + ? prevId + : this.getPrevNodeId(prevId); } } } else { @@ -312,17 +297,6 @@ export class NodeService { return availableTransitions; } - currentNodeHasTransitionLogic() { - const currentNode: any = this.DataService.getCurrentNode(); - if (currentNode != null) { - const transitionLogic = currentNode.transitionLogic; - if (transitionLogic != null) { - return true; - } - } - return false; - } - /** * Evaluate the transition logic for the current node and create branch * path taken events if necessary. diff --git a/src/assets/wise5/services/projectService.ts b/src/assets/wise5/services/projectService.ts index 730e034a63d..9628776a98f 100644 --- a/src/assets/wise5/services/projectService.ts +++ b/src/assets/wise5/services/projectService.ts @@ -772,25 +772,31 @@ export class ProjectService { * Retrieves the project JSON from Config.projectURL and returns it. * If Config.projectURL is undefined, returns null. */ - retrieveProject(parseProject: boolean = true): any { - let projectURL = this.configService.getConfigParam('projectURL'); - if (projectURL == null) { - return null; - } - const headers = new HttpHeaders().set('cache-control', 'no-cache'); - return this.http.get(projectURL, { headers: headers }).pipe( + retrieveProject(): Observable { + return this.makeProjectRequest().pipe( tap((projectJSON: any) => { - if (parseProject) { - this.setProject(projectJSON); - } else { - this.project = projectJSON; - this.metadata = projectJSON.metadata; - } + this.setProject(projectJSON); + return projectJSON; + }) + ); + } + + retrieveProjectWithoutParsing(): Observable { + return this.makeProjectRequest().pipe( + tap((projectJSON: any) => { + this.project = projectJSON; + this.metadata = projectJSON.metadata; return projectJSON; }) ); } + private makeProjectRequest(): Observable { + const projectURL = this.configService.getConfigParam('projectURL'); + const headers = new HttpHeaders().set('cache-control', 'no-cache'); + return this.http.get(projectURL, { headers: headers }); + } + getThemePath(): string { return this.getDefaultThemePath(); } diff --git a/src/assets/wise5/services/studentDataService.ts b/src/assets/wise5/services/studentDataService.ts index bdc6ff0711a..45c73c52230 100644 --- a/src/assets/wise5/services/studentDataService.ts +++ b/src/assets/wise5/services/studentDataService.ts @@ -185,16 +185,6 @@ export class StudentDataService extends DataService { ); } - getNotebookItemsByNodeId(notebook, nodeId) { - const notebookItemsByNodeId = []; - for (const notebookItem of notebook.allItems) { - if (notebookItem.nodeId === nodeId) { - notebookItemsByNodeId.push(notebookItem); - } - } - return notebookItemsByNodeId; - } - populateHistories(events) { this.stackHistory = []; for (const event of events) { @@ -551,63 +541,29 @@ export class StudentDataService extends DataService { return null; } - getStudentWorkByStudentWorkId(studentWorkId) { - for (const componentState of this.studentData.componentStates) { - if (componentState.id === studentWorkId) { - return componentState; - } - } - return null; - } - - getComponentStates() { + getComponentStates(): any[] { return this.studentData.componentStates; } - getComponentStatesByNodeId(nodeId) { - const componentStatesByNodeId = []; - for (const componentState of this.studentData.componentStates) { - if (componentState.nodeId === nodeId) { - componentStatesByNodeId.push(componentState); - } - } - return componentStatesByNodeId; + getComponentStatesByNodeId(nodeId: string): any[] { + return this.studentData.componentStates.filter( + (componentState) => componentState.nodeId === nodeId + ); } - getComponentStatesByNodeIdAndComponentId(nodeId, componentId) { - const componentStatesByNodeIdAndComponentId = []; - for (const componentState of this.studentData.componentStates) { - if (componentState.nodeId === nodeId && componentState.componentId === componentId) { - componentStatesByNodeIdAndComponentId.push(componentState); - } - } - return componentStatesByNodeIdAndComponentId; + getComponentStatesByNodeIdAndComponentId(nodeId: string, componentId: string): any[] { + return this.studentData.componentStates.filter( + (componentState) => + componentState.nodeId === nodeId && componentState.componentId === componentId + ); } - getEvents() { + getEvents(): any[] { return this.studentData.events; } - getEventsByNodeId(nodeId) { - const eventsByNodeId = []; - const events = this.studentData.events; - for (const event of events) { - if (event.nodeId === nodeId) { - eventsByNodeId.push(event); - } - } - return eventsByNodeId; - } - - getEventsByNodeIdAndComponentId(nodeId, componentId) { - const eventsByNodeId = []; - const events = this.studentData.events; - for (const event of events) { - if (event.nodeId === nodeId && event.componentId === componentId) { - eventsByNodeId.push(event); - } - } - return eventsByNodeId; + getEventsByNodeId(nodeId: string): any[] { + return this.studentData.events.filter((event) => event.nodeId === nodeId); } /** diff --git a/src/assets/wise5/vle/node/node.component.ts b/src/assets/wise5/vle/node/node.component.ts index 3692d6ee756..34db370d5f0 100644 --- a/src/assets/wise5/vle/node/node.component.ts +++ b/src/assets/wise5/vle/node/node.component.ts @@ -151,10 +151,7 @@ export class NodeComponent implements OnInit { this.dirtySubmitComponentIds = []; this.updateComponentVisibility(); - if ( - this.nodeService.currentNodeHasTransitionLogic() && - this.nodeService.evaluateTransitionLogicOn('enterNode') - ) { + if (this.nodeService.evaluateTransitionLogicOn('enterNode')) { this.nodeService.evaluateTransitionLogic(); } @@ -204,10 +201,7 @@ export class NodeComponent implements OnInit { ngOnDestroy() { this.stopAutoSaveInterval(); this.nodeUnloaded(this.node.id); - if ( - this.nodeService.currentNodeHasTransitionLogic() && - this.nodeService.evaluateTransitionLogicOn('exitNode') - ) { + if (this.nodeService.evaluateTransitionLogicOn('exitNode')) { this.nodeService.evaluateTransitionLogic(); } this.subscriptions.unsubscribe(); @@ -286,23 +280,21 @@ export class NodeComponent implements OnInit { .saveToServer(componentStates, componentEvents, componentAnnotations) .then((savedStudentDataResponse) => { if (savedStudentDataResponse) { - if (this.nodeService.currentNodeHasTransitionLogic()) { - if (this.nodeService.evaluateTransitionLogicOn('studentDataChanged')) { - this.nodeService.evaluateTransitionLogic(); - } - if (this.nodeService.evaluateTransitionLogicOn('scoreChanged')) { - if (componentAnnotations != null && componentAnnotations.length > 0) { - let evaluateTransitionLogic = false; - for (const componentAnnotation of componentAnnotations) { - if (componentAnnotation != null) { - if (componentAnnotation.type === 'autoScore') { - evaluateTransitionLogic = true; - } + if (this.nodeService.evaluateTransitionLogicOn('studentDataChanged')) { + this.nodeService.evaluateTransitionLogic(); + } + if (this.nodeService.evaluateTransitionLogicOn('scoreChanged')) { + if (componentAnnotations != null && componentAnnotations.length > 0) { + let evaluateTransitionLogic = false; + for (const componentAnnotation of componentAnnotations) { + if (componentAnnotation != null) { + if (componentAnnotation.type === 'autoScore') { + evaluateTransitionLogic = true; } } - if (evaluateTransitionLogic) { - this.nodeService.evaluateTransitionLogic(); - } + } + if (evaluateTransitionLogic) { + this.nodeService.evaluateTransitionLogic(); } } } diff --git a/src/messages.xlf b/src/messages.xlf index 7e8d97075fa..a7b3e43fbef 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -475,7 +475,7 @@ src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html - 135 + 131 src/assets/wise5/authoringTool/peer-grouping/create-new-peer-grouping-dialog/create-new-peer-grouping-dialog.component.html @@ -717,7 +717,7 @@ src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 52 + 51 src/assets/wise5/components/animation/animation-authoring/animation-authoring.component.html @@ -905,7 +905,7 @@ src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 30 + 18 src/assets/wise5/authoringTool/node/advanced/node-advanced-authoring/node-advanced-authoring.component.html @@ -1147,7 +1147,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html - 38 + 39 src/assets/wise5/components/common/feedbackRule/edit-feedback-rules/edit-feedback-rules.component.html @@ -1166,7 +1166,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 161 + 160 src/assets/wise5/components/common/feedbackRule/edit-feedback-rules/edit-feedback-rules.component.html @@ -1454,16 +1454,12 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.8 - src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 74 - - - src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 36 + src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html + 31 src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 39 + 37 @@ -1476,21 +1472,13 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html 26 - - src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html - 28 - src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html 85 - - src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html - 87 - src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 34 + 33 @@ -1513,7 +1501,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/addNode/choose-automated-assessment/choose-automated-assessment.component.html - 40 + 38 src/assets/wise5/authoringTool/addNode/choose-simulation/choose-simulation.component.html @@ -1861,11 +1849,11 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.ts - 100 + 92 src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts - 157 + 148 src/assets/wise5/authoringTool/peer-grouping/author-peer-grouping-dialog/author-peer-grouping-dialog.component.ts @@ -3249,7 +3237,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.The verification code has expired. Verification codes are valid for 10 minutes. Please go back to the Teacher Forgot Password page to generate a new one. src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts - 138 + 129 src/app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.ts @@ -3260,7 +3248,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.The verification code is invalid. Please try again. src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts - 143 + 134 src/app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.ts @@ -3271,7 +3259,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.You have submitted an invalid verification code too many times. For security reasons, we will lock the ability to change your password for 10 minutes. After 10 minutes, please go back to the Teacher Forgot Password page to generate a new verification code. src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts - 148 + 139 src/app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.ts @@ -3282,7 +3270,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Passwords do not match, please try again. src/app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.ts - 153 + 144 @@ -3376,346 +3364,331 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. Getting Started - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 3 - src/app/help/help-home/help-home.component.html - 18 + src/app/help/faq/student-faq/student-faq.component.html + 132 - src/app/help/student-faq/student-faq.component.html - 130 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 407 - src/app/help/teacher-faq/teacher-faq.component.html - 410 + src/app/help/help-home/help-home.component.html + 18 Creating an Account - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 6 All users must create their own account. Teachers must create a teacher account and students must create a student account. Users can create an account by going to the WISE Home Page and clicking on the Register link at the upper right of the screen. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 7,13 Here are instructions on how to create a teacher account. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 14 Go to the WISE Home Page. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 16 Click the Register link at the upper right of the screen. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 17,20 Click on Teacher. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 21 Choose to sign up with your email or with your Google Account. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 22 - src/app/help/student-faq/student-faq.component.html - 19 + src/app/help/faq/student-faq/student-faq.component.html + 21 - src/app/help/teacher-faq/teacher-faq.component.html - 36 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 33 Fill out the form and submit it. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 23 - src/app/help/student-faq/student-faq.component.html - 20 + src/app/help/faq/student-faq/student-faq.component.html + 22 - src/app/help/teacher-faq/teacher-faq.component.html - 37 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 34 If you created an account using your email, you will be given a username that you will need to remember. Your username will be your first name and your last name with no space inbetween. There may be a number added at the end of your username if someone has the same name as you. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 24,28 - src/app/help/student-faq/student-faq.component.html - 21,25 + src/app/help/faq/student-faq/student-faq.component.html + 23,27 - src/app/help/teacher-faq/teacher-faq.component.html - 38,42 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 35,39 Using a WISE Unit - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 31 To use a WISE unit with your class, you must first choose a unit to use and then set up a "Run" of that unit. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 32,35 Here are instructions on how to set up a Run. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 36 Sign in to WISE with your teacher account. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 38 - src/app/help/teacher-faq/teacher-faq.component.html - 46 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 43 Click the "Unit Library" tab. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 39 - src/app/help/teacher-faq/teacher-faq.component.html - 47 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 44 - src/app/help/teacher-faq/teacher-faq.component.html - 204 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 201 Find a project that you would like to use with your class. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 40 - src/app/help/teacher-faq/teacher-faq.component.html - 48 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 45 Click on the project to open the information for the project. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 41 - src/app/help/teacher-faq/teacher-faq.component.html - 49 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 46 Click the "Use With Class" button. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 42 - src/app/help/teacher-faq/teacher-faq.component.html - 50 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 47 Choose the periods you will use the project in. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 43 - src/app/help/teacher-faq/teacher-faq.component.html - 51 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 48 Choose the number of students per team. Choosing 1-3 will allow students to work together in a team. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 44,47 - src/app/help/teacher-faq/teacher-faq.component.html - 52,55 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 49,52 Choose the start date. Students will not be able to use the project until this date. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 48,50 - src/app/help/teacher-faq/teacher-faq.component.html - 56,58 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 53,55 Click "Create Run". - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 51 - src/app/help/teacher-faq/teacher-faq.component.html - 59 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 56 The run will be created and added to your Teacher Home. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 52 - src/app/help/teacher-faq/teacher-faq.component.html - 60 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 57 Copy the "Access Code" for the new run and write it down somewhere like on the white board. Students will need to use this access code to work on the run you created. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 53,56 - src/app/help/teacher-faq/teacher-faq.component.html - 61,64 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 58,61 Teacher FAQ - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 76 - src/app/help/help-home/help-home.component.html - 29 + src/app/help/faq/student-faq/student-faq.component.html + 143 - src/app/help/student-faq/student-faq.component.html - 141 + src/app/help/help-home/help-home.component.html + 29 Questions from teachers. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 78 - src/app/help/help-home/help-home.component.html - 31 + src/app/help/faq/student-faq/student-faq.component.html + 145 - src/app/help/student-faq/student-faq.component.html - 143 + src/app/help/help-home/help-home.component.html + 31 Student FAQ - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 87 - src/app/help/help-home/help-home.component.html - 40 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 418 - src/app/help/teacher-faq/teacher-faq.component.html - 421 + src/app/help/help-home/help-home.component.html + 40 Questions from students. - src/app/help/getting-started/getting-started.component.html + src/app/help/faq/getting-started/getting-started.component.html 89 - src/app/help/help-home/help-home.component.html - 42 - - - src/app/help/teacher-faq/teacher-faq.component.html - 423 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 420 - - - Information for new users. src/app/help/help-home/help-home.component.html - 20 - - - src/app/help/student-faq/student-faq.component.html - 132 - - - src/app/help/teacher-faq/teacher-faq.component.html - 412 + 42 Student Frequently Asked Questions - src/app/help/student-faq/student-faq.component.html + src/app/help/faq/student-faq/student-faq.component.html 3 Table of Contents - src/app/help/student-faq/student-faq.component.html + src/app/help/faq/student-faq/student-faq.component.html 6 - src/app/help/teacher-faq/teacher-faq.component.html - 7 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 6 src/app/privacy/privacy.component.html @@ -3725,184 +3698,184 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. General Questions - src/app/help/student-faq/student-faq.component.html - 8 + src/app/help/faq/student-faq/student-faq.component.html + 9 - src/app/help/student-faq/student-faq.component.html - 14 + src/app/help/faq/student-faq/student-faq.component.html + 16 - src/app/help/teacher-faq/teacher-faq.component.html + src/app/help/faq/teacher-faq/teacher-faq.component.html 9 - src/app/help/teacher-faq/teacher-faq.component.html - 31 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 28 Technical Questions - src/app/help/student-faq/student-faq.component.html - 11 + src/app/help/faq/student-faq/student-faq.component.html + 12 - src/app/help/student-faq/student-faq.component.html - 59 + src/app/help/faq/student-faq/student-faq.component.html + 61 - src/app/help/teacher-faq/teacher-faq.component.html - 28 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 24 - src/app/help/teacher-faq/teacher-faq.component.html - 331 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 328 How do I create an account? - src/app/help/student-faq/student-faq.component.html - 15 + src/app/help/faq/student-faq/student-faq.component.html + 17 - src/app/help/teacher-faq/teacher-faq.component.html - 32 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 29 Go to the Register page. - src/app/help/student-faq/student-faq.component.html - 17 + src/app/help/faq/student-faq/student-faq.component.html + 19 - src/app/help/teacher-faq/teacher-faq.component.html - 34 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 31 Click on Student. - src/app/help/student-faq/student-faq.component.html - 18 + src/app/help/faq/student-faq/student-faq.component.html + 20 I forgot my username, how can I find it? - src/app/help/student-faq/student-faq.component.html - 27 + src/app/help/faq/student-faq/student-faq.component.html + 29 Go to the forgot username page and follow the instructions to retrieve your username. - src/app/help/student-faq/student-faq.component.html - 29,31 + src/app/help/faq/student-faq/student-faq.component.html + 31,33 I forgot my password, how can I change it? - src/app/help/student-faq/student-faq.component.html - 33 + src/app/help/faq/student-faq/student-faq.component.html + 35 Go to the forgot password page and follow the instructions to reset your password. - src/app/help/student-faq/student-faq.component.html - 35 + src/app/help/faq/student-faq/student-faq.component.html + 37 How do I start working on a project? - src/app/help/student-faq/student-faq.component.html - 37 + src/app/help/faq/student-faq/student-faq.component.html + 39 In order to start working on a project you must obtain an Access Code from your teacher. Once you have an Access Code, follow the directions below. - src/app/help/student-faq/student-faq.component.html - 38,41 + src/app/help/faq/student-faq/student-faq.component.html + 40,43 Log into WISE with your student account. - src/app/help/student-faq/student-faq.component.html - 43 + src/app/help/faq/student-faq/student-faq.component.html + 45 Click on the "Add Unit" button at the top right of the Student Home Page. - src/app/help/student-faq/student-faq.component.html - 44,47 + src/app/help/faq/student-faq/student-faq.component.html + 46,49 Enter the Access Code from your teacher. - src/app/help/student-faq/student-faq.component.html - 48 + src/app/help/faq/student-faq/student-faq.component.html + 50 Choose your period. - src/app/help/student-faq/student-faq.component.html - 49 + src/app/help/faq/student-faq/student-faq.component.html + 51 Click "Add". - src/app/help/student-faq/student-faq.component.html - 50 + src/app/help/faq/student-faq/student-faq.component.html + 52 The run will be added to your list of runs. - src/app/help/student-faq/student-faq.component.html - 51 + src/app/help/faq/student-faq/student-faq.component.html + 53 Click "Launch" to open the project. - src/app/help/student-faq/student-faq.component.html - 52 + src/app/help/faq/student-faq/student-faq.component.html + 54 I chose the wrong period, how can I change it? - src/app/help/student-faq/student-faq.component.html - 54 + src/app/help/faq/student-faq/student-faq.component.html + 56 Only your teacher can change your period. Please ask them for assistance. - src/app/help/student-faq/student-faq.component.html - 56 + src/app/help/faq/student-faq/student-faq.component.html + 58 The WISE web site won't load on my web browser. What do I do? - src/app/help/student-faq/student-faq.component.html - 60 + src/app/help/faq/student-faq/student-faq.component.html + 62 - src/app/help/teacher-faq/teacher-faq.component.html - 332 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 329 @@ -3910,521 +3883,525 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.clearing your browser cache and then reloading the WISE web site. - src/app/help/student-faq/student-faq.component.html - 62,68 + src/app/help/faq/student-faq/student-faq.component.html + 64,70 - src/app/help/teacher-faq/teacher-faq.component.html - 334,340 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 331,337 You can try using a different web browser. We recommend using Chrome or Firefox. - src/app/help/student-faq/student-faq.component.html - 69 + src/app/help/faq/student-faq/student-faq.component.html + 71 - src/app/help/teacher-faq/teacher-faq.component.html - 341 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 338 What if I have trouble logging in? - src/app/help/student-faq/student-faq.component.html - 71 + src/app/help/faq/student-faq/student-faq.component.html + 73 - src/app/help/teacher-faq/teacher-faq.component.html - 351 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 348 If you do not remember your username or password click the Lost Username or Password link and follow the instructions. - src/app/help/student-faq/student-faq.component.html - 73,76 + src/app/help/faq/student-faq/student-faq.component.html + 75,78 - src/app/help/teacher-faq/teacher-faq.component.html - 353,356 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 350,353 Can I use WISE in another language? - src/app/help/student-faq/student-faq.component.html - 78 - - - src/app/help/teacher-faq/teacher-faq.component.html - 358 - - - - Yes! - - src/app/help/student-faq/student-faq.component.html - 79 + src/app/help/faq/student-faq/student-faq.component.html + 80 - src/app/help/teacher-faq/teacher-faq.component.html - 359 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 355 Sign into WISE with your student account. - src/app/help/student-faq/student-faq.component.html - 81 + src/app/help/faq/student-faq/student-faq.component.html + 83 Click on your account icon at the top right of the page. This should open a drop down with options. - src/app/help/student-faq/student-faq.component.html - 82,85 + src/app/help/faq/student-faq/student-faq.component.html + 84,87 - src/app/help/teacher-faq/teacher-faq.component.html - 362,365 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 359,362 Click the "Edit Profile" option. - src/app/help/student-faq/student-faq.component.html - 86,89 + src/app/help/faq/student-faq/student-faq.component.html + 88,91 - src/app/help/teacher-faq/teacher-faq.component.html - 366,369 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 363,366 Near the bottom there will be a "Language" setting. - src/app/help/student-faq/student-faq.component.html - 90 + src/app/help/faq/student-faq/student-faq.component.html + 92 - src/app/help/teacher-faq/teacher-faq.component.html - 370 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 367 Change the language to the language of your choice. - src/app/help/student-faq/student-faq.component.html - 91 + src/app/help/faq/student-faq/student-faq.component.html + 93 - src/app/help/teacher-faq/teacher-faq.component.html - 371 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 368 Note 1: If your language is not listed as an option, or you'd like to help us improve the translation, please contact us and we'll help you get started! - src/app/help/student-faq/student-faq.component.html - 92,95 + src/app/help/faq/student-faq/student-faq.component.html + 94,97 - src/app/help/teacher-faq/teacher-faq.component.html - 372,375 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 369,372 Note 2: Changing the language will change the language of the website but will not change the language of the projects. Projects must be independently translated which means that if you want to use a project in another language, you must translate it yourself or find a version of the project that has been translated by someone else. - src/app/help/student-faq/student-faq.component.html - 96,101 + src/app/help/faq/student-faq/student-faq.component.html + 98,103 - src/app/help/teacher-faq/teacher-faq.component.html - 376,381 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 373,378 Who do I contact when I have a problem I can't solve? - src/app/help/student-faq/student-faq.component.html - 103 + src/app/help/faq/student-faq/student-faq.component.html + 105 - src/app/help/teacher-faq/teacher-faq.component.html - 383 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 380 The Contact WISE form will send an email to the WISE technology group. You can find a link to the form at the bottom of the home page. Be sure to include as much information as you can about the problem. We will respond as quickly as we can. - src/app/help/student-faq/student-faq.component.html - 105,110 + src/app/help/faq/student-faq/student-faq.component.html + 107,112 + + + src/app/help/faq/teacher-faq/teacher-faq.component.html + 382,387 + + + + Information for new users. + + src/app/help/faq/student-faq/student-faq.component.html + 134 + + + src/app/help/faq/teacher-faq/teacher-faq.component.html + 409 - src/app/help/teacher-faq/teacher-faq.component.html - 385,390 + src/app/help/help-home/help-home.component.html + 20 Teacher Frequently Asked Questions - src/app/help/teacher-faq/teacher-faq.component.html - 4 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 3 Student Management - src/app/help/teacher-faq/teacher-faq.component.html + src/app/help/faq/teacher-faq/teacher-faq.component.html 12 - src/app/help/teacher-faq/teacher-faq.component.html - 67 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 64 Project Management - src/app/help/teacher-faq/teacher-faq.component.html + src/app/help/faq/teacher-faq/teacher-faq.component.html 15 - src/app/help/teacher-faq/teacher-faq.component.html - 176 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 173 Assessment of Student Work - src/app/help/teacher-faq/teacher-faq.component.html - 19 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 18 - src/app/help/teacher-faq/teacher-faq.component.html - 253 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 250 Real Time Classroom Monitor - src/app/help/teacher-faq/teacher-faq.component.html - 24 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 21 - src/app/help/teacher-faq/teacher-faq.component.html - 309 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 306 Click on "Teacher". - src/app/help/teacher-faq/teacher-faq.component.html - 35 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 32 How do I use a unit with my class? - src/app/help/teacher-faq/teacher-faq.component.html - 44 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 41 Should I register my students for WISE or have them do it themselves? - src/app/help/teacher-faq/teacher-faq.component.html - 68 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 65 WISE makes student registration simple and intuitive -- direct your students to the register page by having them go to the WISE home page and clicking the "Register" link at the upper right. They should be able to register in 10 minutes or less. However, you can also opt to pre-register all of your students and provide them with a copy of their username/password on their first day in the project run. - src/app/help/teacher-faq/teacher-faq.component.html - 69,75 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 66,72 Once a student has created a WISE account they should never need to create another one. - src/app/help/teacher-faq/teacher-faq.component.html - 77,79 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 74,76 Some teachers that pre-register prefer to give all students the same initial password, to decrease problems with students signing in. This is ok, but for security purposes, we advise that you have the students change their passwords once they log in. - src/app/help/teacher-faq/teacher-faq.component.html - 80,84 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 77,81 A student has forgotten his/her username or password. What should I do? - src/app/help/teacher-faq/teacher-faq.component.html - 86 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 83 First, we recommend always having students WRITE DOWN their username/password when they first register. - src/app/help/teacher-faq/teacher-faq.component.html - 88,91 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 85,88 Second, encourage the students to solve the problem themselves. Tell them to go to the WISE home page, click the Lost username/password link, and click the Student button. If the student can answer their security question (created at registration) they can change their password. - src/app/help/teacher-faq/teacher-faq.component.html - 92,96 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 89,93 Third, if the student can't solve the problem directly, you can change their password for them using the instructions below. - src/app/help/teacher-faq/teacher-faq.component.html - 97,100 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 94,97 How do I change a student's password? - src/app/help/teacher-faq/teacher-faq.component.html - 102 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 99 Sign into WISE with your teacher account. - src/app/help/teacher-faq/teacher-faq.component.html - 104 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 101 - src/app/help/teacher-faq/teacher-faq.component.html - 117 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 114 - src/app/help/teacher-faq/teacher-faq.component.html - 147 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 144 - src/app/help/teacher-faq/teacher-faq.component.html - 171 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 168 - src/app/help/teacher-faq/teacher-faq.component.html - 203 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 200 - src/app/help/teacher-faq/teacher-faq.component.html - 225 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 222 - src/app/help/teacher-faq/teacher-faq.component.html - 234 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 231 - src/app/help/teacher-faq/teacher-faq.component.html - 256 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 253 - src/app/help/teacher-faq/teacher-faq.component.html - 361 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 358 Find the run in your Teacher Home Page. - src/app/help/teacher-faq/teacher-faq.component.html - 105 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 102 - src/app/help/teacher-faq/teacher-faq.component.html - 118 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 115 - src/app/help/teacher-faq/teacher-faq.component.html - 148 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 145 - src/app/help/teacher-faq/teacher-faq.component.html - 172 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 169 - src/app/help/teacher-faq/teacher-faq.component.html - 226 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 223 - src/app/help/teacher-faq/teacher-faq.component.html - 235 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 232 - src/app/help/teacher-faq/teacher-faq.component.html - 257 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 254 Click the "Teacher Tools" button on the run. - src/app/help/teacher-faq/teacher-faq.component.html - 106 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 103 - src/app/help/teacher-faq/teacher-faq.component.html - 119 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 116 - src/app/help/teacher-faq/teacher-faq.component.html - 149 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 146 - src/app/help/teacher-faq/teacher-faq.component.html - 258 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 255 Click on the "Manage Students" icon on the left side bar. - src/app/help/teacher-faq/teacher-faq.component.html - 107 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 104 - src/app/help/teacher-faq/teacher-faq.component.html - 120 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 117 - src/app/help/teacher-faq/teacher-faq.component.html - 150 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 147 Find the period the student is in. - src/app/help/teacher-faq/teacher-faq.component.html - 108 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 105 - src/app/help/teacher-faq/teacher-faq.component.html - 121 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 118 - src/app/help/teacher-faq/teacher-faq.component.html - 151 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 148 Find the student and click "Change Password". - src/app/help/teacher-faq/teacher-faq.component.html - 109 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 106 Alternatively, you can change the password for ALL students in the current period (the new password will be applied to all the students in the period). - src/app/help/teacher-faq/teacher-faq.component.html - 110,113 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 107,110 How do I change a student team after they've started a project run? - src/app/help/teacher-faq/teacher-faq.component.html - 115 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 112 Drag-and-drop student names to move them from one team to another. Make sure to save your changes before leaving this window. - src/app/help/teacher-faq/teacher-faq.component.html - 122,125 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 119,122 If you want to move students into a brand new team, click New Team, then drag 1 or more students into the new team. Save your changes. - src/app/help/teacher-faq/teacher-faq.component.html - 126,129 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 123,126 Warning 1 - src/app/help/teacher-faq/teacher-faq.component.html - 131 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 128 If you move Student A into an established team, student A loses all of their current work and inherits the current work of the established team. - src/app/help/teacher-faq/teacher-faq.component.html - 133,134 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 130,131 Warning 2 - src/app/help/teacher-faq/teacher-faq.component.html - 138 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 135 If you move Student A into a newly created (blank) team, student A will lose all of their work. - src/app/help/teacher-faq/teacher-faq.component.html - 140,141 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 137,138 How do I change the period for a student? - src/app/help/teacher-faq/teacher-faq.component.html - 145 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 142 Find the student. - src/app/help/teacher-faq/teacher-faq.component.html - 152 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 149 Click the "Period" link. - src/app/help/teacher-faq/teacher-faq.component.html - 153 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 150 A popup will show up that will allow you to choose a different period for the student. - src/app/help/teacher-faq/teacher-faq.component.html - 154,156 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 151,153 Choose a different period. - src/app/help/teacher-faq/teacher-faq.component.html - 157 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 154 Click "Save Changes". - src/app/help/teacher-faq/teacher-faq.component.html - 158 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 155 Warning - src/app/help/teacher-faq/teacher-faq.component.html - 160 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 157 src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html @@ -4450,359 +4427,366 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. If you move a student to a different period, they will lose all of their work. - src/app/help/teacher-faq/teacher-faq.component.html - 162 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 159 I do not remember my teacher access code that students need to create their account for my new students. - src/app/help/teacher-faq/teacher-faq.component.html - 166,169 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 163,166 The Access Code will be displayed below the run title. - src/app/help/teacher-faq/teacher-faq.component.html - 173 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 170 When should I set up my project run? - src/app/help/teacher-faq/teacher-faq.component.html - 177 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 174 You need to set up your project run anytime before the students can start working on the project. They will need the Access Code for the run in order to start working. - src/app/help/teacher-faq/teacher-faq.component.html - 179,182 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 176,179 How long does it take to run a project? - src/app/help/teacher-faq/teacher-faq.component.html - 184 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 181 Project durations vary from about 3 days to 10 days. Some projects may display an estimation of the number of hours it will take for students to complete the project. - src/app/help/teacher-faq/teacher-faq.component.html - 186,189 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 183,186 Can I shorten a project run to 1 or 2 days? - src/app/help/teacher-faq/teacher-faq.component.html - 191 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 188 It is not recommended to shorten a project. Each project has been carefully designed to lead the student through an inquiry process and cutting it short will result in a less satisfactory educational student experience. Nevertheless, if you are under a time constraint and knowledgeable of the project topic, you could customize the project and shorten it to your specific needs. - src/app/help/teacher-faq/teacher-faq.component.html - 193,199 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 190,196 Where do I find out about lesson plans and standards for WISE projects? - src/app/help/teacher-faq/teacher-faq.component.html - 201 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 198 Find a project you would like to learn about. - src/app/help/teacher-faq/teacher-faq.component.html - 205 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 202 Click on the project. - src/app/help/teacher-faq/teacher-faq.component.html - 206 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 203 This will display a popup that displays information about the project. - src/app/help/teacher-faq/teacher-faq.component.html - 207 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 204 Note: Some projects may not have much information because it is dependent on the author to provide the lesson plan and standards. - src/app/help/teacher-faq/teacher-faq.component.html - 208,211 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 205,208 How do I fit a WISE project into my curriculum? - src/app/help/teacher-faq/teacher-faq.component.html - 213 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 210 In general students will benefit from some pre-teaching of the topic covered in the project. It is really up to the teacher to decide where it best fits to support student learning. Many teachers use a WISE project as a capstone activity while others integrate it into their curriculum at a midpoint. Some teachers elect to use WISE projects an introductory activity for a unit, or as a summation activity for a unit. - src/app/help/teacher-faq/teacher-faq.component.html - 215,221 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 212,218 How do I add a period after creating a run? - src/app/help/teacher-faq/teacher-faq.component.html - 223 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 220 On the run, click the three vertical dots to view the run options. - src/app/help/teacher-faq/teacher-faq.component.html - 227 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 224 - src/app/help/teacher-faq/teacher-faq.component.html - 236 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 233 Click "Edit Settings". - src/app/help/teacher-faq/teacher-faq.component.html - 228 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 225 - src/app/help/teacher-faq/teacher-faq.component.html - 237 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 234 Enter a period name in the "Add New Period" field. - src/app/help/teacher-faq/teacher-faq.component.html - 229 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 226 Click "Add Period". - src/app/help/teacher-faq/teacher-faq.component.html - 230 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 227 How do I delete a period after creating a run? - src/app/help/teacher-faq/teacher-faq.component.html - 232 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 229 Find the period you want to delete and click the "Delete" button next to it. - src/app/help/teacher-faq/teacher-faq.component.html - 238 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 235 Note: You are not allowed to delete a period that has students in it. - src/app/help/teacher-faq/teacher-faq.component.html - 239 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 236 What if I run out of computer lab time but some of my students are not finished with the project? - src/app/help/teacher-faq/teacher-faq.component.html - 241,244 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 238,241 WISE is a web-based learning environments, so students can sign in from a computer at school or at home. This means that students can complete the project from any home or community based computer, per the teacher's discretion. - src/app/help/teacher-faq/teacher-faq.component.html - 246,250 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 243,247 How do I review and grade student work? - src/app/help/teacher-faq/teacher-faq.component.html - 254 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 251 Here you will be able to look at your students' work. - src/app/help/teacher-faq/teacher-faq.component.html - 259 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 256 On the left sidebar you can choose to look at the work by step by clicking "Grade By Step" or you can choose to look at the work by team by clicking "Grade By Team". - src/app/help/teacher-faq/teacher-faq.component.html - 260,263 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 257,260 Next to each student's work, you will be able to give them a score and comment. - src/app/help/teacher-faq/teacher-faq.component.html - 264 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 261 How do students see my comments and scores for their work? - src/app/help/teacher-faq/teacher-faq.component.html - 266 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 263 The student must sign into WISE with their student account. - src/app/help/teacher-faq/teacher-faq.component.html - 268 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 265 Open the project. - src/app/help/teacher-faq/teacher-faq.component.html - 269 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 266 Go to the step that the teacher graded. - src/app/help/teacher-faq/teacher-faq.component.html - 270 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 267 The comment and score will be displayed under the piece of work that was graded. - src/app/help/teacher-faq/teacher-faq.component.html - 271 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 268 What should I look for in my student's answers? - src/app/help/teacher-faq/teacher-faq.component.html - 273 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 270 The following link will give you a sample rubric for grading a step in the Mitosis and Cell Processes project. The rubric is based on our TELS Center research on how students integrate their knowledge of complex science concepts. We hope that it can give you a start in developing your own rubrics for the notes and steps you plan to grade. - src/app/help/teacher-faq/teacher-faq.component.html - 275,280 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 272,277 Sample rubric for assessing student work - src/app/help/teacher-faq/teacher-faq.component.html - 286 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 283 How can I encourage my students to review the graded notes and comments I have made? - src/app/help/teacher-faq/teacher-faq.component.html - 290 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 287 Many teachers find it advantageous to grade the first step or two at the end of the first day of the project run. At the beginning of class the next day they share with the whole class some sample responses and have the class critique the work. - src/app/help/teacher-faq/teacher-faq.component.html - 292,296 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 289,293 How do I find time to grade all of the student work? - src/app/help/teacher-faq/teacher-faq.component.html - 298 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 295 We recommend that you go through the project and select a few steps that you think best demonstrate the students' understanding of the complex concepts covered in the module and grade those steps. Then tell your students they must complete all questions but should concentrate their efforts on the key steps. We recognize that critically grading each step is very time consuming and unpractical. - src/app/help/teacher-faq/teacher-faq.component.html - 300,306 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 297,303 What is the Real Time Classroom Monitor? - src/app/help/teacher-faq/teacher-faq.component.html - 310 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 307 The Real Time Classroom Monitor allows teachers to view student progress in real time. Teachers will be able to see what step a student is on, how much time the student has spent on that step, and how much of the project the student has completed. All of this information will be updated immediately in real time as students work on the project. - src/app/help/teacher-faq/teacher-faq.component.html - 312,317 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 309,314 Does the Real Time Classroom Monitor work on a tablet like the iPad? - src/app/help/teacher-faq/teacher-faq.component.html - 319 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 316 Yes it does. - src/app/help/teacher-faq/teacher-faq.component.html - 321 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 318 Can I use the Real Time Classroom Monitor to pause student screens? - src/app/help/teacher-faq/teacher-faq.component.html - 323 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 320 Yes you can! This can be useful if you need to grab their attention in order to have a class discussion or to make an announcement. - src/app/help/teacher-faq/teacher-faq.component.html - 325,328 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 322,325 How many computers do I need to run WISE? - src/app/help/teacher-faq/teacher-faq.component.html - 343 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 340 We strongly recommend one computer for every two students. Research shows that students benefit from working together as a team of two. - src/app/help/teacher-faq/teacher-faq.component.html - 345,348 + src/app/help/faq/teacher-faq/teacher-faq.component.html + 342,345 + + + + Yes! + + src/app/help/faq/teacher-faq/teacher-faq.component.html + 356 @@ -5409,7 +5393,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 41 + 40 src/assets/wise5/authoringTool/project-list/project-list.component.html @@ -6133,11 +6117,11 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.161 - - Password changed. + + Successfully changed password. src/app/modules/shared/edit-password/edit-password.component.ts - 80 + 81 @@ -6357,6 +6341,10 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.src/app/notebook/notebook-report/notebook-report.component.html 72,74 + + src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html + 11,13 + src/assets/wise5/themes/default/notebook/edit-notebook-item-dialog/edit-notebook-item-dialog.component.html 80,82 @@ -6377,49 +6365,112 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. Password required src/app/password/new-password-and-confirm/new-password-and-confirm.component.html - 12,14 + 23,25 - - Password must be at least 8 characters + + Password Strength: src/app/password/new-password-and-confirm/new-password-and-confirm.component.html - 15,17 + 33 - - Password must have at least one lowercase, one uppercase, and one number character + + Very Weak src/app/password/new-password-and-confirm/new-password-and-confirm.component.html - 18,20 + 35 - - Confirm Password required + + Weak src/app/password/new-password-and-confirm/new-password-and-confirm.component.html - 33,35 + 36 + + + + Good + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.html + 37 + + + + Strong + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.html + 38 + + + + Very Strong + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.html + 39 + + + + Your password needs to: + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.html + 43 Password does not match src/app/password/new-password-and-confirm/new-password-and-confirm.component.html - 36,38 + 67,69 Confirm New Password src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts - 18 + 26 New Password src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts - 23 + 31 + + + + include a letter + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts + 33 + + + + include a number + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts + 34 + + + + be at least 8 characters long + + src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts + 35 + + + + Requirement failed + + src/app/password/password-requirement/password-requirement.component.html + 25 + + + + Requirement passed + + src/app/password/password-requirement/password-requirement.component.html + 28 @@ -7277,21 +7328,21 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Error: First Name and Last Name must only contain characters A-Z, a-z, spaces, or dashes and can not start or end with a space or dash src/app/register/register-user-form/register-user-form.component.ts - 44 + 34 Error: First Name must only contain characters A-Z, a-z, spaces, or dashes and can not start or end with a space or dash src/app/register/register-user-form/register-user-form.component.ts - 46 + 36 Error: Last Name must only contain characters A-Z, a-z, spaces, or dashes and can not start or end with a space or dash src/app/register/register-user-form/register-user-form.component.ts - 48 + 38 @@ -9400,71 +9451,64 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.25,27 - - Back to Unit Plan - - src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 7 - - Show Rubric src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 18 + 6 Download Unit src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 41 + 29 Edit Unit JSON src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 55 + 43 src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 39 + 49 Script Filename src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 67 + 55 Choose src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 74 + 62 Unit URL src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 84 + 72 Copy Unit URL to Clipboard src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 91 + 79 Open Unit URL in New Tab src/assets/wise5/authoringTool/advanced/advanced-project-authoring.component.html - 102 + 90 @@ -9537,18 +9581,25 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.44 + + Advanced Settings + + src/assets/wise5/authoringTool/authoring-tool.component.ts + 92 + + Unit List src/assets/wise5/authoringTool/authoring-tool.component.ts - 92 + 99 You have been inactive for a long time. Do you want to stay logged in? src/assets/wise5/authoringTool/authoring-tool.component.ts - 106 + 113 src/assets/wise5/classroomMonitor/classroom-monitor.component.ts @@ -9563,7 +9614,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Session Timeout src/assets/wise5/authoringTool/authoring-tool.component.ts - 107 + 114 src/assets/wise5/classroomMonitor/classroom-monitor.component.ts @@ -9578,32 +9629,32 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Saving... src/assets/wise5/authoringTool/authoring-tool.component.ts - 131 + 138 Saved src/assets/wise5/authoringTool/authoring-tool.component.ts - 145 + 152 Error Saving Unit. Please refresh the page. src/assets/wise5/authoringTool/authoring-tool.component.ts - 152 + 159 You do not have permission to edit this unit. src/assets/wise5/authoringTool/authoring-tool.component.ts - 159 + 166 src/assets/wise5/authoringTool/authoring-tool.component.ts - 192 + 199 @@ -9629,7 +9680,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 162 + 131 @@ -9644,7 +9695,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 229 + 189 @@ -9659,7 +9710,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 230 + 190 @@ -9721,11 +9772,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html - 110 - - - src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html - 112 + 108 @@ -9769,49 +9816,45 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html 25 - - src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html - 27 - Help src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html - 43,45 + 44,46 User Menu src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html - 48 + 49 src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 80 + 78 Go Home src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html - 73,75 + 74,76 src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 105,107 + 103,105 Sign Out src/assets/wise5/authoringTool/components/top-bar/top-bar.component.html - 77,79 + 78,80 src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 109,111 + 107,109 @@ -10281,7 +10324,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/importComponent/choose-import-component/choose-import-component.component.html - 126 + 122 @@ -11418,109 +11461,105 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 190 - - - src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 63 + 189 Back to unit src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 45 + 44 Add a new component src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 56 + 55 Components src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 71 + 70 Move Components src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 80 + 79 Copy Components src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 90 + 89 Delete Components src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 100 + 99 + Expand All src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 115,117 + 114,116 - Collapse All src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 124,126 + 123,125 This step does not have any components. Click the + button to add a component. src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 131 + 130 Toggle component authoring src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 153 + 152 Select component src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 170 + 169 Click to expand/collapse src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 181 + 180 Copy Component src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 212 + 211 Delete Component src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html - 226 + 225 @@ -12025,69 +12064,94 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Add New Lesson src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 8 + 7 Add New Step src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 19 + 18 Move src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 30 + 29 + + + + Select lesson or step + + src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html + 80 - + Branch point with paths based on + getBranchCriteriaDescription(item.key) + }}"/> src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 121,123 + 91,93 - + Constraint\n\n + getConstraintDescriptions(item.key) + }}"/> src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 131,133 + 101,103 - + Constraints\n\n + getConstraintDescriptions(item.key) + }}"/> src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 141,143 + 111,113 Has Rubric + + src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html + 120 + + + + Select lesson src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html 150 + + Select step + + src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html + 176 + + + src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html + 212 + + Are you sure you want to delete the selected item? src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts - 106 + 93 Are you sure you want to delete the selected items? src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts - 107 + 94 @@ -12204,68 +12268,67 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.96 - - Save + + Recovery View src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 3,5 + 2 - - Go to Authoring View + + Go to Authoring View src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 6 + 14,16 JSON Valid src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 9 + 19 JSON Invalid src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 10 + 20 - - Warning: Modifying the JSON may break the project. Please make a backup copy of the JSON before you modify it. - + + Warning: Modifying the JSON may break the project. Please make a backup copy of the JSON before you modify it. src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 18,21 + 28,31 Potential Problems src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 23 + 33 - + This group references the node ID but the node does not exist: src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 26,29 + 36,39 - + This group references the same node ID multiple times: src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 30,33 + 40,43 This node has a transition to null src/assets/wise5/authoringTool/recovery-authoring/recovery-authoring.component.html - 34 + 44 @@ -12701,28 +12764,28 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Confirm New Student Password src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts - 19 + 20 New Student Password src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts - 22 + 23 Changed password for (). src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts - 70 + 71 Changed password for Student . src/assets/wise5/classroomMonitor/classroomMonitorComponents/manageStudents/change-student-password-dialog/change-student-password-dialog.component.ts - 71 + 72 @@ -13530,7 +13593,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 67 + 65 @@ -13688,7 +13751,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/classroomMonitor/classroomMonitorComponents/shared/top-bar/top-bar.component.html - 48 + 46 src/assets/wise5/vle/notifications-dialog/notifications-dialog.component.html @@ -20821,112 +20884,112 @@ If this problem continues, let your teacher know and move on to the next activit Complete <b></b> src/assets/wise5/services/projectService.ts - 1174 + 1180 Visit <b></b> src/assets/wise5/services/projectService.ts - 1180 + 1186 Correctly answer <b></b> src/assets/wise5/services/projectService.ts - 1186 + 1192 Obtain a score of <b></b> on <b></b> src/assets/wise5/services/projectService.ts - 1201 + 1207 You must choose "" on "" src/assets/wise5/services/projectService.ts - 1209 + 1215 Submit <b></b> time on <b></b> src/assets/wise5/services/projectService.ts - 1221 + 1227 Submit <b></b> times on <b></b> src/assets/wise5/services/projectService.ts - 1223 + 1229 Take the branch path from <b></b> to <b></b> src/assets/wise5/services/projectService.ts - 1230 + 1236 Write <b></b> words on <b></b> src/assets/wise5/services/projectService.ts - 1236 + 1242 "" is visible src/assets/wise5/services/projectService.ts - 1242 + 1248 "" is visitable src/assets/wise5/services/projectService.ts - 1248 + 1254 Add <b></b> note on <b></b> src/assets/wise5/services/projectService.ts - 1255 + 1261 Add <b></b> notes on <b></b> src/assets/wise5/services/projectService.ts - 1257 + 1263 You must fill in <b></b> row in the <b>Table</b> on <b></b> src/assets/wise5/services/projectService.ts - 1264 + 1270 You must fill in <b></b> rows in the <b>Table</b> on <b></b> src/assets/wise5/services/projectService.ts - 1266 + 1272 Wait for your teacher to unlock the item src/assets/wise5/services/projectService.ts - 1269 + 1275 @@ -20954,21 +21017,21 @@ If this problem continues, let your teacher know and move on to the next activit StudentDataService.saveComponentEvent: component, category, event args must not be null src/assets/wise5/services/studentDataService.ts - 251 + 241 StudentDataService.saveComponentEvent: nodeId, componentId, componentType must not be null src/assets/wise5/services/studentDataService.ts - 261 + 251 StudentDataService.saveVLEEvent: category and event args must not be null src/assets/wise5/services/studentDataService.ts - 270 + 260 diff --git a/src/style/themes/_author.scss b/src/style/themes/_author.scss index 121086c46a9..60246720698 100644 --- a/src/style/themes/_author.scss +++ b/src/style/themes/_author.scss @@ -135,7 +135,7 @@ $author-colors: ( 'score': #FFC107, 'secondary-text': mat.get-color-from-palette($foreground, 'secondary-text'), 'selected-bg': mat.get-color-from-palette($author-theme-primary, 50), - 'success': #00C853, + 'success': #008004, 'success-contrast': black, 'text': mat.get-color-from-palette($foreground, 'text'), 'warn': mat.get-color-from-palette($author-theme-warn, 800), diff --git a/src/style/themes/_default.scss b/src/style/themes/_default.scss index b05c71f9886..65eafc54c89 100644 --- a/src/style/themes/_default.scss +++ b/src/style/themes/_default.scss @@ -136,7 +136,7 @@ $default-colors: ( 'score': #FFC107, 'secondary-text': mat.get-color-from-palette($foreground, 'secondary-text'), 'selected-bg': mat.get-color-from-palette($default-theme-primary, 50), - 'success': #00C853, + 'success': #008A05, 'success-contrast': black, 'text': mat.get-color-from-palette($foreground, 'text'), 'warn': mat.get-color-from-palette($default-theme-warn, 800), diff --git a/src/style/themes/_monitor.scss b/src/style/themes/_monitor.scss index cd1878c9b19..7cd647e9cb0 100644 --- a/src/style/themes/_monitor.scss +++ b/src/style/themes/_monitor.scss @@ -135,8 +135,8 @@ $monitor-colors: ( 'score': #FFC107, 'secondary-text': mat.get-color-from-palette($foreground, 'secondary-text'), 'selected-bg': mat.get-color-from-palette($monitor-theme-primary, 50), - 'success': #00C853, + 'success': #008004, 'success-contrast': black, 'text': mat.get-color-from-palette($foreground, 'text'), - 'warn': mat.get-color-from-palette($monitor-theme-warn, 800), + 'warn': 'mat.get-color-from-palette($monitor-theme-warn, 800)', );