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
16 changes: 16 additions & 0 deletions apps/cli-gui/src/assets/angular.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion libs/shared/data/src/lib/directory.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface Directory {
name: string;
isNG?: boolean;
isNG: boolean;
isFavorite?: boolean;
}
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
Original file line number Diff line number Diff line change
@@ -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<string> {
return this.http.get(`${this.API_URL}/homedir`, {
responseType: 'text',
});
}

getDirectoriesInPath(path: string): Observable<Directory[]> {
return this.http.get<Directory[]>(`${this.API_URL}/dir`, {
params: { path },
});
}

getPathSeparator(): Observable<string> {
return this.http.get(`${this.API_URL}/path-sep`, { responseType: 'text' });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="path">
<div *ngFor="let part of pathParts" class="path-part">
<button
*ngIf="part"
mat-button
class="path-part-button"
(click)="onPartClicked(part)"
>
{{ part }}
</button>
<div>{{ separator }}</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.path {
display: flex;
}

.path-part {
display: flex;
align-items: center;
}

.path-part-button {
margin-left: 2px;
}
Original file line number Diff line number Diff line change
@@ -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<FilesystemNavigatorToolbarComponent>;

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');
});
});
});
Original file line number Diff line number Diff line change
@@ -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<string>();

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}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<cli-filesystem-navigator-toolbar
[path]="path"
[separator]="separator"
(pathChange)="onPathChanged($event)"
></cli-filesystem-navigator-toolbar>

<div class="list-wrapper">
<mat-list>
<ng-container *ngFor="let dir of directories">
<mat-list-item (click)="onDirClicked(dir)">
<div matListItemIcon>
<ng-container
[ngTemplateOutlet]="dir.isNG ? angularIcon : folderIcon"
></ng-container>
</div>

<div matListItemTitle>
{{ dir.name }}
</div>
</mat-list-item>

<mat-divider></mat-divider>
</ng-container>
</mat-list>
</div>

<ng-template #folderIcon>
<mat-icon>folder</mat-icon>
</ng-template>

<ng-template #angularIcon>
<img class="angular-logo" src="/assets/angular.svg" />
</ng-template>
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<FilesystemNavigatorComponent>;

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');
});
});
});
Original file line number Diff line number Diff line change
@@ -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<string>();

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);
}
}
2 changes: 2 additions & 0 deletions libs/workspace-manager/src/lib/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './filesystem-navigator/filesystem-navigator-toolbar/filesystem-navigator-toolbar.component';
export * from './filesystem-navigator/filesystem-navigator.component';