Skip to content

Commit

Permalink
feat(NodeAuthoring): Toggle Edit/Preview component (#1994)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Lim-Breitbart <[email protected]>
  • Loading branch information
hirokiterashima and breity authored Dec 16, 2024
1 parent 00ea8b6 commit 5ceb195
Show file tree
Hide file tree
Showing 31 changed files with 552 additions and 889 deletions.
2 changes: 2 additions & 0 deletions src/app/student-teacher-common.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { MathModule } from './math/math.module';
import { MatMenuModule } from '@angular/material/menu';
import { MainMenuComponent } from '../assets/wise5/common/main-menu/main-menu.component';
import { SideMenuComponent } from '../assets/wise5/common/side-menu/side-menu.component';
import { ScrollingModule } from '@angular/cdk/scrolling';

@NgModule({
declarations: [
Expand Down Expand Up @@ -82,6 +83,7 @@ import { SideMenuComponent } from '../assets/wise5/common/side-menu/side-menu.co
NodeStatusIconComponent,
NotebookModule,
ReactiveFormsModule,
ScrollingModule,
StudentTeacherCommonServicesModule
],
exports: [
Expand Down
5 changes: 0 additions & 5 deletions src/app/teacher/authoring-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ProjectAuthoringComponent } from '../../assets/wise5/authoringTool/proj
import { NodeAuthoringComponent } from '../../assets/wise5/authoringTool/node/node-authoring/node-authoring.component';
import { NodeAdvancedAuthoringComponent } from '../../assets/wise5/authoringTool/node/advanced/node-advanced-authoring/node-advanced-authoring.component';
import { NodeAdvancedConstraintAuthoringComponent } from '../../assets/wise5/authoringTool/node/advanced/constraint/node-advanced-constraint-authoring.component';
import { ChooseComponentLocationComponent } from '../../assets/wise5/authoringTool/node/chooseComponentLocation/choose-component-location.component';
import { AddLessonConfigureComponent } from '../../assets/wise5/authoringTool/addLesson/add-lesson-configure/add-lesson-configure.component';
import { ChooseNewNodeTemplateComponent } from '../../assets/wise5/authoringTool/addNode/choose-new-node-template/choose-new-node-template.component';
import { AddYourOwnNodeComponent } from '../../assets/wise5/authoringTool/addNode/add-your-own-node/add-your-own-node.component';
Expand Down Expand Up @@ -153,10 +152,6 @@ const routes: Routes = [
{ path: 'rubric', component: EditNodeRubricComponent }
]
},
{
path: 'choose-component-location',
component: ChooseComponentLocationComponent
},
{
path: 'import-component',
children: [{ path: 'choose-component', component: ChooseImportComponentComponent }]
Expand Down
4 changes: 0 additions & 4 deletions src/app/teacher/authoring-tool.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { WiseTinymceEditorModule } from '../../assets/wise5/directives/wise-tiny
import { NotebookAuthoringComponent } from '../../assets/wise5/authoringTool/notebook-authoring/notebook-authoring.component';
import { StructureAuthoringModule } from '../../assets/wise5/authoringTool/structure/structure-authoring.module';
import { MilestonesAuthoringComponent } from '../../assets/wise5/authoringTool/milestones-authoring/milestones-authoring.component';
import { ChooseComponentLocationComponent } from '../../assets/wise5/authoringTool/node/chooseComponentLocation/choose-component-location.component';
import { TopBarComponent } from '../../assets/wise5/authoringTool/components/top-bar/top-bar.component';
import { ProjectAssetAuthoringModule } from '../../assets/wise5/authoringTool/project-asset-authoring/project-asset-authoring.module';
import { ChooseSimulationComponent } from '../../assets/wise5/authoringTool/addNode/choose-simulation/choose-simulation.component';
Expand Down Expand Up @@ -57,7 +56,6 @@ import { TranslatableTextareaComponent } from '../../assets/wise5/authoringTool/
import { TranslatableRichTextEditorComponent } from '../../assets/wise5/authoringTool/components/translatable-rich-text-editor/translatable-rich-text-editor.component';
import { AddStepButtonComponent } from '../../assets/wise5/authoringTool/add-step-button/add-step-button.component';
import { CreateBranchComponent } from '../../assets/wise5/authoringTool/create-branch/create-branch.component';
import { PreviewComponentButtonComponent } from '../../assets/wise5/authoringTool/components/preview-component-button/preview-component-button.component';
import { EditBranchComponent } from '../../assets/wise5/authoringTool/edit-branch/edit-branch.component';
import { ComponentTypeButtonComponent } from '../../assets/wise5/authoringTool/components/component-type-button/component-type-button.component';
import { MatExpansionModule } from '@angular/material/expansion';
Expand Down Expand Up @@ -92,7 +90,6 @@ import { MatExpansionModule } from '@angular/material/expansion';
AddYourOwnNodeComponent,
AuthoringToolBarComponent,
ChooseAutomatedAssessmentComponent,
ChooseComponentLocationComponent,
ChooseCopyNodeLocationComponent,
ChooseImportStepComponent,
ChooseImportUnitComponent,
Expand All @@ -116,7 +113,6 @@ import { MatExpansionModule } from '@angular/material/expansion';
NodeAdvancedAuthoringModule,
NodeIconAndTitleComponent,
NodeWithMoveAfterButtonComponent,
PreviewComponentButtonComponent,
ProjectAssetAuthoringModule,
ProjectListComponent,
RouterModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
class="l-main"
[ngClass]="{ 'l-main--with-toolbar': showToolbar }"
role="main"
cdkScrollable
>
<router-outlet></router-outlet>
</content>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,65 @@
import {
ApplicationRef,
Component,
ComponentRef,
ElementRef,
EnvironmentInjector,
Input,
ViewChild,
createComponent
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ComponentContent } from '../../common/ComponentContent';
import { components } from '../../components/Components';
import { PreviewComponentComponent } from './preview-component/preview-component.component';
import { EditComponentComponent } from './edit-component/edit-component.component';
import { ComponentFactory } from '../../common/ComponentFactory';
import { Component as WISEComponent } from '../../common/Component';
import { TeacherProjectService } from '../../services/teacherProjectService';
import { MatTooltipModule } from '@angular/material/tooltip';

@Component({
imports: [PreviewComponentComponent, EditComponentComponent, MatTooltipModule],
selector: 'component-authoring',
standalone: true,
template: '<div #component></div>'
styles: [
`
preview-component {
display: block;
position: relative;
cursor: pointer;
}
preview-component:hover {
outline: 3px dashed #aaaaaa;
outline-offset: 8px;
}
preview-component:after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
`
],
template: `@if (editing) {
<edit-component [componentContent]="componentContent" [nodeId]="nodeId" />
} @else {
<preview-component
role="button"
tabindex="0"
(click)="editComponentEvent.emit()"
(keyup.enter)="editComponentEvent.emit()"
[component]="component"
[disabled]="true"
matTooltip="Edit content"
i18n-matTooltip
/>
}`
})
export class ComponentAuthoringComponent {
@Input() private componentContent: ComponentContent;
@ViewChild('component') private componentElementRef: ElementRef;
private componentRef: ComponentRef<any>;
@Input() private nodeId: string;
protected component: WISEComponent;
@Input() componentContent: ComponentContent;
@Input() editing: boolean;
@Output() editComponentEvent: EventEmitter<void> = new EventEmitter<void>();
@Input() nodeId: string;

constructor(
private applicationRef: ApplicationRef,
private injector: EnvironmentInjector
) {}
constructor(private projectService: TeacherProjectService) {}

ngAfterViewInit(): void {
this.componentRef = createComponent(components[this.componentContent.type].authoring, {
hostElement: this.componentElementRef.nativeElement,
environmentInjector: this.injector
});
Object.assign(this.componentRef.instance, {
componentContent: this.componentContent,
nodeId: this.nodeId
});
this.applicationRef.attachView(this.componentRef.hostView);
}

ngOnDestroy(): void {
this.componentRef.destroy();
ngOnChanges(): void {
this.component = new ComponentFactory().getComponent(
this.projectService.injectAssetPaths(this.componentContent),
this.nodeId
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditComponentComponent } from './edit-component.component';
import { Component } from '@angular/core';
import { components } from '../../../components/Components';
import { ComponentContent } from '../../../common/ComponentContent';

@Component({
selector: 'mock-authoring',
template: '<div>Mock Authoring Component</div>'
})
class MockAuthoringComponent {
componentContent: any;
nodeId: string;
}

describe('EditComponentComponent', () => {
let component: EditComponentComponent;
let fixture: ComponentFixture<EditComponentComponent>;
const mockComponentContent = {
type: 'mockComponent'
};
const mockNodeId = 'node1';

beforeEach(async () => {
components['mockComponent'] = {
authoring: MockAuthoringComponent
};

await TestBed.configureTestingModule({
imports: [EditComponentComponent]
}).compileComponents();

fixture = TestBed.createComponent(EditComponentComponent);
component = fixture.componentInstance;
component.componentContent = mockComponentContent as ComponentContent;
component.nodeId = mockNodeId;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should create the dynamic component after view init', () => {
component.ngAfterViewInit();
fixture.detectChanges();
const componentElement = fixture.nativeElement.querySelector('div');
expect(componentElement.textContent).toContain('Mock Authoring Component');
});

it('should pass inputs to the dynamic component', () => {
component.ngAfterViewInit();
fixture.detectChanges();
const componentInstance = (component as any).componentRef.instance;
expect(componentInstance.componentContent).toBe(mockComponentContent);
expect(componentInstance.nodeId).toBe(mockNodeId);
});

it('should destroy the component reference on destroy', () => {
component.ngAfterViewInit();
const destroySpy = spyOn((component as any).componentRef, 'destroy');
component.ngOnDestroy();
expect(destroySpy).toHaveBeenCalled();
});

it('should focus the host element after timeout', (done) => {
component.ngAfterViewInit();
fixture.detectChanges();
setTimeout(() => {
const hostElement = fixture.nativeElement.querySelector('div');
expect(document.activeElement).toBe(hostElement);
done();
}, 0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
ApplicationRef,
Component,
ComponentRef,
ElementRef,
EnvironmentInjector,
Input,
ViewChild,
createComponent
} from '@angular/core';
import { ComponentContent } from '../../../common/ComponentContent';
import { components } from '../../../components/Components';

@Component({
selector: 'edit-component',
standalone: true,
template: '<div #component tabindex="-1"></div>'
})
export class EditComponentComponent {
@Input() componentContent: ComponentContent;
@ViewChild('component') private componentElementRef: ElementRef;
private componentRef: ComponentRef<any>;
@Input() nodeId: string;

constructor(
private applicationRef: ApplicationRef,
private injector: EnvironmentInjector
) {}

ngAfterViewInit(): void {
const hostElement = this.componentElementRef.nativeElement;
this.componentRef = createComponent(components[this.componentContent.type].authoring, {
hostElement: hostElement,
environmentInjector: this.injector
});
Object.assign(this.componentRef.instance, {
componentContent: this.componentContent,
nodeId: this.nodeId
});
this.applicationRef.attachView(this.componentRef.hostView);
setTimeout(() => hostElement.focus());
}

ngOnDestroy(): void {
this.componentRef.destroy();
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ import { components } from '../../../components/Components';
@Component({
selector: 'preview-component',
standalone: true,
template: '<div class="component__wrapper"><div #component></div></div>'
template: '<div #component></div>'
})
export class PreviewComponentComponent {
@Input() protected component: WISEComponent;
@Input() component: WISEComponent;
@ViewChild('component') private componentElementRef: ElementRef;
private componentRef: ComponentRef<WISEComponent>;
@Input() protected periodId: number;
@Output() private starterStateChangedEvent: EventEmitter<any> = new EventEmitter<any>();
@Input() disabled: boolean;
@Input() periodId: number;
@Output() starterStateChangedEvent: EventEmitter<any> = new EventEmitter<any>();

constructor(private applicationRef: ApplicationRef, private injector: EnvironmentInjector) {}
constructor(
private applicationRef: ApplicationRef,
private injector: EnvironmentInjector
) {}

ngAfterViewInit(): void {
this.renderComponent();
Expand All @@ -50,6 +54,7 @@ export class PreviewComponentComponent {
component: this.component,
mode: 'preview',
periodId: this.periodId,
isDisabled: this.disabled,
starterStateChangedEvent: this.starterStateChangedEvent
});
this.applicationRef.attachView(this.componentRef.hostView);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<div class="warn">
<b>{{ message }}</b>
</div>
@if (message) {
<div class="concurrent-authors">
<b class="warn">{{ message }}</b>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ describe('ConcurrentAuthorsMessageComponent', () => {

function ngOnInit() {
describe('ngOnInit()', () => {
it('set empty message when there are no other authors', () => {
expectMessage('["aa"]', '');
});
it('set message to author when there is one other author', () => {
expectMessage(
'["aa","bb"]',
Expand Down
Loading

0 comments on commit 5ceb195

Please sign in to comment.