Skip to content

Commit

Permalink
SF-2508 Change project selector to My Projects page
Browse files Browse the repository at this point in the history
This does not yet remove the Paratext project selection from the
connect-projects component.

Highlighting last selected project needs work.

The connect_project.only_paratext_admins_can_start message is being
reused. This could use refinement.

Need to address when user is offline.
Need to address when user is not logged into PT.
Need to address the delayed loading of unconnected PT projects.
Need to address PT projects information not working.

Co-authored-by: Nathaniel Paulus <[email protected]>
  • Loading branch information
marksvc and Nateowami committed Apr 16, 2024
1 parent 0f17759 commit 1c84756
Show file tree
Hide file tree
Showing 26 changed files with 606 additions and 454 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import { ProjectComponent } from './project/project.component';
import { SettingsComponent } from './settings/settings.component';
import { PageNotFoundComponent } from './shared/page-not-found/page-not-found.component';
import { SettingsAuthGuard, SyncAuthGuard } from './shared/project-router.guard';
import { StartComponent } from './start/start.component';
import { MyProjectsComponent } from './my-projects/my-projects.component';
import { SyncComponent } from './sync/sync.component';

const routes: Routes = [
{ path: 'callback/auth0', component: StartComponent, canActivate: [AuthGuard] },
{ path: 'callback/auth0', component: MyProjectsComponent, canActivate: [AuthGuard] },
{ path: 'connect-project', component: ConnectProjectComponent, canActivate: [AuthGuard] },
{ path: 'login', redirectTo: 'projects', pathMatch: 'full' },
{ path: 'join/:shareKey', component: JoinComponent },
{ path: 'join/:shareKey/:locale', component: JoinComponent },
{ path: 'projects/:projectId/settings', component: SettingsComponent, canActivate: [SettingsAuthGuard] },
{ path: 'projects/:projectId/sync', component: SyncComponent, canActivate: [SyncAuthGuard] },
{ path: 'projects/:projectId', component: ProjectComponent, canActivate: [AuthGuard] },
{ path: 'projects', component: StartComponent, canActivate: [AuthGuard] },
{ path: 'projects', component: MyProjectsComponent, canActivate: [AuthGuard] },
{ path: 'system-administration', component: SystemAdministrationComponent, canActivate: [SystemAdminAuthGuard] },
{ path: '**', component: PageNotFoundComponent }
];
Expand Down
29 changes: 15 additions & 14 deletions src/SIL.XForge.Scripture/ClientApp/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
<button mat-icon-button *ngIf="!isDrawerPermanent && isProjectSelected" (click)="toggleDrawer()">
<mat-icon>menu</mat-icon>
</button>
<span (click)="goHome()" class="title">
<img src="/assets/images/sf_logo_with_name.svg" height="40" fxHide.xs />
<button mat-icon-button fxHide.gt-xs>
<img src="/assets/images/sf.svg" height="38" />
</button>
<a mat-icon-button fxHide.xs [routerLink]="homeUrl$ | async" class="logo">
<img src="/assets/images/sf.svg" height="38" />
</a>
<span class="project-name-wrapper" *ngIf="isProjectSelected" fxHide.xs>
<span class="project-name">{{ selectedProjectDoc?.data?.name }}</span>
<span class="project-short-name">{{ selectedProjectDoc?.data?.shortName }}</span>
</span>
<span class="toolbar-spacer"></span>
<button mat-icon-button title="{{ t('language') }}" [matMenuTriggerFor]="localeMenu" *ngIf="isAppOnline">
Expand Down Expand Up @@ -86,17 +87,22 @@
</div>
<mat-divider></mat-divider>
<button mat-menu-item *ngIf="isSystemAdmin" [disabled]="!isAppOnline" appRouterLink="/system-administration">
<mat-icon>admin_panel_settings</mat-icon>
{{ t("system_administration") }}
</button>
<button mat-menu-item appRouterLink="/projects" id="project-home-link">{{ t("project_home") }}</button>
<button mat-menu-item appRouterLink="/projects" id="project-home-link">
<mat-icon>home</mat-icon> {{ t("my_projects") }}
</button>
<button mat-menu-item *ngIf="canChangePassword" (click)="changePassword()" [disabled]="!isAppOnline">
<mat-icon>vpn_key</mat-icon>
{{ t("change_password") }}
</button>
<button *ngIf="canInstallOnDevice$ | async" mat-menu-item (click)="installOnDevice()" class="install-button">
{{ t("install_on_device") }}
<mat-icon>install_mobile</mat-icon>
<mat-icon>install_mobile</mat-icon> {{ t("install_on_device") }}
</button>
<button mat-menu-item (click)="logOut()" id="log-out-link">
<mat-icon>logout</mat-icon> {{ t("log_out") }}
</button>
<button mat-menu-item (click)="logOut()" id="log-out-link">{{ t("log_out") }}</button>
<mat-divider></mat-divider>
<div class="pseudo-menu-item online-status">
<ng-container *ngIf="isAppOnline">
Expand Down Expand Up @@ -126,11 +132,6 @@
[opened]="isExpanded || isDrawerPermanent"
(closed)="drawerCollapsed()"
>
<app-navigation-project-selector
[projectDocs]="projectDocs"
[selected]="selectedProjectDoc"
(changed)="projectChanged($event)"
></app-navigation-project-selector>
<app-navigation (menuItemClicked)="itemSelected()"></app-navigation>
</mat-drawer>
<!-- The cdkScrollable attribute is needed so the CDK can listen to scroll events within this container -->
Expand Down
28 changes: 28 additions & 0 deletions src/SIL.XForge.Scripture/ClientApp/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,31 @@ header {
flex-direction: column;
width: 255px;
}

.logo {
display: flex;
align-items: center;
justify-content: center;
}

.project-name-wrapper {
display: flex;
flex-direction: column;
line-height: 1em;
max-width: 20em;
min-width: 0;
margin-inline-start: 4px;

.project-name {
font-size: 0.8em;
}

.project-short-name {
font-size: 0.6em;
}

> * {
overflow: hidden;
text-overflow: ellipsis;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { SFProjectProfileDoc } from './core/models/sf-project-profile-doc';
import { SF_TYPE_REGISTRY } from './core/models/sf-type-registry';
import { PermissionsService } from './core/permissions.service';
import { SFProjectService } from './core/sf-project.service';
import { NavigationProjectSelectorComponent } from './navigation-project-selector/navigation-project-selector.component';
import { NavigationComponent } from './navigation/navigation.component';
import { NmtDraftAuthGuard, SettingsAuthGuard, SyncAuthGuard, UsersAuthGuard } from './shared/project-router.guard';
import { paratextUsersFromRoles } from './shared/test-utils';
Expand Down Expand Up @@ -89,7 +88,6 @@ describe('AppComponent', () => {
TestTranslocoModule,
TestOnlineStatusModule.forRoot(),
TestRealtimeModule.forRoot(SF_TYPE_REGISTRY),
NavigationProjectSelectorComponent,
AvatarComponent
],
providers: [
Expand Down
37 changes: 16 additions & 21 deletions src/SIL.XForge.Scripture/ClientApp/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AuthType, getAuthType, User } from 'realtime-server/lib/esm/common/mode
import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
import { TextInfo } from 'realtime-server/lib/esm/scriptureforge/models/text-info';
import { combineLatest, Observable, Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { filter, map, startWith } from 'rxjs/operators';

Check failure on line 11 in src/SIL.XForge.Scripture/ClientApp/src/app/app.component.ts

View workflow job for this annotation

GitHub Actions / check on Ubuntu Node.js (16.x, 8.10.0)

'startWith' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 11 in src/SIL.XForge.Scripture/ClientApp/src/app/app.component.ts

View workflow job for this annotation

GitHub Actions / Build and test (ubuntu-20.04, 8.0.x, 16.15.0, 8.10.0)

'startWith' is defined but never used. Allowed unused vars must match /^_/u

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import startWith.
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
import { AuthService } from 'xforge-common/auth.service';
import { DataLoadingComponent } from 'xforge-common/data-loading-component';
Expand All @@ -20,7 +20,6 @@ import { FeatureFlagsDialogComponent } from 'xforge-common/feature-flags/feature
import { FileService } from 'xforge-common/file.service';
import { I18nService } from 'xforge-common/i18n.service';
import { LocalSettingsService } from 'xforge-common/local-settings.service';
import { LocationService } from 'xforge-common/location.service';
import { UserDoc } from 'xforge-common/models/user-doc';
import { NoticeService } from 'xforge-common/notice.service';
import { OnlineStatusService } from 'xforge-common/online-status.service';
Expand All @@ -32,6 +31,7 @@ import {
import { SFUserProjectsService } from 'xforge-common/user-projects.service';
import { UserService } from 'xforge-common/user.service';
import { issuesEmailTemplate, supportedBrowser } from 'xforge-common/utils';
import { filterNullish } from 'xforge-common/util/rxjs-util';
import versionData from '../../../version.json';
import { environment } from '../environments/environment';
import { SFProjectProfileDoc } from './core/models/sf-project-profile-doc';
Expand All @@ -54,7 +54,6 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest
isExpanded: boolean = false;
versionNumberClickCount = 0;

projectDocs?: SFProjectProfileDoc[];
hasUpdate: boolean = false;

private currentUserDoc?: UserDoc;
Expand All @@ -67,7 +66,6 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest
constructor(
private readonly router: Router,
private readonly authService: AuthService,
private readonly locationService: LocationService,
private readonly userService: UserService,
private readonly projectService: SFProjectService,
private readonly dialogService: DialogService,
Expand Down Expand Up @@ -171,8 +169,8 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest
}
}

get isLoggedIn(): Promise<boolean> {
return this.authService.isLoggedIn;
get homeUrl$(): Observable<string> {
return this.authService.loggedInState$.pipe(map(state => (state.loggedIn ? '/projects' : '/')));
}

get isAppLoading(): boolean {
Expand All @@ -195,15 +193,15 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest
}

get selectedProjectDoc(): SFProjectProfileDoc | undefined {
return this._selectedProjectDoc;
return this.activatedProjectService.projectDoc;
}

get selectedProjectId(): string | undefined {
return this._selectedProjectDoc == null ? undefined : this._selectedProjectDoc.id;
}

get isProjectSelected(): boolean {
return this.selectedProjectId != null;
return this.activatedProjectService.projectId != null;
}

get selectedProjectRole(): SFProjectRole | undefined {
Expand Down Expand Up @@ -243,13 +241,18 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest

const projectDocs$ = this.userProjectsService.projectDocs$;

const selectedProjectDoc$ = projectDocs$.pipe(
map(projectDocs => {
const projectId = this.activatedProjectService.projectId;
return projectId == null ? undefined : projectDocs.find(p => p.id === projectId);
}),
filterNullish()
);

// select the current project
this.subscribe(
combineLatest([projectDocs$, this.activatedProjectService.projectId$]),
async ([projectDocs, projectId]) => {
this.projectDocs = projectDocs;
const selectedProjectDoc = projectId == null ? undefined : this.projectDocs.find(p => p.id === projectId);

combineLatest([selectedProjectDoc$, this.activatedProjectService.projectId$]),
async ([selectedProjectDoc, projectId]) => {
if (this.selectedProjectDeleteSub != null) {
this.selectedProjectDeleteSub.unsubscribe();
this.selectedProjectDeleteSub = undefined;
Expand Down Expand Up @@ -353,14 +356,6 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest
this.authService.logOut();
}

async goHome(): Promise<void> {
if (await this.isLoggedIn) {
this.router.navigateByUrl('/projects');
} else {
this.locationService.go('/');
}
}

projectChanged(value: string): void {
if (value === CONNECT_PROJECT_OPTION) {
if (!this.isDrawerPermanent) {
Expand Down
6 changes: 2 additions & 4 deletions src/SIL.XForge.Scripture/ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { CheckingModule } from './checking/checking.module';
import { ConnectProjectComponent } from './connect-project/connect-project.component';
import { CoreModule } from './core/core.module';
import { JoinComponent } from './join/join.component';
import { NavigationProjectSelectorComponent } from './navigation-project-selector/navigation-project-selector.component';
import { NavigationComponent } from './navigation/navigation.component';
import { ProjectSelectComponent } from './project-select/project-select.component';
import { ProjectComponent } from './project/project.component';
Expand All @@ -36,7 +35,7 @@ import { DeleteProjectDialogComponent } from './settings/delete-project-dialog/d
import { SettingsComponent } from './settings/settings.component';
import { SharedModule } from './shared/shared.module';
import { TextNoteDialogComponent } from './shared/text/text-note-dialog/text-note-dialog.component';
import { StartComponent } from './start/start.component';
import { MyProjectsComponent } from './my-projects/my-projects.component';
import { SyncProgressComponent } from './sync/sync-progress/sync-progress.component';
import { SyncComponent } from './sync/sync.component';
import { TranslateModule } from './translate/translate.module';
Expand All @@ -50,7 +49,7 @@ import { UsersModule } from './users/users.module';
DeleteProjectDialogComponent,
ProjectComponent,
SettingsComponent,
StartComponent,
MyProjectsComponent,
SyncComponent,
ScriptureChooserDialogComponent,
SupportedBrowsersDialogComponent,
Expand Down Expand Up @@ -79,7 +78,6 @@ import { UsersModule } from './users/users.module';
TranslocoModule,
AppRoutingModule,
SharedModule,
NavigationProjectSelectorComponent,
AvatarComponent
],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,10 @@ describe('ConnectProjectComponent', () => {
env.clickElement(env.submitButton);
expect(env.component.paratextIdControl.errors!.required).toBe(true);

when(mockedParatextService.isParatextProjectInSF(anything())).thenReturn(true);
env.changeSelectValue(env.projectSelect, 'pt03');

// The project is already in SF, so do not present settings to configure.
expect(env.settingsCard).toBeNull();
env.clickElement(env.submitButton);

Expand Down Expand Up @@ -367,6 +369,18 @@ describe('ConnectProjectComponent', () => {
verify(mockedSFProjectService.onlineCreate(deepEqual(settings))).once();
verify(mockedRouter.navigate(deepEqual(['/projects', 'project01']))).once();
}));

it('knows what pt project id is being asked to connect to', fakeAsync(() => {
when(mockedRouter.getCurrentNavigation()).thenReturn({
extras: { state: { ptProjectId: 'requested-pt-project-id' } }
} as any);
const env = new TestEnvironment();
env.setupDefaultProjectData();
env.waitForProjectsResponse();
expect(env.component.state).toEqual('input');
expect(env.component.ptProjectId).toEqual('requested-pt-project-id');
expect(env.component.paratextIdControl.value).toEqual('requested-pt-project-id');
}));
});

class TestEnvironment {
Expand Down Expand Up @@ -494,6 +508,10 @@ class TestEnvironment {
return this.fixture.debugElement.query(By.css('app-project-select + mat-error'));
}

get projectIdMessage(): DebugElement {
return this.getElement('#project-id-message');
}

selectSourceProject(projectId: string): void {
this.sourceProjectSelectComponent.value = projectId;
this.fixture.detectChanges();
Expand Down Expand Up @@ -624,4 +642,14 @@ class TestEnvironment {
tick();
this.fixture.detectChanges();
}

click(element: DebugElement): void {
element.nativeElement.click();
tick();
this.fixture.detectChanges();
}

private getElement(query: string): DebugElement {
return this.fixture.debugElement.query(By.css(query));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export class ConnectProjectComponent extends DataLoadingComponent implements OnI
state: 'connecting' | 'loading' | 'input' | 'login' | 'offline' = 'loading';
connectProjectName?: string;
projectDoc?: SFProjectDoc;
/** The Paratext project id of what was requested to connect. */
ptProjectId?: string;

projectLabel = projectLabel;

Expand All @@ -62,6 +64,7 @@ export class ConnectProjectComponent extends DataLoadingComponent implements OnI
) {
super(noticeService);
this.connectProjectForm.disable();
this.ptProjectId = this.router.getCurrentNavigation()?.extras.state?.ptProjectId;
}

get hasConnectableProjects(): boolean {
Expand All @@ -87,7 +90,7 @@ export class ConnectProjectComponent extends DataLoadingComponent implements OnI
}
const paratextId: string = this.paratextIdControl.value;
const project = this._projects.find(p => p.paratextId === paratextId);
return project != null && project.projectId == null;
return project != null && !this.paratextService.isParatextProjectInSF(project);
}

get submitDisabled(): boolean {
Expand Down Expand Up @@ -157,6 +160,8 @@ export class ConnectProjectComponent extends DataLoadingComponent implements OnI
this.state = 'offline';
}
});

if (this.ptProjectId != null) this.paratextIdControl.setValue(this.ptProjectId);
}

logInWithParatext(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export class ParatextService {
.toPromise();
}

/** True if a Paratext project has a corresponding project in SF, whether or not any SF user is connected to the
* project. */
isParatextProjectInSF(project: ParatextProject): boolean {
return project.projectId != null;
}

private get headers(): HttpHeaders {
return new HttpHeaders({
Accept: 'application/json',
Expand Down
Loading

0 comments on commit 1c84756

Please sign in to comment.