I have improved on the projects page like this

1.Allow to delete multiple projects at once (similar to what is done in the image manager).
2. Add an export project action in the actions column. The export icon could be improved.
3.Replace "Duplicate" action by "Save project as" (with same icon as on project settings menu).
4.Delete 'Go to system status' button and add 'System status' entry to top right menu.
5.Delete 'Go to preferences' button and add 'Template preferences' entry to top right menu.
6.Add "Image manager" entry to the top right menu
This commit is contained in:
Rajnikant Lodhi 2022-07-19 17:36:19 +05:30
parent f106ca51da
commit 8c444059ad
10 changed files with 253 additions and 12 deletions

View File

@ -280,6 +280,7 @@ import { DeleteAllImageFilesDialogComponent } from './components/image-manager/d
import { UploadingProcessbarComponent } from './common/uploading-processbar/uploading-processbar.component';
import { ExportPortableProjectComponent } from './components/export-portable-project/export-portable-project.component';
import { NodesMenuConfirmationDialogComponent } from './components/project-map/nodes-menu/nodes-menu-confirmation-dialog/nodes-menu-confirmation-dialog.component';
import { ConfirmationDeleteAllProjectsComponent } from './components/projects/confirmation-delete-all-projects/confirmation-delete-all-projects.component';
@NgModule({
declarations: [
@ -476,6 +477,7 @@ import { NodesMenuConfirmationDialogComponent } from './components/project-map/n
UploadingProcessbarComponent,
ExportPortableProjectComponent,
NodesMenuConfirmationDialogComponent,
ConfirmationDeleteAllProjectsComponent,
],
imports: [
BrowserModule,

View File

@ -999,6 +999,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.exportPortableProjectDialog();
}
}
exportPortableProjectDialog() {
const dialogRef = this.dialog.open(ExportPortableProjectComponent, {
width: '700px',

View File

@ -0,0 +1,34 @@
<div *ngIf="!isDelete && !isUsedFiles">
<h1 mat-dialog-title>Do you want delete all files ?.</h1>
<div mat-dialog-content>
<p>Your selected files</p>
<p *ngFor="let file of deleteData?.deleteFilesPaths; let i = index">{{i+1}}. {{file?.filename}}</p>
</div>
<div mat-dialog-actions align="end">
<button mat-button (click)="deleteAll()">Delete</button>
<button mat-button mat-dialog-close cdkFocusInitial>Cancel</button>
</div>
</div>
<div *ngIf="isDelete && !isUsedFiles">
<h1 align="center" mat-dialog-title>Please wait.</h1>
<div mat-dialog-content align="center">
<mat-spinner color="accent"></mat-spinner>
</div>
</div>
<div *ngIf="isDelete && isUsedFiles">
<div mat-dialog-content>
<div *ngIf="deleteFliesDetails.length > 0">
<h5>Project can't be deleted because image used in one or more template.</h5>
<p *ngFor="let message of deleteFliesDetails; let i = index" [ngClass]="{'deleted-error-text': message?.error?.message}"><span *ngIf="message !=null">{{i+1}}. {{message?.error?.message}}</span></p>
</div>
<div *ngIf="fileNotDeleted.length > 0">
<h5 class="delete-text">{{fileNotDeleted.length}} Projects deleted successfully.</h5>
</div>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmationDeleteAllProjectsComponent } from './confirmation-delete-all-projects.component';
describe('ConfirmationDeleteAllProjectsComponent', () => {
let component: ConfirmationDeleteAllProjectsComponent;
let fixture: ComponentFixture<ConfirmationDeleteAllProjectsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ConfirmationDeleteAllProjectsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmationDeleteAllProjectsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,48 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ProjectService } from '@services/project.service';
import { ToasterService } from '@services/toaster.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Component({
selector: 'app-confirmation-delete-all-projects',
templateUrl: './confirmation-delete-all-projects.component.html',
styleUrls: ['./confirmation-delete-all-projects.component.scss']
})
export class ConfirmationDeleteAllProjectsComponent implements OnInit {
isDelete: boolean = false;
isUsedFiles: boolean = false;
deleteFliesDetails: any = []
fileNotDeleted: any = []
constructor(
@Inject(MAT_DIALOG_DATA) public deleteData: any,
public dialogRef: MatDialogRef<ConfirmationDeleteAllProjectsComponent>,
private projectService: ProjectService,
private toasterService: ToasterService
) { }
ngOnInit(): void {
}
async deleteAll() {
this.isDelete = true
await this.deleteFile()
}
deleteFile() {
const calls = [];
this.deleteData.deleteFilesPaths.forEach(project => {
calls.push(this.projectService.delete(this.deleteData.server, project.project_id).pipe(catchError(error => of(error))))
});
Observable.forkJoin(calls).subscribe(responses => {
this.deleteFliesDetails = responses.filter(x => x !== null)
this.fileNotDeleted = responses.filter(x => x === null)
this.isUsedFiles = true;
this.isDelete = true
});
}
}

View File

@ -2,8 +2,8 @@
<div class="default-header">
<div class="row">
<h1 class="col">Projects</h1>
<button class="col" mat-raised-button (click)="goToSystemStatus()" class="add-button">Go to system status</button>
<button class="col" mat-raised-button (click)="goToPreferences()" class="add-button">Go to preferences</button>
<!-- <button class="col" mat-raised-button (click)="goToSystemStatus()" class="add-button">Go to system status</button>
<button class="col" mat-raised-button (click)="goToPreferences()" class="add-button">Go to preferences</button> -->
<button class="col" mat-raised-button color="primary" (click)="addBlankProject()" class="add-button">
Add blank project
</button>
@ -22,6 +22,21 @@
<div class="default-content">
<div class="mat-elevation-z8">
<mat-table #table [dataSource]="dataSource | projectsfilter: searchText" matSort>
<ng-container matColumnDef="select">
<mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? selectAllImages() : null" [checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</mat-cell>
</ng-container>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let row">
@ -52,22 +67,37 @@
</button>
<button
mat-icon-button
matTooltip="Duplicate project"
matTooltip="Save project as"
matTooltipClass="custom-tooltip"
(click)="duplicate(row)"
*ngIf="row.status == 'closed'"
>
<mat-icon aria-label="Duplicate project">filter_2</mat-icon>
<mat-icon aria-label="Save project as">save</mat-icon>
</button>
<button
mat-icon-button
matTooltip="Delete project"
matTooltip="Export project"
matTooltipClass="custom-tooltip"
(click)="delete(row)"
*ngIf="row.status == 'closed'"
(click)="exportSelectProject(row)"
>
<mat-icon aria-label="Delete project">delete</mat-icon>
<mat-icon aria-label="Export project">arrow_downward</mat-icon>
</button>
</mat-cell>
</ng-container>
<ng-container matColumnDef="delete" >
<mat-header-cell *matHeaderCellDef class="action">
<button mat-button matTooltip="Delete all projects" matTooltipClass="custom-tooltip"*ngIf="(selection.hasValue() && isAllSelected()) || selection.selected.length > 1" (click)="deleteAllFiles()" aria-label="Example icon button with a delete icon">
<mat-icon>delete</mat-icon>
</button>
</mat-header-cell>
<mat-cell *matCellDef="let row" class="action">
<button mat-icon-button matTooltip="Delete project" matTooltipClass="custom-tooltip" (click)="delete(row)" *ngIf="row.status == 'closed' && selection.isSelected(row)" aria-label="Example icon button with a delete icon" >
<mat-icon aria-label="Delete project">delete</mat-icon>
</button>
</mat-cell>
</ng-container>

View File

@ -1,9 +1,10 @@
import { DataSource } from '@angular/cdk/collections';
import { DataSource, SelectionModel } from '@angular/cdk/collections';
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { MatDialog } from '@angular/material/dialog';
import { MatSort, MatSortable } from '@angular/material/sort';
import { ActivatedRoute, Router } from '@angular/router';
import { ExportPortableProjectComponent } from '@components/export-portable-project/export-portable-project.component';
import { ElectronService } from 'ngx-electron';
import { BehaviorSubject, merge, Observable } from 'rxjs';
import { map } from 'rxjs//operators';
@ -17,6 +18,7 @@ import { ToasterService } from '../../services/toaster.service';
import { AddBlankProjectDialogComponent } from './add-blank-project-dialog/add-blank-project-dialog.component';
import { ChooseNameDialogComponent } from './choose-name-dialog/choose-name-dialog.component';
import { ConfirmationBottomSheetComponent } from './confirmation-bottomsheet/confirmation-bottomsheet.component';
import { ConfirmationDeleteAllProjectsComponent } from './confirmation-delete-all-projects/confirmation-delete-all-projects.component';
import { ImportProjectDialogComponent } from './import-project-dialog/import-project-dialog.component';
import { NavigationDialogComponent } from './navigation-dialog/navigation-dialog.component';
@ -29,10 +31,12 @@ export class ProjectsComponent implements OnInit {
server: Server;
projectDatabase = new ProjectDatabase();
dataSource: ProjectDataSource;
displayedColumns = ['name', 'actions'];
displayedColumns = ['select', 'name', 'actions', 'delete'];
settings: Settings;
project: Project;
searchText: string = '';
isAllDelete: boolean = false;
selection = new SelectionModel(true, []);
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ -188,6 +192,70 @@ export class ProjectsComponent implements OnInit {
}
});
}
deleteAllFiles() {
const dialogRef = this.dialog.open(ConfirmationDeleteAllProjectsComponent, {
width: '550px',
maxHeight: '650px',
autoFocus: false,
disableClose: true,
data: {
server: this.server,
deleteFilesPaths: this.selection.selected
}
});
dialogRef.afterClosed().subscribe((isAllfilesdeleted: boolean) => {
if (isAllfilesdeleted) {
this.unChecked()
this.refresh()
this.toasterService.success('All files deleted');
} else {
this.unChecked()
this.refresh()
return false;
}
});
}
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.projectDatabase.data.length;
return numSelected === numRows;
}
selectAllImages() {
this.isAllSelected() ? this.unChecked() : this.allChecked();
}
unChecked() {
this.selection.clear();
this.isAllDelete = false;
}
allChecked() {
this.projectDatabase.data.forEach((row) => this.selection.select(row));
this.isAllDelete = true;
}
exportSelectProject(project: Project){
this.project = project
if(this.project.project_id){
this.exportPortableProjectDialog()
}
}
exportPortableProjectDialog() {
const dialogRef = this.dialog.open(ExportPortableProjectComponent, {
width: '700px',
maxHeight: '850px',
autoFocus: false,
disableClose: true,
data: {serverDetails:this.server,projectDetails:this.project},
});
dialogRef.afterClosed().subscribe((isAddes: boolean) => {});
}
}
export class ProjectDatabase {
@ -208,6 +276,8 @@ export class ProjectDatabase {
this.dataChange.next(this.data.slice());
}
}
}
export class ProjectDataSource extends DataSource<any> {

View File

@ -19,6 +19,19 @@
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="goToSystemStatus()">
<mat-icon>info</mat-icon>
<span>System status</span>
</button>
<button mat-menu-item (click)="goToPreferences()">
<mat-icon>settings_applications</mat-icon>
<span>Template preferences</span>
</button>
<button mat-menu-item>
<!-- [routerLink]="['/controller', controller.id, 'image-manager']" -->
<mat-icon>collections</mat-icon>
<span>Image manager</span>
</button>
<button mat-menu-item routerLink="/settings">
<mat-icon>settings</mat-icon>
<span>Settings</span>

View File

@ -29,6 +29,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
recentlyOpenedServerId: string;
recentlyOpenedProjectId: string;
serverIdProjectList: string;
controller: Server;
constructor(
private electronService: ElectronService,
@ -37,11 +38,15 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
private toasterService: ToasterService,
private progressService: ProgressService,
private router: Router,
private serverService: ServerService
private serverService: ServerService,
private route: ActivatedRoute,
) {}
ngOnInit() {
this.checkIfUserIsLoginPage();
this.controller = this.route.snapshot.data['server'];
this.routeSubscription = this.router.events.subscribe((val) => {
if (val instanceof NavigationEnd) this.checkIfUserIsLoginPage();
});
@ -110,6 +115,18 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
.catch((error) => this.toasterService.error('Cannot navigate to the last opened project'));
}
goToPreferences() {
this.router
.navigate(['/controller', this.controller.id, 'preferences'])
.catch((error) => this.toasterService.error('Cannot navigate to the preferences'));
}
goToSystemStatus() {
this.router
.navigate(['/controller', this.controller.id, 'systemstatus'])
.catch((error) => this.toasterService.error('Cannot navigate to the system status'));
}
@HostListener('window:beforeunload', ['$event'])
async onBeforeUnload($event) {
if (!this.shouldStopServersOnClosing) {
@ -124,6 +141,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
window.close();
return false;
}
ngOnDestroy() {
this.serverStatusSubscription.unsubscribe();