diff --git a/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.html b/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.html index de038fe618b..705e60da797 100644 --- a/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.html +++ b/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.html @@ -1,7 +1,7 @@
  @if (showPosition) { - {{ getNodePosition(nodeId) }}: + {{ nodePosition }}: } - {{ getNodeTitle(nodeId) }} + {{ nodeTitle }}
diff --git a/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.ts b/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.ts index b9701590ea9..1a15f201f9d 100644 --- a/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.ts +++ b/src/assets/wise5/authoringTool/choose-node-location/node-icon-and-title/node-icon-and-title.component.ts @@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core'; import { TeacherProjectService } from '../../../services/teacherProjectService'; import { NodeIconComponent } from '../../../vle/node-icon/node-icon.component'; import { TeacherProjectTranslationService } from '../../../services/teacherProjectTranslationService'; +import { Subscription } from 'rxjs'; @Component({ imports: [NodeIconComponent], @@ -11,18 +12,34 @@ import { TeacherProjectTranslationService } from '../../../services/teacherProje }) export class NodeIconAndTitleComponent { @Input() protected nodeId: string; + protected nodePosition: string; + protected nodeTitle: string; @Input() protected showPosition: boolean; + private subscriptions: Subscription; constructor( private projectService: TeacherProjectService, private projectTranslationService: TeacherProjectTranslationService ) {} - protected getNodePosition(nodeId: string): string { + ngOnInit(): void { + this.nodePosition = this.getNodePosition(this.nodeId); + this.nodeTitle = this.getNodeTitle(this.nodeId); + this.subscriptions = this.projectService.projectParsed$.subscribe(() => { + this.nodePosition = this.getNodePosition(this.nodeId); + this.nodeTitle = this.getNodeTitle(this.nodeId); + }); + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + private getNodePosition(nodeId: string): string { return this.projectService.getNodePositionById(nodeId); } - protected getNodeTitle(nodeId: string): string { + private getNodeTitle(nodeId: string): string { return this.projectService.isDefaultLocale() ? this.projectService.getNodeTitle(nodeId) : this.translateNodeTitle(nodeId); diff --git a/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html b/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html index c76a22937d1..5dbbd54afb2 100644 --- a/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html +++ b/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html @@ -3,18 +3,32 @@ class="lesson flex grow" (opened)="toggleExpanded(true)" (closed)="toggleExpanded(false)" + cdkDrag + [cdkDragData]="{ type: 'group', id: lesson.id }" + [id]="lesson.id" > +
- + @if (batchEditMode) { + + } @else { + drag_indicator + } @@ -51,10 +65,19 @@ -
+
@for (childId of lesson.ids; track childId) {
; @@ -45,12 +47,14 @@ describe('ProjectAuthoringLessonComponent', () => { CopyTranslationsService, DeleteNodeService, DeleteTranslationsService, + MoveNodesService, ProjectService, TeacherDataService, TeacherProjectTranslationService ), MockProvider(TeacherProjectService, { - getNodeTypeSelected: () => signal(NodeTypeSelected.lesson) + getNodeTypeSelected: () => signal(NodeTypeSelected.lesson), + projectParsed$: of() }), provideRouter([]) ] diff --git a/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.ts b/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.ts index fc55047f797..4369f43a9b2 100644 --- a/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.ts +++ b/src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, Output, Signal, ViewEncapsulation } from '@angular/core'; +import { CdkDrag, CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; @@ -17,11 +18,13 @@ import { DeleteNodeService } from '../../services/deleteNodeService'; import { ActivatedRoute, Router } from '@angular/router'; import { DeleteTranslationsService } from '../../services/deleteTranslationsService'; import { AddStepTarget } from '../../../../app/domain/addStepTarget'; +import { MoveNodesService } from '../../services/moveNodesService'; @Component({ encapsulation: ViewEncapsulation.None, imports: [ FormsModule, + DragDropModule, MatExpansionModule, MatCheckboxModule, MatIconModule, @@ -36,6 +39,8 @@ import { AddStepTarget } from '../../../../app/domain/addStepTarget'; templateUrl: './project-authoring-lesson.component.html' }) export class ProjectAuthoringLessonComponent { + allGroupIds: string[]; + @Input() batchEditMode: boolean; @Input() expanded: boolean = true; @Output() onExpandedChanged: EventEmitter = new EventEmitter(); protected idToNode: any = {}; @@ -49,12 +54,14 @@ export class ProjectAuthoringLessonComponent { private dataService: TeacherDataService, private deleteNodeService: DeleteNodeService, private deleteTranslationsService: DeleteTranslationsService, + private moveNodesService: MoveNodesService, private projectService: TeacherProjectService, private route: ActivatedRoute, private router: Router ) {} ngOnInit(): void { + this.allGroupIds = this.projectService.getAllGroupIds(); this.idToNode = this.projectService.idToNode; this.nodeTypeSelected = this.projectService.getNodeTypeSelected(); } @@ -100,4 +107,32 @@ export class ProjectAuthoringLessonComponent { this.projectService.saveProject(); this.projectService.refreshProject(); } + + protected dropNode(event: CdkDragDrop): void { + const { container, currentIndex, item, previousContainer, previousIndex } = event; + if (previousContainer === container) { + moveItemInArray(container.data.nodes, previousIndex, currentIndex); + } else { + // do nothing. the UI will be updated by moveNodesAfter() and refreshProject() calls + } + if (currentIndex == 0) { + this.moveNodesService.moveNodesInsideGroup([item.data.id], container.data.groupId); + } else { + this.moveNodesService.moveNodesAfter([item.data.id], container.data.nodes[currentIndex - 1]); + } + this.projectService.checkPotentialStartNodeIdChangeThenSaveProject().then(() => { + this.projectService.refreshProject(); + }); + } + + // allow a step to drop anywhere except the first step in a first path of a branch activity + // otherwise, the step will be placed after a node that has multiple transitions, which is not allowed + protected notAfterBranchingNode(index: number, item: CdkDrag): boolean { + if (index === 0) return true; + const nodesExceptItem = item.dropContainer.data.nodes.filter( + (nodeId) => nodeId !== item.data.id + ); + const nodeBefore = item.dropContainer.data.idToNode[nodesExceptItem[index - 1]]; + return nodeBefore.transitionLogic.transitions.length <= 1; + } } diff --git a/src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html b/src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html index e5074c0628e..f8c30e1c321 100644 --- a/src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html +++ b/src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html @@ -7,22 +7,32 @@ (keyup.enter)="setCurrentNode(step.id)" role="button" tabindex="0" + cdkDrag + [cdkDragDisabled]="branchPoint" + [cdkDragData]="{ type: 'step', id: step.id }" aria-label="Edit step" i18n-aria-label > - + @if (batchEditMode) { + + } @else if (!branchPoint) { +
+ drag_indicator + }
- @if (isBranchPoint(step.id)) { + @if (branchPoint) {
- + @if (!branchPoint) { + + } - - -
- - -
-
- @if (lessons.length === 0) { -
-
There are no lessons
- -
- } @else { - @for (lesson of lessons; track lesson.id; let first = $first) { -
- - -
+
+
+

+ Batch edit mode +

+ @if (batchEditMode) { + + + } - } -
-
Unused Lessons
- @if (inactiveGroupNodes.length === 0) { -
-
There are no unused lessons
- -
- } @else { -
- @for (groupNode of inactiveGroupNodes; track groupNode.id; let first = $first) { +
+ + +
+
+
+ @if (lessons.length === 0) { +
+
There are no lessons
+ +
+ } @else { + @for (lesson of lessons; track lesson.id; let first = $first) {
- +
} -
- } + } +
-
Unused Steps
- @if (inactiveStepNodes.length === 0) { -
There are no unused steps
- } @else { - @for (inactiveStepNode of inactiveStepNodes; track inactiveStepNode.id) { - @if (getParentGroup(inactiveStepNode.id) == null) { - +
+
Unused Lessons
+ @if (inactiveGroupNodes.length === 0) { +
+
There are no unused lessons
+ +
+ } @else { +
+ @for (groupNode of inactiveGroupNodes; track groupNode.id; let first = $first) { +
+ + +
+ } +
+ } +
+
+
Unused Steps
+ @if (inactiveStepNodes.length === 0) { +
There are no unused steps
+ } @else { + @for (inactiveStepNode of inactiveStepNodes; track inactiveStepNode.id) { + @if (getParentGroup(inactiveStepNode.id) == null) { + + } } } - } +
diff --git a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts index c544a1fa64b..f9c8e9de0b1 100644 --- a/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts +++ b/src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts @@ -18,13 +18,18 @@ import { ExpandEvent } from '../domain/expand-event'; import { DeleteTranslationsService } from '../../services/deleteTranslationsService'; import { ComponentContent } from '../../common/ComponentContent'; import { copy } from '../../common/object/object'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { CdkDrag, CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; +import { MoveNodesService } from '../../services/moveNodesService'; @Component({ imports: [ + DragDropModule, FormsModule, MatButtonModule, MatTooltipModule, MatIconModule, + MatSlideToggle, ProjectAuthoringLessonComponent, ProjectAuthoringStepComponent, AddLessonButtonComponent @@ -33,12 +38,14 @@ import { copy } from '../../common/object/object'; templateUrl: './project-authoring.component.html' }) export class ProjectAuthoringComponent implements OnInit { + protected allGroupIds: string[]; protected allLessonsCollapsed: Signal = computed(() => this.isAllLessonsExpandedValue(false) ); protected allLessonsExpanded: Signal = computed(() => this.isAllLessonsExpandedValue(true) ); + protected batchEditMode: boolean = false; protected inactiveGroupNodes: any[]; private inactiveNodes: any[]; protected inactiveStepNodes: any[]; @@ -54,12 +61,14 @@ export class ProjectAuthoringComponent implements OnInit { private dataService: TeacherDataService, private deleteNodeService: DeleteNodeService, private deleteTranslationsService: DeleteTranslationsService, + private moveNodesService: MoveNodesService, private projectService: TeacherProjectService, private route: ActivatedRoute, private router: Router ) {} ngOnInit(): void { + this.allGroupIds = this.projectService.getAllGroupIds(); this.projectId = Number(this.projectId); this.refreshProject(); this.dataService.setCurrentNode(null); @@ -251,4 +260,50 @@ export class ProjectAuthoringComponent implements OnInit { }); this.projectService.setNodeTypeSelected(nodeTypeSelected); } + + protected dropGroup(event: CdkDragDrop): void { + const { container, currentIndex, item, previousContainer, previousIndex } = event; + if (previousContainer === container) { + moveItemInArray(container.data.nodes, previousIndex, currentIndex); + } else { + // do nothing. the UI will be updated by moveNodesAfter() and refreshProject() calls + } + if (currentIndex == 0) { + this.moveNodesService.moveNodesInsideGroup( + [item.data.id], + container.data.type === 'active' ? 'group0' : 'inactiveGroups' + ); + } else { + this.moveNodesService.moveNodesAfter( + [item.data.id], + container.data.nodes[currentIndex - 1].id + ); + } + this.projectService.checkPotentialStartNodeIdChangeThenSaveProject().then(() => { + this.projectService.refreshProject(); + }); + } + + protected dropInactiveNode(event: CdkDragDrop): void { + const { container, currentIndex, item } = event; + if (currentIndex == 0) { + this.moveNodesService.moveNodesInsideGroup([item.data.id], 'inactiveNodes'); + } else { + this.moveNodesService.moveNodesAfter( + [item.data.id], + container.data.nodes[currentIndex - 1].id + ); + } + this.projectService.checkPotentialStartNodeIdChangeThenSaveProject().then(() => { + this.projectService.refreshProject(); + }); + } + + protected groupPredicate(item: CdkDrag): boolean { + return item.data.type === 'group'; + } + + protected stepPredicate(item: CdkDrag): boolean { + return item.data.type === 'step'; + } } diff --git a/src/assets/wise5/services/teacherProjectService.ts b/src/assets/wise5/services/teacherProjectService.ts index fc81e095df1..4c3a24a5209 100644 --- a/src/assets/wise5/services/teacherProjectService.ts +++ b/src/assets/wise5/services/teacherProjectService.ts @@ -1891,6 +1891,13 @@ export class TeacherProjectService extends ProjectService { ); } + getAllGroupIds(): string[] { + return this.getGroupNodes() + .concat(this.getInactiveGroupNodes()) + .map((node) => node.id) + .concat('inactiveSteps'); + } + uiChanged(): void { this.uiChangedSource.next(); } diff --git a/src/messages.xlf b/src/messages.xlf index 8220c5420a0..9bd6858259b 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -723,7 +723,7 @@ src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 30,33 + 35,38 src/assets/wise5/components/animation/animation-authoring/animation-authoring.component.html @@ -1203,6 +1203,14 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.src/assets/wise5/authoringTool/node/node-authoring/node-authoring.component.html 45,47 + + src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html + 27,31 + + + src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html + 27,32 + src/assets/wise5/components/common/feedbackRule/edit-feedback-rules/edit-feedback-rules.component.html 35,37 @@ -6061,7 +6069,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 19,22 + 24,27 src/assets/wise5/authoringTool/project-list/project-list.component.html @@ -10207,7 +10215,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 74,77 + 97,100 @@ -10784,7 +10792,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 80,82 + 103,105 @@ -10810,7 +10818,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 105,107 + 139,141 @@ -13177,67 +13185,67 @@ The branches will be removed but the steps will remain in the unit. Expand/collapse lesson src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 7,10 + 11,14 Select lesson src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 15,18 + 20,25 Edit lesson src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 26,30 + 40,44 Move lesson src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 36,40 + 50,54 Delete lesson src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 46,50 + 60,64 This lesson has no steps src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.html - 69,72 + 92,95 Are you sure you want to delete this lesson? src/assets/wise5/authoringTool/project-authoring-lesson/project-authoring-lesson.component.ts - 84 + 91 Edit step src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 10,15 + 13,18 src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 63,67 + 73,77 Select step src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 18,22 + 22,26 @@ -13246,7 +13254,7 @@ The branches will be removed but the steps will remain in the unit. }}"/> src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 29,32 + 39,42 @@ -13255,56 +13263,63 @@ The branches will be removed but the steps will remain in the unit. }}"/> src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 42,45 + 52,55 Has rubric/teaching tips src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 54,57 + 64,67 Move step src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 73,77 + 84,88 Copy step src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 83,87 + 95,99 Delete step src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.html - 93,97 + 105,109 Are you sure you want to delete this step? src/assets/wise5/authoringTool/project-authoring-step/project-authoring-step.component.ts - 162 + 163 + + + + Batch edit mode + + src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html + 4,8 Move src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 8,11 + 13,16 + Expand All src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 44,48 + 50,54 src/assets/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestone-class-responses/milestone-class-responses.component.html @@ -13323,7 +13338,7 @@ The branches will be removed but the steps will remain in the unit. - Collapse All src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 54,58 + 60,63 src/assets/wise5/classroomMonitor/notebook-grading/notebook-grading.component.html @@ -13338,28 +13353,28 @@ The branches will be removed but the steps will remain in the unit. There are no lessons src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 60,64 + 73,76 There are no unused lessons src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 83,87 + 106,109 There are no unused steps src/assets/wise5/authoringTool/project-authoring/project-authoring.component.html - 107,109 + 141,143 Are you sure you want to delete the selected item(s)? src/assets/wise5/authoringTool/project-authoring/project-authoring.component.ts - 105 + 114