From ab67d1bd34771f59fe1d38ab2ec18018bcb7d84f Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Fri, 22 Jan 2021 19:12:46 -0500 Subject: [PATCH 01/13] Extracted MilestoneDetailsDialog into its own ts and html files. #2897 --- .../milestoneDetailsDialog.html | 21 ++++ .../milestoneDetailsDialog.ts | 77 +++++++++++++ .../webapp/wise5/services/milestoneService.ts | 105 +----------------- 3 files changed, 104 insertions(+), 99 deletions(-) create mode 100644 src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html create mode 100644 src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts diff --git a/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html new file mode 100644 index 0000000000..986833bfc1 --- /dev/null +++ b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html @@ -0,0 +1,21 @@ + + +
+

{{ 'MILESTONE_DETAILS_TITLE' | translate : { name: $ctrl.milestone.name } }}

+
+
+ + + + + + + {{ ::'CLOSE' | translate }} + + +
\ No newline at end of file diff --git a/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts new file mode 100644 index 0000000000..eaec3e2d0b --- /dev/null +++ b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts @@ -0,0 +1,77 @@ +export class MilestoneDetailsDialog { + title: string; + + static $inject = [ + '$state', + '$mdDialog', + '$event', + 'milestone', + 'hideStudentWork', + 'TeacherDataService' + ]; + + constructor( + private $state, + private $mdDialog, + private $event, + private milestone, + private hideStudentWork, + private TeacherDataService + ) {} + + $onInit() { + this.saveMilestoneOpenedEvent(); + } + + close() { + this.saveMilestoneClosedEvent(); + this.$mdDialog.hide(); + } + + edit() { + this.$mdDialog.hide({ + milestone: this.milestone, + action: 'edit', + $event: this.$event + }); + } + + onShowWorkgroup(workgroup: any) { + this.saveMilestoneClosedEvent(); + this.$mdDialog.hide(); + this.TeacherDataService.setCurrentWorkgroup(workgroup); + this.$state.go('root.nodeProgress'); + } + + onVisitNodeGrading() { + this.$mdDialog.hide(); + } + + saveMilestoneOpenedEvent() { + this.saveMilestoneEvent('MilestoneOpened'); + } + + saveMilestoneClosedEvent() { + this.saveMilestoneEvent('MilestoneClosed'); + } + + saveMilestoneEvent(event: any) { + const context = 'ClassroomMonitor', + nodeId = null, + componentId = null, + componentType = null, + category = 'Navigation', + data = { milestoneId: this.milestone.id }, + projectId = null; + this.TeacherDataService.saveEvent( + context, + nodeId, + componentId, + componentType, + category, + event, + data, + projectId + ); + } +} diff --git a/src/main/webapp/wise5/services/milestoneService.ts b/src/main/webapp/wise5/services/milestoneService.ts index 47580d6e77..561e31ed79 100644 --- a/src/main/webapp/wise5/services/milestoneService.ts +++ b/src/main/webapp/wise5/services/milestoneService.ts @@ -9,6 +9,7 @@ import { TeacherDataService } from './teacherDataService'; import { UtilService } from './utilService'; import { Injectable } from '@angular/core'; import { UpgradeModule } from '@angular/upgrade/static'; +import { MilestoneDetailsDialog } from '../classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog'; @Injectable() export class MilestoneService { @@ -511,40 +512,12 @@ export class MilestoneService { } showMilestoneDetails(milestone: any, $event: any, hideStudentWork: boolean = false) { - const title = this.getTranslation('MILESTONE_DETAILS_TITLE', { - name: milestone.name - }); - const template = ` - -
-

${title}

-
-
- - - - - - - {{ ::'EDIT' | translate }} - - - {{ ::'CLOSE' | translate }} - - -
`; this.upgrade.$injector.get('$mdDialog').show({ parent: angular.element(document.body), - template: template, - ariaLabel: title, + templateUrl: + 'wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html', + controller: MilestoneDetailsDialog, + controllerAs: '$ctrl', fullscreen: true, multiple: true, targetEvent: $event, @@ -554,73 +527,7 @@ export class MilestoneService { $event: $event, milestone: milestone, hideStudentWork: hideStudentWork - }, - controller: [ - '$scope', - '$state', - '$mdDialog', - 'milestone', - '$event', - 'TeacherDataService', - function DialogController( - $scope, - $state, - $mdDialog, - milestone, - $event, - TeacherDataService - ) { - $scope.milestone = milestone; - $scope.hideStudentWork = hideStudentWork; - $scope.event = $event; - $scope.close = function () { - $scope.saveMilestoneClosedEvent(); - $mdDialog.hide(); - }; - $scope.edit = function () { - $mdDialog.hide({ - milestone: $scope.milestone, - action: 'edit', - $event: $event - }); - }; - $scope.onShowWorkgroup = function (workgroup: any) { - $scope.saveMilestoneClosedEvent(); - $mdDialog.hide(); - TeacherDataService.setCurrentWorkgroup(workgroup); - $state.go('root.nodeProgress'); - }; - $scope.onVisitNodeGrading = function () { - $mdDialog.hide(); - }; - $scope.saveMilestoneOpenedEvent = function () { - $scope.saveMilestoneEvent('MilestoneOpened'); - }; - $scope.saveMilestoneClosedEvent = function () { - $scope.saveMilestoneEvent('MilestoneClosed'); - }; - $scope.saveMilestoneEvent = function (event: any) { - const context = 'ClassroomMonitor', - nodeId = null, - componentId = null, - componentType = null, - category = 'Navigation', - data = { milestoneId: $scope.milestone.id }, - projectId = null; - TeacherDataService.saveEvent( - context, - nodeId, - componentId, - componentType, - category, - event, - data, - projectId - ); - }; - $scope.saveMilestoneOpenedEvent(); - } - ] + } }); } } From 87ffdee0e56f8a23b93b76e673f733fe58be33a1 Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Mon, 25 Jan 2021 12:15:24 -0500 Subject: [PATCH 02/13] Upgraded Label authoring to Angular. #2895 --- .../src/app/teacher-hybrid-angular.module.ts | 2 + .../component-authoring.component.ts | 5 + .../wise5/components/label/authoring.html | 373 ------------------ .../label-authoring.component.html | 236 +++++++++++ .../label-authoring.component.scss | 64 +++ .../label-authoring.component.ts | 134 +++++++ .../wise5/components/label/labelAuthoring.ts | 177 --------- .../label/labelAuthoringComponentModule.ts | 9 +- 8 files changed, 447 insertions(+), 553 deletions(-) delete mode 100644 src/main/webapp/wise5/components/label/authoring.html create mode 100644 src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html create mode 100644 src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss create mode 100644 src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts delete mode 100644 src/main/webapp/wise5/components/label/labelAuthoring.ts diff --git a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts index e9a9ff545a..6a5134318d 100644 --- a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts +++ b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts @@ -51,6 +51,7 @@ import { MultipleChoiceAuthoring } from '../../../wise5/components/multipleChoic import { ConceptMapAuthoring } from '../../../wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component'; import { DrawAuthoring } from '../../../wise5/components/draw/draw-authoring/draw-authoring.component'; import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/match-authoring.component'; +import { LabelAuthoring } from '../../../wise5/components/label/label-authoring/label-authoring.component'; @NgModule({ declarations: [ @@ -72,6 +73,7 @@ import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/ EditHTMLAdvancedComponent, EditOutsideUrlAdvancedComponent, HtmlAuthoring, + LabelAuthoring, ManageStudentsComponent, MatchAuthoring, MilestonesComponent, diff --git a/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts b/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts index 6634abd0cc..225b94249e 100644 --- a/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts +++ b/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts @@ -62,6 +62,11 @@ export abstract class ComponentAuthoring { ); } + ngOnDestroy() { + this.componentChangedSubscription.unsubscribe(); + this.starterStateResponseSubscription.unsubscribe(); + } + promptChanged(prompt: string): void { this.promptChange.next(prompt); } diff --git a/src/main/webapp/wise5/components/label/authoring.html b/src/main/webapp/wise5/components/label/authoring.html deleted file mode 100644 index 2e53f91e8a..0000000000 --- a/src/main/webapp/wise5/components/label/authoring.html +++ /dev/null @@ -1,373 +0,0 @@ - - -
-
-
- - {{ ::'SHOW_SAVE_BUTTON' | translate }} - -
-
- - {{ ::'SHOW_SUBMIT_BUTTON' | translate }} - -
-
- - {{ ::'SHOW_ADD_TO_NOTEBOOK_BUTTON' | translate }} - -
-
- - - - -
-
- - - - -
-
-
- - - add - - {{ ::'ADD_CONNECTED_COMPONENT' | translate }} - - -
-
-
- - - - - {{ labelController.getNodePositionAndTitleByNodeId(item.$key) }} - - - - - - - - {{ componentIndex + 1 }}. {{ component.type }} - - ({{ ::'thisComponent' | translate }}) - - - - - - - - - {{ ::'importWork' | translate }} - - - {{ ::'showWork' | translate }} - - - - - - - delete - - {{ ::'DELETE' | translate }} - - - -
-
- - - {{ ::'importWorkAsBackground' | translate }} - - -
- - - - - - - - - - - - -
-
-
- - - {{ ::'importWorkAsBackground' | translate }} - - -
-
-
- -
-
-
-
- - - - -
- - - - - - insert_photo - - {{ ::'chooseAnImage' | translate }} - - -
-
- - - - - - - - -
-
- - - - - - - - - - - - -
-
- - {{ ::'label.canStudentCreateLabels' | translate }} - -
- - {{ ::'label.enableDots' | translate }} - -
- - {{ ::'label.allowStudentToUploadImageForBackground' | translate }} - -
-
-
-
{{ ::'label.starterLabels' | translate }}
- - add - - {{ ::'label.addStarterLabel' | translate }} - - -
-
- {{ ::'label.thereAreNoStarterLabels' | translate }} -
-
-
- - - - - - - - - - palette - - {{ ::'label.viewColors' | translate }} - - -
-
- - {{ ::'label.canStudentEditLabel' | translate }} - -
- - {{ ::'label.canStudentDeleteLabel' | translate }} - -
-
- - {{ ::'label.pointLocation' | translate }} - - - - - - - - - -
-
- - {{ ::'label.textLocation' | translate }} - - - - - - - - - - - - delete - - {{ ::'label.deleteLabel' | translate }} - - -
-
-
- - create - - {{ ::'label.saveStarterLabels' | translate }} - - - - delete_sweep - - {{ ::'label.deleteStarterLabels' | translate }} - - -
-
-
diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html new file mode 100644 index 0000000000..a01d9419f0 --- /dev/null +++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html @@ -0,0 +1,236 @@ + + Prompt + + +
+ + Background Image + + + +
+
+ + Canvas Width (px) + + + + Canvas Height (px) + + +
+
+ + Point Radius Size (px) + + + + Font Size + + + + Label Max Character Width + + +
+
+
+ + Can Student Create Labels + +
+
+ + Enable Dots + +
+
+ + Allow Student to Upload Image for Background + +
+
+
+
+ Starter Labels + +
+
+ There are no starter labels. Click the "Add Label" button to add a starter label. +
+
+
+ + Text + + + + Color + + + +
+
+
+ + Can Student Edit Label + +
+
+ + Can Student Delete Label + +
+
+
+ Point Location + + X + + + + Y + + +
+
+ Text Location + + X + + + + Y + + + + +
+
+
+
+ + +
\ No newline at end of file diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss new file mode 100644 index 0000000000..2115fcb9d5 --- /dev/null +++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss @@ -0,0 +1,64 @@ +.prompt { + width: 100%; +} + +.background-image { + width: 80%; +} + +.input { + margin-right: 20px; +} + +.checkbox { + margin-top: 10px; + margin-bottom: 10px; +} + +.starter-labels-button-container { + margin-top: 20px; + margin-bottom: 20px; +} + +.starter-labels-button-label { + margin-right: 10px; +} + +.starter-label-container { + border: 2px solid #dddddd; + border-radius: 5px; + margin-bottom: 10px; + padding: 20px 20px 10px 20px; +} + +.info-block { + margin-bottom: 20px; + text-align: center; + font-weight: 500; +} + +.label-input { + width: 40%; +} + +.color-input { + width: 30%; +} + +.coordinate-location-label { + margin-right: 20px; +} + +.coordinate-input { + width: 15%; +} + +.starter-labels-buttons-container { + margin-top: 20px; + margin-bottom: 10px; + margin-left: 10px; +} + +.starter-labels-button { + margin-right: 10px; +} \ No newline at end of file diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts new file mode 100644 index 0000000000..f37ce78551 --- /dev/null +++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts @@ -0,0 +1,134 @@ +'use strict'; + +import { Component } from '@angular/core'; +import { ComponentAuthoring } from '../../../authoringTool/components/component-authoring.component'; +import { ConfigService } from '../../../services/configService'; +import { NodeService } from '../../../services/nodeService'; +import { ProjectAssetService } from '../../../../site/src/app/services/projectAssetService'; +import { TeacherProjectService } from '../../../services/teacherProjectService'; +import { Subject, Subscription } from 'rxjs'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; + +@Component({ + selector: 'label-authoring', + templateUrl: 'label-authoring.component.html', + styleUrls: ['label-authoring.component.scss'] +}) +export class LabelAuthoring extends ComponentAuthoring { + numberInputChange: Subject = new Subject(); + textInputChange: Subject = new Subject(); + + numberInputChangeSubscription: Subscription; + textInputChangeSubscription: Subscription; + + constructor( + protected ConfigService: ConfigService, + protected NodeService: NodeService, + protected ProjectAssetService: ProjectAssetService, + protected ProjectService: TeacherProjectService + ) { + super(ConfigService, NodeService, ProjectAssetService, ProjectService); + this.numberInputChangeSubscription = this.numberInputChange + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.componentChanged(); + }); + this.textInputChangeSubscription = this.textInputChange + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.componentChanged(); + }); + } + + ngOnInit() { + super.ngOnInit(); + if (this.authoringComponentContent.enableCircles == null) { + // If this component was created before enableCircles was implemented, we will default it to + // true in the authoring so that the "Enable Dots" checkbox is checked. + this.authoringComponentContent.enableCircles = true; + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.unsubscribeAll(); + } + + unsubscribeAll() { + this.numberInputChangeSubscription.unsubscribe(); + this.textInputChangeSubscription.unsubscribe(); + } + + addLabelClicked(): void { + const newLabel = { + text: $localize`Enter text here`, + color: 'blue', + pointX: 100, + pointY: 100, + textX: 200, + textY: 200, + canEdit: false, + canDelete: false + }; + this.authoringComponentContent.labels.push(newLabel); + this.componentChanged(); + } + + deleteLabelClicked(index: number, label: any): void { + if (confirm($localize`Are you sure you want to delete this label?\n\n${label.textString}`)) { + this.authoringComponentContent.labels.splice(index, 1); + this.componentChanged(); + } + } + + chooseBackgroundImage(): void { + const params = { + isPopup: true, + nodeId: this.nodeId, + componentId: this.componentId, + target: 'background' + }; + this.openAssetChooser(params); + } + + assetSelected({ nodeId, componentId, assetItem, target }): void { + super.assetSelected({ nodeId, componentId, assetItem, target }); + const fileName = assetItem.fileName; + if (target === 'background') { + this.authoringComponentContent.backgroundImage = fileName; + this.componentChanged(); + } + } + + saveStarterLabels(): void { + if (confirm($localize`Are you sure you want to save the starter labels?`)) { + this.NodeService.requestStarterState({ nodeId: this.nodeId, componentId: this.componentId }); + } + } + + saveStarterState(starterState: any): void { + this.authoringComponentContent.labels = starterState; + this.componentChanged(); + } + + compareTextAlphabetically(stringA: string, stringB: string) { + if (stringA < stringB) { + return -1; + } else if (stringA > stringB) { + return 1; + } else { + return 0; + } + } + + deleteStarterLabels(): void { + if (confirm($localize`label.areYouSureYouWantToDeleteAllTheStarterLabels`)) { + this.authoringComponentContent.labels = []; + this.componentChanged(); + } + } + + openColorViewer(): void { + window.open('http://www.javascripter.net/faq/colornam.htm'); + } +} diff --git a/src/main/webapp/wise5/components/label/labelAuthoring.ts b/src/main/webapp/wise5/components/label/labelAuthoring.ts deleted file mode 100644 index df29a8ac22..0000000000 --- a/src/main/webapp/wise5/components/label/labelAuthoring.ts +++ /dev/null @@ -1,177 +0,0 @@ -'use strict'; - -import * as $ from 'jquery'; -import * as fabric from 'fabric'; -window['fabric'] = fabric.fabric; -import html2canvas from 'html2canvas'; -import { Directive } from '@angular/core'; -import { EditComponentController } from '../../authoringTool/components/editComponentController'; - -@Directive() -class LabelAuthoringController extends EditComponentController { - static $inject = [ - '$filter', - '$window', - 'ConfigService', - 'NodeService', - 'NotificationService', - 'ProjectAssetService', - 'ProjectService', - 'UtilService' - ]; - - constructor( - $filter, - private $window, - ConfigService, - NodeService, - NotificationService, - ProjectAssetService, - ProjectService, - UtilService - ) { - super( - $filter, - ConfigService, - NodeService, - NotificationService, - ProjectAssetService, - ProjectService, - UtilService - ); - } - - $onInit() { - super.$onInit(); - if (this.authoringComponentContent.enableCircles == null) { - /* - * If this component was created before enableCircles was implemented, - * we will default it to true in the authoring so that the - * "Enable Dots" checkbox is checked. - */ - this.authoringComponentContent.enableCircles = true; - } - } - - addLabelClicked(): void { - const newLabel = { - text: this.$translate('label.enterTextHere'), - color: 'blue', - pointX: 100, - pointY: 100, - textX: 200, - textY: 200, - canEdit: false, - canDelete: false - }; - this.authoringComponentContent.labels.push(newLabel); - this.componentChanged(); - } - - /** - * Delete a label in the authoring view - * @param index the index of the label in the labels array - */ - deleteLabelClicked(index: number, label: any): void { - const answer = confirm( - this.$translate('label.areYouSureYouWantToDeleteThisLabel', { - selectedLabelText: label.textString - }) - ); - if (answer) { - this.authoringComponentContent.labels.splice(index, 1); - this.componentChanged(); - } - } - - chooseBackgroundImage(): void { - const params = { - isPopup: true, - nodeId: this.nodeId, - componentId: this.componentId, - target: 'background' - }; - this.openAssetChooser(params); - } - - assetSelected({ nodeId, componentId, assetItem, target }): void { - super.assetSelected({ nodeId, componentId, assetItem, target }); - const fileName = assetItem.fileName; - if (target === 'background') { - this.authoringComponentContent.backgroundImage = fileName; - this.componentChanged(); - } - } - - saveStarterLabels(): void { - if (confirm(this.$translate('label.areYouSureYouWantToSaveTheStarterLabels'))) { - this.NodeService.requestStarterState({ nodeId: this.nodeId, componentId: this.componentId }); - } - } - - saveStarterState(starterState: any): void { - starterState.sort(this.labelTextComparator); - this.authoringComponentContent.labels = starterState; - this.componentChanged(); - } - - /** - * A comparator used to sort labels alphabetically - * It should be used like labels.sort(this.labelTextComparator); - * @param labelA a label object - * @param labelB a label object - * @return -1 if labelA comes before labelB - * 1 if labelB comes after labelB - * 0 of the labels are equal - */ - labelTextComparator(labelA: any, labelB: any): number { - if (labelA.text < labelB.text) { - return -1; - } else if (labelA.text > labelB.text) { - return 1; - } else { - if (labelA.color < labelB.color) { - return -1; - } else if (labelA.color > labelB.color) { - return 1; - } else { - if (labelA.pointX < labelB.pointX) { - return -1; - } else if (labelA.pointX > labelB.pointX) { - return 1; - } else { - if (labelA.pointY < labelB.pointY) { - return -1; - } else if (labelA.pointY > labelB.pointY) { - return 1; - } else { - return 0; - } - } - } - } - } - - deleteStarterLabels(): void { - if (confirm(this.$translate('label.areYouSureYouWantToDeleteAllTheStarterLabels'))) { - this.authoringComponentContent.labels = []; - this.componentChanged(); - } - } - - openColorViewer(): void { - this.$window.open('http://www.javascripter.net/faq/colornam.htm'); - } -} - -const LabelAuthoring = { - bindings: { - nodeId: '@', - componentId: '@' - }, - controller: LabelAuthoringController, - controllerAs: 'labelController', - templateUrl: 'wise5/components/label/authoring.html' -}; - -export default LabelAuthoring; diff --git a/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts b/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts index cceefafef9..e6bd14a03c 100644 --- a/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts +++ b/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts @@ -1,15 +1,18 @@ 'use strict'; import * as angular from 'angular'; -import { downgradeInjectable } from '@angular/upgrade/static'; +import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static'; import { LabelService } from './labelService'; -import LabelAuthoring from './labelAuthoring'; import { EditLabelAdvancedComponent } from './edit-label-advanced/edit-label-advanced.component'; +import { LabelAuthoring } from './label-authoring/label-authoring.component'; const labelAuthoringComponentModule = angular .module('labelAuthoringComponentModule', ['pascalprecht.translate']) .service('LabelService', downgradeInjectable(LabelService)) - .component('labelAuthoring', LabelAuthoring) + .directive( + 'labelAuthoring', + downgradeComponent({ component: LabelAuthoring }) as angular.IDirectiveFactory + ) .component('editLabelAdvanced', EditLabelAdvancedComponent) .config([ '$translatePartialLoaderProvider', From 6c76e3f59d8263895e122c043d16d93feba56b24 Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Mon, 25 Jan 2021 14:34:21 -0500 Subject: [PATCH 03/13] Ran i18n translation generation for Label authoring. #2895 --- src/main/webapp/site/src/messages.xlf | 222 ++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index 2419779b02..c7e6ac4b89 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -6024,6 +6024,10 @@ ../../wise5/components/draw/draw-authoring/draw-authoring.component.html 2 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 2 + ../../wise5/components/match/match-authoring/match-authoring.component.html 2 @@ -6047,6 +6051,10 @@ ../../wise5/components/draw/draw-authoring/draw-authoring.component.html 6 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 6 + ../../wise5/components/match/match-authoring/match-authoring.component.html 6 @@ -6105,6 +6113,10 @@ ../../wise5/components/draw/draw-authoring/draw-authoring.component.html 228 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 21 + ../../wise5/components/match/match-authoring/match-authoring.component.html 41 @@ -6189,6 +6201,10 @@ ../../wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.html 87 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 24 + ../../wise5/components/multipleChoice/multiple-choice-authoring/multiple-choice-authoring.component.html 68 @@ -6316,6 +6332,14 @@ app/authoring-tool/edit-component-tags/edit-component-tags.component.html 38 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 206 + + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 232 + ../../wise5/components/match/match-authoring/match-authoring.component.html 79 @@ -6378,6 +6402,10 @@ ../../wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.html 195 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 128 + (Optional) Create a starting state for the concept map by editing the "Student Preview" below and then saving here: @@ -6613,6 +6641,10 @@ ../../wise5/components/draw/draw-authoring/draw-authoring.component.html 277 + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 221 + Delete Starter Drawing @@ -6691,6 +6723,196 @@ 2 + + Background Image + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 13 + + + + Canvas Width (px) + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 31 + + + + Canvas Height (px) + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 38 + + + + Point Radius Size (px) + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 47 + + + + Font Size + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 54 + + + + Label Max Character Width + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 61 + + + + Can Student Create Labels + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 74 + + + + Enable Dots + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 83 + + + + Allow Student to Upload Image for Background + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 92 + + + + Starter Labels + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 99 + + + + Add Starter Label + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 103 + + + + add + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 106 + + + + There are no starter labels. Click the "Add Label" button to add a starter label. + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 113 + + + + Text + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 120 + + + + Enter Label Text Here + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 124 + + + + View Colors + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 137 + + + + Color Palette + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 140 + + + + Can Student Edit Label + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 151 + + + + Can Student Delete Label + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 160 + + + + Point Location + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 167 + + + + X + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 169 + + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 186 + + + + Y + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 176 + + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 193 + + + + Text Location + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 184 + + + + Delete Label + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 203 + + + + Save Starter Labels + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 218 + + + + Delete Starter Labels + + ../../wise5/components/label/label-authoring/label-authoring.component.html + 229 + + Choices From 77c6ad411f269fa3be5fda11439b6f1b5dd6d7c2 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Mon, 25 Jan 2021 11:50:01 -0800 Subject: [PATCH 04/13] Implemented unlinking Google account from WISE for teachers and students. #2870 --- .../teacher/TeacherAPIController.java | 8 - .../user/GoogleUserAPIController.java | 84 ++++ .../controllers/user/UserAPIController.java | 31 +- .../exception/InvalidPasswordExcpetion.java | 6 + src/main/resources/i18n/i18n.properties | 6 + .../copy-project-dialog.component.ts | 1 - .../edit-password.component.html | 10 +- .../edit-password.component.spec.ts | 146 ++++--- .../edit-password/edit-password.component.ts | 26 +- .../src/app/modules/shared/shared.module.ts | 10 +- ...link-google-account-confirm.component.html | 17 + ...link-google-account-confirm.component.scss | 3 + ...k-google-account-confirm.component.spec.ts | 37 ++ ...unlink-google-account-confirm.component.ts | 16 + ...ink-google-account-password.component.html | 46 +++ ...ink-google-account-password.component.scss | 3 + ...-google-account-password.component.spec.ts | 51 +++ ...nlink-google-account-password.component.ts | 38 ++ ...link-google-account-success.component.html | 12 + ...link-google-account-success.component.scss | 3 + ...unlink-google-account-success.component.ts | 18 + .../validators/password-match.validator.ts | 13 + .../src/app/services/user.service.spec.ts | 24 +- .../site/src/app/services/user.service.ts | 18 +- .../edit-profile/edit-profile.component.html | 14 +- .../edit-profile.component.spec.ts | 2 + .../edit-profile/edit-profile.component.ts | 30 +- .../edit-profile/edit-profile.component.html | 14 +- .../edit-profile.component.spec.ts | 2 + .../edit-profile/edit-profile.component.ts | 26 +- src/main/webapp/site/src/messages.xlf | 364 ++++++++++++------ .../user/GoogleUserAPIControllerTest.java | 108 ++++++ .../user/UserAPIControllerTest.java | 62 --- 33 files changed, 938 insertions(+), 311 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java create mode 100644 src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 79a5d8a107..72da0d69c3 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -14,7 +14,6 @@ import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.MessageSource; import org.springframework.security.access.annotation.Secured; import org.springframework.security.acls.model.Permission; import org.springframework.security.core.Authentication; @@ -38,7 +37,6 @@ import org.wise.portal.presentation.web.response.SimpleResponse; import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; -import org.wise.portal.service.mail.IMailFacade; /** * Teacher REST API @@ -55,12 +53,6 @@ public class TeacherAPIController extends UserAPIController { @Autowired private UserDetailsService userDetailsService; - @Autowired - protected IMailFacade mailService; - - @Autowired - protected MessageSource messageSource; - @Value("${google.clientId:}") private String googleClientId; diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java new file mode 100644 index 0000000000..d2cba48c11 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java @@ -0,0 +1,84 @@ +package org.wise.portal.presentation.web.controllers.user; + +import java.util.HashMap; +import java.util.Locale; + +import javax.mail.MessagingException; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.PersistentUserDetails; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.exception.InvalidPasswordExcpetion; + +@RestController +@RequestMapping("/api/google-user") +public class GoogleUserAPIController extends UserAPIController { + + @GetMapping("/check-user-exists") + boolean isGoogleIdExist(@RequestParam String googleUserId) { + return userService.retrieveUserByGoogleUserId(googleUserId) != null; + } + + @GetMapping("/check-user-matches") + boolean isGoogleIdMatches(@RequestParam String googleUserId, @RequestParam String userId) { + User user = userService.retrieveUserByGoogleUserId(googleUserId); + return user != null && user.getId().toString().equals(userId); + } + + @GetMapping("/get-user") + HashMap getUserByGoogleId(@RequestParam String googleUserId) { + User user = userService.retrieveUserByGoogleUserId(googleUserId); + HashMap response = new HashMap(); + if (user == null) { + response.put("status", "error"); + } else { + response.put("status", "success"); + response.put("userId", user.getId()); + response.put("username", user.getUserDetails().getUsername()); + response.put("firstName", user.getUserDetails().getFirstname()); + response.put("lastName", user.getUserDetails().getLastname()); + } + return response; + } + + @Secured("ROLE_USER") + @PostMapping("/unlink-account") + HashMap unlinkGoogleAccount(Authentication auth, @RequestParam String newPassword) + throws InvalidPasswordExcpetion { + if (newPassword.isEmpty()) { + throw new InvalidPasswordExcpetion(); + } + String username = auth.getName(); + User user = userService.retrieveUserByUsername(username); + ((PersistentUserDetails) user.getUserDetails()).setGoogleUserId(null); + userService.updateUserPassword(user, newPassword); + boolean isSendEmail = Boolean.parseBoolean(appProperties.getProperty("send_email_enabled", "false")); + if (isSendEmail && user.isTeacher()) { + this.sendUnlinkGoogleEmail((TeacherUserDetails) user.getUserDetails()); + } + return this.getUserInfo(auth, username); + } + + private void sendUnlinkGoogleEmail(TeacherUserDetails userDetails) { + String[] recipients = { userDetails.getEmailAddress() }; + String subject = messageSource.getMessage("unlink_google_account_success_email_subject", null, + "Successfully Unlinked Google Account", new Locale(userDetails.getLanguage())); + String username = userDetails.getUsername(); + String message = messageSource.getMessage("unlink_google_account_success_email_body", + new Object[]{username}, + "You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created. Your username is: " + username, + new Locale(userDetails.getLanguage())); + try { + mailService.postMail(recipients, subject, message, appProperties.getProperty("portalemailaddress")); + } catch (MessagingException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java index b6ef0251f3..21673c81d5 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.MessageSource; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; @@ -63,6 +64,9 @@ public class UserAPIController { @Autowired protected IMailFacade mailService; + @Autowired + protected MessageSource messageSource; + @Value("${google.clientId:}") protected String googleClientId = ""; @@ -189,33 +193,6 @@ List> getSupportedLanguages() { return langs; } - @GetMapping("/check-google-user-exists") - boolean isGoogleIdExist(@RequestParam String googleUserId) { - return userService.retrieveUserByGoogleUserId(googleUserId) != null; - } - - @GetMapping("/check-google-user-matches") - boolean isGoogleIdMatches(@RequestParam String googleUserId, @RequestParam String userId) { - User user = userService.retrieveUserByGoogleUserId(googleUserId); - return user != null && user.getId().toString().equals(userId); - } - - @GetMapping("/google-user") - HashMap getUserByGoogleId(@RequestParam String googleUserId) { - User user = userService.retrieveUserByGoogleUserId(googleUserId); - HashMap response = new HashMap(); - if (user == null) { - response.put("status", "error"); - } else { - response.put("status", "success"); - response.put("userId", user.getId()); - response.put("username", user.getUserDetails().getUsername()); - response.put("firstName", user.getUserDetails().getFirstname()); - response.put("lastName", user.getUserDetails().getLastname()); - } - return response; - } - private String getLanguageName(String localeString) { if (localeString.toLowerCase().equals("zh_tw")) { return "Chinese (Traditional)"; diff --git a/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java b/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java new file mode 100644 index 0000000000..6d6dac6cbc --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java @@ -0,0 +1,6 @@ +package org.wise.portal.presentation.web.exception; + +public class InvalidPasswordExcpetion extends Exception { + + private static final long serialVersionUID = 1L; +} diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties index 7c050e8dcb..1ffd28e71c 100644 --- a/src/main/resources/i18n/i18n.properties +++ b/src/main/resources/i18n/i18n.properties @@ -308,6 +308,12 @@ teacher_cap.description=Text for the word "Teacher" team_cap=Team team_cap.description=Text for the word "Team" +unlink_google_account_success_email_subject=Successfully Unlinked Google Account +unlink_google_account_success_email_subject.description=Subject text in email to notify user about successfuly unlinking google account + +unlink_google_account_success_email_body=You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.\n\nYour username is: {0}\n\nThank you for using WISE,\nWISE Team +unlink_google_account_success_email_body.description=Body text in email to notify user about successfully unlinking google account + # Root (/) Pages # accountmenu.forgot=Forgot Username or Password? diff --git a/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts b/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts index 8a2db347d5..5347591222 100644 --- a/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts +++ b/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts @@ -5,7 +5,6 @@ import { finalize } from 'rxjs/operators'; import { LibraryProject } from '../libraryProject'; import { LibraryService } from '../../../services/library.service'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { Subscription } from 'rxjs'; @Component({ selector: 'app-copy-project-dialog', diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html index 1a47a25674..71af1f2b7d 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html @@ -57,5 +57,13 @@ -

This account was created using Google and doesn't use a WISE password. If you would like to unlink your Google account, please contact us.

+

This account was created using Google and doesn't use a WISE password.

+

+ +

diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts index 6d33a734a5..fd68f182c1 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts @@ -1,14 +1,17 @@ import { async, 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, of } from 'rxjs'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ReactiveFormsModule } from '@angular/forms'; -import { NO_ERRORS_SCHEMA, Provider } from '@angular/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { By } from '@angular/platform-browser'; import { User } from '../../../domain/user'; import { configureTestSuite } from 'ng-bullet'; +import { MatDialogModule } from '@angular/material/dialog'; +const CORRECT_OLD_PASS = 'a'; +const INCORRECT_OLD_PASS = 'b'; export class MockUserService { getUser(): BehaviorSubject { @@ -22,13 +25,13 @@ export class MockUserService { } changePassword(username, oldPassword, newPassword) { - if (oldPassword === 'a') { - return Observable.create((observer) => { + if (oldPassword === CORRECT_OLD_PASS) { + return new Observable((observer) => { observer.next({ status: 'success', messageCode: 'passwordChanged' }); observer.complete(); }); } else { - return Observable.create((observer) => { + return new Observable((observer) => { observer.next({ status: 'error', messageCode: 'incorrectPassword' }); observer.complete(); }); @@ -36,22 +39,26 @@ export class MockUserService { } } -describe('EditPasswordComponent', () => { - let component: EditPasswordComponent; - let fixture: ComponentFixture; +let component: EditPasswordComponent; +let fixture: ComponentFixture; + +const getSubmitButton = () => { + return fixture.debugElement.nativeElement.querySelector('button[type="submit"]'); +}; - const getSubmitButton = () => { - return fixture.debugElement.nativeElement.querySelector('button[type="submit"]'); - }; +const getUnlinkGoogleAccountButton = () => { + return fixture.debugElement.nativeElement.querySelector('button[id="unlinkGoogleAccount"]'); +}; - const getForm = () => { - return fixture.debugElement.query(By.css('form')); - }; +const getForm = () => { + return fixture.debugElement.query(By.css('form')); +}; +describe('EditPasswordComponent', () => { configureTestSuite(() => { TestBed.configureTestingModule({ declarations: [EditPasswordComponent], - imports: [BrowserAnimationsModule, ReactiveFormsModule, MatSnackBarModule], + imports: [BrowserAnimationsModule, ReactiveFormsModule, MatSnackBarModule, MatDialogModule], providers: [{ provide: UserService, useValue: new MockUserService() }], schemas: [NO_ERRORS_SCHEMA] }); @@ -63,61 +70,60 @@ describe('EditPasswordComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + initialState_disableSubmitButton(); + validForm_enableSubmitButton(); + passwordMismatch_disableSubmitButtonAndInvalidateForm(); + oldPasswordIncorrect_disableSubmitButtonAndShowError(); + formSubmit_disableSubmitButton(); + passwordChanged_handleResponse(); + incorrectPassword_showError(); + notGoogleUser_showUnlinkOption(); + unlinkGoogleButtonClick_showDialog(); +}); +function initialState_disableSubmitButton() { it('should disable submit button and invalidate form on initial state', () => { expect(component.changePasswordFormGroup.valid).toBeFalsy(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + expectSubmitButtonDisabled(); }); +} +function validForm_enableSubmitButton() { it('should enable submit button when form is valid', () => { - component.changePasswordFormGroup.get('oldPassword').setValue('a'); - component.newPasswordFormGroup.get('newPassword').setValue('b'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('b'); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(false); + setPasswords(CORRECT_OLD_PASS, 'b', 'b'); + 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', () => { - component.changePasswordFormGroup.get('oldPassword').setValue('a'); - component.newPasswordFormGroup.get('newPassword').setValue('a'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('b'); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + setPasswords(CORRECT_OLD_PASS, 'a', 'b'); + expectSubmitButtonDisabled(); expect(component.changePasswordFormGroup.valid).toBeFalsy(); }); +} +function oldPasswordIncorrect_disableSubmitButtonAndShowError() { it('should disable submit button and set incorrectPassword error when old password is incorrect', async () => { - component.changePasswordFormGroup.get('oldPassword').setValue('b'); - component.newPasswordFormGroup.get('newPassword').setValue('c'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('c'); - const form = getForm(); - form.triggerEventHandler('submit', null); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + setPasswords(INCORRECT_OLD_PASS, 'c', 'c'); + submitForm(); + expectSubmitButtonDisabled(); expect(component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword')).toBe( true ); }); +} +function formSubmit_disableSubmitButton() { it('should disable submit button when form is successfully submitted', async () => { - component.changePasswordFormGroup.get('oldPassword').setValue('a'); - component.newPasswordFormGroup.get('newPassword').setValue('b'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('b'); - const form = getForm(); - form.triggerEventHandler('submit', null); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + setPasswords(CORRECT_OLD_PASS, 'b', 'b'); + submitForm(); + expectSubmitButtonDisabled(); }); +} +function passwordChanged_handleResponse() { it('should handle the change password response when the password was successfully changed', () => { const resetFormSpy = spyOn(component, 'resetForm'); const snackBarSpy = spyOn(component.snackBar, 'open'); @@ -129,7 +135,9 @@ describe('EditPasswordComponent', () => { expect(resetFormSpy).toHaveBeenCalled(); expect(snackBarSpy).toHaveBeenCalled(); }); +} +function incorrectPassword_showError() { it('should handle the change password response when the password was incorrect', () => { const response = { status: 'error', @@ -140,4 +148,44 @@ describe('EditPasswordComponent', () => { true ); }); -}); +} + +function notGoogleUser_showUnlinkOption() { + it('should hide show option to unlink google account if the user is not a google user', () => { + 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(); + }); +} + +export function expectSubmitButtonDisabled() { + expect(getSubmitButton().disabled).toBe(true); +} + +function expectSubmitButtonEnabled() { + expect(getSubmitButton().disabled).toBe(false); +} + +function submitForm() { + getForm().triggerEventHandler('submit', null); + fixture.detectChanges(); +} + +function setGoogleUser() { + component.isGoogleUser = true; + fixture.detectChanges(); +} + +function setPasswords(oldPass: string, newPass: string, newPassConfirm: string) { + component.changePasswordFormGroup.get('oldPassword').setValue(oldPass); + component.newPasswordFormGroup.get('newPassword').setValue(newPass); + component.newPasswordFormGroup.get('confirmNewPassword').setValue(newPassConfirm); + fixture.detectChanges(); +} diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts index f22788cc5f..8df43f729b 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts @@ -1,15 +1,18 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; +import { MatDialog } from '@angular/material/dialog'; 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 { passwordMatchValidator } from '../validators/password-match.validator'; @Component({ selector: 'app-edit-password', templateUrl: './edit-password.component.html', styleUrls: ['./edit-password.component.scss'] }) -export class EditPasswordComponent implements OnInit { +export class EditPasswordComponent { @ViewChild('changePasswordForm', { static: false }) changePasswordForm; isSaving: boolean = false; isGoogleUser: boolean = false; @@ -19,7 +22,7 @@ export class EditPasswordComponent implements OnInit { newPassword: new FormControl('', [Validators.required]), confirmNewPassword: new FormControl('', [Validators.required]) }, - { validator: this.passwordMatchValidator } + { validator: passwordMatchValidator } ); changePasswordFormGroup: FormGroup = this.fb.group({ @@ -30,6 +33,7 @@ export class EditPasswordComponent implements OnInit { constructor( private fb: FormBuilder, private userService: UserService, + public dialog: MatDialog, public snackBar: MatSnackBar ) {} @@ -39,18 +43,6 @@ export class EditPasswordComponent implements OnInit { }); } - passwordMatchValidator(passwordsFormGroup: FormGroup) { - const newPassword = passwordsFormGroup.get('newPassword').value; - const confirmNewPassword = passwordsFormGroup.get('confirmNewPassword').value; - if (newPassword === confirmNewPassword) { - return null; - } else { - const error = { passwordDoesNotMatch: true }; - passwordsFormGroup.controls['confirmNewPassword'].setErrors(error); - return error; - } - } - saveChanges() { this.isSaving = true; const oldPassword: string = this.getControlFieldValue('oldPassword'); @@ -90,6 +82,10 @@ export class EditPasswordComponent implements OnInit { } } + unlinkGoogleAccount() { + this.dialog.open(UnlinkGoogleAccountConfirmComponent); + } + resetForm() { this.changePasswordForm.resetForm(); } diff --git a/src/main/webapp/site/src/app/modules/shared/shared.module.ts b/src/main/webapp/site/src/app/modules/shared/shared.module.ts index 5ec6a150df..807f04e4e7 100644 --- a/src/main/webapp/site/src/app/modules/shared/shared.module.ts +++ b/src/main/webapp/site/src/app/modules/shared/shared.module.ts @@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -13,6 +14,7 @@ import { MatSelectModule } from '@angular/material/select'; const materialModules = [ MatButtonModule, MatCardModule, + MatDialogModule, MatIconModule, MatInputModule, MatFormFieldModule, @@ -26,6 +28,9 @@ import { HeroSectionComponent } from './hero-section/hero-section.component'; import { SearchBarComponent } from './search-bar/search-bar.component'; import { SelectMenuComponent } from './select-menu/select-menu.component'; import { EditPasswordComponent } from './edit-password/edit-password.component'; +import { UnlinkGoogleAccountConfirmComponent } from './unlink-google-account-confirm/unlink-google-account-confirm.component'; +import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password/unlink-google-account-password.component'; +import { UnlinkGoogleAccountSuccessComponent } from './unlink-google-account-success/unlink-google-account-success.component'; @NgModule({ imports: [ @@ -52,7 +57,10 @@ import { EditPasswordComponent } from './edit-password/edit-password.component'; HeroSectionComponent, SearchBarComponent, SelectMenuComponent, - EditPasswordComponent + EditPasswordComponent, + UnlinkGoogleAccountConfirmComponent, + UnlinkGoogleAccountPasswordComponent, + UnlinkGoogleAccountSuccessComponent ] }) export class SharedModule {} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html new file mode 100644 index 0000000000..1787a19ea9 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html @@ -0,0 +1,17 @@ +

Unlink Google Account

+ +
+

+ If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. +

+

Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue?

+
+
+ + + + diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss new file mode 100644 index 0000000000..0af6d8c18e --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss @@ -0,0 +1,3 @@ +.info-note { + font-weight: bold; +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts new file mode 100644 index 0000000000..400a72a901 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts @@ -0,0 +1,37 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogModule } from '@angular/material/dialog'; +import { configureTestSuite } from 'ng-bullet'; +import { UnlinkGoogleAccountConfirmComponent } from './unlink-google-account-confirm.component'; + +let component: UnlinkGoogleAccountConfirmComponent; +let fixture: ComponentFixture; + +describe('UnlinkGoogleAccountConfirmComponent', () => { + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [UnlinkGoogleAccountConfirmComponent], + imports: [MatDialogModule], + providers: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UnlinkGoogleAccountConfirmComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + continue_closeAllDialogsAndOpenChangePasswordDialog(); +}); + +function continue_closeAllDialogsAndOpenChangePasswordDialog() { + it('continue() should closeAllDialogs and open a new dialog to edit password', () => { + const closeAllDialogSpy = spyOn(component.dialog, 'closeAll'); + const openDialogSpy = spyOn(component.dialog, 'open'); + component.continue(); + expect(closeAllDialogSpy).toHaveBeenCalled(); + expect(openDialogSpy).toHaveBeenCalled(); + }); +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts new file mode 100644 index 0000000000..73a3e6d7f5 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { UnlinkGoogleAccountPasswordComponent } from '../unlink-google-account-password/unlink-google-account-password.component'; + +@Component({ + styleUrls: ['./unlink-google-account-confirm.component.scss'], + templateUrl: './unlink-google-account-confirm.component.html' +}) +export class UnlinkGoogleAccountConfirmComponent { + constructor(public dialog: MatDialog) {} + + continue() { + this.dialog.closeAll(); + this.dialog.open(UnlinkGoogleAccountPasswordComponent); + } +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html new file mode 100644 index 0000000000..88f3989891 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html @@ -0,0 +1,46 @@ +

Unlink Google Account

+ +
+

Create a WISE password:

+
+

+ + New Password + + New Password required + +

+

+ + Confirm New Password + + Confirm Password required + Passwords do not match + +

+
+
+
+ + + + diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss new file mode 100644 index 0000000000..efd343d97f --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss @@ -0,0 +1,3 @@ +mat-dialog-content { + min-width: 600px; +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts new file mode 100644 index 0000000000..d10a68730e --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts @@ -0,0 +1,51 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatDialogModule } from '@angular/material/dialog'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { configureTestSuite } from 'ng-bullet'; +import { Subscription } from 'rxjs'; +import { UserService } from '../../../services/user.service'; +import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password.component'; + +class MockUserService { + unlinkGoogleUser(newPassword: string) { + return new Subscription(); + } +} + +let component: UnlinkGoogleAccountPasswordComponent; +let fixture: ComponentFixture; +let userService = new MockUserService(); + +describe('UnlinkGoogleAccountPasswordComponent', () => { + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [UnlinkGoogleAccountPasswordComponent], + imports: [BrowserAnimationsModule, ReactiveFormsModule, MatDialogModule], + providers: [{ provide: UserService, useValue: userService }], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + beforeEach(() => { + fixture = TestBed.createComponent(UnlinkGoogleAccountPasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + formSubmit_callUserServiceUnlinkGoogleUserFunction(); +}); + +function formSubmit_callUserServiceUnlinkGoogleUserFunction() { + it('should call UserService.UnlinkGoogleUserFunction when form is submitted', () => { + const unlinkFunctionSpy = spyOn(userService, 'unlinkGoogleUser').and.returnValue( + new Subscription() + ); + const newPassword = 'aloha'; + component.newPasswordFormGroup.setValue({ + newPassword: newPassword, + confirmNewPassword: newPassword + }); + component.submit(); + expect(unlinkFunctionSpy).toHaveBeenCalledWith(newPassword); + }); +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts new file mode 100644 index 0000000000..152b168f88 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { UserService } from '../../../services/user.service'; +import { UnlinkGoogleAccountSuccessComponent } from '../unlink-google-account-success/unlink-google-account-success.component'; +import { passwordMatchValidator } from '../validators/password-match.validator'; + +@Component({ + styleUrls: ['./unlink-google-account-password.component.scss'], + templateUrl: './unlink-google-account-password.component.html' +}) +export class UnlinkGoogleAccountPasswordComponent { + isSaving: boolean = false; + newPasswordFormGroup: FormGroup = this.fb.group( + { + newPassword: new FormControl('', [Validators.required]), + confirmNewPassword: new FormControl('', [Validators.required]) + }, + { validator: passwordMatchValidator } + ); + + constructor( + private fb: FormBuilder, + public dialog: MatDialog, + private userService: UserService + ) {} + + submit() { + this.isSaving = true; + this.userService + .unlinkGoogleUser(this.newPasswordFormGroup.get('newPassword').value) + .add(() => { + this.isSaving = false; + this.dialog.closeAll(); + this.dialog.open(UnlinkGoogleAccountSuccessComponent); + }); + } +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html new file mode 100644 index 0000000000..e985c5bb42 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html @@ -0,0 +1,12 @@ +

Unlink Google Account

+ +
+

+ Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. +

+

Your username is: {{username}}

+
+
+ + + diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss new file mode 100644 index 0000000000..0af6d8c18e --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss @@ -0,0 +1,3 @@ +.info-note { + font-weight: bold; +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts new file mode 100644 index 0000000000..1701865273 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { Teacher } from '../../../domain/teacher'; +import { UserService } from '../../../services/user.service'; + +@Component({ + styleUrls: ['unlink-google-account-success.component.scss'], + templateUrl: 'unlink-google-account-success.component.html' +}) +export class UnlinkGoogleAccountSuccessComponent { + username: string; + + constructor(private userService: UserService) {} + + ngOnInit() { + const user = this.userService.getUser().getValue(); + this.username = user.username; + } +} diff --git a/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts b/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts new file mode 100644 index 0000000000..89ac014720 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts @@ -0,0 +1,13 @@ +import { FormGroup } from '@angular/forms'; + +export function passwordMatchValidator(passwordsFormGroup: FormGroup) { + const newPassword = passwordsFormGroup.get('newPassword').value; + const confirmNewPassword = passwordsFormGroup.get('confirmNewPassword').value; + if (newPassword === confirmNewPassword) { + return null; + } else { + const error = { passwordDoesNotMatch: true }; + passwordsFormGroup.controls['confirmNewPassword'].setErrors(error); + return error; + } +} diff --git a/src/main/webapp/site/src/app/services/user.service.spec.ts b/src/main/webapp/site/src/app/services/user.service.spec.ts index 5cb27e9c91..8671e76ce5 100644 --- a/src/main/webapp/site/src/app/services/user.service.spec.ts +++ b/src/main/webapp/site/src/app/services/user.service.spec.ts @@ -1,8 +1,10 @@ -import { TestBed, inject } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { UserService } from './user.service'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { ConfigService } from './config.service'; +let service: UserService; +let http: HttpTestingController; export class MockConfigService {} describe('UserService', () => { @@ -11,9 +13,21 @@ describe('UserService', () => { providers: [UserService, { provide: ConfigService, useClass: MockConfigService }], imports: [HttpClientTestingModule] }); + service = TestBed.inject(UserService); + http = TestBed.inject(HttpTestingController); }); + unlinkGoogleAccount_postToUrl(); +}); - it('should be created', inject([UserService, ConfigService], (service: UserService) => { - expect(service).toBeTruthy(); +function unlinkGoogleAccount_postToUrl() { + it('unlinkGoogleAccount() should make POST request to unlink google account', fakeAsync(() => { + const newPassword = 'my new pass'; + service.unlinkGoogleUser(newPassword); + const unlinkRequest = http.expectOne({ + url: '/api/google-user/unlink-account', + method: 'POST' + }); + unlinkRequest.flush({ response: 'success' }); + tick(); })); -}); +} diff --git a/src/main/webapp/site/src/app/services/user.service.ts b/src/main/webapp/site/src/app/services/user.service.ts index 65d7695e52..b419803a08 100644 --- a/src/main/webapp/site/src/app/services/user.service.ts +++ b/src/main/webapp/site/src/app/services/user.service.ts @@ -12,13 +12,14 @@ import { Student } from '../domain/student'; export class UserService { private userUrl = '/api/user/info'; private user$: BehaviorSubject = new BehaviorSubject(null); - private checkGoogleUserExistsUrl = '/api/user/check-google-user-exists'; - private checkGoogleUserMatchesUrl = '/api/user/check-google-user-matches'; - private googleUserUrl = '/api/user/google-user'; + private checkGoogleUserExistsUrl = '/api/google-user/check-user-exists'; + private checkGoogleUserMatchesUrl = '/api/google-user/check-user-matches'; + private googleUserUrl = '/api/google-user/get-user'; private checkAuthenticationUrl = '/api/user/check-authentication'; private changePasswordUrl = '/api/user/password'; private languagesUrl = '/api/user/languages'; private contactUrl = '/api/contact'; + private unlinkGoogleAccountUrl = '/api/google-user/unlink-account'; isAuthenticated = false; isRecaptchaRequired = false; redirectUrl: string; // redirect here after logging in @@ -126,6 +127,17 @@ export class UserService { return this.http.get(this.checkGoogleUserMatchesUrl, { params: params }); } + unlinkGoogleUser(newPassword: string) { + const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'); + let body = new HttpParams(); + body = body.set('newPassword', newPassword); + return this.http + .post(this.unlinkGoogleAccountUrl, body, { headers: headers }) + .subscribe((user) => { + this.user$.next(user); + }); + } + getUserByGoogleId(googleUserId: string) { let params = new HttpParams(); params = params.set('googleUserId', googleUserId); diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html index e69a78b318..14fe2c4609 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html @@ -42,7 +42,9 @@ Language required

-
+
+ +
This profile is linked to a Google account.
+ +
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts index c338a4a343..5c425f8766 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts @@ -13,6 +13,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { Student } from '../../../domain/student'; import { configureTestSuite } from 'ng-bullet'; +import { MatDialogModule } from '@angular/material/dialog'; export class MockUserService { user: User; @@ -76,6 +77,7 @@ describe('EditProfileComponent', () => { imports: [ BrowserAnimationsModule, ReactiveFormsModule, + MatDialogModule, MatInputModule, MatSelectModule, MatSnackBarModule diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts index ea256cd802..afe3d42111 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts @@ -5,18 +5,22 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { Student } from '../../../domain/student'; import { UserService } from '../../../services/user.service'; import { StudentService } from '../../student.service'; +import { Subscription } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { UnlinkGoogleAccountConfirmComponent } from '../../../modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component'; @Component({ selector: 'app-edit-profile', templateUrl: './edit-profile.component.html', styleUrls: ['./edit-profile.component.scss'] }) -export class EditProfileComponent implements OnInit { +export class EditProfileComponent { user: Student; languages: object[]; changed: boolean = false; isSaving: boolean = false; - + isGoogleUser: boolean = false; + userSubscription: Subscription; editProfileFormGroup: FormGroup = this.fb.group({ firstName: new FormControl({ value: '', disabled: true }, [Validators.required]), lastName: new FormControl({ value: '', disabled: true }, [Validators.required]), @@ -28,6 +32,7 @@ export class EditProfileComponent implements OnInit { private fb: FormBuilder, private studentService: StudentService, private userService: UserService, + public dialog: MatDialog, public snackBar: MatSnackBar ) { this.user = this.getUser().getValue(); @@ -38,10 +43,6 @@ export class EditProfileComponent implements OnInit { this.userService.getLanguages().subscribe((response) => { this.languages = response; }); - - this.editProfileFormGroup.valueChanges.subscribe(() => { - this.changed = true; - }); } getUser() { @@ -52,7 +53,18 @@ export class EditProfileComponent implements OnInit { this.editProfileFormGroup.controls[name].setValue(value); } - ngOnInit() {} + ngOnInit() { + this.editProfileFormGroup.valueChanges.subscribe(() => { + this.changed = true; + }); + this.userSubscription = this.userService.getUser().subscribe((user) => { + this.isGoogleUser = user.isGoogleUser; + }); + } + + ngOnDestroy() { + this.userSubscription.unsubscribe(); + } saveChanges() { this.isSaving = true; @@ -84,4 +96,8 @@ export class EditProfileComponent implements OnInit { } this.isSaving = false; } + + unlinkGoogleAccount() { + this.dialog.open(UnlinkGoogleAccountConfirmComponent); + } } diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html index b4f5bd0f54..a551190844 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html @@ -109,7 +109,9 @@

-
+
+ +
This profile is linked to a Google account.
+ +
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts index e8fe132d86..05b8ad23a2 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts @@ -13,6 +13,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { User } from '../../../domain/user'; import { configureTestSuite } from 'ng-bullet'; +import { MatDialogModule } from '@angular/material/dialog'; export class MockUserService { user: User; @@ -89,6 +90,7 @@ describe('EditProfileComponent', () => { imports: [ BrowserAnimationsModule, ReactiveFormsModule, + MatDialogModule, MatInputModule, MatSelectModule, MatSnackBarModule diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts index 6ed9c50c25..a1c09fd71f 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts @@ -5,13 +5,16 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { UserService } from '../../../services/user.service'; import { Teacher } from '../../../domain/teacher'; import { TeacherService } from '../../teacher.service'; +import { MatDialog } from '@angular/material/dialog'; +import { UnlinkGoogleAccountConfirmComponent } from '../../../modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component'; +import { Subscription } from 'rxjs'; @Component({ selector: 'app-edit-profile', templateUrl: './edit-profile.component.html', styleUrls: ['./edit-profile.component.scss'] }) -export class EditProfileComponent implements OnInit { +export class EditProfileComponent { user: Teacher; schoolLevels: any[] = [ { id: 'ELEMENTARY_SCHOOL', label: $localize`Elementary School` }, @@ -23,6 +26,8 @@ export class EditProfileComponent implements OnInit { languages: object[]; changed: boolean = false; isSaving: boolean = false; + isGoogleUser: boolean = false; + userSubscription: Subscription; editProfileFormGroup: FormGroup = this.fb.group({ firstName: new FormControl({ value: '', disabled: true }, [Validators.required]), @@ -41,6 +46,7 @@ export class EditProfileComponent implements OnInit { private fb: FormBuilder, private teacherService: TeacherService, private userService: UserService, + public dialog: MatDialog, public snackBar: MatSnackBar ) { this.user = this.getUser().getValue(); @@ -57,10 +63,6 @@ export class EditProfileComponent implements OnInit { this.userService.getLanguages().subscribe((response) => { this.languages = response; }); - - this.editProfileFormGroup.valueChanges.subscribe(() => { - this.changed = true; - }); } getUser() { @@ -71,7 +73,19 @@ export class EditProfileComponent implements OnInit { this.editProfileFormGroup.controls[name].setValue(value); } - ngOnInit() {} + ngOnInit() { + this.editProfileFormGroup.valueChanges.subscribe(() => { + this.changed = true; + }); + + this.userSubscription = this.userService.getUser().subscribe((user) => { + this.isGoogleUser = user.isGoogleUser; + }); + } + + ngOnDestroy() { + this.userSubscription.unsubscribe(); + } saveChanges() { this.isSaving = true; diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index e6b1586225..1feee2c605 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -236,29 +236,87 @@ 10 - - Current Password + + Unlink Google Account + + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 1 + + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 1 + + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 1 + app/modules/shared/edit-password/edit-password.component.html - 8 + 66 + + + app/student/account/edit-profile/edit-profile.component.html + 64 + + + app/teacher/account/edit-profile/edit-profile.component.html + 131 - - Current Password required + + Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. - app/modules/shared/edit-password/edit-password.component.html - 15 + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 4 - - Current Password is incorrect + + Your username is: - app/modules/shared/edit-password/edit-password.component.html - 16 + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 7 + + + + Done + + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 11 + + + app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html + 75 + + + app/teacher/create-run-dialog/create-run-dialog.component.html + 85 + + + app/modules/library/share-project-dialog/share-project-dialog.component.html + 70 + + + app/teacher/share-run-dialog/share-run-dialog.component.html + 102 + + + app/teacher/run-settings-dialog/run-settings-dialog.component.html + 76 + + + + Create a WISE password: + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 4 New Password + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 8 + app/modules/shared/edit-password/edit-password.component.html 22 @@ -266,6 +324,10 @@ New Password required + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 15 + app/modules/shared/edit-password/edit-password.component.html 29 @@ -273,6 +335,10 @@ Confirm New Password + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 20 + app/modules/shared/edit-password/edit-password.component.html 34 @@ -280,6 +346,10 @@ Confirm Password required + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 27 + app/modules/shared/edit-password/edit-password.component.html 41 @@ -303,11 +373,155 @@ Passwords do not match + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 28 + app/modules/shared/edit-password/edit-password.component.html 42 + + Cancel + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 35 + + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 11 + + + app/modules/library/copy-project-dialog/copy-project-dialog.component.html + 11 + + + app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html + 47 + + + app/teacher/create-run-dialog/create-run-dialog.component.html + 63 + + + app/teacher/use-with-class-warning-dialog/use-with-class-warning-dialog.component.html + 15 + + + app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html + 20 + + + app/student/add-project-dialog/add-project-dialog.component.html + 23 + + + app/student/team-sign-in-dialog/team-sign-in-dialog.component.html + 68 + + + app/teacher/share-run-dialog/share-run-dialog.component.html + 100 + + + app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html + 62 + + + app/authoring-tool/import-step/choose-import-step-location/choose-import-step-location.component.html + 40 + + + app/authoring-tool/add-component/choose-new-component/choose-new-component.component.html + 23 + + + app/authoring-tool/add-component/choose-new-component-location/choose-new-component-location.component.html + 41 + + + + Submit + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 43 + + + app/contact/contact-form/contact-form.component.html + 112 + + + app/forgot/student/forgot-student-password/forgot-student-password.component.html + 25 + + + app/forgot/teacher/forgot-teacher-username/forgot-teacher-username.component.html + 25 + + + app/forgot/teacher/forgot-teacher-password/forgot-teacher-password.component.html + 25 + + + app/forgot/student/forgot-student-password-security/forgot-student-password-security.component.html + 28 + + + app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.html + 41 + + + app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.html + 39 + + + app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.html + 26 + + + + If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 4 + + + + Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue? + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 7 + + + + Continue + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 15 + + + + Current Password + + app/modules/shared/edit-password/edit-password.component.html + 8 + + + + Current Password required + + app/modules/shared/edit-password/edit-password.component.html + 15 + + + + Current Password is incorrect + + app/modules/shared/edit-password/edit-password.component.html + 16 + + Change Password @@ -323,8 +537,8 @@ 7 - - This account was created using Google and doesn't use a WISE password. If you would like to unlink your Google account, please contact us. + + This account was created using Google and doesn't use a WISE password. app/modules/shared/edit-password/edit-password.component.html 60 @@ -1068,57 +1282,6 @@ 7 - - Cancel - - app/modules/library/copy-project-dialog/copy-project-dialog.component.html - 11 - - - app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html - 47 - - - app/teacher/create-run-dialog/create-run-dialog.component.html - 63 - - - app/teacher/use-with-class-warning-dialog/use-with-class-warning-dialog.component.html - 15 - - - app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html - 20 - - - app/student/add-project-dialog/add-project-dialog.component.html - 23 - - - app/student/team-sign-in-dialog/team-sign-in-dialog.component.html - 68 - - - app/teacher/share-run-dialog/share-run-dialog.component.html - 100 - - - app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html - 62 - - - app/authoring-tool/import-step/choose-import-step-location/choose-import-step-location.component.html - 40 - - - app/authoring-tool/add-component/choose-new-component/choose-new-component.component.html - 23 - - - app/authoring-tool/add-component/choose-new-component-location/choose-new-component-location.component.html - 41 - - Copy @@ -1244,29 +1407,6 @@ 69 - - Done - - app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html - 75 - - - app/teacher/create-run-dialog/create-run-dialog.component.html - 85 - - - app/modules/library/share-project-dialog/share-project-dialog.component.html - 70 - - - app/teacher/share-run-dialog/share-run-dialog.component.html - 102 - - - app/teacher/run-settings-dialog/run-settings-dialog.component.html - 76 - - Use with Class @@ -2282,41 +2422,6 @@ 104 - - Submit - - app/contact/contact-form/contact-form.component.html - 112 - - - app/forgot/student/forgot-student-password/forgot-student-password.component.html - 25 - - - app/forgot/teacher/forgot-teacher-username/forgot-teacher-username.component.html - 25 - - - app/forgot/teacher/forgot-teacher-password/forgot-teacher-password.component.html - 25 - - - app/forgot/student/forgot-student-password-security/forgot-student-password-security.component.html - 28 - - - app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.html - 41 - - - app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.html - 39 - - - app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.html - 26 - - WISE Features @@ -5287,11 +5392,22 @@ Save Changes app/student/account/edit-profile/edit-profile.component.html - 53 + 55 app/teacher/account/edit-profile/edit-profile.component.html - 120 + 122 + + + + This profile is linked to a Google account. + + app/student/account/edit-profile/edit-profile.component.html + 58 + + + app/teacher/account/edit-profile/edit-profile.component.html + 125 diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java new file mode 100644 index 0000000000..b66900f8ff --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java @@ -0,0 +1,108 @@ +package org.wise.portal.presentation.web.controllers.user; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.HashMap; + +import org.easymock.TestSubject; +import org.junit.Test; +import org.wise.portal.presentation.web.exception.InvalidPasswordExcpetion; + +public class GoogleUserAPIControllerTest extends UserAPIControllerTest { + + @TestSubject + private GoogleUserAPIController controller = new GoogleUserAPIController(); + + @Test + public void isGoogleIdExist_GoogleUserExists_ReturnTrue() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); + replay(userService); + assertTrue(controller.isGoogleIdExist(STUDENT1_GOOGLE_ID)); + verify(userService); + } + + @Test + public void isGoogleIdExist_InvalidGoogleUserId_ReturnFalse() { + String invalidGoogleId = "google-id-not-exists-in-db"; + expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); + replay(userService); + assertFalse(controller.isGoogleIdExist(invalidGoogleId)); + verify(userService); + } + + @Test + public void isGoogleIdMatches_GoogleUserIdAndUserIdMatch_ReturnTrue() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); + replay(userService); + assertTrue(controller.isGoogleIdMatches(STUDENT1_GOOGLE_ID, student1Id.toString())); + verify(userService); + } + + @Test + public void isGoogleIdMatches_InvalidGoogleUserId_ReturnFalse() { + String invalidGoogleId = "google-id-not-exists-in-db"; + expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); + replay(userService); + assertFalse(controller.isGoogleIdMatches(invalidGoogleId, student1Id.toString())); + verify(userService); + } + + @Test + public void isGoogleIdMatches_GoogleUserIdAndUserIdDoNotMatch_ReturnFalse() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(teacher1); + replay(userService); + assertFalse(controller.isGoogleIdMatches(STUDENT1_GOOGLE_ID, teacher1.toString())); + verify(userService); + } + + @Test + public void getUserByGoogleId_GoogleUserExists_ReturnSuccessResponse() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); + replay(userService); + HashMap response = controller.getUserByGoogleId(STUDENT1_GOOGLE_ID); + assertEquals("success", response.get("status")); + assertEquals(student1.getId(), response.get("userId")); + verify(userService); + } + + @Test + public void getUserByGoogleId_InvalidGoogleUserId_ReturnErrorResponse() { + String invalidGoogleId = "google-id-not-exists-in-db"; + expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); + replay(userService); + HashMap response = controller.getUserByGoogleId(invalidGoogleId); + assertEquals("error", response.get("status")); + verify(userService); + } + + @Test + public void unlinkGoogleAccount_InvalidNewPassword_ThrowException() { + expect(userService.retrieveUserByUsername(STUDENT_USERNAME)).andReturn(student1); + replay(userService); + String newPass = ""; + try { + controller.unlinkGoogleAccount(studentAuth, newPass); + fail("InvalidPasswordException was expected"); + } catch (Exception e) { + } + } + + @Test + public void unlinkGoogleAccount_ValidNewPassword_ReturnUpdatedUserMap() + throws InvalidPasswordExcpetion { + String newPassword = "my new pass"; + assertTrue(student1.getUserDetails().isGoogleUser()); + expect(userService.retrieveUserByUsername(STUDENT_USERNAME)).andReturn(student1).times(2); + expect(userService.updateUserPassword(student1, newPassword)).andReturn(student1); + expect(appProperties.getProperty("send_email_enabled", "false")).andReturn("false"); + replay(userService, appProperties); + controller.unlinkGoogleAccount(studentAuth, newPassword); + verify(userService, appProperties); + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java index 251fcb0b47..c5b901ce8f 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java @@ -143,68 +143,6 @@ public void getSupportedLanguages_ThreeSupportedLocales_ReturnLanguageArray() { verify(appProperties); } - @Test - public void isGoogleIdExist_GoogleUserExists_ReturnTrue() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); - replay(userService); - assertTrue(userAPIController.isGoogleIdExist(STUDENT1_GOOGLE_ID)); - verify(userService); - } - - @Test - public void isGoogleIdExist_InvalidGoogleUserId_ReturnFalse() { - String invalidGoogleId = "google-id-not-exists-in-db"; - expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); - replay(userService); - assertFalse(userAPIController.isGoogleIdExist(invalidGoogleId)); - verify(userService); - } - - @Test - public void isGoogleIdMatches_GoogleUserIdAndUserIdMatch_ReturnTrue() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); - replay(userService); - assertTrue(userAPIController.isGoogleIdMatches(STUDENT1_GOOGLE_ID, student1Id.toString())); - verify(userService); - } - - @Test - public void isGoogleIdMatches_InvalidGoogleUserId_ReturnFalse() { - String invalidGoogleId = "google-id-not-exists-in-db"; - expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); - replay(userService); - assertFalse(userAPIController.isGoogleIdMatches(invalidGoogleId, student1Id.toString())); - verify(userService); - } - - @Test - public void isGoogleIdMatches_GoogleUserIdAndUserIdDoNotMatch_ReturnFalse() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(teacher1); - replay(userService); - assertFalse(userAPIController.isGoogleIdMatches(STUDENT1_GOOGLE_ID, teacher1.toString())); - verify(userService); - } - - @Test - public void getUserByGoogleId_GoogleUserExists_ReturnSuccessResponse() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); - replay(userService); - HashMap response = userAPIController.getUserByGoogleId(STUDENT1_GOOGLE_ID); - assertEquals("success", response.get("status")); - assertEquals(student1.getId(), response.get("userId")); - verify(userService); - } - - @Test - public void getUserByGoogleId_InvalidGoogleUserId_ReturnErrorResponse() { - String invalidGoogleId = "google-id-not-exists-in-db"; - expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); - replay(userService); - HashMap response = userAPIController.getUserByGoogleId(invalidGoogleId); - assertEquals("error", response.get("status")); - verify(userService); - } - @Test public void isNameValid_InvalidName_ReturnFalse() { assertFalse(userAPIController.isNameValid("")); From b8d87ca506e8e06cd6886b72968fba58ca8df93d Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Mon, 25 Jan 2021 12:22:16 -0800 Subject: [PATCH 05/13] Extracted chooseBackgroundImage to parent. Fixed typo label.textString -> label.text. #2895 --- .../components/component-authoring.component.ts | 10 ++++++++++ .../concept-map-authoring.component.ts | 10 ---------- .../draw-authoring/draw-authoring.component.ts | 10 ---------- .../label-authoring.component.html | 6 +++--- .../label-authoring/label-authoring.component.ts | 16 +++------------- 5 files changed, 16 insertions(+), 36 deletions(-) diff --git a/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts b/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts index 225b94249e..67ae4403ea 100644 --- a/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts +++ b/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts @@ -108,6 +108,16 @@ export abstract class ComponentAuthoring { }); } + chooseBackgroundImage(): void { + const params = { + isPopup: true, + nodeId: this.nodeId, + componentId: this.componentId, + target: 'background' + }; + this.openAssetChooser(params); + } + openAssetChooser(params: any): any { return this.ProjectAssetService.openAssetChooser(params).then((data: any) => { return this.assetSelected(data); diff --git a/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts b/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts index 5a140470a4..9fbf33365e 100644 --- a/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts +++ b/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts @@ -156,16 +156,6 @@ export class ConceptMapAuthoring extends ComponentAuthoring { } } - chooseBackgroundImage(): void { - const params = { - isPopup: true, - nodeId: this.nodeId, - componentId: this.componentId, - target: 'background' - }; - this.openAssetChooser(params); - } - chooseNodeImage(conceptMapNodeId: string): void { const params = { isPopup: true, diff --git a/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts b/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts index 930150944a..c17659ba9a 100644 --- a/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts +++ b/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts @@ -176,16 +176,6 @@ export class DrawAuthoring extends ComponentAuthoring { this.componentChanged(); } - chooseBackgroundImage(): void { - const params = { - isPopup: true, - nodeId: this.nodeId, - componentId: this.componentId, - target: 'background' - }; - this.openAssetChooser(params); - } - chooseStampImage(stampIndex: number): void { const params = { isPopup: true, diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html index a01d9419f0..9aa57edb6b 100644 --- a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html +++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html @@ -99,7 +99,7 @@ Starter Labels -
\ No newline at end of file + diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts index f37ce78551..2c2ea4ff2b 100644 --- a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts +++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts @@ -59,7 +59,7 @@ export class LabelAuthoring extends ComponentAuthoring { this.textInputChangeSubscription.unsubscribe(); } - addLabelClicked(): void { + addLabel(): void { const newLabel = { text: $localize`Enter text here`, color: 'blue', @@ -74,23 +74,13 @@ export class LabelAuthoring extends ComponentAuthoring { this.componentChanged(); } - deleteLabelClicked(index: number, label: any): void { - if (confirm($localize`Are you sure you want to delete this label?\n\n${label.textString}`)) { + deleteLabel(index: number, label: any): void { + if (confirm($localize`Are you sure you want to delete this label?\n\n${label.text}`)) { this.authoringComponentContent.labels.splice(index, 1); this.componentChanged(); } } - chooseBackgroundImage(): void { - const params = { - isPopup: true, - nodeId: this.nodeId, - componentId: this.componentId, - target: 'background' - }; - this.openAssetChooser(params); - } - assetSelected({ nodeId, componentId, assetItem, target }): void { super.assetSelected({ nodeId, componentId, assetItem, target }); const fileName = assetItem.fileName; From f592801f5960bd30e9f7fe9513d2382c2d65d510 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Mon, 25 Jan 2021 15:10:21 -0800 Subject: [PATCH 06/13] Removed duplication in milestoneservice tests. #2897 --- .../src/app/services/milestoneService.spec.ts | 214 +++--------------- 1 file changed, 26 insertions(+), 188 deletions(-) diff --git a/src/main/webapp/site/src/app/services/milestoneService.spec.ts b/src/main/webapp/site/src/app/services/milestoneService.spec.ts index a9d7ec91b9..69177122c3 100644 --- a/src/main/webapp/site/src/app/services/milestoneService.spec.ts +++ b/src/main/webapp/site/src/app/services/milestoneService.spec.ts @@ -48,6 +48,10 @@ const aggregateAutoScoresSample = { const possibleScoresKi = [1, 2, 3, 4, 5]; +const sampleAggregateData = { + counts: createScoreCounts([10, 20, 30, 40, 50]) +}; + const reportSettingsCustomScoreValuesSample = { customScoreValues: { ki: [1, 2, 3, 4] @@ -964,205 +968,39 @@ function isPercentOfScoresNotEqualTo() { function getComparatorSum() { describe('getComparatorSum()', () => { - getGreaterThanSum(); - getGreaterThanOrEqualToSum(); - getLessThanSum(); - getEqualToSum(); - getNotEqualToSum(); - }); -} - -function getGreaterThanSum() { - const aggregateData = { - counts: createScoreCounts([10, 20, 30, 40, 50]) - }; - it('should get greater than sum with score 1', () => { - const satisfyCriterion = { value: 1 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThan - ) - ).toEqual(140); - }); - it('should get greater than sum with score 2', () => { - const satisfyCriterion = { value: 2 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThan - ) - ).toEqual(120); - }); - it('should get greater than sum with score 3', () => { - const satisfyCriterion = { value: 3 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThan - ) - ).toEqual(90); - }); - it('should get greater than sum with score 4', () => { - const satisfyCriterion = { value: 4 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThan - ) - ).toEqual(50); + getComparatorSum_greaterThan0_ReturnSumAll(); + getComparatorSum_greaterThan3_ReturnSumPartial(); + getComparatorSum_greaterThan5_Return0(); }); } -function getGreaterThanOrEqualToSum() { - const aggregateData = { - counts: createScoreCounts([10, 20, 30, 40, 50]) - }; - it('should get greater than or equal to sum with score 1', () => { - const satisfyCriterion = { value: 1 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThanEqualTo - ) - ).toEqual(150); - }); - it('should get greater than or equal to sum with score 2', () => { - const satisfyCriterion = { value: 2 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThanEqualTo - ) - ).toEqual(140); - }); - it('should get greater than or equal to sum with score 3', () => { - const satisfyCriterion = { value: 3 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThanEqualTo - ) - ).toEqual(120); - }); - it('should get greater than or equal to sum with score 4', () => { - const satisfyCriterion = { value: 4 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThanEqualTo - ) - ).toEqual(90); - }); - it('should get greater than or equal to sum with score 5', () => { - const satisfyCriterion = { value: 5 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.greaterThanEqualTo - ) - ).toEqual(50); - }); +function expectComparatorResult(satisfyCriterionValue: number, expectedResult: number) { + const satisfyCriterion = { value: satisfyCriterionValue }; + expect( + service.getComparatorSum( + satisfyCriterion, + sampleAggregateData, + possibleScoresKi, + utilService.greaterThan + ) + ).toEqual(expectedResult); } -function getLessThanSum() { - const aggregateData = { - counts: createScoreCounts([10, 20, 30, 40, 50]) - }; - it('should get less than sum with score 2', () => { - const satisfyCriterion = { value: 2 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.lessThan - ) - ).toEqual(10); - }); - it('should get less than sum with score 3', () => { - const satisfyCriterion = { value: 3 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.lessThan - ) - ).toEqual(30); - }); - it('should get less than sum with score 4', () => { - const satisfyCriterion = { value: 4 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.lessThan - ) - ).toEqual(60); - }); - it('should get less than sum with score 5', () => { - const satisfyCriterion = { value: 5 }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.lessThan - ) - ).toEqual(100); +function getComparatorSum_greaterThan0_ReturnSumAll() { + it('should get greater than sum with score 0', () => { + expectComparatorResult(0, 150); }); } -function getEqualToSum() { - it('should return the sum of scores equal to value', () => { - const satisfyCriterion = { value: 3 }; - const aggregateData = { - counts: createScoreCounts([10, 20, 30, 40, 50]) - }; - expect( - service.getComparatorSum( - satisfyCriterion, - aggregateData, - possibleScoresKi, - utilService.equalTo - ) - ).toEqual(30); +function getComparatorSum_greaterThan3_ReturnSumPartial() { + it('should get greater than sum with score 3', () => { + expectComparatorResult(3, 90); }); } -function getNotEqualToSum() { - const aggregateData = { - counts: { 1: 2, 2: 0, 3: 1, 4: 0, 5: 0 }, - scoreCount: 3 - }; - it('should return the sum of scores not equal to value', () => { - const result = service.getComparatorSum( - satisfyCriterionSample, - aggregateData, - possibleScoresKi, - utilService.notEqualTo - ); - expect(result).toBe(2); +function getComparatorSum_greaterThan5_Return0() { + it('should get greater than sum with score 5', () => { + expectComparatorResult(5, 0); }); } From 6f28ba9bf0942ee017e3e39588c3828b12e66714 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Tue, 26 Jan 2021 09:24:00 -0800 Subject: [PATCH 07/13] Aded unlinkGoogleAccount() to teacher edit profile. #2870 --- .../student/account/edit-profile/edit-profile.component.ts | 2 +- .../teacher/account/edit-profile/edit-profile.component.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts index afe3d42111..f083a25656 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; import { MatSnackBar } from '@angular/material/snack-bar'; diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts index a1c09fd71f..a6e0d95779 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -142,4 +142,8 @@ export class EditProfileComponent { this.snackBar.open($localize`An error occurred. Please try again.`); } } + + unlinkGoogleAccount() { + this.dialog.open(UnlinkGoogleAccountConfirmComponent); + } } From 0d46bcdd44a9e020ace15285e0c7890ba856f925 Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Wed, 27 Jan 2021 15:06:26 -0500 Subject: [PATCH 08/13] Upgraded Table authoring to Angular. #2902 --- .../src/app/teacher-hybrid-angular.module.ts | 2 + src/main/webapp/site/src/messages.xlf | 123 +++++ .../wise5/components/table/authoring.html | 381 ---------------- .../table-authoring.component.html | 177 ++++++++ .../table-authoring.component.scss | 62 +++ .../table-authoring.component.ts | 364 +++++++++++++++ .../wise5/components/table/tableAuthoring.ts | 428 ------------------ .../table/tableAuthoringComponentModule.ts | 9 +- 8 files changed, 734 insertions(+), 812 deletions(-) delete mode 100644 src/main/webapp/wise5/components/table/authoring.html create mode 100644 src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html create mode 100644 src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss create mode 100644 src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts delete mode 100644 src/main/webapp/wise5/components/table/tableAuthoring.ts diff --git a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts index 6a5134318d..a7e51fad62 100644 --- a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts +++ b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts @@ -52,6 +52,7 @@ import { ConceptMapAuthoring } from '../../../wise5/components/conceptMap/concep import { DrawAuthoring } from '../../../wise5/components/draw/draw-authoring/draw-authoring.component'; import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/match-authoring.component'; import { LabelAuthoring } from '../../../wise5/components/label/label-authoring/label-authoring.component'; +import { TableAuthoring } from '../../../wise5/components/table/table-authoring/table-authoring.component'; @NgModule({ declarations: [ @@ -87,6 +88,7 @@ import { LabelAuthoring } from '../../../wise5/components/label/label-authoring/ RubricAuthoringComponent, StatusIconComponent, StepInfoComponent, + TableAuthoring, WorkgroupInfoComponent, WorkgroupNodeScoreComponent, WorkgroupSelectAutocompleteComponent, diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index c7e6ac4b89..f268ec041c 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -6040,6 +6040,10 @@ ../../wise5/components/openResponse/open-response-authoring/open-response-authoring.component.html 11
+ + ../../wise5/components/table/table-authoring/table-authoring.component.html + 2 +
Enter Prompt Here @@ -6067,6 +6071,10 @@ ../../wise5/components/openResponse/open-response-authoring/open-response-authoring.component.html 15 + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 6 + Background Image (Optional) @@ -6356,6 +6364,14 @@ ../../wise5/components/multipleChoice/multiple-choice-authoring/multiple-choice-authoring.component.html 124 + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 58 + + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 99 + Show Node Labels @@ -7289,6 +7305,113 @@ 6 + + Columns + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 13 + + + + Rows + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 20 + + + + Global Cell Size + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 27 + + + + Insert Column Before + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 44 + + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 47 + + + + Delete Column + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 55 + + + + Insert Column After + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 67 + + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 70 + + + + Insert Row Before + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 85 + + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 88 + + + + Delete Row + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 96 + + + + Insert Row After + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 108 + + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 111 + + + + Editable + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 130 + + + + Column Cell Size + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 152 + + + + Make All Cells Editable + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 167 + + + + Make All Cells Uneditable + + ../../wise5/components/table/table-authoring/table-authoring.component.html + 174 + + (Team ) diff --git a/src/main/webapp/wise5/components/table/authoring.html b/src/main/webapp/wise5/components/table/authoring.html deleted file mode 100644 index 08bbdb80d3..0000000000 --- a/src/main/webapp/wise5/components/table/authoring.html +++ /dev/null @@ -1,381 +0,0 @@ -
- -
-
-
-
- - {{ ::'SHOW_SAVE_BUTTON' | translate }} - -
-
- - {{ ::'SHOW_SUBMIT_BUTTON' | translate }} - -
-
- - {{ ::'SHOW_ADD_TO_NOTEBOOK_BUTTON' | translate }} - -
-
- - - - -
-
-

{{ ::'table.dataExplorer' | translate }}

-
- - {{ ::'table.enableDataExplorer' | translate }} - -
-
-
-

{{ ::'table.allowedGraphTypes' | translate }}

- - {{ ::'table.scatterPlot' | translate }} - - - {{ ::'table.lineGraph' | translate }} - - - {{ ::'table.barGraph' | translate }} - -
-
- - {{ ::'table.showScatterPlotRegressionLine' | translate }} - -
-
- - - - -
-
- - - - -
-
-
- - - - - - {{ ::'table.yAxis' | translate }} {{ $index + 1 }} - - - - -
-
-
- - {{ ::'table.canStudentEditAxisLabels' | translate }} - -
-
-
-
- - - - -
-
-
- - - add - - {{ ::'ADD_CONNECTED_COMPONENT' | translate }} - - -
-
-
- - - - - {{tableController.getNodePositionAndTitleByNodeId(item.$key)}} - - - - - - - - {{ componentIndex + 1 }}. {{component.type}} - - ({{ ::'thisComponent' | translate }}) - - - - - - - - - {{ ::'importWork' | translate }} - - - {{ ::'showWork' | translate }} - - - - - - - - {{ ::'merge' | translate }} - - - {{ ::'append' | translate }} - - - - - - - delete - - {{ ::'DELETE' | translate }} - - - - - - {{ ::'table.onlyShowDataAtMouseXPosition' | translate }} - - -
-
-
- -
-
-
- - - - -
-
- - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - -
- - loupe - - {{ ::'table.insertColumnBefore' | translate }} - - - - delete - - {{ ::'table.deleteColumn' | translate }} - - - - loupe - - {{ ::'table.insertColumnAfter' | translate }} - - -
- - loupe - - {{ ::'table.insertRowBefore' | translate }} - - -
- - delete - - {{ ::'table.deleteRow' | translate }} - - -
- - loupe - - {{ ::'table.insertRowAfter' | translate }} - - -
- - - -
- - {{ ::'table.editableByStudent' | translate }} - -
-   -
- - - - - - -
-
-
- - {{ ::'table.makeAllCellsEditable' | translate }} - - - {{ ::'table.makeAllCellsUneditable' | translate }} - -
-
-
-
diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html new file mode 100644 index 0000000000..5995f387dd --- /dev/null +++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html @@ -0,0 +1,177 @@ + + Prompt + + +
+ + Columns + + + + Rows + + + + Global Cell Size + + +
+ + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + + Editable + +
+
+
+
+ Optional +
+
+
+ + Column Cell Size + + +
+
+
+ + +
\ No newline at end of file diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss new file mode 100644 index 0000000000..cdd97d67ff --- /dev/null +++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss @@ -0,0 +1,62 @@ +.prompt { + width: 100%; +} + +.size-input { + width: 120px; + margin-right: 20px; +} + +.table { + width: 100%; +} + +.rotate90 { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.rotate270 { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); +} + +.column-buttons { + margin: 5px; +} + +.row-buttons { + margin-top: 5px; + margin-bottom: 5px; +} + +.spacer { + width: 56px; +} + +.outer-cell-container { + border: 1px solid black; + padding: 5px; + width: auto; +} + +.inner-cell-container { + border: 1px solid black; + padding: 10px; +} + +.cell-text { + width: 100%; +} + +.blank-row-cell { + height: 20px; +} + +::ng-deep .mat-form-field-infix { + width: auto !important; +} + +.make-all-cells-editable-buttons { + margin: 10px; +} \ No newline at end of file diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts new file mode 100644 index 0000000000..46e7b3c3c6 --- /dev/null +++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts @@ -0,0 +1,364 @@ +'use strict'; + +import { Component } from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; +import { ProjectAssetService } from '../../../../site/src/app/services/projectAssetService'; +import { ComponentAuthoring } from '../../../authoringTool/components/component-authoring.component'; +import { ConfigService } from '../../../services/configService'; +import { NodeService } from '../../../services/nodeService'; +import { TeacherProjectService } from '../../../services/teacherProjectService'; + +@Component({ + selector: 'table-authoring', + templateUrl: 'table-authoring.component.html', + styleUrls: ['table-authoring.component.scss'] +}) +export class TableAuthoring extends ComponentAuthoring { + columnCellSizes: any; + + numColumnsChange: Subject = new Subject(); + numRowsChange: Subject = new Subject(); + globalCellSizeChange: Subject = new Subject(); + inputChange: Subject = new Subject(); + + numColumnsChangeSubscription: Subscription; + numRowsChangeSubscription: Subscription; + globalCellSizeChangeSubscription: Subscription; + inputChangeSubscription: Subscription; + + constructor( + protected ConfigService: ConfigService, + protected NodeService: NodeService, + protected ProjectAssetService: ProjectAssetService, + protected ProjectService: TeacherProjectService + ) { + super(ConfigService, NodeService, ProjectAssetService, ProjectService); + this.numColumnsChangeSubscription = this.numColumnsChange + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.tableNumColumnsChanged(); + }); + this.numRowsChangeSubscription = this.numRowsChange + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.tableNumRowsChanged(); + }); + this.globalCellSizeChangeSubscription = this.globalCellSizeChange + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.componentChanged(); + }); + this.inputChangeSubscription = this.inputChange + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.componentChanged(); + }); + } + + ngOnInit() { + super.ngOnInit(); + this.columnCellSizes = this.parseColumnCellSizes(this.componentContent); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.unsubscribeAll(); + } + + unsubscribeAll() { + this.numColumnsChangeSubscription.unsubscribe(); + this.numRowsChangeSubscription.unsubscribe(); + this.globalCellSizeChangeSubscription.unsubscribe(); + this.inputChangeSubscription.unsubscribe(); + } + + tableNumRowsChanged(): void { + const oldValue = this.getNumRowsInTableData(); + const newValue = this.authoringComponentContent.numRows; + if (newValue < oldValue) { + if (this.areRowsAfterEmpty(newValue)) { + this.tableSizeChanged(); + } else { + if (confirm($localize`Are you sure you want to decrease the number of rows?`)) { + this.tableSizeChanged(); + } else { + this.authoringComponentContent.numRows = oldValue; + } + } + } else { + this.tableSizeChanged(); + } + } + + areRowsAfterEmpty(rowIndex: number): boolean { + const oldNumRows = this.getNumRowsInTableData(); + for (let r = rowIndex; r < oldNumRows; r++) { + if (!this.isRowEmpty(r)) { + return false; + } + } + return true; + } + + isRowEmpty(rowIndex: number): boolean { + const tableData = this.authoringComponentContent.tableData; + for (const cell of tableData[rowIndex]) { + if (cell.text != null && cell.text != '') { + return false; + } + } + return true; + } + + tableNumColumnsChanged(): void { + const oldValue = this.getNumColumnsInTableData(); + const newValue = this.authoringComponentContent.numColumns; + if (newValue < oldValue) { + if (this.areColumnsAfterEmpty(newValue)) { + this.tableSizeChanged(); + } else { + if (confirm($localize`Are you sure you want to decrease the number of columns?`)) { + this.tableSizeChanged(); + } else { + this.authoringComponentContent.numColumns = oldValue; + } + } + } else { + this.tableSizeChanged(); + } + } + + areColumnsAfterEmpty(columnIndex: number): boolean { + const oldNumColumns = this.getNumColumnsInTableData(); + for (let c = columnIndex; c < oldNumColumns; c++) { + if (!this.isColumnEmpty(c)) { + return false; + } + } + return true; + } + + isColumnEmpty(columnIndex: number): boolean { + for (const row of this.authoringComponentContent.tableData) { + const cell = row[columnIndex]; + if (cell.text != null && cell.text != '') { + return false; + } + } + return true; + } + + tableSizeChanged(): void { + this.authoringComponentContent.tableData = this.getUpdatedTable( + this.authoringComponentContent.numRows, + this.authoringComponentContent.numColumns + ); + this.componentChanged(); + } + + /** + * Create a table with the given dimensions. Populate the cells with the cells from the old table. + * @param newNumRows the number of rows in the new table + * @param newNumColumns the number of columns in the new table + * @returns a new table + */ + getUpdatedTable(newNumRows: number, newNumColumns: number): any { + const newTable = []; + for (let r = 0; r < newNumRows; r++) { + const newRow = []; + for (let c = 0; c < newNumColumns; c++) { + let cell = this.getCellObjectFromTableData(c, r); + if (cell == null) { + cell = this.createEmptyCell(); + } + newRow.push(cell); + } + newTable.push(newRow); + } + return newTable; + } + + /** + * Get the cell object at the given x, y location + * @param x the column number (zero indexed) + * @param y the row number (zero indexed) + * @returns the cell at the given x, y location or null if there is none + */ + getCellObjectFromTableData(x: number, y: number): any { + let cellObject = null; + const tableData = this.authoringComponentContent.tableData; + if (tableData != null) { + const row = tableData[y]; + if (row != null) { + cellObject = row[x]; + } + } + return cellObject; + } + + createEmptyCell(): any { + return { + text: '', + editable: true, + size: null + }; + } + + insertRow(y: number): void { + const tableData = this.authoringComponentContent.tableData; + const newRow = []; + const numColumns = this.authoringComponentContent.numColumns; + for (let c = 0; c < numColumns; c++) { + const newCell = this.createEmptyCell(); + const cellSize = this.columnCellSizes[c]; + if (cellSize != null) { + newCell.size = cellSize; + } + newRow.push(newCell); + } + tableData.splice(y, 0, newRow); + this.authoringComponentContent.numRows++; + this.componentChanged(); + } + + deleteRow(y: number): void { + if (confirm($localize`Are you sure you want to delete this row?`)) { + const tableData = this.authoringComponentContent.tableData; + if (tableData != null) { + tableData.splice(y, 1); + this.authoringComponentContent.numRows--; + } + this.componentChanged(); + } + } + + insertColumn(x: number): void { + const tableData = this.authoringComponentContent.tableData; + const numRows = this.authoringComponentContent.numRows; + for (let r = 0; r < numRows; r++) { + const tempRow = tableData[r]; + if (tempRow != null) { + const newCell = this.createEmptyCell(); + tempRow.splice(x, 0, newCell); + } + } + this.authoringComponentContent.numColumns++; + this.parseColumnCellSizes(this.authoringComponentContent); + this.componentChanged(); + } + + deleteColumn(x: number): void { + if (confirm($localize`Are you sure you want to delete this column?`)) { + const tableData = this.authoringComponentContent.tableData; + const numRows = this.authoringComponentContent.numRows; + for (let r = 0; r < numRows; r++) { + const tempRow = tableData[r]; + if (tempRow != null) { + tempRow.splice(x, 1); + } + } + this.authoringComponentContent.numColumns--; + this.parseColumnCellSizes(this.authoringComponentContent); + this.componentChanged(); + } + } + + /** + * Get the number of rows in the table data. This is slightly different from just getting the + * numRows field in the component content. Usually the number of rows will be the same. In some + * cases it can be different such as during authoring immediately after the author changes the + * number of rows using the number of rows input. + * @return {number} The number of rows in the table data. + */ + getNumRowsInTableData(): number { + return this.authoringComponentContent.tableData.length; + } + + /** + * Get the number of columns in the table data. This is slightly different from just getting the + * numColumns field in the component content. Usually the number of columns will be the same. In + * some cases it can be different such as during authoring immediately after the author changes + * the number of columns using the number of columns input. + * @return {number} The number of columns in the table data. + */ + getNumColumnsInTableData(): number { + const tableData = this.authoringComponentContent.tableData; + if (tableData.length > 0) { + return tableData[0].length; + } + return 0; + } + + setAllCellsUneditable(): void { + this.setAllCellsIsEditable(false); + this.componentChanged(); + } + + setAllCellsEditable(): void { + this.setAllCellsIsEditable(true); + this.componentChanged(); + } + + setAllCellsIsEditable(isEditable: boolean): void { + const tableData = this.authoringComponentContent.tableData; + for (let r = 0; r < tableData.length; r++) { + const row = tableData[r]; + for (let c = 0; c < row.length; c++) { + const cell = row[c]; + cell.editable = isEditable; + } + } + } + + /** + * Parse the column cell sizes. We will get the column cell sizes by looking at the size value of + * each cell in the first row. + * @param componentContent the component content + */ + parseColumnCellSizes(componentContent: any): any { + const columnCellSizes = {}; + const tableData = componentContent.tableData; + const firstRow = tableData[0]; + if (firstRow != null) { + for (let x = 0; x < firstRow.length; x++) { + const cell = firstRow[x]; + columnCellSizes[x] = cell.size; + } + } + return columnCellSizes; + } + + columnSizeChanged(index: number): void { + let cellSize = this.columnCellSizes[index]; + if (cellSize == '') { + cellSize = null; + } + this.setColumnCellSizes(index, cellSize); + } + + setColumnCellSizes(column: number, size: number): void { + const tableData = this.authoringComponentContent.tableData; + for (let r = 0; r < tableData.length; r++) { + const row = tableData[r]; + const cell = row[column]; + if (cell != null) { + cell.size = size; + } + } + this.componentChanged(); + } + + automaticallySetConnectedComponentFieldsIfPossible(connectedComponent) { + if (connectedComponent.type === 'importWork' && connectedComponent.action == null) { + connectedComponent.action = 'merge'; + } else if (connectedComponent.type === 'showWork') { + connectedComponent.action = null; + } + } + + connectedComponentTypeChanged(connectedComponent) { + this.automaticallySetConnectedComponentFieldsIfPossible(connectedComponent); + this.componentChanged(); + } +} diff --git a/src/main/webapp/wise5/components/table/tableAuthoring.ts b/src/main/webapp/wise5/components/table/tableAuthoring.ts deleted file mode 100644 index beef4e7fef..0000000000 --- a/src/main/webapp/wise5/components/table/tableAuthoring.ts +++ /dev/null @@ -1,428 +0,0 @@ -'use strict'; - -import { Directive } from '@angular/core'; -import { EditComponentController } from '../../authoringTool/components/editComponentController'; - -@Directive() -class TableAuthoringController extends EditComponentController { - columnCellSizes: any; - - static $inject = [ - '$filter', - 'ConfigService', - 'NodeService', - 'NotificationService', - 'ProjectAssetService', - 'ProjectService', - 'UtilService' - ]; - - constructor( - $filter, - ConfigService, - NodeService, - NotificationService, - ProjectAssetService, - ProjectService, - UtilService - ) { - super( - $filter, - ConfigService, - NodeService, - NotificationService, - ProjectAssetService, - ProjectService, - UtilService - ); - } - - $onInit() { - super.$onInit(); - this.columnCellSizes = this.parseColumnCellSizes(this.componentContent); - } - - tableNumRowsChanged(oldValue: number): void { - if (this.authoringComponentContent.numRows < oldValue) { - if (this.areRowsAfterEmpty(this.authoringComponentContent.numRows)) { - this.tableSizeChanged(); - } else { - if (confirm(this.$translate('table.areYouSureYouWantToDecreaseTheNumberOfRows'))) { - this.tableSizeChanged(); - } else { - this.authoringComponentContent.numRows = oldValue; - } - } - } else { - this.tableSizeChanged(); - } - } - - /** - * Determine if the rows after the given index are empty. - * @param rowIndex The index of the row to start checking at. This value is zero indexed. - * @return {boolean} True if the row at the given index and all the rows after are empty. - * False if the row at the given index or any row after the row index is not empty. - */ - areRowsAfterEmpty(rowIndex: number): boolean { - const oldNumRows = this.getNumRowsInTableData(); - for (let r = rowIndex; r < oldNumRows; r++) { - if (!this.isRowEmpty(r)) { - return false; - } - } - return true; - } - - /** - * Determine if a row has cells that are all empty string. - * @param rowIndex The row index. This value is zero indexed. - * @returns {boolean} True if the text in all the cells in the row are empty string. - * False if the text in any cell in the row is not empty string. - */ - isRowEmpty(rowIndex: number): boolean { - const tableData = this.authoringComponentContent.tableData; - for (const cell of tableData[rowIndex]) { - if (cell.text != null && cell.text != '') { - return false; - } - } - return true; - } - - /** - * The author has changed the number of columns. - * @param oldValue The previous number of columns. - */ - tableNumColumnsChanged(oldValue: number): void { - if (this.authoringComponentContent.numColumns < oldValue) { - // the author is reducing the number of columns - if (this.areColumnsAfterEmpty(this.authoringComponentContent.numColumns)) { - // the columns that we will delete are empty so we will remove the columns - this.tableSizeChanged(); - } else { - if (confirm(this.$translate('table.areYouSureYouWantToDecreaseTheNumberOfColumns'))) { - this.tableSizeChanged(); - } else { - this.authoringComponentContent.numColumns = oldValue; - } - } - } else { - // the author is increasing the number of columns - this.tableSizeChanged(); - } - } - - /** - * Determine if the columns after the given index are empty. - * @param columnIndex The index of the column to start checking at. This value is zero indexed. - * @return {boolean} True if the column at the given index and all the columns after are empty. - * False if the column at the given index or any column after the column index is not empty. - */ - areColumnsAfterEmpty(columnIndex: number): boolean { - const oldNumColumns = this.getNumColumnsInTableData(); - for (let c = columnIndex; c < oldNumColumns; c++) { - if (!this.isColumnEmpty(c)) { - return false; - } - } - return true; - } - - /** - * Determine if a column has cells that are all empty string. - * @param columnIndex The column index. This value is zero indexed. - * @returns {boolean} True if the text in all the cells in the column are empty string. - * False if the text in any cell in the column is not empty string. - */ - isColumnEmpty(columnIndex: number): boolean { - for (const row of this.authoringComponentContent.tableData) { - const cell = row[columnIndex]; - if (cell.text != null && cell.text != '') { - return false; - } - } - return true; - } - - /** - * The table size has changed in the authoring view so we will update it - */ - tableSizeChanged(): void { - this.authoringComponentContent.tableData = this.getUpdatedTableSize( - this.authoringComponentContent.numRows, - this.authoringComponentContent.numColumns - ); - this.componentChanged(); - } - - /** - * Create a table with the given dimensions. Populate the cells with - * the cells from the old table. - * @param newNumRows the number of rows in the new table - * @param newNumColumns the number of columns in the new table - * @returns a new table - */ - getUpdatedTableSize(newNumRows: number, newNumColumns: number): any { - const newTable = []; - for (let r = 0; r < newNumRows; r++) { - const newRow = []; - for (let c = 0; c < newNumColumns; c++) { - let cell = this.getCellObjectFromComponentContent(c, r); - if (cell == null) { - cell = this.createEmptyCell(); - } - newRow.push(cell); - } - newTable.push(newRow); - } - return newTable; - } - - /** - * Get the cell object at the given x, y location - * @param x the column number (zero indexed) - * @param y the row number (zero indexed) - * @returns the cell at the given x, y location or null if there is none - */ - getCellObjectFromComponentContent(x: number, y: number): any { - let cellObject = null; - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - const row = tableData[y]; - if (row != null) { - cellObject = row[x]; - } - } - return cellObject; - } - - createEmptyCell(): any { - return { - text: '', - editable: true, - size: null - }; - } - - /** - * Insert a row into the table from the authoring view - * @param y the row number to insert at - */ - insertRow(y: number): void { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - const newRow = []; - const numColumns = this.authoringComponentContent.numColumns; - for (let c = 0; c < numColumns; c++) { - const newCell = this.createEmptyCell(); - const cellSize = this.columnCellSizes[c]; - if (cellSize != null) { - newCell.size = cellSize; - } - newRow.push(newCell); - } - tableData.splice(y, 0, newRow); - this.authoringComponentContent.numRows++; - } - this.componentChanged(); - } - - /** - * Delete a row in the table from the authoring view - * @param y the row number to delete - */ - deleteRow(y: number): void { - if (confirm(this.$translate('table.areYouSureYouWantToDeleteThisRow'))) { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - tableData.splice(y, 1); - this.authoringComponentContent.numRows--; - } - this.componentChanged(); - } - } - - /** - * Insert a column into the table from the authoring view - * @param x the column number to insert at - */ - insertColumn(x: number): void { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - const numRows = this.authoringComponentContent.numRows; - for (let r = 0; r < numRows; r++) { - const tempRow = tableData[r]; - if (tempRow != null) { - const newCell = this.createEmptyCell(); - tempRow.splice(x, 0, newCell); - } - } - this.authoringComponentContent.numColumns++; - this.parseColumnCellSizes(this.authoringComponentContent); - } - this.componentChanged(); - } - - /** - * Delete a column in the table from the authoring view - * @param x the column number to delete - */ - deleteColumn(x: number): void { - if (confirm(this.$translate('table.areYouSureYouWantToDeleteThisColumn'))) { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - const numRows = this.authoringComponentContent.numRows; - for (let r = 0; r < numRows; r++) { - const tempRow = tableData[r]; - if (tempRow != null) { - tempRow.splice(x, 1); - } - } - this.authoringComponentContent.numColumns--; - this.parseColumnCellSizes(this.authoringComponentContent); - } - this.componentChanged(); - } - } - - /** - * Get the number of rows in the table data. This is slightly different from - * just getting the numRows field in the component content. Usually the - * number of rows will be the same. In some cases it can be different - * such as during authoring immediately after the author changes the number - * of rows using the number of rows input. - * @return {number} The number of rows in the table data. - */ - getNumRowsInTableData(): number { - return this.authoringComponentContent.tableData.length; - } - - /** - * Get the number of columns in the table data. This is slightly different from - * just getting the numColumns field in the component content. Usually the - * number of columns will be the same. In some cases it can be different - * such as during authoring immediately after the author changes the number - * of columns using the number of columns input. - * @return {number} The number of columns in the table data. - */ - getNumColumnsInTableData(): number { - const tableData = this.authoringComponentContent.tableData; - if (tableData.length > 0) { - return tableData[0].length; - } - return 0; - } - - makeAllCellsUneditable(): void { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - for (let r = 0; r < tableData.length; r++) { - const row = tableData[r]; - if (row != null) { - for (let c = 0; c < row.length; c++) { - const cell = row[c]; - if (cell != null) { - cell.editable = false; - } - } - } - } - } - this.componentChanged(); - } - - makeAllCellsEditable(): void { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - for (let r = 0; r < tableData.length; r++) { - const row = tableData[r]; - if (row != null) { - for (let c = 0; c < row.length; c++) { - const cell = row[c]; - if (cell != null) { - cell.editable = true; - } - } - } - } - } - this.componentChanged(); - } - - /** - * Parse the column cell sizes. We will get the column cell sizes by looking - * at size value of each column in the first row. - * @param componentContent the component content - */ - parseColumnCellSizes(componentContent: any): any { - const columnCellSizes = {}; - const tableData = componentContent.tableData; - if (tableData != null) { - const firstRow = tableData[0]; - if (firstRow != null) { - for (let x = 0; x < firstRow.length; x++) { - const cell = firstRow[x]; - columnCellSizes[x] = cell.size; - } - } - } - return columnCellSizes; - } - - columnSizeChanged(index: number): void { - if (index != null) { - let cellSize = this.columnCellSizes[index]; - if (cellSize == '') { - cellSize = null; - } - this.setColumnCellSizes(index, cellSize); - } - } - - /** - * Set the cell sizes for all the cells in a column - * @param column the column number - * @param size the cell size - */ - setColumnCellSizes(column: number, size: number): void { - const tableData = this.authoringComponentContent.tableData; - if (tableData != null) { - for (let r = 0; r < tableData.length; r++) { - const row = tableData[r]; - if (row != null) { - const cell = row[column]; - if (cell != null) { - cell.size = size; - } - } - } - } - this.componentChanged(); - } - - automaticallySetConnectedComponentFieldsIfPossible(connectedComponent) { - if (connectedComponent.type === 'importWork' && connectedComponent.action == null) { - connectedComponent.action = 'merge'; - } else if (connectedComponent.type === 'showWork') { - connectedComponent.action = null; - } - } - - connectedComponentTypeChanged(connectedComponent) { - this.automaticallySetConnectedComponentFieldsIfPossible(connectedComponent); - this.componentChanged(); - } -} - -const TableAuthoring = { - bindings: { - nodeId: '@', - componentId: '@' - }, - controller: TableAuthoringController, - controllerAs: 'tableController', - templateUrl: 'wise5/components/table/authoring.html' -}; - -export default TableAuthoring; diff --git a/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts b/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts index 60f9c96360..c202b806cd 100644 --- a/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts +++ b/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts @@ -2,14 +2,17 @@ import * as angular from 'angular'; import { TableService } from './tableService'; -import { downgradeInjectable } from '@angular/upgrade/static'; -import TableAuthoring from './tableAuthoring'; +import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static'; import { EditTableAdvancedComponent } from './edit-table-advanced/edit-table-advanced.component'; +import { TableAuthoring } from './table-authoring/table-authoring.component'; const tableAuthoringComponentModule = angular .module('tableAuthoringComponentModule', ['pascalprecht.translate']) .service('TableService', downgradeInjectable(TableService)) - .component('tableAuthoring', TableAuthoring) + .directive( + 'tableAuthoring', + downgradeComponent({ component: TableAuthoring }) as angular.IDirectiveFactory + ) .component('editTableAdvanced', EditTableAdvancedComponent) .config([ '$translatePartialLoaderProvider', From 2c44f999f4d7c1055baa9cc2d7e3a61dc7034125 Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Wed, 27 Jan 2021 15:50:59 -0500 Subject: [PATCH 09/13] Upgraded Discussion authoring to Angular. #2908 --- .../src/app/teacher-hybrid-angular.module.ts | 2 + src/main/webapp/site/src/messages.xlf | 22 +++++++++ .../components/discussion/authoring.html | 28 ----------- .../discussion-authoring.component.html | 28 +++++++++++ .../discussion-authoring.component.scss | 8 +++ .../discussion-authoring.component.ts | 24 +++++++++ .../discussion/discussionAuthoring.ts | 49 ------------------- .../discussionAuthoringComponentModule.ts | 9 ++-- .../discussion/discussionService.ts | 2 +- 9 files changed, 91 insertions(+), 81 deletions(-) delete mode 100644 src/main/webapp/wise5/components/discussion/authoring.html create mode 100644 src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html create mode 100644 src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss create mode 100644 src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts delete mode 100644 src/main/webapp/wise5/components/discussion/discussionAuthoring.ts diff --git a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts index 6a5134318d..276c4afc0c 100644 --- a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts +++ b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts @@ -52,6 +52,7 @@ import { ConceptMapAuthoring } from '../../../wise5/components/conceptMap/concep import { DrawAuthoring } from '../../../wise5/components/draw/draw-authoring/draw-authoring.component'; import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/match-authoring.component'; import { LabelAuthoring } from '../../../wise5/components/label/label-authoring/label-authoring.component'; +import { DiscussionAuthoring } from '../../../wise5/components/discussion/discussion-authoring/discussion-authoring.component'; @NgModule({ declarations: [ @@ -65,6 +66,7 @@ import { LabelAuthoring } from '../../../wise5/components/label/label-authoring/ ComponentSelectComponent, ConceptMapAuthoring, DrawAuthoring, + DiscussionAuthoring, EditComponentRubricComponent, EditComponentJsonComponent, EditComponentMaxScoreComponent, diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index c7e6ac4b89..42884991b0 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -6024,6 +6024,10 @@ ../../wise5/components/draw/draw-authoring/draw-authoring.component.html 2 + + ../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html + 2 + ../../wise5/components/label/label-authoring/label-authoring.component.html 2 @@ -6051,6 +6055,10 @@ ../../wise5/components/draw/draw-authoring/draw-authoring.component.html 6 + + ../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html + 6 + ../../wise5/components/label/label-authoring/label-authoring.component.html 6 @@ -6653,6 +6661,20 @@ 285 + + Students can upload and use images in their posts + + ../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html + 17 + + + + Students must create a post before viewing classmates' posts + + ../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html + 26 + + Rubric diff --git a/src/main/webapp/wise5/components/discussion/authoring.html b/src/main/webapp/wise5/components/discussion/authoring.html deleted file mode 100644 index 1c169b45ce..0000000000 --- a/src/main/webapp/wise5/components/discussion/authoring.html +++ /dev/null @@ -1,28 +0,0 @@ -
-
- - - - - - - {{ ::'discussion.allowUploadedImagesInPosts' | translate }} - - -
- - - {{ ::'discussion.gateClassmateResponses' | translate }} - - -
-
diff --git a/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html new file mode 100644 index 0000000000..d457e04955 --- /dev/null +++ b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html @@ -0,0 +1,28 @@ + + Prompt + + +
+ + Students can upload and use images in their posts + +
+
+ + Students must create a post before viewing classmates' posts + +
\ No newline at end of file diff --git a/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss new file mode 100644 index 0000000000..7221a74cf4 --- /dev/null +++ b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss @@ -0,0 +1,8 @@ +.prompt { + width: 100%; +} + +.checkbox-container { + margin-top: 5px; + margin-bottom: 15px; +} \ No newline at end of file diff --git a/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts new file mode 100644 index 0000000000..ce81455ece --- /dev/null +++ b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts @@ -0,0 +1,24 @@ +'use strict'; + +import { Component } from '@angular/core'; +import { ProjectAssetService } from '../../../../site/src/app/services/projectAssetService'; +import { ComponentAuthoring } from '../../../authoringTool/components/component-authoring.component'; +import { ConfigService } from '../../../services/configService'; +import { NodeService } from '../../../services/nodeService'; +import { TeacherProjectService } from '../../../services/teacherProjectService'; + +@Component({ + selector: 'discussion-authoring', + templateUrl: 'discussion-authoring.component.html', + styleUrls: ['discussion-authoring.component.scss'] +}) +export class DiscussionAuthoring extends ComponentAuthoring { + constructor( + protected ConfigService: ConfigService, + protected NodeService: NodeService, + protected ProjectAssetService: ProjectAssetService, + protected ProjectService: TeacherProjectService + ) { + super(ConfigService, NodeService, ProjectAssetService, ProjectService); + } +} diff --git a/src/main/webapp/wise5/components/discussion/discussionAuthoring.ts b/src/main/webapp/wise5/components/discussion/discussionAuthoring.ts deleted file mode 100644 index d30b4b0c2f..0000000000 --- a/src/main/webapp/wise5/components/discussion/discussionAuthoring.ts +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -import { Directive } from '@angular/core'; -import { EditComponentController } from '../../authoringTool/components/editComponentController'; - -@Directive() -class DiscussionAuthoringController extends EditComponentController { - static $inject = [ - '$filter', - 'ConfigService', - 'NodeService', - 'NotificationService', - 'ProjectAssetService', - 'ProjectService', - 'UtilService' - ]; - - constructor( - $filter, - ConfigService, - NodeService, - NotificationService, - ProjectAssetService, - ProjectService, - UtilService - ) { - super( - $filter, - ConfigService, - NodeService, - NotificationService, - ProjectAssetService, - ProjectService, - UtilService - ); - } -} - -const DiscussionAuthoring = { - bindings: { - nodeId: '@', - componentId: '@' - }, - controller: DiscussionAuthoringController, - controllerAs: 'discussionController', - templateUrl: 'wise5/components/discussion/authoring.html' -}; - -export default DiscussionAuthoring; diff --git a/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts b/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts index 77bf783b60..9acf77bcbe 100644 --- a/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts +++ b/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts @@ -1,16 +1,19 @@ 'use strict'; import * as angular from 'angular'; -import { downgradeInjectable } from '@angular/upgrade/static'; +import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static'; import { DiscussionService } from './discussionService'; -import DiscussionAuthoring from './discussionAuthoring'; import { EditDiscussionAdvancedComponent } from './edit-discussion-advanced/edit-discussion-advanced.component'; +import { DiscussionAuthoring } from './discussion-authoring/discussion-authoring.component'; const discussionAuthoringComponentModule = angular .module('discussionAuthoringComponentModule', ['pascalprecht.translate']) .service('DiscussionService', downgradeInjectable(DiscussionService)) - .component('discussionAuthoring', DiscussionAuthoring) .component('editDiscussionAdvanced', EditDiscussionAdvancedComponent) + .directive( + 'discussionAuthoring', + downgradeComponent({ component: DiscussionAuthoring }) as angular.IDirectiveFactory + ) .config([ '$translatePartialLoaderProvider', ($translatePartialLoaderProvider) => { diff --git a/src/main/webapp/wise5/components/discussion/discussionService.ts b/src/main/webapp/wise5/components/discussion/discussionService.ts index 7249536a45..78467bf01e 100644 --- a/src/main/webapp/wise5/components/discussion/discussionService.ts +++ b/src/main/webapp/wise5/components/discussion/discussionService.ts @@ -39,7 +39,7 @@ export class DiscussionService extends ComponentService { createComponent() { const component: any = super.createComponent(); component.type = 'Discussion'; - component.prompt = this.getTranslation('ENTER_PROMPT_HERE'); + component.prompt = ''; component.isStudentAttachmentEnabled = true; component.gateClassmateResponses = true; return component; From 6e50560b5e4fc2bf7ca2314be4c9182b346e7156 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Thu, 28 Jan 2021 10:36:32 -0800 Subject: [PATCH 10/13] Cleaned up table authoring code. #2902 --- .../table-authoring/table-authoring.component.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts index 46e7b3c3c6..a1e97f3052 100644 --- a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts +++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts @@ -104,7 +104,7 @@ export class TableAuthoring extends ComponentAuthoring { isRowEmpty(rowIndex: number): boolean { const tableData = this.authoringComponentContent.tableData; for (const cell of tableData[rowIndex]) { - if (cell.text != null && cell.text != '') { + if (!this.isEmpty(cell.text)) { return false; } } @@ -142,13 +142,17 @@ export class TableAuthoring extends ComponentAuthoring { isColumnEmpty(columnIndex: number): boolean { for (const row of this.authoringComponentContent.tableData) { const cell = row[columnIndex]; - if (cell.text != null && cell.text != '') { + if (!this.isEmpty(cell.text)) { return false; } } return true; } + isEmpty(txt: string): boolean { + return txt == null || txt == ''; + } + tableSizeChanged(): void { this.authoringComponentContent.tableData = this.getUpdatedTable( this.authoringComponentContent.numRows, @@ -301,11 +305,8 @@ export class TableAuthoring extends ComponentAuthoring { } setAllCellsIsEditable(isEditable: boolean): void { - const tableData = this.authoringComponentContent.tableData; - for (let r = 0; r < tableData.length; r++) { - const row = tableData[r]; - for (let c = 0; c < row.length; c++) { - const cell = row[c]; + for (const row of this.authoringComponentContent.tableData) { + for (const cell of row) { cell.editable = isEditable; } } From d125cbf84db1029e89aac7aa9b55da050d79a68b Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Fri, 29 Jan 2021 15:03:15 -0500 Subject: [PATCH 11/13] Cleaned up some Table authoring code. #2902 --- .../table-authoring.component.html | 4 +++ .../table-authoring.component.ts | 26 ++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html index 5995f387dd..9fcbfba3af 100644 --- a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html +++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html @@ -13,6 +13,7 @@ Columns @@ -20,6 +21,7 @@ Rows @@ -27,6 +29,7 @@ Global Cell Size @@ -152,6 +155,7 @@ Column Cell Size diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts index a1e97f3052..0d4cfa8f31 100644 --- a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts +++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts @@ -209,7 +209,7 @@ export class TableAuthoring extends ComponentAuthoring { }; } - insertRow(y: number): void { + insertRow(rowIndex: number): void { const tableData = this.authoringComponentContent.tableData; const newRow = []; const numColumns = this.authoringComponentContent.numColumns; @@ -221,46 +221,42 @@ export class TableAuthoring extends ComponentAuthoring { } newRow.push(newCell); } - tableData.splice(y, 0, newRow); + tableData.splice(rowIndex, 0, newRow); this.authoringComponentContent.numRows++; this.componentChanged(); } - deleteRow(y: number): void { + deleteRow(rowIndex: number): void { if (confirm($localize`Are you sure you want to delete this row?`)) { const tableData = this.authoringComponentContent.tableData; if (tableData != null) { - tableData.splice(y, 1); + tableData.splice(rowIndex, 1); this.authoringComponentContent.numRows--; } this.componentChanged(); } } - insertColumn(x: number): void { + insertColumn(columnIndex: number): void { const tableData = this.authoringComponentContent.tableData; const numRows = this.authoringComponentContent.numRows; for (let r = 0; r < numRows; r++) { - const tempRow = tableData[r]; - if (tempRow != null) { - const newCell = this.createEmptyCell(); - tempRow.splice(x, 0, newCell); - } + const row = tableData[r]; + const newCell = this.createEmptyCell(); + row.splice(columnIndex, 0, newCell); } this.authoringComponentContent.numColumns++; this.parseColumnCellSizes(this.authoringComponentContent); this.componentChanged(); } - deleteColumn(x: number): void { + deleteColumn(columnIndex: number): void { if (confirm($localize`Are you sure you want to delete this column?`)) { const tableData = this.authoringComponentContent.tableData; const numRows = this.authoringComponentContent.numRows; for (let r = 0; r < numRows; r++) { - const tempRow = tableData[r]; - if (tempRow != null) { - tempRow.splice(x, 1); - } + const row = tableData[r]; + row.splice(columnIndex, 1); } this.authoringComponentContent.numColumns--; this.parseColumnCellSizes(this.authoringComponentContent); From ffd530cb511d3e3609b9464bbf6b0b26b05941d8 Mon Sep 17 00:00:00 2001 From: breity Date: Fri, 29 Jan 2021 13:27:06 -0800 Subject: [PATCH 12/13] Styled and updated text for unlink Google account dialogs, student and teacher profile pages. #2870 --- .../edit-password.component.html | 18 +- .../edit-password.component.scss | 9 +- .../edit-password/edit-password.component.ts | 4 +- ...link-google-account-confirm.component.html | 23 ++- ...link-google-account-confirm.component.scss | 7 +- ...unlink-google-account-confirm.component.ts | 4 +- ...ink-google-account-password.component.html | 87 ++++----- ...ink-google-account-password.component.scss | 5 +- ...nlink-google-account-password.component.ts | 4 +- ...link-google-account-success.component.html | 11 +- ...link-google-account-success.component.scss | 5 +- .../edit-profile/edit-profile.component.html | 43 +++-- .../edit-profile/edit-profile.component.scss | 17 +- .../edit-profile/edit-profile.component.ts | 4 +- .../edit-profile/edit-profile.component.html | 37 ++-- .../edit-profile/edit-profile.component.scss | 13 ++ .../edit-profile/edit-profile.component.ts | 4 +- .../edit-run-warning-dialog.component.html | 4 +- src/main/webapp/site/src/messages.xlf | 177 +++++++++++------- .../site/src/style/layout/_section.scss | 2 +- 20 files changed, 280 insertions(+), 198 deletions(-) diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html index 71af1f2b7d..e2fd57a2df 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html @@ -57,13 +57,17 @@
-

This account was created using Google and doesn't use a WISE password.

-

-

diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss index 940482c43e..60fab472cc 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss @@ -11,6 +11,11 @@ form { } } -.notice { - margin: 0 auto; +.google-icon { + height: 1.8em; + width: auto; +} + +.unlink { + margin: 8px 0; } diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts index 8df43f729b..7539bc803e 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts @@ -83,7 +83,9 @@ export class EditPasswordComponent { } unlinkGoogleAccount() { - this.dialog.open(UnlinkGoogleAccountConfirmComponent); + this.dialog.open(UnlinkGoogleAccountConfirmComponent, { + panelClass: 'mat-dialog--sm' + }); } resetForm() { diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html index 1787a19ea9..9fd9a47c13 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html @@ -1,17 +1,16 @@ -

Unlink Google Account

+

+ Google logo + Unlink Google Account + + warning +

-

- If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. -

-

Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue?

+

To remove the link to your Google account, you will be asked to create a WISE password. In the future, you'll sign in to WISE using your username and password.

+

You will no longer be able to sign in to WISE using Google. Would you like to continue?

- - - + + + Continue diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss index 0af6d8c18e..257ab6ea45 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss @@ -1,3 +1,4 @@ -.info-note { - font-weight: bold; -} +.google-icon { + height: 1.4em; + width: auto; +} \ No newline at end of file diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts index 73a3e6d7f5..99a4fe2fd8 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts @@ -11,6 +11,8 @@ export class UnlinkGoogleAccountConfirmComponent { continue() { this.dialog.closeAll(); - this.dialog.open(UnlinkGoogleAccountPasswordComponent); + this.dialog.open(UnlinkGoogleAccountPasswordComponent, { + panelClass: 'mat-dialog--sm' + }); } } diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html index 88f3989891..e5c496120f 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html @@ -1,46 +1,41 @@ -

Unlink Google Account

- -
-

Create a WISE password:

-
-

- - New Password - - New Password required - -

-

- - Confirm New Password - - Confirm Password required - Passwords do not match - -

-
-
-
- - - - +

+ Google logo + Unlink Google Account +

+
+ +

Create a WISE password:

+ + New Password + + New Password required + + + Confirm New Password + + Confirm Password required + Passwords do not match + +
+ + + + +
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss index efd343d97f..6629b7fe81 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss @@ -1,3 +1,4 @@ -mat-dialog-content { - min-width: 600px; +.google-icon { + height: 1.4em; + width: auto; } diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts index 152b168f88..da0a00f3a0 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts @@ -32,7 +32,9 @@ export class UnlinkGoogleAccountPasswordComponent { .add(() => { this.isSaving = false; this.dialog.closeAll(); - this.dialog.open(UnlinkGoogleAccountSuccessComponent); + this.dialog.open(UnlinkGoogleAccountSuccessComponent, { + panelClass: 'mat-dialog--sm' + }); }); } } diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html index e985c5bb42..c9d91706f6 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html @@ -1,10 +1,11 @@ -

Unlink Google Account

+

+ Google logo + Unlink Google Account +

-

- Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. -

-

Your username is: {{username}}

+

Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.

+

Your username is: {{ username }}.

diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss index 0af6d8c18e..6629b7fe81 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss @@ -1,3 +1,4 @@ -.info-note { - font-weight: bold; +.google-icon { + height: 1.4em; + width: auto; } diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html index 14fe2c4609..10dd31c105 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html @@ -1,5 +1,5 @@
-
+

First Name @@ -42,28 +42,31 @@ Language required

-
+
+ +
- - -
This profile is linked to a Google account.
- -
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss index 36ff60ef97..a1e699e818 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss @@ -3,10 +3,19 @@ '~style/abstracts/functions', '~style/abstracts/mixins'; -form { +.inputs { max-width: breakpoint('sm.min'); +} + +.actions { + margin-top: 8px; +} + +.google-icon { + height: 1.8em; + width: auto; +} - @media (max-width: breakpoint('sm.max')) { - margin: 0 auto; - } +.unlink { + margin: 8px 0; } diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts index f083a25656..fe6d7b3452 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts @@ -98,6 +98,8 @@ export class EditProfileComponent { } unlinkGoogleAccount() { - this.dialog.open(UnlinkGoogleAccountConfirmComponent); + this.dialog.open(UnlinkGoogleAccountConfirmComponent, { + panelClass: 'mat-dialog--sm' + }); } } diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html index a551190844..159c38c1b1 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html @@ -105,31 +105,36 @@ Help us translate WISE! Visit https://crowdin.com/project/wise. - Language required + + Language required +

-
+
- -
This profile is linked to a Google account.
- -
+
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss index dbd834f0b1..1888291374 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss @@ -21,3 +21,16 @@ } } } + +.actions { + margin-top: 8px; +} + +.google-icon { + height: 1.8em; + width: auto; +} + +.unlink { + margin: 8px 0; +} diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts index a6e0d95779..01c612ea48 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts @@ -144,6 +144,8 @@ export class EditProfileComponent { } unlinkGoogleAccount() { - this.dialog.open(UnlinkGoogleAccountConfirmComponent); + this.dialog.open(UnlinkGoogleAccountConfirmComponent, { + panelClass: 'mat-dialog--sm' + }); } } diff --git a/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html b/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html index 1f9c2f19f8..c196fa4e2f 100644 --- a/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html +++ b/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html @@ -1,7 +1,7 @@ -

+

Edit Classroom Unit - warning + warning

diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index 1feee2c605..873438e45e 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -236,23 +236,47 @@ 10 - - Unlink Google Account + + Google logo app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html - 1 + 2 app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 1 + 2 app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html - 1 + 2 app/modules/shared/edit-password/edit-password.component.html - 66 + 61 + + + app/register/register-teacher/register-teacher.component.html + 28 + + + app/register/register-teacher-complete/register-teacher-complete.component.html + 14 + + + app/register/register-student-complete/register-student-complete.component.html + 14 + + + app/register/register-student/register-student.component.html + 39 + + + app/register/register-google-user-already-exists/register-google-user-already-exists.component.html + 10 + + + app/student/team-sign-in-dialog/team-sign-in-dialog.component.html + 59 app/student/account/edit-profile/edit-profile.component.html @@ -260,28 +284,55 @@ app/teacher/account/edit-profile/edit-profile.component.html - 131 + 132 - - Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. + + Unlink Google Account app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html - 4 + 3 + + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 3 + + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 3 + + + app/modules/shared/edit-password/edit-password.component.html + 70 + + + app/student/account/edit-profile/edit-profile.component.html + 68 + + + app/teacher/account/edit-profile/edit-profile.component.html + 136 - - Your username is: + + Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created. app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html 7 + + Your username is: . + + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 8 + + Done app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html - 11 + 12 app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html @@ -308,14 +359,14 @@ Create a WISE password: app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 4 + 7 New Password app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 8 + 9 app/modules/shared/edit-password/edit-password.component.html @@ -326,7 +377,7 @@ New Password required app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 15 + 16 app/modules/shared/edit-password/edit-password.component.html @@ -337,7 +388,7 @@ Confirm New Password app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 20 + 19 app/modules/shared/edit-password/edit-password.component.html @@ -348,7 +399,7 @@ Confirm Password required app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 27 + 26 app/modules/shared/edit-password/edit-password.component.html @@ -375,7 +426,7 @@ Passwords do not match app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 28 + 27 app/modules/shared/edit-password/edit-password.component.html @@ -386,11 +437,11 @@ Cancel app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 35 + 31 app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html - 11 + 14 app/modules/library/copy-project-dialog/copy-project-dialog.component.html @@ -445,7 +496,7 @@ Submit app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 43 + 37 app/contact/contact-form/contact-form.component.html @@ -480,18 +531,33 @@ 26 - - If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. + + Warning app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 5 + + + app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html 4 + + app/help/teacher-faq/teacher-faq.component.html + 85 + - - Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue? + + To remove the link to your Google account, you will be asked to create a WISE password. In the future, you'll sign in to WISE using your username and password. app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html - 7 + 9 + + + + You will no longer be able to sign in to WISE using Google. Would you like to continue? + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 10 @@ -537,8 +603,8 @@ 7 - - This account was created using Google and doesn't use a WISE password. + + This account was created using Google and doesn't use a WISE password. app/modules/shared/edit-password/edit-password.component.html 60 @@ -3943,13 +4009,6 @@ 84 - - Warning - - app/help/teacher-faq/teacher-faq.component.html - 85 - - If you move a student to a different period, they will lose all of their work. @@ -4675,33 +4734,6 @@ 22 - - Google logo - - app/register/register-teacher/register-teacher.component.html - 28 - - - app/register/register-teacher-complete/register-teacher-complete.component.html - 14 - - - app/register/register-student-complete/register-student-complete.component.html - 14 - - - app/register/register-student/register-student.component.html - 39 - - - app/register/register-google-user-already-exists/register-google-user-already-exists.component.html - 10 - - - app/student/team-sign-in-dialog/team-sign-in-dialog.component.html - 59 - - Sign up with Google @@ -5383,31 +5415,27 @@ app/student/account/edit-profile/edit-profile.component.html 42 - - app/teacher/account/edit-profile/edit-profile.component.html - 108 - Save Changes app/student/account/edit-profile/edit-profile.component.html - 55 + 57 app/teacher/account/edit-profile/edit-profile.component.html - 122 + 125 This profile is linked to a Google account. app/student/account/edit-profile/edit-profile.component.html - 58 + 65 app/teacher/account/edit-profile/edit-profile.component.html - 125 + 133 @@ -5908,6 +5936,13 @@ 107 + + Language required + + app/teacher/account/edit-profile/edit-profile.component.html + 108 + + Back to Unit Plan diff --git a/src/main/webapp/site/src/style/layout/_section.scss b/src/main/webapp/site/src/style/layout/_section.scss index bc80d059f1..25b5b018a6 100644 --- a/src/main/webapp/site/src/style/layout/_section.scss +++ b/src/main/webapp/site/src/style/layout/_section.scss @@ -20,7 +20,7 @@ } .section__tab { - padding: 24px 0; + padding: 24px 4px; @media (min-width: breakpoint('sm.min')) { padding: 24px 16px; From d174d5b164c94b699f41653dc0163816751205d5 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Fri, 29 Jan 2021 14:21:14 -0800 Subject: [PATCH 13/13] Bumped version number to 5.20.5 --- package-lock.json | 2 +- package.json | 2 +- pom.xml | 2 +- src/main/resources/version.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79aeae432d..0f8dabb348 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wise", - "version": "5.20.3", + "version": "5.20.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 57910be09b..71c70cf53c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wise", - "version": "5.20.3", + "version": "5.20.5", "description": "Web-based Inquiry Science Environment", "main": "app.js", "browserslist": [ diff --git a/pom.xml b/pom.xml index 76ef099376..d7c44772ce 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ wise war Web-based Inquiry Science Environment - 5.20.3 + 5.20.5 http://wise5.org diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt index 6628d512cc..fa20f9833e 100644 --- a/src/main/resources/version.txt +++ b/src/main/resources/version.txt @@ -1 +1 @@ -5.20.3 +5.20.5