diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index 9284745988..7d3cf3d6d7 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -224,6 +224,8 @@ import {FTaskSheetViewComponent} from './units/states/tasks/viewer/directives/f- import {TasksViewerComponent} from './units/states/tasks/tasks-viewer/tasks-viewer.component'; import {UnitCodeComponent} from './common/unit-code/unit-code.component'; import {GradeService} from './common/services/grade.service'; +import { IndexComponent as UnitsIndexComponent } from './units/states/index/index.component'; + @NgModule({ // Components we declare @@ -325,6 +327,7 @@ import {GradeService} from './common/services/grade.service'; FUsersComponent, FTaskBadgeComponent, FUnitsComponent, + UnitsIndexComponent, ], // Services we provide providers: [ diff --git a/src/app/doubtfire-angularjs.module.ts b/src/app/doubtfire-angularjs.module.ts index 868e381213..7e3b47b092 100644 --- a/src/app/doubtfire-angularjs.module.ts +++ b/src/app/doubtfire-angularjs.module.ts @@ -1,9 +1,5 @@ -// # -// # Doubtfire - A lightweight, modern learning management system -// # -// # Doubtfire is modularised into many modules, as indicated by the directory -// # tree inside app/ -// # + +import './units/states/index/index.ajs.module'; import * as angular from 'angular'; import {downgradeInjectable, downgradeComponent} from '@angular/upgrade/static'; @@ -27,7 +23,7 @@ import 'angulartics/dist/angulartics.min.js'; import 'angulartics-google-analytics/lib/angulartics-google-analytics.js'; import 'angular-md5/angular-md5.js'; -// Ok... here is what we need to convert! + import 'build/templates-app.js'; import 'build/assets/wav-worker.js'; @@ -113,7 +109,6 @@ import 'build/src/app/units/states/edit/edit.js'; import 'build/src/app/units/states/rollover/directives/unit-dates-selector/unit-dates-selector.js'; import 'build/src/app/units/states/rollover/directives/directives.js'; import 'build/src/app/units/states/rollover/rollover.js'; -import 'build/src/app/units/states/index/index.js'; import 'build/src/app/units/states/students-list/students-list.js'; import 'build/src/app/units/states/analytics/analytics.js'; import 'build/src/app/common/filters/filters.js'; @@ -225,6 +220,8 @@ import {FUnitsComponent} from './admin/states/f-units/f-units.component'; import {MarkedPipe} from './common/pipes/marked.pipe'; import {AlertService} from './common/services/alert.service'; import {GradeService} from './common/services/grade.service'; +// + export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.config', 'doubtfire.sessions', @@ -237,7 +234,9 @@ export const DoubtfireAngularJSModule = angular.module('doubtfire', [ 'doubtfire.visualisations', ]); -// Downgrade angular modules that we need... +// + + // factory -> service DoubtfireAngularJSModule.factory('AboutDoubtfireModal', downgradeInjectable(AboutDoubtfireModal)); DoubtfireAngularJSModule.factory( diff --git a/src/app/doubtfire.states.ts b/src/app/doubtfire.states.ts index b9f95e88af..b95c7a4058 100644 --- a/src/app/doubtfire.states.ts +++ b/src/app/doubtfire.states.ts @@ -1,4 +1,5 @@ import {NgHybridStateDeclaration} from '@uirouter/angular-hybrid'; + import {InstitutionSettingsComponent} from './admin/institution-settings/institution-settings.component'; import {HomeComponent} from './home/states/home/home.component'; import {WelcomeComponent} from './welcome/welcome.component'; @@ -9,19 +10,18 @@ import {AcceptEulaComponent} from './eula/accept-eula/accept-eula.component'; import {FUsersComponent} from './admin/states/f-users/f-users.component'; import {FUnitsComponent} from './admin/states/f-units/f-units.component'; +/* IMPORT */ +import {unitsIndexState} from './units/states/index/index.state'; + /* * Use this file to store any states that are sourced by angular components. */ -/** - * Define the institution settings state - used to edit campus data. - */ const institutionSettingsState: NgHybridStateDeclaration = { - name: 'institutionsettings', // This is the name of the state to jump to - so ui-sref="institutionsettings" to jump here - url: '/admin/institution-settings', // You get here with this url + name: 'institutionsettings', + url: '/admin/institution-settings', views: { main: { - // Main body links to angular component component: InstitutionSettingsComponent, }, }, @@ -32,11 +32,10 @@ const institutionSettingsState: NgHybridStateDeclaration = { }; const usersState: NgHybridStateDeclaration = { - name: 'admin/users', // This is the name of the state to jump to - so ui-sref="users" to jump here - url: '/admin/users', // You get here with this url + name: 'admin/users', + url: '/admin/users', views: { main: { - // Main body links to angular component component: FUsersComponent, }, }, @@ -46,9 +45,6 @@ const usersState: NgHybridStateDeclaration = { }, }; -/** - * Define the new home state. - */ const HomeState: NgHybridStateDeclaration = { name: 'home', url: '/home', @@ -63,98 +59,6 @@ const HomeState: NgHybridStateDeclaration = { }, }; -// const unitParentState: NgHybridStateDeclaration = { -// name: 'units', -// url: '/units/:unit_id', -// // template for the parent state -// views: { -// main: { -// component: IndexComponent, -// }, -// }, -// resolve: { -// unit: function ($stateParams) { -// const unitService = AppInjector.get(UnitService); -// const globalState = AppInjector.get(GlobalStateService); -// globalState.onLoad(() => {}); -// console.log($stateParams); -// unitService.query({ id: $stateParams.unit_id }).subscribe((unit) => { -// console.log($stateParams.unit_id); -// console.log(unit); -// return unit; -// }); -// }, -// unitRole: function ($stateParams) { -// const globalStateService = AppInjector.get(GlobalStateService); - -// globalStateService.unitRolesSubject.subscribe((unitRoles) => { -// return unitRoles.find((unitRole) => unitRole.id === $stateParams.unit_id); -// }); -// }, -// }, -// }; - -/** - * Define the new home state. - */ -// const InboxState: NgHybridStateDeclaration = { -// name: 'inbox', -// url: '/units/:unit_id/inbox/:task_key', - -// params: { -// // unitRole: UnitRole, -// // taskKey: null, -// // taskData: -// // unit, -// }, -// views: { -// main: { -// component: InboxComponent, -// }, -// }, -// data: { -// task: 'Task Inbox', -// pageTitle: '_Home_', -// roleWhitelist: ['Tutor', 'Convenor', 'Admin'], -// }, -// resolve: { -// unit$: function ($stateParams) { -// const unitService = AppInjector.get(UnitService); -// const globalState = AppInjector.get(GlobalStateService); -// globalState.onLoad(() => {}); -// console.log($stateParams); -// return unitService.get({ id: $stateParams.unit_id }); -// }, -// unitRole$: function ($stateParams) { -// const globalStateService = AppInjector.get(GlobalStateService); - -// const result = globalStateService.loadedUnitRoles.values.pipe( -// map((unitRoles) => unitRoles.find((unitRole) => unitRole.id == $stateParams.unit_id)) -// ); -// return result; -// }, -// taskData$: function () { -// const taskService = AppInjector.get(TaskService); -// const taskData = { -// taskKey: null, -// source: null, -// selectedTask: null, -// onSelectedTaskChange: (task) =>{ -// const taskKey = task?.taskKey() -// $scope.taskData.taskKey = taskKey -// setTaskKeyAsUrlParams(task); -// } -// } -// taskData.source = taskService.queryTasksForTaskInbox.bind(taskService); -// taskData.taskDefMode = false; -// return of(taskData); -// }, -// }, -// }; - -/** - * Define the welcome state. - */ const WelcomeState: NgHybridStateDeclaration = { name: 'welcome', url: '/welcome', @@ -169,9 +73,6 @@ const WelcomeState: NgHybridStateDeclaration = { }, }; -/** - * Define the Sign In state. - */ const SignInState: NgHybridStateDeclaration = { name: 'sign_in', url: '/sign_in?dest¶ms&authToken&username', @@ -185,9 +86,6 @@ const SignInState: NgHybridStateDeclaration = { }, }; -/** - * Define the Edit Profile state. - */ const EditProfileState: NgHybridStateDeclaration = { name: 'edit_profile', url: '/edit_profile', @@ -234,9 +132,7 @@ const ViewAllProjectsState: NgHybridStateDeclaration = { name: 'view-all-projects', url: '/view-all-projects', resolve: { - 'mode': function () { - return 'student'; - }, + mode: () => 'student', }, views: { main: { @@ -250,16 +146,13 @@ const ViewAllProjectsState: NgHybridStateDeclaration = { }; const AdministerUnits: NgHybridStateDeclaration = { - name: 'admin/units', // This is the name of the state to jump to - so ui-sref="users" to jump here - url: '/admin/units', // You get here with this url + name: 'admin/units', + url: '/admin/units', resolve: { - 'mode': function () { - return 'admin'; - }, + mode: () => 'admin', }, views: { main: { - // Main body links to angular component component: FUnitsComponent, }, }, @@ -269,15 +162,11 @@ const AdministerUnits: NgHybridStateDeclaration = { }, }; - const ViewAllUnits: NgHybridStateDeclaration = { name: 'view-all-units', url: '/view-all-units', - // passes 'mode' as @Input to the component resolve: { - 'mode': function () { - return 'tutor'; - }, + mode: () => 'tutor', }, views: { main: { @@ -286,14 +175,11 @@ const ViewAllUnits: NgHybridStateDeclaration = { }, data: { pageTitle: 'Teaching Periods', - mode: 'tutor', roleWhitelist: ['Tutor', 'Convenor', 'Admin'], }, }; -/** - * Export the list of states we have created in angular - */ +/* FINAL EXPORTED STATE LIST */ export const doubtfireStates = [ institutionSettingsState, TeachingPeriodsState, @@ -306,4 +192,7 @@ export const doubtfireStates = [ ViewAllProjectsState, ViewAllUnits, AdministerUnits, + + /* STRICT MIGRATION STATE */ + unitsIndexState, ]; diff --git a/src/app/projects/states/index/index.coffee b/src/app/projects/states/index/index.coffee index 8a0f4dfde8..c1c1cca96f 100644 --- a/src/app/projects/states/index/index.coffee +++ b/src/app/projects/states/index/index.coffee @@ -10,7 +10,8 @@ angular.module('doubtfire.projects.states.index', []) views: main: controller: "ProjectsIndexStateCtrl" - templateUrl: "units/states/index/index.tpl.html" # We can re-use unit's index here + template: "" # Unit index is now handled by Angular state units/index + data: pageTitle: "_Home_" roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'] diff --git a/src/app/units/states/index/index.ajs.module.ts b/src/app/units/states/index/index.ajs.module.ts new file mode 100644 index 0000000000..33164abc2c --- /dev/null +++ b/src/app/units/states/index/index.ajs.module.ts @@ -0,0 +1,9 @@ +import angular from 'angular'; + +/** + + * + * i have kept the module name so AngularJS dependency loading does not crash, + * but the actual state is now defined in Angular (see index.state.ts). + */ +export const UnitsIndexAjsModule = angular.module('doubtfire.units.states.index', []); diff --git a/src/app/units/states/index/index.coffee b/src/app/units/states/index/index.coffee deleted file mode 100644 index 7a7346a319..0000000000 --- a/src/app/units/states/index/index.coffee +++ /dev/null @@ -1,50 +0,0 @@ -angular.module('doubtfire.units.states.index', []) - -# -# Root state for units -# -.config(($stateProvider) -> - $stateProvider.state 'units/index', { - url: "/units/:unitId" - abstract: true - views: - main: - controller: "UnitsIndexStateCtrl" - templateUrl: "units/states/index/index.tpl.html" - data: - pageTitle: "_Home_" - roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'] - } -) - -.controller("UnitsIndexStateCtrl", ($scope, $rootScope, $state, $stateParams, newUnitService, newProjectService, listenerService, GlobalStateService, newUserService, alertService) -> - # Error - required unitId is missing! - unitId = +$stateParams.unitId - return $state.go('home') unless unitId - - GlobalStateService.onLoad () -> - # Load assessing unit role - $scope.unitRole = GlobalStateService.loadedUnitRoles.currentValues.find((unitRole) -> unitRole.unit.id == unitId) - - if (! $scope.unitRole?) && ( newUserService.currentUser.role == "Admin" || newUserService.currentUser.role == "Auditor" ) - $scope.unitRole = newUserService.adminOrAuditorRoleFor(newUserService.currentUser.role, unitId, newUserService.currentUser) - - # Go home if no unit role was found - return $state.go('home') unless $scope.unitRole? - - GlobalStateService.setView("UNIT", $scope.unitRole) - - newUnitService.get(unitId).subscribe({ - next: (unit)-> - newProjectService.loadStudents(unit).subscribe({ - next: (students)-> - $scope.unit = unit - error: (err)-> - alertService.error( "Error loading students: " + err, 8000) - setTimeout((()-> $state.go('home')), 5000) - }) - error: (err)-> - alertService.error( "Error loading unit: " + err, 8000) - setTimeout((()-> $state.go('home')), 5000) - }) -) diff --git a/src/app/units/states/index/index.component.html b/src/app/units/states/index/index.component.html new file mode 100644 index 0000000000..997583f46a --- /dev/null +++ b/src/app/units/states/index/index.component.html @@ -0,0 +1,10 @@ + +
+ +

Loading unit details...

+
+ + +
+ +
diff --git a/src/app/units/states/index/index.component.scss b/src/app/units/states/index/index.component.scss new file mode 100644 index 0000000000..4ff4cc7b23 --- /dev/null +++ b/src/app/units/states/index/index.component.scss @@ -0,0 +1,4 @@ +.large-notice-block { + text-align: center; + padding: 2rem; +} diff --git a/src/app/units/states/index/index.component.ts b/src/app/units/states/index/index.component.ts new file mode 100644 index 0000000000..d37f9d1d1d --- /dev/null +++ b/src/app/units/states/index/index.component.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import {Component, Inject, OnInit} from '@angular/core'; +import {StateService, UIRouterGlobals} from '@uirouter/angular'; + +@Component({ + selector: 'f-units-index', + templateUrl: './index.component.html', + styleUrls: ['./index.component.scss'], +}) +export class IndexComponent implements OnInit { + unitRole: any; + unit: any; + unitId!: number; + project: any; + + constructor( + private state: StateService, + private globals: UIRouterGlobals, + + // These are still AngularJS services in the hybrid app, so inject by string token: + @Inject('GlobalStateService') private globalStateService: any, + @Inject('newUserService') private newUserService: any, + @Inject('newUnitService') private newUnitService: any, + @Inject('newProjectService') private newProjectService: any, + @Inject('alertService') private alertService: any, + ) {} + + ngOnInit(): void { + // Get unitId from URL params + this.unitId = +this.globals.params['unitId']; + + // If no unitId → redirect to home + if (!this.unitId) { + this.state.go('home'); + return; + } + + // Wait for global state to load (same behaviour as old Coffee/AngularJS) + this.globalStateService.onLoad(() => { + // Find the role for this unit + this.unitRole = + this.globalStateService.loadedUnitRoles?.currentValues?.find( + (unitRole: any) => unitRole?.unit?.id === this.unitId, + ); + + // Admin / Auditor fallback + if ( + !this.unitRole && + (this.newUserService?.currentUser?.role === 'Admin' || + this.newUserService?.currentUser?.role === 'Auditor') + ) { + this.unitRole = this.newUserService.adminOrAuditorRoleFor( + this.newUserService.currentUser.role, + this.unitId, + this.newUserService.currentUser, + ); + } + + // If still no role, redirect to home + if (!this.unitRole) { + this.state.go('home'); + return; + } + + // Set the app to "UNIT view" + this.globalStateService.setView('UNIT', this.unitRole); + + // Load the unit and students (same as old behaviour) + this.newUnitService.get(this.unitId).subscribe({ + next: (unit: any) => { + this.newProjectService.loadStudents(unit).subscribe({ + next: () => { + this.unit = unit; + }, + error: (err: any) => { + this.alertService.error('Error loading students: ' + err, 8000); + setTimeout(() => this.state.go('home'), 5000); + }, + }); + }, + error: (err: any) => { + this.alertService.error('Error loading unit: ' + err, 8000); + setTimeout(() => this.state.go('home'), 5000); + }, + }); + }); + } +} diff --git a/src/app/units/states/index/index.state.ts b/src/app/units/states/index/index.state.ts new file mode 100644 index 0000000000..2a959e16ba --- /dev/null +++ b/src/app/units/states/index/index.state.ts @@ -0,0 +1,21 @@ +import {NgHybridStateDeclaration} from '@uirouter/angular-hybrid'; +import {IndexComponent} from './index.component'; + +/** + * STRICT Angular replacement for: + * src/app/units/states/index/index.coffee + */ +export const unitsIndexState: NgHybridStateDeclaration = { + name: 'units/index', + url: '/units/:unitId', + abstract: true, + views: { + main: { + component: IndexComponent, + }, + }, + data: { + pageTitle: '_Home_', + roleWhitelist: ['Student', 'Tutor', 'Convenor', 'Admin', 'Auditor'], + }, +}; diff --git a/src/app/units/states/index/index.tpl.html b/src/app/units/states/index/index.tpl.html deleted file mode 100644 index 795574f613..0000000000 --- a/src/app/units/states/index/index.tpl.html +++ /dev/null @@ -1,7 +0,0 @@ -
- -

Loading unit details...

-
-
- -