merged with master

This commit is contained in:
Timo Bejan 2020-10-08 11:34:34 +03:00
parent 4f486aa4b4
commit bbee5cadeb
23 changed files with 800 additions and 403 deletions

View File

@ -1,129 +1,144 @@
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 {NgxDropzoneModule} from "ngx-dropzone";
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 { 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 { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { InitialsAvatarComponent } from './common/initials-avatar/initials-avatar.component';
import { StatusBarComponent } from './components/status-bar/status-bar.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
}
@NgModule({
declarations: [AppComponent, BaseScreenComponent, ProjectListingScreenComponent, ProjectOverviewScreenComponent, AddEditProjectDialogComponent, ConfirmationDialogComponent, FilePreviewScreenComponent, PdfViewerComponent, FileDetailsDialogComponent, ProjectDetailsDialogComponent, FullPageLoadingIndicatorComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
ReactiveFormsModule,
HttpClientModule,
AuthModule,
IconsModule,
ApiModule,
MatDialogModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
RouterModule.forRoot([
{
path: '',
redirectTo: 'ui/projects',
pathMatch: 'full',
},
{
path: 'ui',
component: BaseScreenComponent,
children: [
{
path: 'projects',
component: ProjectListingScreenComponent,
canActivate: [AuthGuard]
},
{
path: 'projects/:projectId',
component: ProjectOverviewScreenComponent,
canActivate: [AuthGuard]
},
{
path: 'projects/:projectId/file/:fileId',
component: FilePreviewScreenComponent,
canActivate: [AuthGuard]
}
]
}
declarations: [
AppComponent,
BaseScreenComponent,
ProjectListingScreenComponent,
ProjectOverviewScreenComponent,
AddEditProjectDialogComponent,
ConfirmationDialogComponent,
FilePreviewScreenComponent,
PdfViewerComponent,
FileDetailsDialogComponent,
ProjectDetailsDialogComponent,
FullPageLoadingIndicatorComponent,
InitialsAvatarComponent,
StatusBarComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
ReactiveFormsModule,
HttpClientModule,
AuthModule,
IconsModule,
ApiModule,
MatDialogModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
RouterModule.forRoot([
{
path: '',
redirectTo: 'ui/projects',
pathMatch: 'full'
},
{
path: 'ui',
component: BaseScreenComponent,
children: [
{
path: 'projects',
component: ProjectListingScreenComponent,
canActivate: [AuthGuard]
},
{
path: 'projects/:projectId',
component: ProjectOverviewScreenComponent,
canActivate: [AuthGuard]
},
{
path: 'projects/:projectId/file/:fileId',
component: FilePreviewScreenComponent,
canActivate: [AuthGuard]
}
]
}
]),
NgpSortModule,
MatToolbarModule,
MatButtonModule,
MatMenuModule,
MatIconModule,
MatTooltipModule,
MatSnackBarModule,
MatTabsModule,
MatButtonToggleModule,
MatFormFieldModule,
ToastrModule.forRoot(),
MatSelectModule,
MatSidenavModule,
FileUploadModule,
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
MatProgressSpinnerModule
],
providers: [ {
]),
NgpSortModule,
MatToolbarModule,
MatButtonModule,
MatMenuModule,
MatIconModule,
MatTooltipModule,
MatSnackBarModule,
MatTabsModule,
MatButtonToggleModule,
MatFormFieldModule,
ToastrModule.forRoot(),
MatSelectModule,
MatSidenavModule,
FileUploadModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
MatProgressSpinnerModule
],
providers: [{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: ApiPathInterceptorService,
useClass: ApiPathInterceptorService
}, {
provide: APP_INITIALIZER,
multi: true,
useFactory: languageInitializer,
deps: [LanguageService]
}],
bootstrap: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@ -0,0 +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>

View File

@ -0,0 +1,11 @@
@import "../../../assets/styles/red-variables";
* {
font-size: 13px;
line-height: 16px;
}
.flex-row {
flex-wrap: wrap;
gap: 12px;
}

View File

@ -0,0 +1,37 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'redaction-initials-avatar',
templateUrl: './initials-avatar.component.html',
styleUrls: ['./initials-avatar.component.scss']
})
export class InitialsAvatarComponent implements OnInit {
@Input()
public username: string;
@Input()
public color: 'gray' | 'red' = 'gray';
@Input()
public size: 'small' | 'large' = 'small';
@Input()
public withName = false;
constructor() { }
ngOnInit(): void {
}
public get initials(): string {
if (!this.username) {
return '?'
}
return this.username
.split(' ')
.filter((value, idx) => idx < 2)
.map((str) => str[0])
.join('');
}
}

View File

@ -0,0 +1,6 @@
<div class="rectangle-container">
<div *ngFor="let rect of config" [className]="'section-wrapper flex-' + rect.length">
<div *ngIf="rect.title" class="subtitle">{{ rect.title }}</div>
<div [className]="'rectangle ' + rect.color "></div>
</div>
</div>

View File

@ -0,0 +1,49 @@
@import '../../../assets/styles/red-variables';
.rectangle-container {
flex: 1;
display: flex;
width: 100%;
.subtitle {
margin-bottom: 8px;
}
.section-wrapper:first-child {
.rectangle {
border-radius: 6px 0 0 6px;
}
}
.section-wrapper:last-child {
.rectangle {
border-radius: 0 6px 6px 0;
}
&:first-child {
.rectangle {
border-radius: 6px;
}
}
}
.rectangle {
height: 4px;
&.grey {
background-color: $grey-4;
}
&.yellow {
background-color: $yellow-1;
}
&.blue {
background-color: $blue-1;
}
&.green {
background-color: $green-1;
}
}
}

View File

@ -0,0 +1,22 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'redaction-status-bar',
templateUrl: './status-bar.component.html',
styleUrls: ['./status-bar.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class StatusBarComponent implements OnInit {
@Input()
public config: {
length: number,
color: 'green' | 'blue' | 'red' | 'grey' | 'yellow',
title?: string,
}[] = [];
constructor() {
}
ngOnInit(): void {
}
}

View File

@ -8,25 +8,31 @@
<button mat-menu-item routerLink="/ui/projects"
translate="top-bar.navigation-items.projects.label">
</button>
<button *ngIf="appStateService.activeProject" [routerLink]="'/ui/projects/'+appStateService.activeProject.projectId"
<button *ngIf="appStateService.activeProject"
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId"
mat-menu-item>{{appStateService.activeProject.projectName}}</button>
<button *ngIf="appStateService.activeFile" [routerLink]="'/ui/projects/'+appStateService.activeProject.projectId+'/file/'+appStateService.activeFile.fileId"
<button *ngIf="appStateService.activeFile"
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId+'/file/'+appStateService.activeFile.fileId"
mat-menu-item>{{appStateService.activeFile.filename}}</button>
</mat-menu>
</div>
<div class="menu left visible-lg">
<button color="primary" mat-flat-button routerLink="/ui/projects"
translate="top-bar.navigation-items.projects.label"></button>
<a class="breadcrumb" routerLink="/ui/projects"
translate="top-bar.navigation-items.projects.label"></a>
<div *ngIf="appStateService.activeProject" class="breadcrumb">
<mat-icon svgIcon="red:chevron-right"></mat-icon>
</div>
<button *ngIf="appStateService.activeProject" [routerLink]="'/ui/projects/'+appStateService.activeProject.projectId" color="accent"
mat-flat-button>{{appStateService.activeProject.projectName}}</button>
<a *ngIf="appStateService.activeProject" class="breadcrumb"
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId">
{{appStateService.activeProject.projectName}}
</a>
<div *ngIf="appStateService.activeFile" class="breadcrumb">
<mat-icon svgIcon="red:chevron-right"></mat-icon>
</div>
<button *ngIf="appStateService.activeFile" [routerLink]="'/ui/projects/'+appStateService.activeProject.projectId+'/file/'+appStateService.activeFile.fileId" color="accent"
mat-flat-button>{{appStateService.activeFile.filename}}</button>
<a *ngIf="appStateService.activeFile" class="breadcrumb"
[routerLink]="'/ui/projects/'+appStateService.activeProject.projectId+'/file/'+appStateService.activeFile.fileId">
{{appStateService.activeFile.filename}}
</a>
</div>
<div class="menu right">
<button [matMenuTriggerFor]="menu" mat-button translate="top-bar.navigation-items.my-account.label"></button>
@ -52,7 +58,5 @@
<div class="divider"></div>
</div>
<div class="red-content">
<div class="red-content-inner">
<router-outlet></router-outlet>
</div>
<router-outlet></router-outlet>
</div>

View File

@ -1,35 +1,68 @@
<section class="center-section" *ngIf="viewReady">
<div class="page-header">
<div class="heading-xl" translate="projects.header.label"></div>
<button (click)="openAddProjectDialog()" color="accent" mat-flat-button translate="projects.add-new.label"></button>
<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>
<div *ngIf="appStateService.allProjects?.length === 0 " translate="projects.no-projects.label"></div>
<div class="listing">
<div *ngFor="let project of appStateService.allProjects" [routerLink]="'/ui/projects/'+project.projectId"
class="list-entry clickable">
<div class="list-entry-content">
<div class="listing-title">
{{project.projectName}}
</div>
<div class="listing-subtitle">
{{project.description}}
</div>
<div class="listing-subtitle">
{{project.date | date:'short'}}
<div class="flex red-content-inner">
<div class="table-container">
<div class="table-header">
<span class="subheading">
{{'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>
<div class="list-entry-actions">
<button (click)="editProject($event,project)" mat-icon-button
[matTooltip]="'projects.edit.action.label'|translate">
<mat-icon svgIcon="red:edit"></mat-icon>
</button>
<button (click)="deleteProject($event,project)" color="warn" mat-icon-button
[matTooltip]="'projects.delete.action.label'|translate">
<mat-icon svgIcon="red:delete"></mat-icon>
</button>
<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>
<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="on-hover-wrapper">
<div class="on-hover">
<div>d</div>
<div>s</div>
</div>
</div>
</div>
</div>
</div>
<div class="right-fixed-container"></div>
</div>
</section>

View File

@ -1,7 +1,9 @@
@import "../../../assets/styles/red-mixins";
:host {
max-width: 1100px;
margin: 0 auto;
padding: 20px;
.stats-subtitle {
margin-top: 6px;
}
.stats-bar {
width: 160px;
}

View File

@ -5,66 +5,130 @@
<div *ngIf="appStateService.activeProject" class="page-header">
<div class="heading-xl clamp-1">{{appStateService.activeProject.projectName}}</div>
<button (click)="fileInput.click()" color="accent" mat-flat-button
translate="project-overview.upload-files.label"></button>
<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.document.label"></div>
</div>
<button (click)="fileInput.click()" color="warn" mat-flat-button
translate="project-overview.upload-document.label"></button>
<input #fileInput (change)="uploadFiles($event.target.files)" class="file-upload-input" multiple="true"
type="file">
</div>
<div class="flex-row">
<div class="heading-l clamp-2">{{appStateService.activeProject?.description}}</div>
<mat-form-field *ngIf="appStateService.projectFiles && appStateService.projectFiles.length > 0">
<mat-label>{{'project-overview.sorting.label' | translate}}</mat-label>
<mat-select (valueChange)="sortingChanged($event)" [value]="sorting" color="primary">
<mat-option *ngFor="let option of sortOptions" [value]="option.value">
<mat-icon [svgIcon]="option.icon"></mat-icon>
{{option.label | translate}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="break-20"></div>
<div class="listing">
<div *ngFor="let fileStatus of appStateService.projectFiles | sortBy: sorting.order:sorting.name; trackBy:fileId"
[class.clickable]="fileStatus.status === 'PROCESSED'"
[routerLink]="fileStatus.status === 'PROCESSED' ? ['/ui/projects/'+projectId+'/file/'+fileStatus.fileId] : []"
class="list-entry xl">
<div class="list-entry-content">
<div class="listing-title one-line slim break-all">
{{fileStatus.filename}}
</div>
<div class="listing-subtitle">
{{'project-overview.file-listing.file-entry.status.label'| translate:fileStatus}}
</div>
<div class="listing-subtitle">
{{'project-overview.file-listing.file-entry.number-of-pages.label'| translate:fileStatus}}
</div>
<div class="listing-subtitle">
{{'project-overview.file-listing.file-entry.number-of-analyses.label'| translate:fileStatus}}
</div>
<div class="listing-subtitle">
{{'project-overview.file-listing.file-entry.added.label'| translate:{added: fileStatus.added | date:'short'} }}
</div>
<div *ngIf="fileStatus.lastUpdated" class="listing-subtitle">
{{'project-overview.file-listing.file-entry.last-updated.label'| translate:{lastUpdated: fileStatus.lastUpdated | date:'short'} }}
<div class="flex red-content-inner">
<div class="table-container">
<div class="table-header">
<span class="subheading">
{{'project-overview.table-header.title.label'| translate:{ length: appStateService.projectFiles?.length || 0 } }}
</span>
<div class="actions">
<div translate="project-overview.table-header.bulk-select.label"></div>
<div translate="project-overview.table-header.recent.label"></div>
</div>
</div>
<div class="list-entry-actions">
<button (click)="deleteFile($event,fileStatus)" color="warn" mat-icon-button
[matTooltip]="'project-overview.delete.action.label'|translate">
<mat-icon svgIcon="red:delete"></mat-icon>
</button>
<button (click)="reanalyseFile($event,fileStatus)" color="primary" mat-icon-button
[matTooltip]="'project-overview.reanalyse.action.label'|translate">
<mat-icon svgIcon="red:refresh"></mat-icon>
</button>
<div class="table-header">
<redaction-status-bar [config]="[{ length: 2, color: 'yellow', title: 'text 1'}, { length: 1, color: 'green', title: 'text 2'}]"></redaction-status-bar>
</div>
<div class="table-col-names">
<div class="flex-3 subtitle min-width" translate="project-overview.table-col-names.name.label"></div>
<div class="flex-2 subtitle min-width" translate="project-overview.table-col-names.added-on.label"></div>
<div class="flex-1 subtitle min-width" translate="project-overview.table-col-names.added-by.label"></div>
<div class="flex-1 subtitle min-width" translate="project-overview.table-col-names.assigned-to.label"></div>
<div class="subtitle status-container" translate="project-overview.table-col-names.status.label"></div>
</div>
<div class="table-item"
*ngFor="let fileStatus of appStateService.projectFiles | 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 }}
</div>
<div class="subtitle flex-2 min-width">
{{ fileStatus.added | date:'d MMM. yyyy, hh:mm a' }}
</div>
<div class="subtitle flex-1 min-width">
Timo Bejan
</div>
<div class="subtitle flex-1 min-width">
<redaction-initials-avatar
withName="true"
></redaction-initials-avatar>
</div>
<div class=" status-container">
<div class="status-bar-wrapper">
<redaction-status-bar
[config]="[{ color: 'yellow', length: 1 }]"
></redaction-status-bar>
</div>
</div>
<div class="on-hover-wrapper">
<div class="on-hover">
<div>d</div>
<div>s</div>
<div>v</div>
</div>
</div>
</div>
</div>
<div class="project-details-container right-fixed-container">
<div class="actions-row">
<div>Edit</div>
<div>Delete</div>
<div>View</div>
</div>
<div class="subtitle stats-subtitle mt-20">
<div>
{{ appStateService.projectFiles.length }}
</div>
<div>9</div>
<div>
{{ appStateService.activeProject.date | date:'d MMM. yyyy' }}
</div>
</div>
<div class="heading-xl mt-20">
{{ appStateService.activeProject.projectName }}
</div>
<div class="owner flex-row mt-20">
<redaction-initials-avatar username="Timo Bejan"
size="large"
withName="true"
></redaction-initials-avatar>
</div>
<div class="description mt-20">
{{ appStateService.activeProject.description }}
</div>
<div class="project-team mt-20">
<div class="subheading" translate="project-overview.project-details.project-team.label"></div>
<div class="flex mt-20">
<div *ngFor="let username of ['S H', 'D O', 'E G', 'D V', 'J A', 'T H', 'P B']" class="member">
<redaction-initials-avatar [username]="username" size="large"></redaction-initials-avatar>
</div>
<div class="member">
<div class="oval large">+2</div>
</div>
<div class="member">
<div class="oval red large">+</div>
</div>
</div>
</div>
</div>
</div>
<button (click)="showDetailsDialog($event)" aria-label="details" class="details-button" color="primary" mat-fab>
<mat-icon svgIcon="red:info"></mat-icon>
</button>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>

View File

@ -1,14 +1,45 @@
.listing {
position: relative;
height: calc(100vh - (61px + 80px + 70px + 80px));
overflow: auto;
}
@import "../../../assets/styles/red-variables";
.file-upload-input {
display: none;
}
.min-width {
min-width: 60px;
}
.status-container {
display: flex;
justify-content: flex-end;
width: 40px;
.status-bar-wrapper {
width: 40px;
}
}
.table-header redaction-status-bar {
width: 100%;
padding-bottom: 10px;
}
.project-details-container {
.actions-row {
display: flex;
> div {
padding: 10px;
}
}
.description {
font-size: 13px;
line-height: 18px;
}
.project-team {
.member:not(:last-child) {
margin-right: 5px;
}
}
}

View File

@ -1,19 +1,19 @@
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,
ProjectControllerService,
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";
} 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';
@Component({
@ -25,24 +25,26 @@ 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'}, label: 'project-overview.sorting.file-name-asc.label', icon: 'red:sort-asc'
value: { name: 'filename', order: 'asc' },
label: 'project-overview.sorting.file-name-asc.label',
icon: 'red:sort-asc'
}];
sorting: any = this.sortOptions[0].value;
projectId: string;
@ -78,7 +80,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
const dialogRef = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw',
autoFocus: false,
autoFocus: false
});
dialogRef.afterClosed().subscribe(result => {
@ -116,7 +118,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
this._fileDropOverlayService.cleanupFileDropHandling();
if (this._fileStatusInterval) {
this._fileStatusInterval = null;
clearInterval(this._fileStatusInterval)
clearInterval(this._fileStatusInterval);
}
}

View File

@ -68,7 +68,38 @@
}
}
},
"filters": {
"filter-by": {
"label": "Filter by:"
},
"status": {
"label": "Status"
},
"people": {
"label": "People"
},
"due-date": {
"label": "Due Date"
},
"project": {
"label": "Project"
},
"document": {
"label": "Document"
}
},
"projects": {
"table-header": {
"title": {
"label": "{{length}} active projects"
},
"bulk-select": {
"label": "Bulk select"
},
"recent": {
"label": "Recent"
}
},
"add-edit-dialog": {
"header-new": {
"label": "New Project"
@ -146,6 +177,37 @@
}
},
"project-overview": {
"table-header": {
"title": {
"label": "{{length}} documents"
},
"bulk-select": {
"label": "Bulk select"
},
"recent": {
"label": "Recent"
}
},
"table-col-names": {
"name": {
"label": "Name"
},
"added-on": {
"label": "Added on"
},
"added-by": {
"label": "Added by"
},
"assigned-to": {
"label": "Assigned to"
},
"status": {
"label": "Status"
}
},
"sorting": {
"label": "Sorting",
"last-updated-desc": {
@ -196,14 +258,24 @@
}
}
},
"project-details": {
"project-team": {
"label": "Project team"
}
},
"header": {
"label": "Project Overview"
},
"upload-files": {
"label": "Upload Files"
"upload-document": {
"label": "Upload Document"
},
"no-project": {
"label": "Requested project: {{projectId}} does not exist! <a href='/ui/projects'>Back to Project Listing. <a/>"
}
},
"initials-avatar": {
"unassigned": {
"label": "Unassigned"
}
}
}

View File

@ -0,0 +1,38 @@
@import "red-variables";
.oval {
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
height: 24px;
width: 24px;
border-radius: 12px;
font-size: 10px;
border: 1px solid #E2E4E9;
&.large {
height: 32px;
width: 32px;
border-radius: 16px;
font-size: 13px;
}
&.gray {
background-color: $grey-4;
border: none;
}
&.red {
background-color: $red-1;
color: $white;
border: none;
}
}
.stats-subtitle {
display: flex;
> div:not(:last-child) {
margin-right: 12px;
}
}

View File

@ -10,7 +10,6 @@
label {
height: 14px;
opacity: 0.6;
color: $grey-1;
font-size: 11px;
font-weight: 500;
letter-spacing: 0;

View File

@ -6,4 +6,5 @@
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -1,154 +1,81 @@
@import "red-variables";
@import "red-mixins";
@media only screen and (max-width: 720px) {
.listing {
.list-entry {
width: 100% !important;
}
}
}
html, body {
margin: 0;
padding: 0;
height: 100vh;
font-family: 'Inter', sans-serif;
color: $grey-1;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20px;
&.slim {
padding-bottom: 0;
}
padding: 0 24px;
height: 50px;
box-shadow: 0 2px 4px 0 $grey-4;
position: fixed;
top: 61px;
width: 100vw;
box-sizing: border-box;
background-color: $white;
z-index: 1;
}
.listing {
width: 100%;
display: inline-flex;
flex-wrap: wrap;
.list-entry {
box-sizing: border-box;
height: 122px;
width: 310px;
border: 1px solid $grey-1;
border-radius: 2px;
background-color: $white;
padding: 20px 24px;
margin-right: 22px;
margin-bottom: 22px;
display: flex;
flex-direction: row;
transition: background-color 0.35s ease-in-out;
&.xl {
width: 100%;
padding: 16px 24px;
}
&:hover {
background-color: $grey-2;
.list-entry-actions {
mat-icon {
opacity: 1;
}
}
}
&.clickable:hover {
background-color: $grey-1;
cursor: pointer;
color: $white;
.list-entry-actions {
button.mat-primary {
mat-icon {
color: $white;
}
}
}
.listing-title, .listing-subtitle {
color: $white;
}
}
.list-entry-content {
display: flex;
flex: 1;
flex-direction: column;
}
.list-entry-actions {
width: 18px;
margin-left: 22px;
display: flex;
flex-direction: column;
margin-top: -4px;
mat-icon {
opacity: 0;
transition: opacity 0.35s ease-in-out;
width: 20px;
height: 20px;
}
}
.listing-title {
height: 44px;
color: #283241;
font-family: Inter, sans-serf, serif;
font-size: 18px;
font-weight: bold;
letter-spacing: 0;
margin-bottom: 17px;
line-height: 22px;
@include line-clamp(2);
transition: color 0.35s ease-in-out;
&.one-line {
height: 22px;
@include line-clamp(1);
}
&.slim {
margin-bottom: 9px;
}
&.break-all {
word-break: break-all;
}
}
.listing-subtitle {
height: 12px;
opacity: 0.7;
color: #283241;
font-family: Inconsolata, monospace, monospace;
font-size: 12px;
letter-spacing: 0;
line-height: 12px;
@include line-clamp(1);
transition: color 0.35s ease-in-out;
}
}
.red-content-inner {
margin-top: 50px;
}
.right-fixed-container {
border-left: 1px solid $grey-4;
height: 100%;
width: 330px;
padding: 24px;
position: fixed;
right: 0;
}
.filters {
font-size: 13px;
line-height: 14px;
> div {
padding: 10px 14px;
}
}
.flex-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.flex-2 {
flex: 2;
}
.flex-3 {
flex: 3;
}
.mt-20 {
margin-top: 20px;
}
.table-container {
height: calc(100vh - 61px - 50px);
width: calc(100vw - 379px);
}
.break-20 {
height: 20px;
background: transparent;
@ -163,7 +90,6 @@ html, body {
.detail-row {
opacity: 1;
color: $grey-1;
font-family: Inconsolata, monospace, monospace;
font-size: 14px;
letter-spacing: 0;
@ -171,13 +97,6 @@ html, body {
height: 18px;
}
.center-section {
max-width: 1100px;
margin: 0 auto;
padding: 20px;
}
.red-top-bar {
height: 61px;
width: 100%;
@ -189,8 +108,7 @@ html, body {
height: 60px;
display: flex;
justify-content: space-between;
padding-left: 60px;
padding-right: 60px;
padding: 0 24px;
.menu {
display: flex;
@ -199,15 +117,23 @@ html, body {
}
.breadcrumb {
padding-left: 8px;
padding-right: 8px;
color: $yellow-1;
text-decoration: none;
color: $grey-1;
font-size: 13px;
line-height: 18px;
font-weight: 600;
@include line-clamp(1);
max-width: 320px;
mat-icon {
height: 14px;
width: 14px;
height: 10px;
width: 10px;
padding: 0 8px;
}
&:last-child {
color: $red-1;
}
}
.divider {
@ -221,7 +147,6 @@ html, body {
width: 100vw;
height: calc(100vh - 61px);
overflow: auto;
}
.hidden {

View File

@ -0,0 +1,88 @@
@import "red-variables";
@import "red-mixins";
.table-header {
background-color: rgba(226, 228, 233, 0.9);
padding: 7px 24px 9px;
display: flex;
justify-content: space-between;
align-items: center;
.actions {
display: flex;
font-size: 13px;
div {
padding: 10px 14px;
}
}
}
.table-col-names {
display: flex;
text-transform: uppercase;
> div {
padding: 8px 24px;
font-weight: 600;
border-bottom: 1px solid rgba(226, 228, 233, 0.9);
}
}
.table-item {
display: flex;
align-items: center;
height: 80px;
border-bottom: 1px solid rgba(226, 228, 233, 0.9);
> div:not(.on-hover) {
padding: 0 24px;
}
.table-item-title {
font-size: 13px;
font-weight: 600;
line-height: 18px;
@include line-clamp(1);
}
.table-item-title--large {
font-size: 16px;
line-height: 20px;
}
.on-hover-wrapper {
width: 0;
height: 0;
padding: 0 !important;
align-self: flex-start;
.on-hover {
position: relative;
right: 142px;
height: 80px;
width: 142px;
background: linear-gradient(90deg, rgba(249, 250, 251, 0) 0%, #F9FAFB 100%);
display: none;
justify-content: flex-end;
align-items: center;
div {
margin-right: 12px;
}
}
}
&:hover {
background-color: #F9FAFB;
.on-hover {
display: flex;
}
.rectangle {
background-color: $grey-4 !important;
}
}
}

View File

@ -2,8 +2,9 @@
@import "red-mixins";
button {
font-family: Inconsolata, monospace !important;
font-weight: 700 !important;
font-family: Inter, sans-serif !important;
font-weight: 400 !important;
border-radius: 17px !important;
}
a {
@ -16,23 +17,34 @@ a {
}
.heading-xl {
color: $grey-1;
font-family: Inter, sans-serf;
font-size: 32px;
font-weight: bold;
letter-spacing: 0;
line-height: 39px;
font-size: 24px;
font-weight: 600;
line-height: 29px;
}
.heading-l {
color: #283241;
font-family: Inter, sans-serf;
font-family: Inter, sans-serif;
font-size: 18px;
font-weight: bold;
letter-spacing: 0;
line-height: 22px;
}
.subheading {
text-transform: uppercase;
opacity: 0.7;
font-size: 11px;
font-weight: 600;
letter-spacing: 0;
line-height: 14px;
}
.subtitle {
opacity: 0.7;
font-size: 11px;
line-height: 14px;
}
.clamp-1 {
@include line-clamp(1);

View File

@ -7,4 +7,6 @@
@import "red-dialog";
@import "red-input";
@import "red-media-queries";
@import "red-tables";
@import "red-components";
@import "red-controls";

View File

@ -9,9 +9,9 @@ $dark: #000;
$grey-1: #283241;
$grey-2: #ECECEE;
$grey-3: #aaacb3;
$grey-4: #E2E4E9;
$blue-1: #4875F7;
$red-1: #F65757;
$yellow-1: #FFB83B;
$green-1: #46CE7D;

View File

@ -2694,7 +2694,7 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base64-js@1.3.1, base64-js@^1.0.2:
base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
@ -6782,11 +6782,6 @@ jest@26.2.2:
import-local "^3.0.2"
jest-cli "^26.2.2"
js-sha256@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -6949,21 +6944,6 @@ karma-source-map-support@1.4.0:
dependencies:
source-map-support "^0.5.5"
keycloak-angular@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/keycloak-angular/-/keycloak-angular-8.0.1.tgz#29851e7aded21925faa051c69dfa5872bda6661f"
integrity sha512-q68vcaFiSYNjbzPM1v+6LohMpWUyus9hcQBi2rNz06xOtWuRU4U6t5vQgoim6bDhtkhWpR5+a3SYl0lzUJKyrw==
dependencies:
tslib "^2.0.0"
keycloak-js@10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-10.0.2.tgz#f0cf5b942627c5221f1466552c40e4624503b77b"
integrity sha512-7nkg4Ob1khHGcNbuK36AMndKUEuIQFpNlWU9ygWs7nSBPCI9VZ8dJjjXfKJHm0ewgcqLFGPIJ6bxxRlfcQ6sLg==
dependencies:
base64-js "1.3.1"
js-sha256 "0.9.0"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"