Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli-gui/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<ng-container *ngIf="core.coreState$ | async as coreState">
<header class="main-header">
<header class="main-header" *ngIf="coreState.projectNames?.length">
<div class="project-selector">
<mat-select name="project-selector" id="project-selector">
<mat-option
Expand Down
4 changes: 2 additions & 2 deletions apps/cli-gui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { MatSelectModule } from '@angular/material/select';
import { RouterOutlet } from '@angular/router';
Expand All @@ -7,7 +7,7 @@ import { CoreService } from './core/core.service';

@Component({
standalone: true,
imports: [NgFor, NgIf, AsyncPipe, RouterOutlet, MatSelectModule],
imports: [NgIf, AsyncPipe, RouterOutlet, MatSelectModule, NgForOf],
selector: 'cli-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
Expand Down
17 changes: 12 additions & 5 deletions apps/cli-gui/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { Routes } from '@angular/router';

import { currentWorkspaceGuard } from './guards/current-workspace.guard';

export const APP_ROUTES: Routes = [
{
path: '',
canActivate: [currentWorkspaceGuard],
children: [
{
path: '',
redirectTo: 'generators',
pathMatch: 'full',
},
{
path: 'generators',
loadChildren: () => import('./generators/generators.routes'),
loadComponent: () =>
import('@angular-cli-gui/generators').then(
(m) => m.GeneratorsComponent
),
},
{
path: 'configuration',
Expand All @@ -22,10 +33,6 @@ export const APP_ROUTES: Routes = [
(m) => m.ExecutorsComponent
),
},
{
path: '**',
redirectTo: 'generators',
},
],
},
{
Expand Down
51 changes: 51 additions & 0 deletions apps/cli-gui/src/app/guards/current-workspace.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { CURRENT_WORKSPACE_PATH } from '@angular-cli-gui/shared/data';
import { ConnectWorkspaceService } from '@angular-cli-gui/workspace-manager';
import { catchError, map, Observable, of, retry, Subject, tap } from 'rxjs';

import { CoreService } from '../core/core.service';

export const currentWorkspaceGuard = (): Observable<boolean> => {
const router = inject(Router);
const http = inject(HttpClient);
const connectWorkspaceService = inject(ConnectWorkspaceService);
const core = inject(CoreService);
const retrySubject = new Subject<void>();
const projectNames$ = http.get<string[]>('/api/workspace');
const currentWorkspacePath = sessionStorage.getItem(CURRENT_WORKSPACE_PATH);

return projectNames$.pipe(
// Save projects to state
tap((projectNames) => {
core.update({
projectNames,
currentProjectName: projectNames?.[0],
});
}),
// Map to true to allow navigation
map(() => true),
catchError(() => {
if (!currentWorkspacePath) {
router.navigate(['workspace-manager', 'connect-workspace']);
return of(false);
}

// if path saved in local storage is invalid somehow (maybe deleted workspace)
// remove it from local storage so on 2nd retry cycle user will be navigated to workspace connection screen
sessionStorage.removeItem(CURRENT_WORKSPACE_PATH);

// Connect to workspace using local storage path and retry to get projectNames
return connectWorkspaceService
.connectWorkspace(currentWorkspacePath)
.pipe(
map(() => {
retrySubject.next();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the implementation but we need something that can be shared and reused, maybe we should consider adding ng-query

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind, but won't it make integrating this project with the angular repo more difficult? It might be a good idea to verify with them before relying on 3rd party libraries that aren't 100% necessary.

return false;
})
);
}),
retry({ delay: () => retrySubject })
);
};
19 changes: 1 addition & 18 deletions apps/cli-gui/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { APP_INITIALIZER } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import {
provideRouter,
withEnabledBlockingInitialNavigation,
} from '@angular/router';
import { map } from 'rxjs';

import { AppComponent } from './app/app.component';
import { APP_ROUTES } from './app/app.routes';
import { CoreService } from './app/core/core.service';

bootstrapApplication(AppComponent, {
providers: [
provideRouter(APP_ROUTES, withEnabledBlockingInitialNavigation()),
provideHttpClient(),
provideAnimations(),
{
provide: APP_INITIALIZER,
useFactory: (http: HttpClient, core: CoreService) => () =>
http.get<string[]>(`/api/workspace`).pipe(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need the list of projects and the current project? How do you plan to run generators?

I thought we'd keep the last workspace path in LS, so if we have one, we connect to it, and there is no need to re-connect on every page reload.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right- added this logic in the guard

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use sessionStorage instead, so you won't have to deal with invalidation. its also better UX IMO, the data will be persistent until the tab is closed (which should be the same as terminating the gui process)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

map((projectNames) =>
core.update({
projectNames,
currentProjectName: projectNames?.[0],
})
)
),
deps: [HttpClient, CoreService],
multi: true,
},
],
}).catch((err) => console.error(err));
1 change: 1 addition & 0 deletions libs/shared/data/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/exec-result.interface';
export * from './lib/generate-component-args.interface';
export * from './lib/directory.interface';
export * from './lib/local-storage-keys.consts';
1 change: 1 addition & 0 deletions libs/shared/data/src/lib/local-storage-keys.consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CURRENT_WORKSPACE_PATH = 'currentWorkspacePath';
1 change: 1 addition & 0 deletions libs/workspace-manager/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './lib/workspace-manager/workspace-manager.component';
export * from './lib/workspace-manager/workspace-manager.routes';
export * from './lib/workspace-manager/data-access/connect-workspace.service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { CURRENT_WORKSPACE_PATH } from '@angular-cli-gui/shared/data';
import { Observable, tap } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class ConnectWorkspaceService {
http = inject(HttpClient);
router = inject(Router);

connectWorkspace(path: string): Observable<void> {
return this.http.post<void>('/api/workspace/connect', { path }).pipe(
tap(() => {
sessionStorage.setItem(CURRENT_WORKSPACE_PATH, path);
this.router.navigate(['']);
})
);
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
<p>connect-workspace works!</p>
<form
[formGroup]="form"
class="connect-workspace-form"
(ngSubmit)="connectWorkspace()"
>
<h2 class="mat-title">Connect your Angular workspace</h2>
<mat-form-field class="workspace-path-field" appearance="outline">
<mat-label>The absolute path to your workspace</mat-label>
<input formControlName="path" matInput type="text" />
</mat-form-field>
<button mat-raised-button color="primary" type="submit">Connect</button>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.connect-workspace-form {
display: grid;
place-content: center;
height: 100%;

.workspace-path-field {
min-width: 350px;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';

import { ConnectWorkspaceComponent } from './connect-workspace.component';

Expand All @@ -8,7 +11,12 @@ describe('ConnectWorkspaceComponent', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConnectWorkspaceComponent],
imports: [
ConnectWorkspaceComponent,
HttpClientTestingModule,
RouterTestingModule,
NoopAnimationsModule,
],
}).compileComponents();

fixture = TestBed.createComponent(ConnectWorkspaceComponent);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import {
FormControl,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';

import { ConnectWorkspaceService } from '../../data-access/connect-workspace.service';

@Component({
selector: 'cli-connect-workspace',
standalone: true,
imports: [CommonModule],
imports: [CommonModule, MatInputModule, MatButtonModule, ReactiveFormsModule],
templateUrl: './connect-workspace.component.html',
styleUrls: ['./connect-workspace.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConnectWorkspaceComponent {}
export class ConnectWorkspaceComponent {
connectService = inject(ConnectWorkspaceService);
form = new FormGroup(
{
path: new FormControl('', [Validators.required]),
},
{ updateOn: 'submit' }
);

connectWorkspace(): void {
if (!this.form.valid) return;
const path = this.form.controls.path.value ?? '';
this.connectService.connectWorkspace(path).subscribe();
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
<p>workspace-manager works!</p>
<router-outlet></router-outlet>