work in progress ui fine tune
@ -1,36 +1,36 @@
|
||||
{
|
||||
"/project": {
|
||||
"target": "http://ingress.redaction-timo-dev-latest.178.63.47.73.xip.io",
|
||||
"target": "https://timo-redaction-dev.iqser.cloud/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/reanalyse": {
|
||||
"target": "http://ingress.redaction-timo-dev-latest.178.63.47.73.xip.io",
|
||||
"target": "https://timo-redaction-dev.iqser.cloud/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/upload": {
|
||||
"target": "http://ingress.redaction-timo-dev-latest.178.63.47.73.xip.io",
|
||||
"target": "https://timo-redaction-dev.iqser.cloud/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/download": {
|
||||
"target": "http://ingress.redaction-timo-dev-latest.178.63.47.73.xip.io",
|
||||
"target": "https://timo-redaction-dev.iqser.cloud/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/delete": {
|
||||
"target": "http://ingress.redaction-timo-dev-latest.178.63.47.73.xip.io",
|
||||
"target": "https://timo-redaction-dev.iqser.cloud/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"/status": {
|
||||
"target": "http://ingress.redaction-timo-dev-latest.178.63.47.73.xip.io",
|
||||
"target": "https://timo-redaction-dev.iqser.cloud/",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
|
||||
@ -1 +1,2 @@
|
||||
<router-outlet></router-outlet>
|
||||
<redaction-full-page-loading-indicator [displayed]="appLoadStateService.loading | async"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {AppLoadStateService} from "./utils/app-load-state.service";
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-root',
|
||||
@ -7,5 +8,8 @@ import {Component} from '@angular/core';
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
constructor(public appLoadStateService: AppLoadStateService){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,50 +1,56 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {APP_INITIALIZER, NgModule} from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
|
||||
import { BaseScreenComponent } from './screens/base-screen/base-screen.component';
|
||||
import { ProjectListingScreenComponent } from './screens/project-listing-screen/project-listing-screen.component';
|
||||
import { ProjectOverviewScreenComponent } from './screens/project-overview-screen/project-overview-screen.component';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { ApiModule } from '@redaction/red-ui-http';
|
||||
import { ApiPathInterceptorService } from './interceptor/api-path-interceptor.service';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { languageInitializer } from './i18n/language.initializer';
|
||||
import { LanguageService } from './i18n/language.service';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { IconsModule } from './icons/icons.module';
|
||||
import { AddEditProjectDialogComponent } from './screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ConfirmationDialogComponent } from './common/confirmation-dialog/confirmation-dialog.component';
|
||||
import { FilePreviewScreenComponent } from './screens/file/file-preview-screen/file-preview-screen.component';
|
||||
import { PdfViewerComponent } from './screens/file/pdf-viewer/pdf-viewer.component';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { NgpSortModule } from 'ngp-sort-pipe';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { FileDetailsDialogComponent } from './screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '../environments/environment';
|
||||
import { ProjectDetailsDialogComponent } from './screens/project-overview-screen/project-details-dialog/project-details-dialog.component';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { AuthGuard } from './auth/auth.guard';
|
||||
import { FileUploadModule } from './upload/file-upload.module';
|
||||
import { FullPageLoadingIndicatorComponent } from './utils/full-page-loading-indicator/full-page-loading-indicator.component';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { InitialsAvatarComponent } from './common/initials-avatar/initials-avatar.component';
|
||||
import { StatusBarComponent } from './components/status-bar/status-bar.component';
|
||||
import {AppComponent} from './app.component';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';
|
||||
import {BaseScreenComponent} from './screens/base-screen/base-screen.component';
|
||||
import {ProjectListingScreenComponent} from './screens/project-listing-screen/project-listing-screen.component';
|
||||
import {ProjectOverviewScreenComponent} from './screens/project-overview-screen/project-overview-screen.component';
|
||||
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||
import {ApiModule} from '@redaction/red-ui-http';
|
||||
import {ApiPathInterceptorService} from './interceptor/api-path-interceptor.service';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
|
||||
import {MatMenuModule} from '@angular/material/menu';
|
||||
import {languageInitializer} from './i18n/language.initializer';
|
||||
import {LanguageService} from './i18n/language.service';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {IconsModule} from './icons/icons.module';
|
||||
import {AddEditProjectDialogComponent} from './screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component';
|
||||
import {MatDialogModule} from '@angular/material/dialog';
|
||||
import {MatSnackBarModule} from '@angular/material/snack-bar';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {ConfirmationDialogComponent} from './common/confirmation-dialog/confirmation-dialog.component';
|
||||
import {FilePreviewScreenComponent} from './screens/file/file-preview-screen/file-preview-screen.component';
|
||||
import {PdfViewerComponent} from './screens/file/pdf-viewer/pdf-viewer.component';
|
||||
import {MatTabsModule} from '@angular/material/tabs';
|
||||
import {MatButtonToggleModule} from '@angular/material/button-toggle';
|
||||
import {NgpSortModule} from 'ngp-sort-pipe';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {MatSelectModule} from '@angular/material/select';
|
||||
import {MatSidenavModule} from '@angular/material/sidenav';
|
||||
import {FileDetailsDialogComponent} from './screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component';
|
||||
import {ToastrModule} from 'ngx-toastr';
|
||||
import {ServiceWorkerModule} from '@angular/service-worker';
|
||||
import {environment} from '../environments/environment';
|
||||
import {ProjectDetailsDialogComponent} from './screens/project-overview-screen/project-details-dialog/project-details-dialog.component';
|
||||
import {AuthModule} from './auth/auth.module';
|
||||
import {AuthGuard} from './auth/auth.guard';
|
||||
import {FileUploadModule} from './upload/file-upload.module';
|
||||
import {FullPageLoadingIndicatorComponent} from './utils/full-page-loading-indicator/full-page-loading-indicator.component';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {InitialsAvatarComponent} from './common/initials-avatar/initials-avatar.component';
|
||||
import {StatusBarComponent} from './components/status-bar/status-bar.component';
|
||||
import {LogoComponent} from './logo/logo.component';
|
||||
import {AuthInterceptorService} from "./interceptor/auth-interceptor.service";
|
||||
import {CompositeRouteGuard} from "./utils/composite-route.guard";
|
||||
import {AppStateGuard} from "./state/app-state.guard";
|
||||
import {ChartsModule} from "ng2-charts";
|
||||
import { SimpleDoughnutChartComponent } from './simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
|
||||
export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
@ -64,11 +70,14 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
ProjectDetailsDialogComponent,
|
||||
FullPageLoadingIndicatorComponent,
|
||||
InitialsAvatarComponent,
|
||||
StatusBarComponent
|
||||
StatusBarComponent,
|
||||
LogoComponent,
|
||||
SimpleDoughnutChartComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
ChartsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
AuthModule,
|
||||
@ -95,17 +104,26 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectListingScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, AppStateGuard],
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:projectId',
|
||||
component: ProjectOverviewScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, AppStateGuard],
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:projectId/file/:fileId',
|
||||
component: FilePreviewScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, AppStateGuard],
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -125,13 +143,17 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
FileUploadModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
providers: [{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
multi: true,
|
||||
useClass: ApiPathInterceptorService
|
||||
}, {
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
multi: true,
|
||||
useClass: AuthInterceptorService
|
||||
}, {
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
|
||||
@ -5,14 +5,19 @@ import {AuthConfig, OAuthService} from "angular-oauth2-oidc";
|
||||
import {AppConfigKey, AppConfigService} from "../app-config/app-config.service";
|
||||
import {map} from "rxjs/operators";
|
||||
import {JwksValidationHandler} from "angular-oauth2-oidc-jwks";
|
||||
import {UserService} from "../user/user.service";
|
||||
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
private _configured = false;
|
||||
|
||||
constructor(private readonly _oauthService: OAuthService, private readonly _appConfigService: AppConfigService) {
|
||||
constructor(private readonly _oauthService: OAuthService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _appConfigService: AppConfigService) {
|
||||
}
|
||||
|
||||
private async _configure() {
|
||||
@ -32,12 +37,16 @@ export class AuthGuard implements CanActivate {
|
||||
return this._checkToken();
|
||||
}
|
||||
|
||||
private _checkToken() {
|
||||
private async _checkToken() {
|
||||
const expired = this._oauthService.getAccessTokenExpiration() - new Date().getTime() < 0;
|
||||
if (!this._oauthService.getAccessToken() || expired) {
|
||||
this._oauthService.initLoginFlow();
|
||||
return false;
|
||||
}
|
||||
if (!this._userService.user) {
|
||||
await this._userService.loadCurrentUser();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div class="flex-row">
|
||||
<div [className]="color + ' oval ' + size">{{initials}}</div>
|
||||
<div *ngIf="withName" class="clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
|
||||
<div *ngIf="withName" class="name clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@import "../../../assets/styles/red-variables";
|
||||
|
||||
* {
|
||||
.name {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-initials-avatar',
|
||||
@ -10,7 +10,7 @@ export class InitialsAvatarComponent implements OnInit {
|
||||
public username: string;
|
||||
|
||||
@Input()
|
||||
public color: 'gray' | 'red' = 'gray';
|
||||
public color: 'red-white' | 'gray-red' | 'gray-dark' = 'gray-dark';
|
||||
|
||||
@Input()
|
||||
public size: 'small' | 'large' = 'small';
|
||||
@ -18,7 +18,8 @@ export class InitialsAvatarComponent implements OnInit {
|
||||
@Input()
|
||||
public withName = false;
|
||||
|
||||
constructor() { }
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
@ -13,6 +13,31 @@ export class IconsModule {
|
||||
private iconRegistry: MatIconRegistry,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
'calendar',
|
||||
sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/calendar.svg')
|
||||
);
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
'files',
|
||||
sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/files.svg')
|
||||
);
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
'user',
|
||||
sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/user.svg')
|
||||
);
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
'stats',
|
||||
sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/stats.svg')
|
||||
);
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
'drop-down',
|
||||
sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/drop-down-arrow.svg')
|
||||
);
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
'plus',
|
||||
|
||||
24
apps/red-ui/src/app/interceptor/auth-interceptor.service.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {AppConfigService} from "../app-config/app-config.service";
|
||||
import {catchError} from "rxjs/operators";
|
||||
import {OAuthService} from "angular-oauth2-oidc";
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptorService implements HttpInterceptor {
|
||||
|
||||
constructor(private readonly _appConfigService: AppConfigService, private readonly _oauthService: OAuthService) {
|
||||
}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(req).pipe(catchError((err: any) => {
|
||||
if (err instanceof HttpErrorResponse) {
|
||||
if (err.status === 401) {
|
||||
this._oauthService.initLoginFlow();
|
||||
}
|
||||
}
|
||||
return of(err);
|
||||
}));
|
||||
}
|
||||
}
|
||||
4
apps/red-ui/src/app/logo/logo.component.html
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="redacto-logo">
|
||||
<div class="line-1"></div>
|
||||
<div class="line-2"></div>
|
||||
</div>
|
||||
0
apps/red-ui/src/app/logo/logo.component.scss
Normal file
15
apps/red-ui/src/app/logo/logo.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-logo',
|
||||
templateUrl: './logo.component.html',
|
||||
styleUrls: ['./logo.component.scss']
|
||||
})
|
||||
export class LogoComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,33 +9,43 @@
|
||||
translate="top-bar.navigation-items.projects.label">
|
||||
</button>
|
||||
<button *ngIf="appStateService.activeProject"
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId"
|
||||
mat-menu-item>{{appStateService.activeProject.projectName}}</button>
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.project.projectId"
|
||||
mat-menu-item>{{appStateService.activeProject.project.projectName}}</button>
|
||||
<button *ngIf="appStateService.activeFile"
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId+'/file/'+appStateService.activeFile.fileId"
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.project.projectId+'/file/'+appStateService.activeFile.fileId"
|
||||
mat-menu-item>{{appStateService.activeFile.filename}}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="menu left visible-lg">
|
||||
<a class="breadcrumb" routerLink="/ui/projects"
|
||||
translate="top-bar.navigation-items.projects.label"></a>
|
||||
translate="top-bar.navigation-items.projects.label"></a>
|
||||
<div *ngIf="appStateService.activeProject" class="breadcrumb">
|
||||
<mat-icon svgIcon="red:chevron-right"></mat-icon>
|
||||
</div>
|
||||
<a *ngIf="appStateService.activeProject" class="breadcrumb"
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId">
|
||||
{{appStateService.activeProject.projectName}}
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.project.projectId">
|
||||
{{appStateService.activeProject.project.projectName}}
|
||||
</a>
|
||||
<div *ngIf="appStateService.activeFile" class="breadcrumb">
|
||||
<mat-icon svgIcon="red:chevron-right"></mat-icon>
|
||||
</div>
|
||||
<a *ngIf="appStateService.activeFile" class="breadcrumb"
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId+'/file/'+appStateService.activeFile.fileId">
|
||||
[routerLink]="'/ui/projects/'+appStateService.activeProject.project.projectId+'/file/'+appStateService.activeFile.fileId">
|
||||
{{appStateService.activeFile.filename}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="center">
|
||||
<redaction-logo></redaction-logo>
|
||||
<div class="app-name" translate="app-name.label"></div>
|
||||
</div>
|
||||
<div class="menu right">
|
||||
<button [matMenuTriggerFor]="menu" mat-button translate="top-bar.navigation-items.my-account.label"></button>
|
||||
<button [matMenuTriggerFor]="menu" mat-button>
|
||||
<div class="account-button-wrapper">
|
||||
<redaction-initials-avatar color="red-white" size="small" [username]="user?.name"></redaction-initials-avatar>
|
||||
<span>{{user?.name}}</span>
|
||||
<mat-icon svgIcon="red:drop-down"></mat-icon>
|
||||
</div>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
|
||||
<button [matMenuTriggerFor]="language" mat-menu-item
|
||||
|
||||
@ -1 +1,17 @@
|
||||
@import "../../../assets/styles/red-variables";
|
||||
|
||||
|
||||
.account-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
redaction-initials-avatar{
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 10px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Component} from '@angular/core';
|
||||
import {UserService} from "../../user/user.service";
|
||||
import {AppStateService} from "../../state/app-state.service";
|
||||
import {LanguageService} from "../../i18n/language.service";
|
||||
@ -8,17 +8,17 @@ import {LanguageService} from "../../i18n/language.service";
|
||||
templateUrl: './base-screen.component.html',
|
||||
styleUrls: ['./base-screen.component.scss']
|
||||
})
|
||||
export class BaseScreenComponent implements OnInit {
|
||||
|
||||
export class BaseScreenComponent {
|
||||
|
||||
constructor(
|
||||
public readonly appStateService: AppStateService,
|
||||
private readonly _languageService: LanguageService,
|
||||
private readonly _userService: UserService) {
|
||||
private readonly _userService: UserService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
|
||||
logout() {
|
||||
|
||||
@ -37,8 +37,7 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
this._activatedRoute.params.subscribe(params => {
|
||||
this.projectId = params.projectId;
|
||||
this.fileId = params.fileId;
|
||||
this.appStateService.activateFile(this.projectId, this.fileId).subscribe(() => {
|
||||
});
|
||||
this.appStateService.activateFile(this.projectId, this.fileId)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {Project, ProjectControllerService} from "@redaction/red-ui-http";
|
||||
import {Project} from "@redaction/red-ui-http";
|
||||
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {NotificationService, NotificationType} from "../../../notification/notification.service";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {AppStateService} from "../../../state/app-state.service";
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-project-dialog',
|
||||
@ -15,9 +14,7 @@ export class AddEditProjectDialogComponent implements OnInit {
|
||||
projectForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private readonly _projectControllerService: ProjectControllerService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
public dialogRef: MatDialogRef<AddEditProjectDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public project: Project) {
|
||||
@ -31,21 +28,11 @@ export class AddEditProjectDialogComponent implements OnInit {
|
||||
|
||||
}
|
||||
|
||||
saveProject() {
|
||||
async saveProject() {
|
||||
const project: Project = this._formToObject();
|
||||
if (this.project?.projectId) {
|
||||
this._projectControllerService.updateProject(project, this.project.projectId).subscribe(() => {
|
||||
this.dialogRef.close();
|
||||
}, () => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('projects.add-edit-dialog.errors.save'), null, NotificationType.ERROR);
|
||||
})
|
||||
} else {
|
||||
this._projectControllerService.createProject(project).subscribe(() => {
|
||||
this.dialogRef.close();
|
||||
}, () => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('projects.add-edit-dialog.errors.save'), null, NotificationType.ERROR);
|
||||
})
|
||||
}
|
||||
project.projectId = this.project?.projectId;
|
||||
await this._appStateService.addOrUpdateProject(project);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
private _formToObject(): Project {
|
||||
|
||||
@ -1,69 +1,81 @@
|
||||
<section *ngIf="viewReady">
|
||||
|
||||
<div class="page-header">
|
||||
<div class="filters flex-row">
|
||||
<span translate="filters.filter-by.label"></span>
|
||||
<div translate="filters.status.label"></div>
|
||||
<div translate="filters.people.label"></div>
|
||||
<div translate="filters.due-date.label"></div>
|
||||
<div translate="filters.project.label"></div>
|
||||
<div translate="filters.document.label"></div>
|
||||
</div>
|
||||
<button (click)="openAddProjectDialog()" color="warn" mat-flat-button translate="projects.add-new.label"></button>
|
||||
<div class="page-header">
|
||||
<div class="filters flex-row">
|
||||
<span translate="filters.filter-by.label"></span>
|
||||
<div translate="filters.status.label"></div>
|
||||
<div translate="filters.people.label"></div>
|
||||
<div translate="filters.due-date.label"></div>
|
||||
<div translate="filters.project.label"></div>
|
||||
<div translate="filters.document.label"></div>
|
||||
</div>
|
||||
<button (click)="openAddProjectDialog()" color="warn" mat-flat-button class="add-project-btn">
|
||||
<mat-icon svgIcon="red:plus">
|
||||
</mat-icon>
|
||||
<span translate="projects.add-new.label"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="appStateService.allProjects?.length === 0 " translate="projects.no-projects.label"></div>
|
||||
<div *ngIf="appStateService.allProjects?.length === 0 " translate="projects.no-projects.label"></div>
|
||||
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="table-header">
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="table-header">
|
||||
<span class="subheading">
|
||||
{{'projects.table-header.title.label'| translate:{ length: appStateService.allProjects?.length || 0 } }}
|
||||
{{'projects.table-header.title.label'| translate:{length: appStateService.allProjects?.length || 0} }}
|
||||
</span>
|
||||
<div class="actions">
|
||||
<div translate="projects.table-header.bulk-select.label"></div>
|
||||
<div translate="projects.table-header.recent.label"></div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div translate="projects.table-header.bulk-select.label"></div>
|
||||
<div translate="projects.table-header.recent.label"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-content">
|
||||
<div *ngFor="let project of appStateService.allProjects"
|
||||
[routerLink]="'/ui/projects/'+project.projectId"
|
||||
class="table-item"
|
||||
>
|
||||
<div class="flex-2">
|
||||
<div class="table-item-title table-item-title--large">
|
||||
{{project.projectName}}
|
||||
</div>
|
||||
<div class="subtitle stats-subtitle">
|
||||
<div>12</div>
|
||||
<div>9</div>
|
||||
<div>25 Dec. 2020</div>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<div *ngFor="let pw of appStateService.allProjects | sortBy:'desc':'projectDate'"
|
||||
[routerLink]="'/ui/projects/'+pw.project.projectId"
|
||||
class="table-item"
|
||||
>
|
||||
<div class="flex-2">
|
||||
<div class="table-item-title table-item-title--large">
|
||||
{{pw.project.projectName}}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<redaction-initials-avatar username="Timo Bejan"
|
||||
withName="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="stats-bar">
|
||||
<redaction-status-bar
|
||||
[config]="[{ color: 'yellow', length: 2}, { length: 1, color: 'green'}]"
|
||||
></redaction-status-bar>
|
||||
</div>
|
||||
<div class="subtitle stats-subtitle">
|
||||
<div><mat-icon svgIcon="red:files"></mat-icon>{{documentCount(pw)}}</div>
|
||||
<div><mat-icon svgIcon="red:user"></mat-icon>{{userCount(pw)}}</div>
|
||||
<div><mat-icon svgIcon="red:calendar"></mat-icon>{{pw.project.date | date:'mediumDate'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<redaction-initials-avatar [username]="user.name"
|
||||
withName="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="stats-bar">
|
||||
<redaction-status-bar
|
||||
[config]="[{ color: 'yellow', length: 2}, { length: 1, color: 'green'}]"
|
||||
></redaction-status-bar>
|
||||
</div>
|
||||
|
||||
<div class="on-hover-wrapper">
|
||||
<div class="on-hover">
|
||||
<div>d</div>
|
||||
<div>s</div>
|
||||
<div class="on-hover-wrapper">
|
||||
<div class="on-hover">
|
||||
<div (click)="deleteProject($event,pw.project)">
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</div>
|
||||
<div (click)="editProject($event,pw.project)">
|
||||
<mat-icon svgIcon="red:edit"></mat-icon>
|
||||
</div>
|
||||
<div (click)="showDetailsDialog($event,pw)">
|
||||
<mat-icon svgIcon="red:stats"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-fixed-container"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
<div class="right-fixed-container">
|
||||
<!-- <redaction-simple-doughnut-chart [initialValues]="[120,26]">-->
|
||||
|
||||
<!-- </redaction-simple-doughnut-chart>-->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,8 +2,22 @@
|
||||
|
||||
.stats-subtitle {
|
||||
margin-top: 6px;
|
||||
|
||||
mat-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-bar {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.add-project-btn {
|
||||
mat-icon {
|
||||
width: 14px;
|
||||
margin-right: 10px;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@ import {MatDialog} from "@angular/material/dialog";
|
||||
import {AddEditProjectDialogComponent} from "./add-edit-project-dialog/add-edit-project-dialog.component";
|
||||
import {ConfirmationDialogComponent} from "../../common/confirmation-dialog/confirmation-dialog.component";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {NotificationService, NotificationType} from "../../notification/notification.service";
|
||||
import {AppStateService} from "../../state/app-state.service";
|
||||
import {NotificationService} from "../../notification/notification.service";
|
||||
import {AppStateService, ProjectWrapper} from "../../state/app-state.service";
|
||||
import {UserService} from "../../user/user.service";
|
||||
import {ProjectDetailsDialogComponent} from "../project-overview-screen/project-details-dialog/project-details-dialog.component";
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-project-listing-screen',
|
||||
@ -14,30 +16,29 @@ import {AppStateService} from "../../state/app-state.service";
|
||||
})
|
||||
export class ProjectListingScreenComponent implements OnInit {
|
||||
|
||||
viewReady = false;
|
||||
|
||||
constructor(
|
||||
public readonly appStateService: AppStateService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _projectControllerService: ProjectControllerService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _dialog: MatDialog) {
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._reloadProjects(true);
|
||||
this.appStateService.reset();
|
||||
}
|
||||
|
||||
openAddProjectDialog(project?: Project): void {
|
||||
const dialogRef = this._dialog.open(AddEditProjectDialogComponent, {
|
||||
this._dialog.open(AddEditProjectDialogComponent, {
|
||||
width: '400px',
|
||||
maxWidth: '90vw',
|
||||
data: project
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
this._reloadProjects();
|
||||
});
|
||||
}
|
||||
|
||||
editProject($event: MouseEvent, project: Project) {
|
||||
@ -54,21 +55,26 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this._projectControllerService.deleteProject(project.projectId).subscribe(() => {
|
||||
this._reloadProjects();
|
||||
}, () => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('projects.delete.delete-failed.label', project), null, NotificationType.ERROR)
|
||||
});
|
||||
this.appStateService.deleteProject(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _reloadProjects(initial: boolean = false) {
|
||||
this.appStateService.reset();
|
||||
this.appStateService.loadAllProjects().subscribe(() => {
|
||||
if (initial) {
|
||||
this.viewReady = true;
|
||||
}
|
||||
showDetailsDialog($event: MouseEvent, project: ProjectWrapper) {
|
||||
$event.stopPropagation();
|
||||
this._dialog.open(ProjectDetailsDialogComponent, {
|
||||
width: '600px',
|
||||
maxWidth: '90vw',
|
||||
data: project
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
documentCount(project: ProjectWrapper) {
|
||||
return project.files.length;
|
||||
}
|
||||
|
||||
userCount(project: ProjectWrapper) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {FileStatus, FileUploadControllerService, Project, ReanalysisControllerService} from "@redaction/red-ui-http";
|
||||
import {FileUploadControllerService, ReanalysisControllerService} from "@redaction/red-ui-http";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {download} from "../../../utils/file-download-utils";
|
||||
|
||||
export interface ProjectDetails {
|
||||
project: Project;
|
||||
files: FileStatus[];
|
||||
}
|
||||
import {ProjectWrapper} from "../../../state/app-state.service";
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-project-details-dialog',
|
||||
@ -19,7 +15,7 @@ export class ProjectDetailsDialogComponent implements OnInit {
|
||||
private readonly _fileUploadControllerService: FileUploadControllerService,
|
||||
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
||||
public dialogRef: MatDialogRef<ProjectDetailsDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public projectDetails: ProjectDetails) {
|
||||
@Inject(MAT_DIALOG_DATA) public projectDetails: ProjectWrapper) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
<section *ngIf="viewReady">
|
||||
<div *ngIf="!appStateService.activeProject"
|
||||
[innerHTML]="'project-overview.no-project.label' | translate:{projectId: projectId}"
|
||||
class="heading-l"></div>
|
||||
@ -22,7 +21,7 @@
|
||||
<div class="left-container">
|
||||
<div class="table-header">
|
||||
<span class="subheading">
|
||||
{{'project-overview.table-header.title.label'| translate:{ length: appStateService.projectFiles?.length || 0 } }}
|
||||
{{'project-overview.table-header.title.label'| translate:{ length: appStateService.activeProject?.files.length || 0 } }}
|
||||
</span>
|
||||
<div class="actions">
|
||||
<div translate="project-overview.table-header.bulk-select.label"></div>
|
||||
@ -43,7 +42,7 @@
|
||||
</div>
|
||||
|
||||
<div class="table-item"
|
||||
*ngFor="let fileStatus of appStateService.projectFiles | sortBy: sorting.order:sorting.name; trackBy:fileId"
|
||||
*ngFor="let fileStatus of appStateService.activeProject.files | sortBy: sorting.order:sorting.name; trackBy:fileId"
|
||||
[routerLink]="fileStatus.status === 'PROCESSED' ? ['/ui/projects/'+projectId+'/file/'+fileStatus.fileId] : []">
|
||||
<div class="flex-3 table-item-title min-width">
|
||||
{{ fileStatus.filename }}
|
||||
@ -83,23 +82,29 @@
|
||||
|
||||
<div class="project-details-container right-fixed-container">
|
||||
<div class="actions-row">
|
||||
<div>Edit</div>
|
||||
<div>Delete</div>
|
||||
<div>View</div>
|
||||
<div (click)="deleteProject($event)">
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</div>
|
||||
<div (click)="editProject($event)">
|
||||
<mat-icon svgIcon="red:edit"></mat-icon>
|
||||
</div>
|
||||
<div (click)="showDetailsDialog($event)">
|
||||
<mat-icon svgIcon="red:stats"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="subtitle stats-subtitle mt-20">
|
||||
<div>
|
||||
{{ appStateService.projectFiles.length }}
|
||||
{{ appStateService.activeProject.files.length }}
|
||||
</div>
|
||||
<div>9</div>
|
||||
<div>
|
||||
{{ appStateService.activeProject.date | date:'d MMM. yyyy' }}
|
||||
{{ appStateService.activeProject.project.date | date:'d MMM. yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="heading-xl mt-20">
|
||||
{{ appStateService.activeProject.projectName }}
|
||||
{{ appStateService.activeProject.project.projectName }}
|
||||
</div>
|
||||
|
||||
<div class="owner flex-row mt-20">
|
||||
@ -110,7 +115,7 @@
|
||||
</div>
|
||||
|
||||
<div class="description mt-20">
|
||||
{{ appStateService.activeProject.description }}
|
||||
{{ appStateService.activeProject.project.description }}
|
||||
</div>
|
||||
|
||||
<div class="project-team mt-20">
|
||||
@ -129,6 +134,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {
|
||||
FileStatus,
|
||||
FileUploadControllerService,
|
||||
@ -7,13 +7,17 @@ import {
|
||||
ReanalysisControllerService,
|
||||
StatusControllerService
|
||||
} from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '../../notification/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfirmationDialogComponent } from '../../common/confirmation-dialog/confirmation-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AppStateService } from '../../state/app-state.service';
|
||||
import { ProjectDetailsDialogComponent } from './project-details-dialog/project-details-dialog.component';
|
||||
import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop-overlay.service';
|
||||
import {NotificationService, NotificationType} from '../../notification/notification.service';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {ConfirmationDialogComponent} from '../../common/confirmation-dialog/confirmation-dialog.component';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {AppStateService} from '../../state/app-state.service';
|
||||
import {ProjectDetailsDialogComponent} from './project-details-dialog/project-details-dialog.component';
|
||||
import {FileDropOverlayService} from '../../upload/file-drop/service/file-drop-overlay.service';
|
||||
import {FileUploadModel} from "../../upload/model/file-upload.model";
|
||||
import {FileUploadService} from "../../upload/file-upload.service";
|
||||
import {UploadStatusOverlayService} from "../../upload/upload-status-dialog/service/upload-status-overlay.service";
|
||||
import {AddEditProjectDialogComponent} from "../project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -23,26 +27,25 @@ import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop
|
||||
})
|
||||
export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
|
||||
viewReady = false;
|
||||
|
||||
@ViewChild('dropzoneComponent', { static: true }) dropZoneComponent;
|
||||
@ViewChild('dropzoneComponent', {static: true}) dropZoneComponent;
|
||||
|
||||
dragActive = false;
|
||||
|
||||
sortOptions: any[] = [{
|
||||
value: { name: 'lastUpdated', order: 'desc' },
|
||||
value: {name: 'lastUpdated', order: 'desc'},
|
||||
label: 'project-overview.sorting.last-updated-desc.label',
|
||||
icon: 'red:sort-desc'
|
||||
}, {
|
||||
value: { name: 'lastUpdated', order: 'asc' },
|
||||
value: {name: 'lastUpdated', order: 'asc'},
|
||||
label: 'project-overview.sorting.last-updated-asc.label',
|
||||
icon: 'red:sort-asc'
|
||||
}, {
|
||||
value: { name: 'filename', order: 'desc' },
|
||||
value: {name: 'filename', order: 'desc'},
|
||||
label: 'project-overview.sorting.file-name-desc.label',
|
||||
icon: 'red:sort-desc'
|
||||
}, {
|
||||
value: { name: 'filename', order: 'asc' },
|
||||
value: {name: 'filename', order: 'asc'},
|
||||
label: 'project-overview.sorting.file-name-asc.label',
|
||||
icon: 'red:sort-asc'
|
||||
}];
|
||||
@ -57,6 +60,8 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _fileUploadService: FileUploadService,
|
||||
private _uploadStatusOverlayService: UploadStatusOverlayService,
|
||||
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
||||
private readonly _router: Router,
|
||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||
@ -64,7 +69,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
private readonly _projectControllerService: ProjectControllerService) {
|
||||
this._activatedRoute.params.subscribe(params => {
|
||||
this.projectId = params.projectId;
|
||||
this._loadProject(true);
|
||||
this.appStateService.activateProject(this.projectId);
|
||||
});
|
||||
}
|
||||
|
||||
@ -101,16 +106,10 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
|
||||
showDetailsDialog($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
const dialogRef = this._dialog.open(ProjectDetailsDialogComponent, {
|
||||
this._dialog.open(ProjectDetailsDialogComponent, {
|
||||
width: '600px',
|
||||
maxWidth: '90vw',
|
||||
data: {
|
||||
project: this.appStateService.activeProject,
|
||||
files: this.appStateService.projectFiles
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
this._getFileStatus();
|
||||
data: this.appStateService.activeProject
|
||||
});
|
||||
}
|
||||
|
||||
@ -124,29 +123,61 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
|
||||
reanalyseFile($event: MouseEvent, fileStatus: FileStatus) {
|
||||
$event.stopPropagation();
|
||||
this._reanalysisControllerService.reanalyseFile(this.appStateService.activeProject.projectId, fileStatus.fileId).subscribe(() => {
|
||||
this._reanalysisControllerService.reanalyseFile(this.appStateService.activeProject.project.projectId, fileStatus.fileId).subscribe(() => {
|
||||
this._getFileStatus();
|
||||
});
|
||||
}
|
||||
|
||||
private _loadProject(initial: boolean = false) {
|
||||
this.appStateService.activateProject(this.projectId).subscribe(() => {
|
||||
if (initial) {
|
||||
this.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getFileStatus() {
|
||||
this.appStateService.getActiveProjectStatus(true).subscribe(() => {
|
||||
});
|
||||
this.appStateService.reloadActiveProjectFiles();
|
||||
}
|
||||
|
||||
fileId(index, item) {
|
||||
return item.fileId;
|
||||
}
|
||||
|
||||
uploadFiles(files: any) {
|
||||
uploadFiles(files: FileList | File[]) {
|
||||
|
||||
const uploadFiles: FileUploadModel[] = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
uploadFiles.push({
|
||||
file: file,
|
||||
progress: 0,
|
||||
completed: false,
|
||||
error: null
|
||||
})
|
||||
}
|
||||
|
||||
this._fileUploadService.uploadFiles(uploadFiles);
|
||||
this._uploadStatusOverlayService.openStatusOverlay();
|
||||
}
|
||||
|
||||
editProject($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
this._dialog.open(AddEditProjectDialogComponent, {
|
||||
width: '400px',
|
||||
maxWidth: '90vw',
|
||||
data: this.appStateService.activeProject.project
|
||||
});
|
||||
}
|
||||
|
||||
deleteProject($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
const dialogRef = this._dialog.open(ConfirmationDialogComponent, {
|
||||
width: '400px',
|
||||
maxWidth: '90vw',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.appStateService.deleteProject(this.appStateService.activeProject.project);
|
||||
this.ngOnDestroy();
|
||||
this._router.navigate(['/ui/projects']);
|
||||
console.log('navigate?');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
<svg height="160" width="160" viewBox="0 0 160 160" class="donut-chart">
|
||||
<g *ngFor="let val of chartData; let i = index">
|
||||
<circle attr.cx="{{cx}}"
|
||||
attr.cy="{{cy}}"
|
||||
attr.r="{{radius}}"
|
||||
attr.stroke="{{colors[i]}}"
|
||||
attr.stroke-width="{{strokeWidth}}"
|
||||
attr.stroke-dasharray="{{adjustedCircumference}}"
|
||||
attr.stroke-dashoffset="calculateStrokeDashOffset(value, circumference)"
|
||||
fill="transparent"
|
||||
attr.transform="{{returnCircleTransformValue(i)}}}"/>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
@ -0,0 +1,108 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-simple-doughnut-chart',
|
||||
templateUrl: './simple-doughnut-chart.component.html',
|
||||
styleUrls: ['./simple-doughnut-chart.component.scss']
|
||||
})
|
||||
export class SimpleDoughnutChartComponent implements OnInit {
|
||||
|
||||
@Input() initialValues: [];
|
||||
@Input() angleOffset: number = -90;
|
||||
@Input() colors: string[] = ["#6495ED", "goldenrod", "#cd5c5c", "thistle", "lightgray"];
|
||||
@Input() cx: number = 80;
|
||||
@Input() cy: number = 80;
|
||||
@Input() radius: number = 60;
|
||||
@Input() strokeWidth: 30;
|
||||
|
||||
chartData: any[] = [];
|
||||
sortedValues: number[] = [];
|
||||
perimeter: number;
|
||||
data: any[] = [];
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.sortInitialValues();
|
||||
this.calculateChartData();
|
||||
}
|
||||
|
||||
get adjustedCircumference() {
|
||||
return this.circumference - 2
|
||||
};
|
||||
|
||||
get circumference() {
|
||||
return 2 * Math.PI * this.radius
|
||||
};
|
||||
|
||||
get dataTotal() {
|
||||
return this.sortedValues.reduce((acc, val) => acc + val)
|
||||
};
|
||||
|
||||
calculateChartData() {
|
||||
this.sortedValues.forEach((dataVal, index) => {
|
||||
const {x, y} = this.calculateTextCoords(dataVal, this.angleOffset)
|
||||
// start at -90deg so that the largest segment is perpendicular to top
|
||||
const data = {
|
||||
degrees: this.angleOffset,
|
||||
textX: x,
|
||||
textY: y
|
||||
}
|
||||
this.chartData.push(data)
|
||||
this.angleOffset = this.dataPercentage(dataVal) * 360 + this.angleOffset
|
||||
})
|
||||
}
|
||||
|
||||
calculateStrokeDashOffset(dataVal, circumference) {
|
||||
const strokeDiff = this.dataPercentage(dataVal) * circumference
|
||||
return circumference - strokeDiff
|
||||
}
|
||||
|
||||
|
||||
calculateTextCoords(dataVal, angleOffset) {
|
||||
// t must be radians
|
||||
// x(t) = r cos(t) + j
|
||||
// y(t) = r sin(t) + j
|
||||
|
||||
const angle = (this.dataPercentage(dataVal) * 360) / 2 + angleOffset
|
||||
const radians = this.degreesToRadians(angle)
|
||||
|
||||
const textCoords = {
|
||||
x: this.radius * Math.cos(radians) + this.cx,
|
||||
y: this.radius * Math.sin(radians) + this.cy
|
||||
}
|
||||
return textCoords
|
||||
}
|
||||
|
||||
|
||||
degreesToRadians(angle) {
|
||||
return angle * (Math.PI / 180)
|
||||
}
|
||||
|
||||
|
||||
dataPercentage(dataVal) {
|
||||
return dataVal / this.dataTotal
|
||||
}
|
||||
|
||||
|
||||
percentageString(dataVal) {
|
||||
return `${Math.round(this.dataPercentage(dataVal) * 100)}%`
|
||||
}
|
||||
|
||||
returnCircleTransformValue(index) {
|
||||
return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`
|
||||
}
|
||||
|
||||
|
||||
segmentBigEnough(dataVal) {
|
||||
return Math.round(this.dataPercentage(dataVal) * 100) > 5
|
||||
}
|
||||
|
||||
sortInitialValues() {
|
||||
return this.sortedValues = this.initialValues.sort((a, b) => b - a)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
21
apps/red-ui/src/app/state/app-state.guard.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree} from "@angular/router";
|
||||
import {Observable} from "rxjs";
|
||||
import {AppStateService} from "./app-state.service";
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AppStateGuard implements CanActivate {
|
||||
|
||||
constructor(private readonly _appStateService: AppStateService) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
return this._appStateService.loadAllProjects().then(t => {
|
||||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,72 +1,147 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {FileStatus, Project, ProjectControllerService, StatusControllerService} from "@redaction/red-ui-http";
|
||||
import {map, mergeMap, tap} from "rxjs/operators";
|
||||
import {NotificationService, NotificationType} from "../notification/notification.service";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
|
||||
export interface AppState {
|
||||
|
||||
projects: ProjectWrapper[];
|
||||
activeProject: ProjectWrapper;
|
||||
activeFile: FileStatus;
|
||||
totalAnalysedPages?: number;
|
||||
totalDocuments?: number;
|
||||
}
|
||||
|
||||
export class ProjectWrapper {
|
||||
constructor(public project: Project, public files: FileStatus[]) {
|
||||
|
||||
}
|
||||
|
||||
get projectDate() {
|
||||
return this.project.date;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AppStateService {
|
||||
|
||||
private _projects: Project[];
|
||||
private _activeProjectFiles: FileStatus[];
|
||||
private _activeProject: Project;
|
||||
private _activeFile: FileStatus;
|
||||
private _appState: AppState;
|
||||
|
||||
constructor(private readonly _projectControllerService: ProjectControllerService,
|
||||
private readonly _statusControllerService: StatusControllerService) {
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _projectControllerService: ProjectControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _statusControllerService: StatusControllerService) {
|
||||
this._appState = {
|
||||
projects: [],
|
||||
activeProject: null,
|
||||
activeFile: null
|
||||
}
|
||||
}
|
||||
|
||||
get activeProject(): ProjectWrapper {
|
||||
return this._appState.activeProject;
|
||||
}
|
||||
|
||||
get activeProject(): Project {
|
||||
return this._activeProject;
|
||||
get allProjects(): ProjectWrapper[] {
|
||||
return this._appState.projects;
|
||||
}
|
||||
|
||||
get activeFile(): FileStatus {
|
||||
return this._activeFile;
|
||||
return this._appState.activeFile;
|
||||
}
|
||||
|
||||
get allProjects(): Project[] {
|
||||
return this._projects;
|
||||
async loadAllProjects() {
|
||||
const projects = await this._projectControllerService.getProjects().toPromise();
|
||||
this._appState.projects = projects.map(p => {
|
||||
return new ProjectWrapper(p, []);
|
||||
});
|
||||
for (let project of projects) {
|
||||
await this.getFiles(project.projectId);
|
||||
}
|
||||
this._computeStats();
|
||||
}
|
||||
|
||||
get projectFiles(): FileStatus[] {
|
||||
return this._activeProjectFiles;
|
||||
async getFiles(projectId: string) {
|
||||
const files = await this._statusControllerService.getProjectStatus(projectId).toPromise();
|
||||
const project = this._appState.projects.find(p => p.project.projectId === projectId);
|
||||
project.files = files;
|
||||
this._computeStats();
|
||||
return files;
|
||||
}
|
||||
|
||||
loadAllProjects() {
|
||||
return this._projectControllerService.getProjects().pipe(tap((projects: Project[]) => {
|
||||
this._projects = projects;
|
||||
}));
|
||||
}
|
||||
|
||||
activateProject(projectId: string) {
|
||||
this._activeProjectFiles = null;
|
||||
this._activeFile = null;
|
||||
return this._projectControllerService.getProject(projectId).pipe(tap(project => {
|
||||
this._activeProject = project;
|
||||
})).pipe(mergeMap(() => {
|
||||
return this.getActiveProjectStatus();
|
||||
}));
|
||||
}
|
||||
|
||||
getActiveProjectStatus(update: boolean = false) {
|
||||
return this._statusControllerService.getProjectStatus(this._activeProject.projectId).pipe(tap((files: FileStatus[]) => {
|
||||
this._activeProjectFiles = files;
|
||||
}))
|
||||
this._appState.activeFile = null;
|
||||
this._appState.activeProject = this._appState.projects.find(p => p.project.projectId === projectId);
|
||||
if (!this._appState.activeProject) {
|
||||
this._router.navigate(['/ui/projects']);
|
||||
}
|
||||
return this._appState.activeProject;
|
||||
}
|
||||
|
||||
activateFile(projectId: string, fileId: string) {
|
||||
return this.activateProject(projectId).pipe(map((files: FileStatus[]) => {
|
||||
const file = files.find(f => f.fileId === fileId);
|
||||
this._activeFile = file;
|
||||
return file;
|
||||
}));
|
||||
this._appState.activeFile = null;
|
||||
this._appState.activeProject = this._appState.projects.find(p => p.project.projectId === projectId);
|
||||
this._appState.activeFile = this._appState.activeProject.files.find(f => f.fileId === fileId);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._activeProject = null;
|
||||
this._activeProjectFiles = null;
|
||||
this._activeFile = null;
|
||||
this._appState.activeFile = null;
|
||||
this._appState.activeProject = null;
|
||||
}
|
||||
|
||||
deleteProject(project: Project) {
|
||||
this._projectControllerService.deleteProject(project.projectId).subscribe(() => {
|
||||
const index = this._appState.projects.findIndex(p => p.project.projectId === project.projectId);
|
||||
this._appState.projects.splice(index, 1);
|
||||
this._appState.projects = [...this._appState.projects];
|
||||
}, () => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('projects.delete.delete-failed.label', project), null, NotificationType.ERROR)
|
||||
});
|
||||
}
|
||||
|
||||
async addOrUpdateProject(project: Project) {
|
||||
try {
|
||||
let updatedProject;
|
||||
if (project?.projectId) {
|
||||
updatedProject = await this._projectControllerService.updateProject(project, project.projectId).toPromise();
|
||||
} else {
|
||||
updatedProject = await this._projectControllerService.createProject(project).toPromise();
|
||||
}
|
||||
const foundProject = this._appState.projects.find(p => p.project.projectId === updatedProject.projectId);
|
||||
if (foundProject) {
|
||||
Object.assign(foundProject.project, updatedProject);
|
||||
} else {
|
||||
this._appState.projects.push(new ProjectWrapper(updatedProject, []));
|
||||
}
|
||||
this._appState.projects = [...this._appState.projects];
|
||||
} catch (error) {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('projects.add-edit-dialog.errors.save'), null, NotificationType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeStats() {
|
||||
let totalAnalysedPages = 0;
|
||||
let totalDocuments = 0;
|
||||
this._appState.projects.forEach(p => {
|
||||
totalDocuments += p.files.length;
|
||||
p.files.forEach(f => {
|
||||
totalAnalysedPages += f.numberOfPages;
|
||||
})
|
||||
})
|
||||
|
||||
this._appState.totalAnalysedPages = totalAnalysedPages;
|
||||
this._appState.totalDocuments = totalDocuments;
|
||||
|
||||
}
|
||||
|
||||
async reloadActiveProjectFiles() {
|
||||
await this.getFiles(this._appState.activeProject.project.projectId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ export class FileUploadService {
|
||||
uploadFiles(files: FileUploadModel[]) {
|
||||
this.files.push(...files);
|
||||
files.forEach(newFile => {
|
||||
this._fileUploadControllerService.uploadFileForm(newFile.file, this._appStateService.activeProject.projectId, 'events', true).subscribe((event) => {
|
||||
this._fileUploadControllerService.uploadFileForm(newFile.file, this._appStateService.activeProject.project.projectId, 'events', true).subscribe((event) => {
|
||||
if (event.type === HttpEventType.UploadProgress) {
|
||||
newFile.progress = Math.round((event.loaded / (event.total || event.loaded) * 100));
|
||||
}
|
||||
@ -42,6 +42,6 @@ export class FileUploadService {
|
||||
}
|
||||
|
||||
stopAllUploads() {
|
||||
|
||||
this.files = [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {OAuthService} from "angular-oauth2-oidc";
|
||||
import {OAuthService, UserInfo} from "angular-oauth2-oidc";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
|
||||
private _currentUser: UserInfo;
|
||||
|
||||
constructor(private _oauthService: OAuthService) {
|
||||
}
|
||||
|
||||
@ -13,4 +15,13 @@ export class UserService {
|
||||
this._oauthService.logOut();
|
||||
}
|
||||
|
||||
async loadCurrentUser() {
|
||||
this._currentUser = await this._oauthService.loadUserProfile();
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._currentUser;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
17
apps/red-ui/src/app/utils/app-load-state.service.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AppLoadStateService {
|
||||
private _loadingEvent = new EventEmitter();
|
||||
|
||||
get loading(): Observable<boolean> {
|
||||
return this._loadingEvent;
|
||||
}
|
||||
|
||||
pushLoadingEvent(event: boolean) {
|
||||
this._loadingEvent.next(event);
|
||||
}
|
||||
}
|
||||
49
apps/red-ui/src/app/utils/composite-route.guard.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError, mergeMap, tap } from 'rxjs/operators';
|
||||
import {AppLoadStateService} from "./app-load-state.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CompositeRouteGuard implements CanActivate {
|
||||
|
||||
|
||||
constructor(protected router: Router, protected injector: Injector, private appLoadStateService: AppLoadStateService) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
this.appLoadStateService.pushLoadingEvent(true);
|
||||
let compositeCanActivateObservable: Observable<boolean> = of(true);
|
||||
|
||||
const routeGuards = route.data.routeGuards;
|
||||
|
||||
if (routeGuards) {
|
||||
for (let i = 0; i < routeGuards.length; i++) {
|
||||
const routeGuard = this.injector.get(routeGuards[i]);
|
||||
const canActivateObservable: Observable<boolean> = routeGuard.canActivate(route, state);
|
||||
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
|
||||
mergeMap(bool => {
|
||||
if (!bool) {
|
||||
return of(false);
|
||||
} else {
|
||||
return canActivateObservable;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
|
||||
tap(() => {
|
||||
this.appLoadStateService.pushLoadingEvent(false);
|
||||
}),
|
||||
catchError(() => {
|
||||
this.appLoadStateService.pushLoadingEvent(false);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
|
||||
return compositeCanActivateObservable;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
"app-name": {
|
||||
"label": "Redacto"
|
||||
},
|
||||
"common": {
|
||||
"confirmation-dialog": {
|
||||
"title": {
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
"app-name": {
|
||||
"label": "Redacto"
|
||||
},
|
||||
"upload-status": {
|
||||
"dialog": {
|
||||
"title": {
|
||||
|
||||
31
apps/red-ui/src/assets/icons/general/calendar.svg
Normal file
@ -0,0 +1,31 @@
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 309.49 309.49" style="enable-background:new 0 0 309.49 309.49;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M5.504,303.826h298.484c3.039,0,5.502-2.464,5.502-5.503V31.43c0-3.039-2.463-5.502-5.502-5.502h-60.576V11.166
|
||||
c0-3.039-2.465-5.502-5.504-5.502c-3.039,0-5.502,2.463-5.502,5.502v14.762h-44.436V11.166c0-3.039-2.465-5.502-5.504-5.502
|
||||
c-3.039,0-5.502,2.463-5.502,5.502v14.762h-44.436V11.166c0-3.039-2.463-5.502-5.504-5.502c-3.039,0-5.502,2.463-5.502,5.502
|
||||
v14.762H77.084V11.166c0-3.039-2.463-5.502-5.502-5.502c-3.039,0-5.504,2.463-5.504,5.502v14.762H5.504
|
||||
C2.465,25.928,0,28.391,0,31.43v266.894C0,301.362,2.465,303.826,5.504,303.826z M11.006,36.934h55.072v22.76
|
||||
c0,3.039,2.465,5.503,5.504,5.503c3.039,0,5.502-2.464,5.502-5.503v-22.76h44.439v22.76c0,3.039,2.463,5.503,5.502,5.503
|
||||
c3.041,0,5.504-2.464,5.504-5.503v-22.76h44.436v22.76c0,3.039,2.463,5.503,5.502,5.503c3.039,0,5.504-2.464,5.504-5.503v-22.76
|
||||
h44.436v22.76c0,3.039,2.463,5.503,5.502,5.503c3.039,0,5.504-2.464,5.504-5.503v-22.76h55.072V292.82H11.006V36.934z"/>
|
||||
<path d="M271.705,104.438H37.789c-3.039,0-5.502,2.463-5.502,5.502v163.799c0,3.039,2.463,5.503,5.502,5.503h233.916
|
||||
c3.039,0,5.502-2.464,5.502-5.503V109.939C277.207,106.9,274.744,104.438,271.705,104.438z M266.201,268.235H43.293V115.443
|
||||
h222.908V268.235z"/>
|
||||
<path d="M56.451,225.957c-3.039,0-5.504,2.463-5.504,5.502c0,3.039,2.465,5.504,5.504,5.504h31.711v17.695
|
||||
c0,3.039,2.465,5.502,5.504,5.502c3.039,0,5.502-2.463,5.502-5.502v-17.695h29.713v17.695c0,3.039,2.465,5.502,5.504,5.502
|
||||
c3.039,0,5.502-2.463,5.502-5.502v-17.695h29.715v17.695c0,3.039,2.465,5.502,5.504,5.502c3.039,0,5.502-2.463,5.502-5.502v-17.695
|
||||
h29.719v17.695c0,3.039,2.463,5.502,5.502,5.502c3.039,0,5.504-2.463,5.504-5.502v-17.695h31.707c3.039,0,5.504-2.465,5.504-5.504
|
||||
c0-3.039-2.465-5.502-5.504-5.502h-31.707v-28.615h31.707c3.039,0,5.504-2.463,5.504-5.502c0-3.039-2.465-5.504-5.504-5.504
|
||||
h-31.707v-28.614h31.707c3.039,0,5.504-2.464,5.504-5.503c0-3.039-2.465-5.503-5.504-5.503h-31.707v-17.691
|
||||
c0-3.039-2.465-5.503-5.504-5.503c-3.039,0-5.502,2.464-5.502,5.503v17.691h-29.719v-17.691c0-3.039-2.463-5.503-5.502-5.503
|
||||
c-3.039,0-5.504,2.464-5.504,5.503v17.691h-29.715v-17.691c0-3.039-2.463-5.503-5.502-5.503c-3.039,0-5.504,2.464-5.504,5.503
|
||||
v17.691H99.168v-17.691c0-3.039-2.463-5.503-5.502-5.503c-3.039,0-5.504,2.464-5.504,5.503v17.691H56.451
|
||||
c-3.039,0-5.504,2.464-5.504,5.503c0,3.039,2.465,5.503,5.504,5.503h31.711v28.614H56.451c-3.039,0-5.504,2.465-5.504,5.504
|
||||
c0,3.039,2.465,5.502,5.504,5.502h31.711v28.615H56.451z M139.887,225.957v-28.615h29.715v28.615H139.887z M210.326,225.957
|
||||
h-29.719v-28.615h29.719V225.957z M210.326,157.722v28.614h-29.719v-28.614H210.326z M169.602,157.722v28.614h-29.715v-28.614
|
||||
H169.602z M99.168,157.722h29.713v28.614H99.168V157.722z M99.168,197.342h29.713v28.615H99.168V197.342z"/>
|
||||
<path d="M71.582,90.321h166.326c3.039,0,5.504-2.464,5.504-5.503c0-3.039-2.465-5.503-5.504-5.503H71.582
|
||||
c-3.039,0-5.504,2.464-5.504,5.503C66.078,87.857,68.543,90.321,71.582,90.321z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
9
apps/red-ui/src/assets/icons/general/drop-down-arrow.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
width="255px" height="255px" viewBox="0 0 255 255" style="enable-background:new 0 0 255 255;" xml:space="preserve">
|
||||
<g>
|
||||
<g id="arrow-drop-down">
|
||||
<polygon points="0,63.75 127.5,191.25 255,63.75 "/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 308 B |
13
apps/red-ui/src/assets/icons/general/files.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg id="Layer_1" enable-background="new 0 0 510 510" height="512" viewBox="0 0 510 510" width="512"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="m120 255h270v30h-270z"/>
|
||||
<path d="m180 180h210v30h-210z"/>
|
||||
<path
|
||||
d="m360 44.906v-44.906h-210v44.906h-90v465.094h390v-465.094zm-180-14.906h150v60h-150zm240 450h-330v-405.094h60v45.094h210v-45.094h60z"/>
|
||||
<path d="m120.469 180h29.063v30h-29.063z"/>
|
||||
<path d="m120 405h270v30h-270z"/>
|
||||
<path d="m180 330h210v30h-210z"/>
|
||||
<path d="m120.469 330h29.063v30h-29.063z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 563 B |
@ -10,35 +10,5 @@
|
||||
C485.371,388.667,512,324.38,512,256S485.371,123.333,437.02,74.98z M256,70c30.327,0,55,24.673,55,55c0,30.327-24.673,55-55,55
|
||||
c-30.327,0-55-24.673-55-55C201,94.673,225.673,70,256,70z M326,420H186v-30h30V240h-30v-30h110v180h30V420z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 790 B |
@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<defs></defs>
|
||||
<g fill="none" fill-rule="evenodd" id="Settings" stroke="none" stroke-width="1">
|
||||
<g fill="currentColor" id="Data-Sources" transform="translate(-564.000000, -592.000000)">
|
||||
<g id="Group-17" transform="translate(554.000000, 582.000000)">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
14
apps/red-ui/src/assets/icons/general/stats.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<circle cx="437" cy="86.281" r="15"/>
|
||||
<circle cx="377" cy="86.281" r="15"/>
|
||||
<circle cx="317" cy="86.281" r="15"/>
|
||||
<path
|
||||
d="m15 497h482c8.284 0 15-6.716 15-15v-332-120c0-8.284-6.716-15-15-15h-482c-8.284 0-15 6.716-15 15v120 332c0 8.284 6.716 15 15 15zm15-452h452v90h-452zm0 120h452v302h-452z"/>
|
||||
<path
|
||||
d="m437 407h-16v-91c0-8.284-6.716-15-15-15h-60c-8.284 0-15 6.716-15 15v91h-30v-197c0-8.284-6.716-15-15-15h-60c-8.284 0-15 6.716-15 15v197h-30v-148.764c0-8.284-6.716-15-15-15h-60c-8.284 0-15 6.716-15 15v148.764h-16c-8.284 0-15 6.716-15 15s6.716 15 15 15h362c8.284 0 15-6.716 15-15s-6.716-15-15-15zm-286 0h-30v-133.764h30zm120 0h-30v-182h30zm120 0h-30v-76h30z"/>
|
||||
<path
|
||||
d="m173.343 71.282h-98.343c-8.284 0-15 6.716-15 15s6.716 15 15 15h98.343c8.284 0 15-6.716 15-15s-6.716-15-15-15z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 986 B |
10
apps/red-ui/src/assets/icons/general/user.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 19.738 19.738" style="enable-background:new 0 0 19.738 19.738;" xml:space="preserve">
|
||||
<g>
|
||||
<path style="fill:#010002;" d="M18.18,19.738h-2c0-3.374-2.83-6.118-6.311-6.118s-6.31,2.745-6.31,6.118h-2
|
||||
c0-4.478,3.729-8.118,8.311-8.118C14.451,11.62,18.18,15.26,18.18,19.738z"/>
|
||||
<path style="fill:#010002;" d="M9.87,10.97c-3.023,0-5.484-2.462-5.484-5.485C4.385,2.461,6.846,0,9.87,0
|
||||
c3.025,0,5.486,2.46,5.486,5.485S12.895,10.97,9.87,10.97z M9.87,2C7.948,2,6.385,3.563,6.385,5.485S7.948,8.97,9.87,8.97
|
||||
c1.923,0,3.486-1.563,3.486-3.485S11.791,2,9.87,2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 665 B |
@ -10,6 +10,11 @@
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
border: 1px solid #E2E4E9;
|
||||
font-family: Inter, sans-serif;
|
||||
letter-spacing: 0;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
|
||||
|
||||
&.large {
|
||||
height: 32px;
|
||||
@ -18,12 +23,17 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&.gray {
|
||||
&.gray-dark {
|
||||
background-color: $grey-4;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.red {
|
||||
&.gray-red {
|
||||
background-color: $grey-4;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.red-white {
|
||||
background-color: $red-1;
|
||||
color: $white;
|
||||
border: none;
|
||||
|
||||
@ -34,3 +34,8 @@
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.icon-10 {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
24
apps/red-ui/src/assets/styles/red-logo.scss
Normal file
@ -0,0 +1,24 @@
|
||||
@import "red-variables";
|
||||
|
||||
.redacto-logo {
|
||||
height: 14px;
|
||||
width: 22px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
.line-1 {
|
||||
height: 6px;
|
||||
width: 16px;
|
||||
border-radius: 3px;
|
||||
background-color: $red-1;
|
||||
}
|
||||
|
||||
.line-2 {
|
||||
height: 6px;
|
||||
width: 22px;
|
||||
border-radius: 6px;
|
||||
background-color: $red-1;
|
||||
}
|
||||
}
|
||||
@ -41,18 +41,28 @@ html, body {
|
||||
|
||||
> div {
|
||||
padding: 10px;
|
||||
|
||||
mat-icon {
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
|
||||
> div {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
}
|
||||
> div {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
@ -106,7 +116,7 @@ html, body {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0;
|
||||
line-height: 14px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.red-top-bar {
|
||||
@ -122,6 +132,23 @@ html, body {
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
margin-left: 16px;
|
||||
height: 20px;
|
||||
color: #283241;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -10,3 +10,4 @@
|
||||
@import "red-tables";
|
||||
@import "red-components";
|
||||
@import "red-controls";
|
||||
@import "red-logo";
|
||||
|
||||
@ -45,7 +45,9 @@
|
||||
"@pdftron/webviewer": "^7.0.1",
|
||||
"angular-oauth2-oidc": "^10.0.3",
|
||||
"angular-oauth2-oidc-jwks": "^9.0.0",
|
||||
"chart.js": "^2.9.3",
|
||||
"file-saver": "^2.0.2",
|
||||
"ng2-charts": "^2.4.2",
|
||||
"ng2-file-upload": "^1.4.0",
|
||||
"ngp-sort-pipe": "^0.0.4",
|
||||
"ngx-dropzone": "^2.2.2",
|
||||
|
||||
51
yarn.lock
@ -1808,6 +1808,13 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/chart.js@^2.9.24":
|
||||
version "2.9.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.25.tgz#a22d18f6f7cb5b4499f39de600c4c520f635a58f"
|
||||
integrity sha512-SPgPISpaGM42WL9Dezms4+8fInHxAXDzTs8DwPxhGuJT+jETXlVITTbe8bvK1n+sMmyyelR9B4w0lwMkA24oJg==
|
||||
dependencies:
|
||||
moment "^2.10.2"
|
||||
|
||||
"@types/color-name@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
@ -3203,6 +3210,29 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
chart.js@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
|
||||
integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
|
||||
dependencies:
|
||||
chartjs-color "^2.1.0"
|
||||
moment "^2.10.2"
|
||||
|
||||
chartjs-color-string@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
|
||||
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
|
||||
chartjs-color@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
|
||||
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
|
||||
dependencies:
|
||||
chartjs-color-string "^0.6.0"
|
||||
color-convert "^1.9.3"
|
||||
|
||||
check-more-types@^2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
||||
@ -3430,7 +3460,7 @@ collection-visit@^1.0.0:
|
||||
map-visit "^1.0.0"
|
||||
object-visit "^1.0.0"
|
||||
|
||||
color-convert@^1.9.0, color-convert@^1.9.1:
|
||||
color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
@ -7142,6 +7172,11 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash-es@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
||||
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
@ -7554,6 +7589,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdir
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
moment@^2.10.2:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
moment@^2.27.0, moment@^2.28.0:
|
||||
version "2.28.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.28.0.tgz#cdfe73ce01327cee6537b0fafac2e0f21a237d75"
|
||||
@ -7694,6 +7734,15 @@ ng-packagr@^10.1.2:
|
||||
stylus "^0.54.7"
|
||||
terser "^5.0.0"
|
||||
|
||||
ng2-charts@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/ng2-charts/-/ng2-charts-2.4.2.tgz#6b2d0a1a66911247c2e8c3d8602ba9aa7339bf22"
|
||||
integrity sha512-mY3C2uKCaApHCQizS2YxEOqQ7sSZZLxdV6N1uM9u/VvUgVtYvlPtdcXbKpN52ak93ZE22I73DiLWVDnDNG4/AQ==
|
||||
dependencies:
|
||||
"@types/chart.js" "^2.9.24"
|
||||
lodash-es "^4.17.15"
|
||||
tslib "^2.0.0"
|
||||
|
||||
ng2-file-upload@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.4.0.tgz#8dea28d573234c52af474ad2a4001b335271e5c4"
|
||||
|
||||