Implemented the ability to choose a folder for a library. Implemented an admin landing page that will showcase the different management items.

This commit is contained in:
Joseph Milazzo 2020-12-16 12:14:01 -06:00
parent e06e34083c
commit 2679a52aec
20 changed files with 305 additions and 17 deletions

View file

@ -99,7 +99,7 @@
"src/assets"
],
"styles": [
"src/styles.scss"
"src/styles.scss",
],
"scripts": []
}

50
package-lock.json generated
View file

@ -353,6 +353,11 @@
"tslib": "^2.0.0"
}
},
"@angular/elements": {
"version": "9.1.12",
"resolved": "https://registry.npmjs.org/@angular/elements/-/elements-9.1.12.tgz",
"integrity": "sha512-0zjGlx2HxRWr4BiGfS9lY0i/MDfe2u3evn37svmTw72UmLsfaAR7XBa+5W2chgPCb5/1LyQAY/h02tsUEFlvYA=="
},
"@angular/forms": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.0.4.tgz",
@ -1563,6 +1568,16 @@
"to-fast-properties": "^2.0.0"
}
},
"@circlon/angular-tree-component": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@circlon/angular-tree-component/-/angular-tree-component-10.0.1.tgz",
"integrity": "sha512-yLXK01Z8TdtVnzcJbt8LVShtP2AFx8CA0Jwmj+ubHKyY5wOmO6pXebDdB1x0G9HEYYljl5wFhKoZLEZnG1tSAQ==",
"requires": {
"lodash-es": "^4.17.15",
"mobx": "~4.14.1",
"tslib": "^2.0.0"
}
},
"@istanbuljs/schema": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
@ -6562,6 +6577,31 @@
}
}
},
"jqwidgets-ng": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/jqwidgets-ng/-/jqwidgets-ng-11.0.1.tgz",
"integrity": "sha512-V4iFpZps7P9bdnXX8pn55tGjPCz1Wu7a5jEXzwlN9ICdR428Du4aldLyJPR1fClDNyqIMXpHsn+EADt16lsdgg==",
"requires": {
"@angular/cdk": "^9.1.1",
"@angular/elements": "^9.0.5"
},
"dependencies": {
"@angular/cdk": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.4.tgz",
"integrity": "sha512-iw2+qHMXHYVC6K/fttHeNHIieSKiTEodVutZoOEcBu9rmRTGbLB26V/CRsfIRmA1RBk+uFYWc6UQZnMC3RdnJQ==",
"requires": {
"parse5": "^5.0.0"
}
},
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
}
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -7042,6 +7082,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"lodash-es": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
"integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -7561,6 +7606,11 @@
"minimist": "^1.2.5"
}
},
"mobx": {
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.14.1.tgz",
"integrity": "sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View file

@ -0,0 +1,23 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class LibraryService {
baseUrl = environment.apiUrl;
constructor(private httpClient: HttpClient) { }
listDirectories(rootPath: string) {
let query = '';
if (rootPath !== undefined && rootPath.length > 0) {
query = '?path=' + rootPath;
}
return this.httpClient.get<string[]>(this.baseUrl + 'library/list' + query);
}
}

View file

@ -1,14 +1,16 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdminGuard } from '../_guards/admin.guard';
import { DashboardComponent } from './dashboard/dashboard.component';
import { UsersComponent } from './users/users.component';
const routes: Routes = [
{path: '**', component: UsersComponent, pathMatch: 'full'},
{path: '**', component: DashboardComponent, pathMatch: 'full'},
{
runGuardsAndResolvers: 'always',
canActivate: [AdminGuard],
children: [
{path: '/dashboard', component: DashboardComponent},
{path: '/users', component: UsersComponent}
]
}

View file

@ -3,14 +3,17 @@ import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import { UsersComponent } from './users/users.component';
import { ToastrModule } from 'ngx-toastr';
import { DashboardComponent } from './dashboard/dashboard.component';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [UsersComponent],
declarations: [UsersComponent, DashboardComponent],
imports: [
CommonModule,
AdminRoutingModule
AdminRoutingModule,
NgbNavModule
],
providers: []
})

View file

@ -0,0 +1,18 @@
<h1>Admin Dashboard</h1>
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
<a ngbNavLink>{{ tab | titlecase }}</a>
<ng-template ngbNavContent>
<ng-container *ngIf="tab === 'users'">
<app-users></app-users>
</ng-container>
<ng-container *ngIf="tab === 'libraries'">
Library management here
</ng-container>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>

View file

@ -0,0 +1,19 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
tabs = ['users', 'libraries'];
counter = this.tabs.length + 1;
active = this.tabs[0];
constructor() { }
ngOnInit(): void {
}
}

View file

@ -1,3 +1,11 @@
<button class="btn btn-primary" (click)="addFolder('')">Add Folder</button>
<div class="container">
<h2></h2>
</div>
<h2>Members:</h2>
<div class="container">
<ul>

View file

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { DirectoryPickerComponent, DirectoryPickerResult } from 'src/app/directory-picker/directory-picker.component';
import { MemberService } from 'src/app/member.service';
import { Member } from 'src/app/_models/member';
@ -10,8 +12,10 @@ import { Member } from 'src/app/_models/member';
export class UsersComponent implements OnInit {
members: Member[] = [];
closeResult = ''; // Debug code
@ViewChild('content') content: any;
constructor(private memberService: MemberService) { }
constructor(private memberService: MemberService, private modalService: NgbModal) { }
ngOnInit(): void {
console.log('User Component');
@ -20,4 +24,16 @@ export class UsersComponent implements OnInit {
});
}
addFolder(library: string) {
const modalRef = this.modalService.open(DirectoryPickerComponent);
//modalRef.componentInstance.name = 'World';
modalRef.closed.subscribe((closeResult: DirectoryPickerResult) => {
console.log('Closed Result', closeResult);
if (closeResult.success) {
console.log('Add folder path to Library');
}
});
}
}

View file

@ -14,6 +14,9 @@ import { UserLoginComponent } from './user-login/user-login.component';
import { ToastrModule } from 'ngx-toastr';
import { ErrorInterceptor } from './_interceptors/error.interceptor';
import { LibraryComponent } from './library/library.component';
import { DirectoryPickerComponent } from './directory-picker/directory-picker.component';
@NgModule({
declarations: [
@ -21,7 +24,8 @@ import { LibraryComponent } from './library/library.component';
HomeComponent,
NavHeaderComponent,
UserLoginComponent,
LibraryComponent
LibraryComponent,
DirectoryPickerComponent
],
imports: [
HttpClientModule,
@ -38,6 +42,7 @@ import { LibraryComponent } from './library/library.component';
{provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true}
],
entryComponents: [DirectoryPickerComponent],
bootstrap: [AppComponent]
})
export class AppModule { }

View file

@ -0,0 +1,27 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Choose a Directory</h4>
<button type="button" class="close" aria-label="Close" (click)="close()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<!-- <nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item {{'active' ? route === routeStack.peek() : ''}}" *ngFor="let route of routeStack.items">{{getStem(route)}}</li>
</ol>
</nav> -->
<div class="list-group">
<button *ngIf="routeStack.peek() !== undefined" (click)="goBack()" class="list-group-item list-group-item-action"><i class="fa fa-arrow-left mr-2"></i>Back</button>
<button *ngFor="let folder of folders" class="list-group-item list-group-item-action" (click)="selectNode(folder)">
<span>{{getStem(folder)}}</span>
<button class="btn btn-primary pull-right" (click)="shareFolder(folder, $event)">Share</button>
</button>
<div class="text-center" *ngIf="folders.length === 0">
There are no folders here
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="close()">Cancel</button>
</div>

View file

@ -0,0 +1,103 @@
import { Component, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { BehaviorSubject } from 'rxjs';
import { LibraryService } from '../_services/library.service';
class Stack {
items: any[];
constructor() {
this.items = [];
}
isEmpty() {
return this.items.length === 0;
}
peek() {
if (!this.isEmpty()) {
return this.items[this.items.length - 1];
}
}
pop() {
if (this.isEmpty()) {
return undefined;
}
return this.items.pop();
}
push(item: any) {
this.items.push(item);
}
}
export interface DirectoryPickerResult {
success: boolean;
folderPath: string;
}
@Component({
selector: 'app-directory-picker',
templateUrl: './directory-picker.component.html',
styleUrls: ['./directory-picker.component.scss']
})
export class DirectoryPickerComponent implements OnInit {
currentRoot = '';
folders: string[] = [];
routeStack: Stack = new Stack();
constructor(public modal: NgbActiveModal, private libraryService: LibraryService) {
}
ngOnInit(): void {
this.loadChildren(this.currentRoot);
}
selectNode(folderName: string) {
this.currentRoot = folderName;
this.routeStack.push(folderName);
this.loadChildren(folderName);
}
goBack() {
this.routeStack.pop();
this.currentRoot = this.routeStack.peek();
this.loadChildren(this.currentRoot);
}
loadChildren(path: string) {
this.libraryService.listDirectories(path).subscribe(folders => {
this.folders = folders;
});
}
shareFolder(folderName: string, event: any) {
console.log(`You selected ${folderName} as your folder to share!`);
event.preventDefault();
event.stopPropagation();
this.modal.close({success: true, folderPath: folderName});
}
close() {
this.modal.close({success: false, folderPath: undefined});
}
getStem(path: string): string {
const lastPath = this.routeStack.peek();
if (lastPath) {
let replaced = path.replace(lastPath, '');
if (replaced.startsWith('/') || replaced.startsWith('\\')) {
replaced = replaced.substr(1, replaced.length);
}
return replaced;
}
return path;
}
}

View file

@ -31,6 +31,8 @@
<app-user-login></app-user-login>
</ng-container>
<app-directory-picker></app-directory-picker>
</div>

View file

@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { take } from 'rxjs/operators';
import { User } from '../_models/user';
import { AccountService } from '../_services/account.service';
@Component({
selector: 'app-library',
@ -7,9 +10,14 @@ import { Component, OnInit } from '@angular/core';
})
export class LibraryComponent implements OnInit {
constructor() { }
user: User | undefined;
constructor(public accountService: AccountService) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.user = user;
});
}
}

View file

@ -13,11 +13,11 @@
<!-- TODO: Put SignalR notification button dropdown here. -->
<div class=" nav-item dropdown" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
<a dropdownToggle class="dropdown-toggle text-light ml-2">{{user.username | titlecase}}</a>
<div class="dropdown-menu mt-3" *dropdownMenu>
<a *ngIf="user.isAdmin" class="dropdown-item" routerLink="/admin/users">Logout</a>
<a (click)="logout()" class="dropdown-item">Logout</a>
<div ngbDropdown class=" nav-item dropdown" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
<button class="btn btn-outline-primary" ngbDropdownToggle>{{user.username | titlecase}}</button>
<div ngbDropdownMenu >
<button ngbDropdownItem routerLink="/admin/users">Server Settings</button>
<button ngbDropdownItem *ngIf="user.isAdmin"(click)="logout()">Logout</button>
</div>
</div>

View file

@ -6,8 +6,10 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">
<body class="mat-typography" theme="dark">
<app-root></app-root>
</body>
</html>

View file

@ -43,3 +43,5 @@
// background-color: darken(#cc7b19, 10%) !important;
// }
// }
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }