diff --git a/apps/cli-gui/src/assets/angular.svg b/apps/cli-gui/src/assets/angular.svg new file mode 100644 index 0000000..bf081ac --- /dev/null +++ b/apps/cli-gui/src/assets/angular.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/libs/shared/data/src/lib/directory.interface.ts b/libs/shared/data/src/lib/directory.interface.ts index 9ba312a..79ba25c 100644 --- a/libs/shared/data/src/lib/directory.interface.ts +++ b/libs/shared/data/src/lib/directory.interface.ts @@ -1,5 +1,5 @@ export interface Directory { name: string; - isNG?: boolean; + isNG: boolean; isFavorite?: boolean; } diff --git a/libs/workspace-manager/src/lib/data-access/workspace-manager-api.service.spec.ts b/libs/workspace-manager/src/lib/data-access/workspace-manager-api.service.spec.ts new file mode 100644 index 0000000..eb4d3b2 --- /dev/null +++ b/libs/workspace-manager/src/lib/data-access/workspace-manager-api.service.spec.ts @@ -0,0 +1,49 @@ +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { WorkspaceManagerApiService } from './workspace-manager-api.service'; + +describe('WorkspaceManagerApiService', () => { + let service: WorkspaceManagerApiService; + let http: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + http = TestBed.inject(HttpTestingController); + service = TestBed.inject(WorkspaceManagerApiService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('getHomeDir', () => { + it('should call the get homedir api', () => { + service.getHomeDir().subscribe(); + const request = http.expectOne('/api/workspace-manager/homedir'); + expect(request.request.method).toEqual('GET'); + }); + }); + + describe('getDirectoriesInPath', () => { + it('should call get directories in path api with path as params', () => { + service.getDirectoriesInPath('/mock/path').subscribe(); + const req = http.expectOne('/api/workspace-manager/dir?path=/mock/path'); + expect(req.request.method).toEqual('GET'); + }); + }); + + describe('getPathSeparator', () => { + it('should call the get path separator api', () => { + service.getPathSeparator().subscribe(); + const req = http.expectOne('/api/workspace-manager/path-sep'); + expect(req.request.method).toEqual('GET'); + }); + }); +}); diff --git a/libs/workspace-manager/src/lib/data-access/workspace-manager-api.service.ts b/libs/workspace-manager/src/lib/data-access/workspace-manager-api.service.ts new file mode 100644 index 0000000..3cf6d12 --- /dev/null +++ b/libs/workspace-manager/src/lib/data-access/workspace-manager-api.service.ts @@ -0,0 +1,28 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { Directory } from '@angular-cli-gui/shared/data'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class WorkspaceManagerApiService { + private readonly http = inject(HttpClient); + private readonly API_URL = '/api/workspace-manager'; + + getHomeDir(): Observable { + return this.http.get(`${this.API_URL}/homedir`, { + responseType: 'text', + }); + } + + getDirectoriesInPath(path: string): Observable { + return this.http.get(`${this.API_URL}/dir`, { + params: { path }, + }); + } + + getPathSeparator(): Observable { + return this.http.get(`${this.API_URL}/path-sep`, { responseType: 'text' }); + } +} diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.html b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.html new file mode 100644 index 0000000..cb34111 --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.html @@ -0,0 +1,13 @@ +
+
+ +
{{ separator }}
+
+
diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.scss b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.scss new file mode 100644 index 0000000..27ecc9c --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.scss @@ -0,0 +1,12 @@ +.path { + display: flex; +} + +.path-part { + display: flex; + align-items: center; +} + +.path-part-button { + margin-left: 2px; +} diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.spec.ts b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.spec.ts new file mode 100644 index 0000000..a5d49aa --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.spec.ts @@ -0,0 +1,38 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilesystemNavigatorToolbarComponent } from './filesystem-navigator-toolbar.component'; + +describe('FilesystemNavigatorToolbarComponent', () => { + let component: FilesystemNavigatorToolbarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FilesystemNavigatorToolbarComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(FilesystemNavigatorToolbarComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('path', '/mock/path'); + fixture.componentRef.setInput('separator', '/'); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('ngOnChanges', () => { + it('should split the path input and save the parts', () => { + expect(component.pathParts).toEqual(['mock', 'path']); + }); + }); + + describe('onPathClicked', () => { + it('should emit path changed event with the new path', () => { + const pathChangeEventSpy = jest.spyOn(component.pathChange, 'emit'); + component.onPartClicked('mock'); + expect(pathChangeEventSpy).toHaveBeenCalledWith('/mock'); + }); + }); +}); diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.ts b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.ts new file mode 100644 index 0000000..4800abd --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component.ts @@ -0,0 +1,40 @@ +import { NgForOf, NgIf } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; + +@Component({ + selector: 'cli-filesystem-navigator-toolbar', + standalone: true, + imports: [NgForOf, NgIf, MatButtonModule], + templateUrl: './filesystem-navigator-toolbar.component.html', + styleUrls: ['./filesystem-navigator-toolbar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FilesystemNavigatorToolbarComponent implements OnChanges { + @Input() path: string | null = null; + @Input() separator: string | null = ''; + @Output() pathChange = new EventEmitter(); + + pathParts: string[] = []; + + ngOnChanges(): void { + this.pathParts = (this.path as string) + .split(this.separator as string) + .filter((v) => !!v); + } + + onPartClicked(pathPart: string): void { + const index = this.pathParts.indexOf(pathPart); + const newPath = this.pathParts + .slice(0, index + 1) + .join(this.separator as string); + this.pathChange.emit(`${this.separator}${newPath}`); + } +} diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.html b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.html new file mode 100644 index 0000000..65984b1 --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.html @@ -0,0 +1,33 @@ + + +
+ + + +
+ +
+ +
+ {{ dir.name }} +
+
+ + +
+
+
+ + + folder + + + + + diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.scss b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.scss new file mode 100644 index 0000000..0e6b4fe --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.scss @@ -0,0 +1,17 @@ +:host { + .list-wrapper { + overflow: auto; + margin-top: 16px; + height: inherit; + max-height: 100%; + } + + mat-list-item:hover { + cursor: pointer; + } + + .angular-logo { + width: 25px; + height: 25px; + } +} diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.spec.ts b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.spec.ts new file mode 100644 index 0000000..93a8c72 --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.spec.ts @@ -0,0 +1,44 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Directory } from '@angular-cli-gui/shared/data'; + +import { FilesystemNavigatorComponent } from './filesystem-navigator.component'; + +describe('FilesystemNavigatorComponent', () => { + let component: FilesystemNavigatorComponent; + let fixture: ComponentFixture; + + const directoriesMock: Directory[] = []; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FilesystemNavigatorComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(FilesystemNavigatorComponent); + component = fixture.componentInstance; + component.path = '/mock/path'; + component.directories = directoriesMock; + component.separator = '/'; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('onPathChanged', () => { + it('should emit path changed event', () => { + const pathChangeEmitSpy = jest.spyOn(component.pathChange, 'emit'); + component.onPathChanged('new/path'); + expect(pathChangeEmitSpy).toHaveBeenCalledWith('new/path'); + }); + }); + + describe('onDirClicked', () => { + it('should append the dir name to the current path and emit path changed event', () => { + const pathChangeEmitSpy = jest.spyOn(component.pathChange, 'emit'); + component.onDirClicked({ name: 'abc', isNG: false }); + expect(pathChangeEmitSpy).toHaveBeenCalledWith('/mock/path/abc'); + }); + }); +}); diff --git a/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.ts b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.ts new file mode 100644 index 0000000..3373eb3 --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/filesystem-navigator/filesystem-navigator.component.ts @@ -0,0 +1,52 @@ +import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { Directory } from '@angular-cli-gui/shared/data'; + +import { FilesystemNavigatorToolbarComponent } from './filesystem-navigator-toolbar/filesystem-navigator-toolbar.component'; + +@Component({ + selector: 'cli-filesystem-navigator', + standalone: true, + imports: [ + MatListModule, + NgForOf, + NgIf, + MatDividerModule, + MatIconModule, + FilesystemNavigatorToolbarComponent, + AsyncPipe, + NgTemplateOutlet, + ], + templateUrl: './filesystem-navigator.component.html', + styleUrls: ['./filesystem-navigator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FilesystemNavigatorComponent { + @Input() path: string | null = null; + @Input() directories: Directory[] | null = []; + @Input() separator: string | null = ''; + + @Output() pathChange = new EventEmitter(); + + onPathChanged(path: string): void { + this.emitPathChangeEvent(path); + } + + onDirClicked(directory: Directory): void { + const path = `${this.path}${this.separator}${directory.name}`; + this.onPathChanged(path); + } + + private emitPathChangeEvent(path: string): void { + this.pathChange.emit(path); + } +} diff --git a/libs/workspace-manager/src/lib/ui/index.ts b/libs/workspace-manager/src/lib/ui/index.ts new file mode 100644 index 0000000..9c6f94f --- /dev/null +++ b/libs/workspace-manager/src/lib/ui/index.ts @@ -0,0 +1,2 @@ +export * from './filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component'; +export * from './filesystem-navigator/filesystem-navigator.component';