auto-formatted entire codebase with prettier

This commit is contained in:
Timo Bejan 2020-10-27 16:00:23 +02:00
parent b0a1222409
commit 69d7f48108
167 changed files with 9152 additions and 7499 deletions

View File

@ -1,3 +1,15 @@
{
"singleQuote": true
"useTabs": false,
"printWidth": 100,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "none",
"overrides": [
{
"files": "{apps}/**/*.html",
"options": {
"parser": "angular"
}
}
]
}

View File

@ -1,7 +1,9 @@
# Redaction
## Swagger Generated Code
To regnerate http rune swaagger
```
BASE=http://ingress.redaction-timo-dev-401.178.63.47.73.xip.io/
URL="$BASE"v2/api-docs?group=redaction-gateway-v1
@ -12,19 +14,19 @@ swagger-codegen generate -i "$URL" -l typescript-angular -o /tmp/swagger
## To Create a new Stack in rancher
Goto rancher.iqser.com: Select Cluster `Development`,
go to apps, click launch and select `Redaction` from the `dev` section.
go to apps, click launch and select `Redaction` from the `dev` section.
Add a new name and a new namespace.
Select `answers-development.yaml` and add it to answers `Edit as yaml`.
For HTTPS / Cloudflare domain go to `workloads` -> `Loadbalancing` -> `select your stack`
For HTTPS / Cloudflare domain go to `workloads` -> `Loadbalancing` -> `select your stack`
Add cloudflare certificate and specify a hostname to use `timo-redaction-dev.iqser.cloud`
## Keycloak Staging Config
keycloak:
authServerUrl: 'https://redkc-staging.iqser.cloud/auth'
client:
secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4'
keycloak:
authServerUrl: 'https://redkc-staging.iqser.cloud/auth'
client:
secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4'
## Default Testing URL
@ -35,13 +37,12 @@ timo-redaction-dev.iqser.cloud
## Test Users
| username | role | comment |
| --- | --- | --- |
| guest | | cannot use the application |
| user | RED_USER | |
| red_manager | RED_MANAGER | |
| useradmin | RED_ADMIN, RED_USER | has super power ! |
| manageradmin | RED_ADMIN RED_MANAGER RED_USER | has super super power ! |
| username | role | comment |
| ------------ | ------------------------------ | -------------------------- |
| guest | | cannot use the application |
| user | RED_USER | |
| red_manager | RED_MANAGER | |
| useradmin | RED_ADMIN, RED_USER | has super power ! |
| manageradmin | RED_ADMIN RED_MANAGER RED_USER | has super super power ! |
Password for all users is `OsloImWinter`

View File

@ -1,171 +1,157 @@
{
"version": 1,
"projects": {
"red-ui": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "apps/red-ui",
"sourceRoot": "apps/red-ui/src",
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/apps/red-ui",
"index": "apps/red-ui/src/index.html",
"main": "apps/red-ui/src/main.ts",
"polyfills": "apps/red-ui/src/polyfills.ts",
"tsConfig": "apps/red-ui/tsconfig.app.json",
"aot": true,
"assets": [
"apps/red-ui/src/favicon.ico",
{
"glob": "**/*",
"input": "node_modules/@pdftron/webviewer/public/",
"output": "/assets/wv-resources/"
},
{
"glob": "**/*",
"input": "apps/red-ui/src/assets/",
"output": "/assets/"
},
"apps/red-ui/src/manifest.webmanifest"
],
"styles": [
"apps/red-ui/src/styles.scss"
],
"scripts": [
"node_modules/@pdftron/webviewer/webviewer.min.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/red-ui/src/environments/environment.ts",
"with": "apps/red-ui/src/environments/environment.prod.ts"
"version": 1,
"projects": {
"red-ui": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
"root": "apps/red-ui",
"sourceRoot": "apps/red-ui/src",
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/apps/red-ui",
"index": "apps/red-ui/src/index.html",
"main": "apps/red-ui/src/main.ts",
"polyfills": "apps/red-ui/src/polyfills.ts",
"tsConfig": "apps/red-ui/tsconfig.app.json",
"aot": true,
"assets": [
"apps/red-ui/src/favicon.ico",
{
"glob": "**/*",
"input": "node_modules/@pdftron/webviewer/public/",
"output": "/assets/wv-resources/"
},
{
"glob": "**/*",
"input": "apps/red-ui/src/assets/",
"output": "/assets/"
},
"apps/red-ui/src/manifest.webmanifest"
],
"styles": ["apps/red-ui/src/styles.scss"],
"scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/red-ui/src/environments/environment.ts",
"with": "apps/red-ui/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
],
"serviceWorker": true,
"ngswConfigPath": "apps/red-ui/ngsw-config.json"
}
}
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "red-ui:build",
"proxyConfig": "apps/red-ui/proxy.conf.json"
},
"configurations": {
"production": {
"browserTarget": "red-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "red-ui:build"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["apps/red-ui/tsconfig.app.json"],
"exclude": ["**/node_modules/**", "!apps/red-ui/**/*"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "apps/red-ui/jest.config.js",
"passWithNoTests": true
}
}
],
"serviceWorker": true,
"ngswConfigPath": "apps/red-ui/ngsw-config.json"
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "red-ui:build",
"proxyConfig": "apps/red-ui/proxy.conf.json"
},
"configurations": {
"production": {
"browserTarget": "red-ui:build:production"
"red-ui-http": {
"projectType": "library",
"root": "libs/red-ui-http",
"sourceRoot": "libs/red-ui-http/src",
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "libs/red-ui-http/tsconfig.lib.json",
"project": "libs/red-ui-http/ng-package.json"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["libs/red-ui-http/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/red-ui-http/**/*"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/red-ui-http/jest.config.js",
"passWithNoTests": true
}
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "red-ui:build"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/red-ui/tsconfig.app.json"
],
"exclude": [
"**/node_modules/**",
"!apps/red-ui/**/*"
]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "apps/red-ui/jest.config.js",
"passWithNoTests": true
}
}
}
},
"red-ui-http": {
"projectType": "library",
"root": "libs/red-ui-http",
"sourceRoot": "libs/red-ui-http/src",
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "libs/red-ui-http/tsconfig.lib.json",
"project": "libs/red-ui-http/ng-package.json"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/red-ui-http/tsconfig.lib.json"
],
"exclude": [
"**/node_modules/**",
"!libs/red-ui-http/**/*"
]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/red-ui-http/jest.config.js",
"passWithNoTests": true
}
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
}
},
"cli": {
"defaultCollection": "@nrwl/angular",
"analytics": false
},
"schematics": {
"@nrwl/angular:application": {
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
"cli": {
"defaultCollection": "@nrwl/angular",
"analytics": false
},
"@nrwl/angular:library": {
"unitTestRunner": "jest"
}
},
"defaultProject": "red-ui"
"schematics": {
"@nrwl/angular:application": {
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"@nrwl/angular:library": {
"unitTestRunner": "jest"
}
},
"defaultProject": "red-ui"
}

View File

@ -1,21 +1,21 @@
module.exports = {
name: 'red-ui',
preset: '../../jest.config.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsConfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
astTransformers: [
'jest-preset-angular/build/InlineFilesTransformer',
'jest-preset-angular/build/StripStylesTransformer',
],
name: 'red-ui',
preset: '../../jest.config.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsConfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
astTransformers: [
'jest-preset-angular/build/InlineFilesTransformer',
'jest-preset-angular/build/StripStylesTransformer'
]
}
},
},
coverageDirectory: '../../coverage/apps/red-ui',
snapshotSerializers: [
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
'jest-preset-angular/build/AngularSnapshotSerializer.js',
'jest-preset-angular/build/HTMLCommentSerializer.js',
],
coverageDirectory: '../../coverage/apps/red-ui',
snapshotSerializers: [
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
'jest-preset-angular/build/AngularSnapshotSerializer.js',
'jest-preset-angular/build/HTMLCommentSerializer.js'
]
};

View File

@ -1,30 +1,21 @@
{
"$schema": "../../node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
"$schema": "../../node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/index.html", "/manifest.webmanifest", "/*.css", "/*.js"]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"]
}
}
]
}

View File

@ -1,56 +1,56 @@
{
"/project": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/reanalyze": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/upload": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/user": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/download": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/delete": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/status": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/dictionary": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/manualRedaction": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
"/project": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/reanalyze": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/upload": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/user": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/download": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/delete": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/status": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/dictionary": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
},
"/manualRedaction": {
"target": "https://timo-redaction-dev.iqser.cloud/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}

View File

@ -1,18 +1,11 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {HttpClientModule} from "@angular/common/http";
import {AppConfigService} from "./app-config.service";
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { AppConfigService } from './app-config.service';
@NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule,
],
providers: [
AppConfigService,
],
declarations: [],
imports: [CommonModule, HttpClientModule],
providers: [AppConfigService]
})
export class AppConfigModule {
}
export class AppConfigModule {}

View File

@ -1,36 +1,34 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {tap} from "rxjs/operators";
import {Observable} from "rxjs";
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
export enum AppConfigKey {
OAUTH_URL = "OAUTH_URL",
OAUTH_CLIENT_ID = "OAUTH_CLIENT_ID",
API_URL = "API_URL",
PDFTRON_LICENSE ="PDFTRON_LICENSE",
ADMIN_CONTACT_NAME="ADMIN_CONTACT_NAME",
ADMIN_CONTACT_URL="ADMIN_CONTACT_URL",
OAUTH_URL = 'OAUTH_URL',
OAUTH_CLIENT_ID = 'OAUTH_CLIENT_ID',
API_URL = 'API_URL',
PDFTRON_LICENSE = 'PDFTRON_LICENSE',
ADMIN_CONTACT_NAME = 'ADMIN_CONTACT_NAME',
ADMIN_CONTACT_URL = 'ADMIN_CONTACT_URL'
}
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class AppConfigService {
private _config: { [key in AppConfigKey]?: any } = {};
private _config: { [key in AppConfigKey]?: any } = {};
constructor(private readonly _httpClient: HttpClient) {}
constructor(private readonly _httpClient: HttpClient) {
}
loadAppConfig(): Observable<any> {
return this._httpClient.get<any>("/assets/config/config.json").pipe(tap(config => {
this._config = config;
}));
}
getConfig(key: AppConfigKey, defaultValue?: any) {
return this._config[key] ? this._config[key] : defaultValue;
}
loadAppConfig(): Observable<any> {
return this._httpClient.get<any>('/assets/config/config.json').pipe(
tap((config) => {
this._config = config;
})
);
}
getConfig(key: AppConfigKey, defaultValue?: any) {
return this._config[key] ? this._config[key] : defaultValue;
}
}

View File

@ -1,2 +1,4 @@
<router-outlet></router-outlet>
<redaction-full-page-loading-indicator [displayed]="appLoadStateService.loading | async"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="appLoadStateService.loading | async"
></redaction-full-page-loading-indicator>

View File

@ -1,15 +1,11 @@
import {Component} from '@angular/core';
import {AppLoadStateService} from "./utils/app-load-state.service";
import { Component } from '@angular/core';
import { AppLoadStateService } from './utils/app-load-state.service';
@Component({
selector: 'redaction-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
selector: 'redaction-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(public appLoadStateService: AppLoadStateService){
}
constructor(public appLoadStateService: AppLoadStateService) {}
}

View File

@ -57,146 +57,150 @@ import { AuthErrorComponent } from './screens/auth-error/auth-error.component';
import { RedRoleGuard } from './auth/red-role.guard';
import { MatListModule } from '@angular/material/list';
import { AssignOwnerDialogComponent } from './dialogs/assign-owner-dialog/assign-owner-dialog.component';
import {MatDatepickerModule} from "@angular/material/datepicker";
import {MatNativeDateModule} from "@angular/material/core";
import {MatInputModule} from "@angular/material/input";
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatInputModule } from '@angular/material/input';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
}
@NgModule({
declarations: [
AppComponent,
BaseScreenComponent,
ProjectListingScreenComponent,
ProjectOverviewScreenComponent,
AddEditProjectDialogComponent,
ConfirmationDialogComponent,
FilePreviewScreenComponent,
PdfViewerComponent,
FileDetailsDialogComponent,
ProjectDetailsDialogComponent,
AssignOwnerDialogComponent,
FullPageLoadingIndicatorComponent,
InitialsAvatarComponent,
StatusBarComponent,
LogoComponent,
SimpleDoughnutChartComponent,
ManualRedactionDialogComponent,
AnnotationIconComponent,
AuthErrorComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
AuthModule,
IconsModule,
ApiModule,
MatDialogModule,
MatNativeDateModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
RouterModule.forRoot([
{
path: '',
redirectTo: 'ui/projects',
pathMatch: 'full'
},
{
path: 'auth-error',
component: AuthErrorComponent,
canActivate: [AuthGuard]
},
{
path: 'ui',
component: BaseScreenComponent,
children: [
{
path: 'projects',
component: ProjectListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
declarations: [
AppComponent,
BaseScreenComponent,
ProjectListingScreenComponent,
ProjectOverviewScreenComponent,
AddEditProjectDialogComponent,
ConfirmationDialogComponent,
FilePreviewScreenComponent,
PdfViewerComponent,
FileDetailsDialogComponent,
ProjectDetailsDialogComponent,
AssignOwnerDialogComponent,
FullPageLoadingIndicatorComponent,
InitialsAvatarComponent,
StatusBarComponent,
LogoComponent,
SimpleDoughnutChartComponent,
ManualRedactionDialogComponent,
AnnotationIconComponent,
AuthErrorComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
AuthModule,
IconsModule,
ApiModule,
MatDialogModule,
MatNativeDateModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
},
{
path: 'projects/:projectId',
component: ProjectOverviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}),
RouterModule.forRoot([
{
path: '',
redirectTo: 'ui/projects',
pathMatch: 'full'
},
{
path: 'auth-error',
component: AuthErrorComponent,
canActivate: [AuthGuard]
},
{
path: 'ui',
component: BaseScreenComponent,
children: [
{
path: 'projects',
component: ProjectListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'projects/:projectId',
component: ProjectOverviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'projects/:projectId/file/:fileId',
component: FilePreviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
}
]
}
},
{
path: 'projects/:projectId/file/:fileId',
component: FilePreviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
}
]
}
]),
NgpSortModule,
MatToolbarModule,
MatButtonModule,
MatSlideToggleModule,
MatMenuModule,
MatIconModule,
MatTooltipModule,
MatSnackBarModule,
MatTabsModule,
MatButtonToggleModule,
MatFormFieldModule,
ToastrModule.forRoot(),
MatSelectModule,
MatSidenavModule,
FileUploadModule,
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
MatProgressSpinnerModule,
MatCheckboxModule,
MatListModule,
MatDatepickerModule,
MatInputModule
],
providers: [{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: ApiPathInterceptorService
}, {
provide: APP_INITIALIZER,
multi: true,
useFactory: languageInitializer,
deps: [LanguageService]
}],
bootstrap: [AppComponent]
]),
NgpSortModule,
MatToolbarModule,
MatButtonModule,
MatSlideToggleModule,
MatMenuModule,
MatIconModule,
MatTooltipModule,
MatSnackBarModule,
MatTabsModule,
MatButtonToggleModule,
MatFormFieldModule,
ToastrModule.forRoot(),
MatSelectModule,
MatSidenavModule,
FileUploadModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
MatProgressSpinnerModule,
MatCheckboxModule,
MatListModule,
MatDatepickerModule,
MatInputModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: ApiPathInterceptorService
},
{
provide: APP_INITIALIZER,
multi: true,
useFactory: languageInitializer,
deps: [LanguageService]
}
],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private router: Router, private route: ActivatedRoute) {
route.queryParamMap.subscribe(queryParams => {
if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
this.router.navigate([], {
queryParams: {
'state': null,
'session_state': null,
'code': null
},
queryParamsHandling: 'merge'
constructor(private router: Router, private route: ActivatedRoute) {
route.queryParamMap.subscribe((queryParams) => {
if (
queryParams.has('code') ||
queryParams.has('state') ||
queryParams.has('session_state')
) {
this.router.navigate([], {
queryParams: {
state: null,
session_state: null,
code: null
},
queryParamsHandling: 'merge'
});
}
});
}
});
}
}
}

View File

@ -1,30 +1,30 @@
import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, Router, RouterStateSnapshot} from "@angular/router";
import {KeycloakAuthGuard, KeycloakService} from "keycloak-angular";
import {UserService} from "../user/user.service";
import {AppLoadStateService} from "../utils/app-load-state.service";
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
import { UserService } from '../user/user.service';
import { AppLoadStateService } from '../utils/app-load-state.service';
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class AuthGuard extends KeycloakAuthGuard {
constructor(
protected readonly _router: Router,
protected readonly _keycloak: KeycloakService,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {
super(_router, _keycloak);
}
public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (!this.authenticated) {
await this._keycloak.login({
redirectUri: window.location.origin + state.url,
});
constructor(
protected readonly _router: Router,
protected readonly _keycloak: KeycloakService,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {
super(_router, _keycloak);
}
await this._userService.loadCurrentUser();
return true;
}
public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (!this.authenticated) {
await this._keycloak.login({
redirectUri: window.location.origin + state.url
});
}
await this._userService.loadCurrentUser();
return true;
}
}

View File

@ -1,59 +1,50 @@
import {APP_INITIALIZER, NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {HttpClientModule} from "@angular/common/http";
import {AppConfigModule} from "../app-config/app-config.module";
import {KeycloakAngularModule, KeycloakService} from "keycloak-angular";
import {AppConfigKey, AppConfigService} from "../app-config/app-config.service";
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { AppConfigModule } from '../app-config/app-config.module';
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { AppConfigKey, AppConfigService } from '../app-config/app-config.service';
export function keycloakInitializer(keycloak: KeycloakService, appConfigService: AppConfigService) {
return () => {
return appConfigService.loadAppConfig().toPromise().then(() => {
let url = appConfigService.getConfig(AppConfigKey.OAUTH_URL);
url = url.replace(/\/$/, ""); // remove trailing slash
const realm = url.substring(url.lastIndexOf("/") + 1, url.length);
url = url.substr(0, url.lastIndexOf("/realms"));
return keycloak.init({
config: {
url: url,
realm: realm,
clientId: appConfigService.getConfig(AppConfigKey.OAUTH_CLIENT_ID)
},
initOptions: {
checkLoginIframe: false,
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/oauth/silent-refresh.html',
flow: 'standard'
},
enableBearerInterceptor: true,
})
});
}
return () => {
return appConfigService
.loadAppConfig()
.toPromise()
.then(() => {
let url = appConfigService.getConfig(AppConfigKey.OAUTH_URL);
url = url.replace(/\/$/, ''); // remove trailing slash
const realm = url.substring(url.lastIndexOf('/') + 1, url.length);
url = url.substr(0, url.lastIndexOf('/realms'));
return keycloak.init({
config: {
url: url,
realm: realm,
clientId: appConfigService.getConfig(AppConfigKey.OAUTH_CLIENT_ID)
},
initOptions: {
checkLoginIframe: false,
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/oauth/silent-refresh.html',
flow: 'standard'
},
enableBearerInterceptor: true
});
});
};
}
@NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule,
KeycloakAngularModule,
AppConfigModule,
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: keycloakInitializer,
multi: true,
deps: [KeycloakService, AppConfigService],
},
],
declarations: [],
imports: [CommonModule, HttpClientModule, KeycloakAngularModule, AppConfigModule],
providers: [
{
provide: APP_INITIALIZER,
useFactory: keycloakInitializer,
multi: true,
deps: [KeycloakService, AppConfigService]
}
]
})
export class AuthModule {
}
export class AuthModule {}

View File

@ -1,37 +1,34 @@
import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router";
import {KeycloakService} from "keycloak-angular";
import {UserService} from "../user/user.service";
import {AppLoadStateService} from "../utils/app-load-state.service";
import {Observable} from "rxjs";
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { KeycloakService } from 'keycloak-angular';
import { UserService } from '../user/user.service';
import { AppLoadStateService } from '../utils/app-load-state.service';
import { Observable } from 'rxjs';
class UrlTree {
}
class UrlTree {}
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class RedRoleGuard implements CanActivate {
constructor(
protected readonly _router: Router,
protected readonly _keycloak: KeycloakService,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return new Observable(obs => {
if (!this._userService.user.hasAnyREDRoles) {
this._router.navigate(['/auth-error']);
this._appLoadStateService.pushLoadingEvent(false);
obs.next(false);
obs.complete();
} else {
obs.next(true);
obs.complete();
}
})
}
constructor(
protected readonly _router: Router,
protected readonly _keycloak: KeycloakService,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return new Observable((obs) => {
if (!this._userService.user.hasAnyREDRoles) {
this._router.navigate(['/auth-error']);
this._appLoadStateService.pushLoadingEvent(false);
obs.next(false);
obs.complete();
} else {
obs.next(true);
obs.complete();
}
});
}
}

View File

@ -1,19 +1,20 @@
<section class="dialog">
<div [translate]="confirmationDialogInput.title"
class="dialog-header heading-l">
</div>
<div [translate]="confirmationDialogInput.title" class="dialog-header heading-l"></div>
<div class="dialog-content">
<p [translate]="confirmationDialogInput.question"></p>
</div>
<div class="dialog-content">
<p [translate]="confirmationDialogInput.question"></p>
</div>
<div class="dialog-actions">
<button (click)="confirm()" color="primary"
mat-flat-button>{{confirmationDialogInput.confirmationText | translate}}</button>
<button (click)="deny()" color="primary" mat-flat-button>{{confirmationDialogInput.denyText | translate}}</button>
</div>
<div class="dialog-actions">
<button (click)="confirm()" color="primary" mat-flat-button>
{{ confirmationDialogInput.confirmationText | translate }}
</button>
<button (click)="deny()" color="primary" mat-flat-button>
{{ confirmationDialogInput.denyText | translate }}
</button>
</div>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</section>

View File

@ -1,56 +1,54 @@
import {Component, Inject, OnInit} from '@angular/core';
import {TranslateService} from "@ngx-translate/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import { Component, Inject, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
export class ConfirmationDialogInput {
public title?: string;
public question?: string;
public confirmationText?: string;
public denyText?: string;
public title?: string;
public question?: string;
public confirmationText?: string;
public denyText?: string;
constructor(options: ConfirmationDialogInput) {
this.title = options.title || ConfirmationDialogInput.default().title;
this.question = options.question || ConfirmationDialogInput.default().question;
this.confirmationText =
options.confirmationText || ConfirmationDialogInput.default().confirmationText;
this.denyText = options.denyText || ConfirmationDialogInput.default().denyText;
}
constructor(options: ConfirmationDialogInput) {
this.title = options.title || ConfirmationDialogInput.default().title;
this.question = options.question || ConfirmationDialogInput.default().question;
this.confirmationText = options.confirmationText || ConfirmationDialogInput.default().confirmationText;
this.denyText = options.denyText || ConfirmationDialogInput.default().denyText;
}
static default() {
return new ConfirmationDialogInput({
title: 'common.confirmation-dialog.title.label',
question: 'common.confirmation-dialog.description.label',
confirmationText: 'common.confirmation-dialog.confirm.label',
denyText: 'common.confirmation-dialog.deny.label'
});
}
static default() {
return new ConfirmationDialogInput({
title: 'common.confirmation-dialog.title.label',
question: 'common.confirmation-dialog.description.label',
confirmationText: 'common.confirmation-dialog.confirm.label',
denyText: 'common.confirmation-dialog.deny.label'
});
}
}
@Component({
selector: 'redaction-confirmation-dialog',
templateUrl: './confirmation-dialog.component.html',
styleUrls: ['./confirmation-dialog.component.scss']
selector: 'redaction-confirmation-dialog',
templateUrl: './confirmation-dialog.component.html',
styleUrls: ['./confirmation-dialog.component.scss']
})
export class ConfirmationDialogComponent implements OnInit {
constructor(
private readonly _translateService: TranslateService,
public dialogRef: MatDialogRef<ConfirmationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public confirmationDialogInput: ConfirmationDialogInput) {
if (!confirmationDialogInput) {
this.confirmationDialogInput = ConfirmationDialogInput.default();
constructor(
private readonly _translateService: TranslateService,
public dialogRef: MatDialogRef<ConfirmationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public confirmationDialogInput: ConfirmationDialogInput
) {
if (!confirmationDialogInput) {
this.confirmationDialogInput = ConfirmationDialogInput.default();
}
}
}
ngOnInit(): void {
}
ngOnInit(): void {}
deny() {
this.dialogRef.close();
}
deny() {
this.dialogRef.close();
}
confirm() {
this.dialogRef.close(true);
}
confirm() {
this.dialogRef.close(true);
}
}

View File

@ -1,6 +1,6 @@
@import "../../../assets/styles/red-variables";
@import '../../../assets/styles/red-variables';
.flex-row {
flex-wrap: wrap;
gap: 12px;
flex-wrap: wrap;
gap: 12px;
}

View File

@ -1,3 +1,3 @@
<div [ngClass]="type" class="icon">
<span>{{ type[0] }}</span>
<span>{{ type[0] }}</span>
</div>

View File

@ -1,68 +1,72 @@
@import "../../../assets/styles/red-variables";
@import '../../../assets/styles/red-variables';
.icon {
height: 16px;
width: 16px;
font-size: 11px;
line-height: 14px;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
text-transform: uppercase;
color: $white;
height: 16px;
width: 16px;
font-size: 11px;
line-height: 14px;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
text-transform: uppercase;
color: $white;
}
.suggestion {
width: 0;
height: 0;
border: 9px solid transparent;
border-bottom-color: $grey-1;
position: relative;
top: -9px;
&:after {
content: '';
position: absolute;
left: -9px;
top: 9px;
width: 0;
height: 0;
border: 9px solid transparent;
border-top-color: $grey-1;
}
border-bottom-color: $grey-1;
position: relative;
top: -9px;
span {
transform: translateY(9px);
z-index: 2;
}
&:after {
content: '';
position: absolute;
left: -9px;
top: 9px;
width: 0;
height: 0;
border: 9px solid transparent;
border-top-color: $grey-1;
}
span {
transform: translateY(9px);
z-index: 2;
}
}
.hint, .comment, .ignore {
border-radius: 50%;
.hint,
.comment,
.ignore {
border-radius: 50%;
}
.hint, .redaction, .comment {
background-color: $grey-1;
.hint,
.redaction,
.comment {
background-color: $grey-1;
}
.request{
background-color: $blue-1;
.request {
background-color: $blue-1;
}
.ignore {
background-color: $grey-5;
background-color: $grey-5;
}
.hint_only {
background-color: $orange-1;
background-color: $orange-1;
}
.vertebrate {
background-color: $green-1;
background-color: $green-1;
}
.names {
background-color: $yellow-2;
background-color: $yellow-2;
}

View File

@ -1,16 +1,14 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'redaction-annotation-icon',
templateUrl: './annotation-icon.component.html',
styleUrls: ['./annotation-icon.component.scss']
selector: 'redaction-annotation-icon',
templateUrl: './annotation-icon.component.html',
styleUrls: ['./annotation-icon.component.scss']
})
export class AnnotationIconComponent implements OnInit {
@Input() public type: 'hint' | 'redaction' | 'suggestion' | 'ignore' | 'comment' | 'request';
@Input() public type: 'hint' | 'redaction' | 'suggestion' | 'ignore' | 'comment' | 'request';
constructor() {
}
constructor() {}
ngOnInit(): void {
}
ngOnInit(): void {}
}

View File

@ -1,30 +1,48 @@
<div [class]="'container flex ' + direction">
<svg attr.height="{{size}}" attr.width="{{size}}" attr.viewBox="0 0 {{size}} {{size}}" class="donut-chart">
<g *ngFor="let value of parsedConfig; let i = index">
<circle attr.cx="{{cx}}"
attr.cy="{{cy}}"
attr.r="{{radius}}"
[class]="value.color"
attr.stroke-width="{{strokeWidth}}"
attr.stroke-dasharray="{{circumference}}"
attr.stroke-dashoffset="{{calculateStrokeDashOffset(value.value)}}"
fill="transparent"
attr.transform="{{returnCircleTransformValue(i)}}" />
</g>
</svg>
<svg
attr.height="{{ size }}"
attr.width="{{ size }}"
attr.viewBox="0 0 {{ size }} {{ size }}"
class="donut-chart"
>
<g *ngFor="let value of parsedConfig; let i = index">
<circle
attr.cx="{{ cx }}"
attr.cy="{{ cy }}"
attr.r="{{ radius }}"
[class]="value.color"
attr.stroke-width="{{ strokeWidth }}"
attr.stroke-dasharray="{{ circumference }}"
attr.stroke-dashoffset="{{ calculateStrokeDashOffset(value.value) }}"
fill="transparent"
attr.transform="{{ returnCircleTransformValue(i) }}"
/>
</g>
</svg>
<div class="text-container" [style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + strokeWidth + 'px;'">
<div class="heading-xl">{{ dataTotal }}</div>
<div class="projects-text mt-5">{{ subtitle | translate }}</div>
</div>
<div class="breakdown-container">
<div>
<div *ngFor="let val of parsedConfig">
<redaction-status-bar [small]="true"
[config]="[{ length: val.value, color: val.color, label: val.value + ' ' + (val.label | translate | lowercase) }]">
</redaction-status-bar>
</div>
<div
class="text-container"
[style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + strokeWidth + 'px;'"
>
<div class="heading-xl">{{ dataTotal }}</div>
<div class="projects-text mt-5">{{ subtitle | translate }}</div>
</div>
<div class="breakdown-container">
<div>
<div *ngFor="let val of parsedConfig">
<redaction-status-bar
[small]="true"
[config]="[
{
length: val.value,
color: val.color,
label: val.value + ' ' + (val.label | translate | lowercase)
}
]"
>
</redaction-status-bar>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,40 +1,39 @@
@import "../../../assets/styles/red-variables";
@import '../../../assets/styles/red-variables';
.container {
position: relative;
display: flex;
align-items: center;
gap: 20px;
position: relative;
display: flex;
align-items: center;
gap: 20px;
&.column {
flex-direction: column;
}
&.column {
flex-direction: column;
}
}
.text-container {
position: absolute;
top: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
position: absolute;
top: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.breakdown-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
div {
width: fit-content;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
}
div {
width: fit-content;
display: flex;
flex-direction: column;
gap: 8px;
}
}
// 'UNPROCESSED'
// | 'REPROCESS'
@ -46,43 +45,43 @@
// | 'APPROVED';
circle {
&.UNASSIGNED {
stroke: $grey-5;
}
&.UNASSIGNED {
stroke: $grey-5;
}
&.UNPROCESSED {
stroke: $grey-3;
}
&.UNPROCESSED {
stroke: $grey-3;
}
&.UNDER_REVIEW {
stroke: $yellow-1;
}
&.UNDER_REVIEW {
stroke: $yellow-1;
}
&.UNDER_APPROVAL {
stroke: $blue-4;
}
&.UNDER_APPROVAL {
stroke: $blue-4;
}
&.APPROVED {
stroke: $blue-3;
}
&.APPROVED {
stroke: $blue-3;
}
&.PROCESSING {
stroke: $green-2
}
&.PROCESSING {
stroke: $green-2;
}
&.REPROCESS {
stroke: $green-1
}
&.REPROCESS {
stroke: $green-1;
}
&.ERROR {
stroke: $red-1;
}
&.ERROR {
stroke: $red-1;
}
&.ACTIVE {
stroke: $primary;
}
&.ACTIVE {
stroke: $primary;
}
&.ARCHIVED {
stroke: rgba($red-1, 0.1);
}
&.ARCHIVED {
stroke: rgba($red-1, 0.1);
}
}

View File

@ -1,79 +1,75 @@
import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Color } from '../../utils/types';
export class DoughnutChartConfig {
value: number;
color: Color;
label: string;
value: number;
color: Color;
label: string;
}
@Component({
selector: 'redaction-simple-doughnut-chart',
templateUrl: './simple-doughnut-chart.component.html',
styleUrls: ['./simple-doughnut-chart.component.scss']
selector: 'redaction-simple-doughnut-chart',
templateUrl: './simple-doughnut-chart.component.html',
styleUrls: ['./simple-doughnut-chart.component.scss']
})
export class SimpleDoughnutChartComponent implements OnChanges {
@Input() subtitle: string;
@Input() config: DoughnutChartConfig[] = [];
@Input() radius = 85;
@Input() strokeWidth = 20;
@Input() direction: 'row' | 'column' = 'column';
@Input() subtitle: string;
@Input() config: DoughnutChartConfig[] = [];
@Input() radius = 85;
@Input() strokeWidth = 20;
@Input() direction: 'row' | 'column' = 'column';
public chartData: any[] = [];
public perimeter: number;
public cx = 0;
public cy = 0;
public size = 0;
public chartData: any[] = [];
public perimeter: number;
public cx = 0;
public cy = 0;
public size = 0;
constructor() {}
constructor() {
}
ngOnChanges(changes: SimpleChanges): void {
this.calculateChartData();
this.cx = this.radius + this.strokeWidth / 2;
this.cy = this.radius + this.strokeWidth / 2;
this.size = this.strokeWidth + this.radius * 2;
}
ngOnChanges(changes: SimpleChanges): void {
this.calculateChartData();
this.cx = this.radius + (this.strokeWidth / 2);
this.cy = this.radius + (this.strokeWidth / 2);
this.size = this.strokeWidth+(this.radius * 2) ;
}
get circumference() {
return 2 * Math.PI * this.radius;
}
get circumference() {
return 2 * Math.PI * this.radius;
};
get dataTotal() {
return this.config.map((v) => v.value).reduce((acc, val) => acc + val, 0);
}
get dataTotal() {
return this.config.map(v => v.value).reduce((acc, val) => acc + val, 0);
};
calculateChartData() {
const newData = [];
let angleOffset = -90;
this.config.forEach((dataVal) => {
const data = {
degrees: angleOffset
};
newData.push(data);
angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset;
});
this.chartData = newData;
}
calculateChartData() {
const newData = [];
let angleOffset = -90;
this.config.forEach((dataVal) => {
const data = {
degrees: angleOffset,
}
newData.push(data)
angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset;
})
this.chartData = newData;
}
calculateStrokeDashOffset(dataVal) {
const strokeDiff = this.dataPercentage(dataVal) * this.circumference;
return this.circumference - strokeDiff;
}
calculateStrokeDashOffset(dataVal) {
const strokeDiff = this.dataPercentage(dataVal) * this.circumference
return this.circumference - strokeDiff;
}
dataPercentage(dataVal) {
return dataVal / this.dataTotal;
}
returnCircleTransformValue(index) {
return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`;
}
// Eliminate items with value = 0
public get parsedConfig() {
return this.config.filter((el) => el.value);
}
dataPercentage(dataVal) {
return dataVal / this.dataTotal;
}
returnCircleTransformValue(index) {
return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`;
}
// Eliminate items with value = 0
public get parsedConfig() {
return this.config.filter((el) => el.value);
}
}

View File

@ -2,28 +2,26 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { Color } from '../../utils/types';
@Component({
selector: 'redaction-status-bar',
templateUrl: './status-bar.component.html',
styleUrls: ['./status-bar.component.scss'],
encapsulation: ViewEncapsulation.None,
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: Color,
label?: string,
}[] = [];
@Input()
public config: {
length: number;
color: Color;
label?: string;
}[] = [];
@Input()
public small = false;
@Input()
public small = false;
@Input()
public labelClass = '';
@Input()
public labelClass = '';
constructor() {
}
constructor() {}
ngOnInit(): void {
}
ngOnInit(): void {}
}

View File

@ -1,46 +1,44 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {Project} from "@redaction/red-ui-http";
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {AppStateService} from "../../state/app-state.service";
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Project } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '../../state/app-state.service';
@Component({
selector: 'redaction-add-edit-project-dialog',
templateUrl: './add-edit-project-dialog.component.html',
styleUrls: ['./add-edit-project-dialog.component.scss']
selector: 'redaction-add-edit-project-dialog',
templateUrl: './add-edit-project-dialog.component.html',
styleUrls: ['./add-edit-project-dialog.component.scss']
})
export class AddEditProjectDialogComponent implements OnInit {
projectForm: FormGroup;
projectForm: FormGroup;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<AddEditProjectDialogComponent>,
@Inject(MAT_DIALOG_DATA) public project: Project
) {}
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<AddEditProjectDialogComponent>,
@Inject(MAT_DIALOG_DATA) public project: Project) {
}
ngOnInit(): void {
this.projectForm = this._formBuilder.group({
projectName: [this.project?.projectName, Validators.required],
description: [this.project?.description],
dueDate: [this.project?.dueDate]
});
}
async saveProject() {
const project: Project = this._formToObject();
project.projectId = this.project?.projectId;
await this._appStateService.addOrUpdateProject(project);
this.dialogRef.close(true);
}
private _formToObject(): Project {
return {
projectName: this.projectForm.get('projectName').value,
description: this.projectForm.get('description').value,
dueDate: this.projectForm.get('dueDate').value
ngOnInit(): void {
this.projectForm = this._formBuilder.group({
projectName: [this.project?.projectName, Validators.required],
description: [this.project?.description],
dueDate: [this.project?.dueDate]
});
}
async saveProject() {
const project: Project = this._formToObject();
project.projectId = this.project?.projectId;
await this._appStateService.addOrUpdateProject(project);
this.dialogRef.close(true);
}
private _formToObject(): Project {
return {
projectName: this.projectForm.get('projectName').value,
description: this.projectForm.get('description').value,
dueDate: this.projectForm.get('dueDate').value
};
}
}
}

View File

@ -1,41 +1,48 @@
<section class="dialog">
<div [translate]="'assign-' + data.type + '-owner.dialog.title.label'"
class="dialog-header heading-l">
</div>
<div
[translate]="'assign-' + data.type + '-owner.dialog.title.label'"
class="dialog-header heading-l"
></div>
<form (submit)="saveUsers()" [formGroup]="usersForm">
<div class="dialog-content">
<form (submit)="saveUsers()" [formGroup]="usersForm">
<div class="dialog-content">
<div class="red-input-group">
<mat-form-field>
<mat-label>{{
'assign-' + data.type + '-owner.dialog.single-user.label' | translate
}}</mat-label>
<mat-select formControlName="singleUser">
<mat-option
*ngFor="let userId of singleUsersSelectOptions"
[value]="userId"
>
{{ userService.getNameForId(userId) }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="red-input-group">
<mat-form-field *ngIf="data.type === 'project'">
<mat-label>{{
'assign-' + data.type + '-owner.dialog.multi-user.label' | translate
}}</mat-label>
<mat-select formControlName="userList" multiple="true">
<mat-option *ngFor="let userId of multiUsersSelectOptions" [value]="userId">
{{ userService.getNameForId(userId) }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="red-input-group">
<mat-form-field>
<mat-label>{{'assign-' + data.type + '-owner.dialog.single-user.label' | translate}}</mat-label>
<mat-select formControlName="singleUser">
<mat-option *ngFor="let userId of singleUsersSelectOptions" [value]="userId">
{{userService.getNameForId(userId)}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="red-input-group">
<mat-form-field *ngIf="data.type === 'project' ">
<mat-label>{{'assign-' + data.type + '-owner.dialog.multi-user.label' | translate}}</mat-label>
<mat-select formControlName="userList" multiple="true">
<mat-option *ngFor="let userId of multiUsersSelectOptions" [value]="userId">
{{userService.getNameForId(userId)}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button type="submit" [disabled]="!usersForm.valid">
{{ 'assign-' + data.type + '-owner.dialog.save.label' | translate }}
</button>
</div>
</form>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button type="submit"
[disabled]="!usersForm.valid"> {{'assign-' + data.type + '-owner.dialog.save.label' | translate}}</button>
</div>
</form>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</section>

View File

@ -1,4 +1,3 @@
.red-input-group {
max-width: 200px;
max-width: 200px;
}

View File

@ -1,90 +1,113 @@
import {Component, Inject} from '@angular/core';
import {FileStatus, Project, ProjectControllerService, StatusControllerService} from '@redaction/red-ui-http';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {AppStateService} from '../../state/app-state.service';
import {UserService} from '../../user/user.service';
import {NotificationService, NotificationType} from '../../notification/notification.service';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { Component, Inject } from '@angular/core';
import {
FileStatus,
Project,
ProjectControllerService,
StatusControllerService
} from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AppStateService } from '../../state/app-state.service';
import { UserService } from '../../user/user.service';
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
class DialogData {
type: 'file' | 'project';
project?: Project;
file?: FileStatus;
type: 'file' | 'project';
project?: Project;
file?: FileStatus;
}
@Component({
selector: 'redaction-project-details-dialog',
templateUrl: './assign-owner-dialog.component.html',
styleUrls: ['./assign-owner-dialog.component.scss']
selector: 'redaction-project-details-dialog',
templateUrl: './assign-owner-dialog.component.html',
styleUrls: ['./assign-owner-dialog.component.scss']
})
export class AssignOwnerDialogComponent {
usersForm: FormGroup;
usersForm: FormGroup;
constructor(public readonly userService: UserService,
private readonly _projectControllerService: ProjectControllerService,
private readonly _notificationService: NotificationService,
private readonly _formBuilder: FormBuilder,
private readonly _statusControllerService: StatusControllerService,
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<AssignOwnerDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData) {
this._loadData();
}
private _loadData() {
if (this.data.type === 'project') {
const project = this.data.project;
this.usersForm = this._formBuilder.group({
singleUser: [project?.ownerId, Validators.required],
userList: [project?.memberIds]
});
constructor(
public readonly userService: UserService,
private readonly _projectControllerService: ProjectControllerService,
private readonly _notificationService: NotificationService,
private readonly _formBuilder: FormBuilder,
private readonly _statusControllerService: StatusControllerService,
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<AssignOwnerDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData
) {
this._loadData();
}
if (this.data.type === 'file') {
const file = this.data.file;
this.usersForm = this._formBuilder.group({
singleUser: [file?.currentReviewer]
});
}
}
private _loadData() {
if (this.data.type === 'project') {
const project = this.data.project;
this.usersForm = this._formBuilder.group({
singleUser: [project?.ownerId, Validators.required],
userList: [project?.memberIds]
});
}
async saveUsers() {
try {
if (this.data.type === 'project') {
const ownerId = this.usersForm.get('singleUser').value;
const memberIds = this.usersForm.get('userList').value;
const project = Object.assign({},this.data.project);
project.memberIds = memberIds;
project.ownerId = ownerId;
await this._appStateService.addOrUpdateProject(project);
this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + project.projectName);
}
if (this.data.type === 'file') {
const reviewerId = this.usersForm.get('singleUser').value;
await this._statusControllerService.assignProjectOwner1(this._appStateService.activeProjectId, this.data.file.fileId, reviewerId).toPromise();
this.data.file.currentReviewer = reviewerId;
this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(reviewerId) + ' to file: ' + this.data.file.filename);
}
} catch (error) {
this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR);
if (this.data.type === 'file') {
const file = this.data.file;
this.usersForm = this._formBuilder.group({
singleUser: [file?.currentReviewer]
});
}
}
this.dialogRef.close();
}
async saveUsers() {
try {
if (this.data.type === 'project') {
const ownerId = this.usersForm.get('singleUser').value;
const memberIds = this.usersForm.get('userList').value;
const project = Object.assign({}, this.data.project);
project.memberIds = memberIds;
project.ownerId = ownerId;
await this._appStateService.addOrUpdateProject(project);
this._notificationService.showToastNotification(
'Successfully assigned ' +
this.userService.getNameForId(ownerId) +
' to project: ' +
project.projectName
);
}
if (this.data.type === 'file') {
const reviewerId = this.usersForm.get('singleUser').value;
get singleUsersSelectOptions() {
return this.data.type === 'file' ? this._appStateService.activeProject.project.memberIds : this.userService.managerUsers.map(m => m.userId);
}
await this._statusControllerService
.assignProjectOwner1(
this._appStateService.activeProjectId,
this.data.file.fileId,
reviewerId
)
.toPromise();
this.data.file.currentReviewer = reviewerId;
this._notificationService.showToastNotification(
'Successfully assigned ' +
this.userService.getNameForId(reviewerId) +
' to file: ' +
this.data.file.filename
);
}
} catch (error) {
this._notificationService.showToastNotification(
'Failed: ' + error.error.message,
null,
NotificationType.ERROR
);
}
get multiUsersSelectOptions() {
return this.userService.managerUsers.map(m => m.userId);
}
this.dialogRef.close();
}
get singleUsersSelectOptions() {
return this.data.type === 'file'
? this._appStateService.activeProject.project.memberIds
: this.userService.managerUsers.map((m) => m.userId);
}
get multiUsersSelectOptions() {
return this.userService.managerUsers.map((m) => m.userId);
}
}

View File

@ -1,40 +1,53 @@
<section class="dialog">
<div [translate]="'file-details.dialog.title.label'" class="dialog-header heading-l"></div>
<div [translate]="'file-details.dialog.title.label'"
class="dialog-header heading-l">
</div>
<div class="dialog-content">
<div class="file-details">
<div class="detail-row">
{{fileStatus.filename}}
</div>
<div class="detail-row">
{{'project-overview.file-listing.file-entry.status.label'| translate:fileStatus}}
</div>
<div class="detail-row">
{{'project-overview.file-listing.file-entry.number-of-pages.label'| translate:fileStatus}}
</div>
<div class="detail-row">
{{'project-overview.file-listing.file-entry.number-of-analyses.label'| translate:fileStatus}}
</div>
<div class="detail-row">
{{'project-overview.file-listing.file-entry.added.label'| translate:{added: fileStatus.added | date:'short'} }}
</div>
<div *ngIf="fileStatus.lastUpdated" class="detail-row">
{{'project-overview.file-listing.file-entry.last-updated.label'| translate:{lastUpdated: fileStatus.lastUpdated | date:'short'} }}
</div>
<div class="dialog-content">
<div class="file-details">
<div class="detail-row">
{{ fileStatus.filename }}
</div>
<div class="detail-row">
{{
'project-overview.file-listing.file-entry.status.label' | translate: fileStatus
}}
</div>
<div class="detail-row">
{{
'project-overview.file-listing.file-entry.number-of-pages.label'
| translate: fileStatus
}}
</div>
<div class="detail-row">
{{
'project-overview.file-listing.file-entry.number-of-analyses.label'
| translate: fileStatus
}}
</div>
<div class="detail-row">
{{
'project-overview.file-listing.file-entry.added.label'
| translate: { added: fileStatus.added | date: 'short' }
}}
</div>
<div *ngIf="fileStatus.lastUpdated" class="detail-row">
{{
'project-overview.file-listing.file-entry.last-updated.label'
| translate: { lastUpdated: fileStatus.lastUpdated | date: 'short' }
}}
</div>
</div>
</div>
</div>
<div class="dialog-actions">
<button (click)="downloadRedactionReport()" color="primary" mat-flat-button
translate="file-details.dialog.actions.download-redaction-report.label"></button>
</div>
<div class="dialog-actions">
<button
(click)="downloadRedactionReport()"
color="primary"
mat-flat-button
translate="file-details.dialog.actions.download-redaction-report.label"
></button>
</div>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</section>

View File

@ -1,2 +1 @@
@import "../../../assets/styles/red-variables";
@import '../../../assets/styles/red-variables';

View File

@ -1,29 +1,27 @@
import {Component, Inject, OnInit} from '@angular/core';
import {FileStatus, FileUploadControllerService} from "@redaction/red-ui-http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {download} from "../../utils/file-download-utils";
import { Component, Inject, OnInit } from '@angular/core';
import { FileStatus, FileUploadControllerService } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { download } from '../../utils/file-download-utils';
@Component({
selector: 'redaction-file-details-dialog',
templateUrl: './file-details-dialog.component.html',
styleUrls: ['./file-details-dialog.component.scss']
selector: 'redaction-file-details-dialog',
templateUrl: './file-details-dialog.component.html',
styleUrls: ['./file-details-dialog.component.scss']
})
export class FileDetailsDialogComponent implements OnInit {
constructor(
private readonly _fileUploadControllerService: FileUploadControllerService,
public dialogRef: MatDialogRef<FileDetailsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public fileStatus: FileStatus
) {}
ngOnInit(): void {}
constructor(
private readonly _fileUploadControllerService: FileUploadControllerService,
public dialogRef: MatDialogRef<FileDetailsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public fileStatus: FileStatus) {
}
ngOnInit(): void {
}
downloadRedactionReport() {
this._fileUploadControllerService.downloadRedactionReport({fileIds: [this.fileStatus.fileId]}, true, 'response').subscribe(data => {
download(data, 'redaction-report-' + this.fileStatus.filename + ".docx");
});
}
downloadRedactionReport() {
this._fileUploadControllerService
.downloadRedactionReport({ fileIds: [this.fileStatus.fileId] }, true, 'response')
.subscribe((data) => {
download(data, 'redaction-report-' + this.fileStatus.filename + '.docx');
});
}
}

View File

@ -1,94 +1,139 @@
import {Component, Inject, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {AppStateService} from "../../state/app-state.service";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '../../state/app-state.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
AddRedactionRequest,
DictionaryControllerService,
ManualRedactionControllerService,
TypeValue
} from "@redaction/red-ui-http";
import {NotificationService, NotificationType} from "../../notification/notification.service";
import {TranslateService} from "@ngx-translate/core";
import {map} from "rxjs/operators";
import {Observable} from "rxjs";
import {UserService} from "../../user/user.service";
AddRedactionRequest,
DictionaryControllerService,
ManualRedactionControllerService,
TypeValue
} from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { UserService } from '../../user/user.service';
@Component({
selector: 'redaction-manual-redaction-dialog',
templateUrl: './manual-redaction-dialog.component.html',
styleUrls: ['./manual-redaction-dialog.component.scss']
selector: 'redaction-manual-redaction-dialog',
templateUrl: './manual-redaction-dialog.component.html',
styleUrls: ['./manual-redaction-dialog.component.scss']
})
export class ManualRedactionDialogComponent implements OnInit {
redactionForm: FormGroup;
dictionaries: Observable<Array<TypeValue>>;
isDocumentAdmin: boolean;
redactionForm: FormGroup;
dictionaries: Observable<Array<TypeValue>>;
isDocumentAdmin: boolean;
constructor(
private readonly _appStateService: AppStateService,
private readonly _userService: UserService,
private readonly _formBuilder: FormBuilder,
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService,
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _dictionaryControllerService: DictionaryControllerService,
public dialogRef: MatDialogRef<ManualRedactionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public addRedactionRequest: AddRedactionRequest
) {}
constructor(
private readonly _appStateService: AppStateService,
private readonly _userService: UserService,
private readonly _formBuilder: FormBuilder,
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService,
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _dictionaryControllerService: DictionaryControllerService,
public dialogRef: MatDialogRef<ManualRedactionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public addRedactionRequest: AddRedactionRequest) {
}
async ngOnInit() {
this.isDocumentAdmin = (this._appStateService.isActiveProjectOwner || this._appStateService.isActiveFileDocumentReviewer) && this._userService.user.isManager;
const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required];
this.redactionForm = this._formBuilder.group({
addToDictionary: [false],
reason: [null, Validators.required],
dictionary: [null, Validators.required],
comment: commentField,
});
this.dictionaries = this._dictionaryControllerService.getAllTypes().pipe(map(r => r.types));
}
handleAddRedaction() {
if (this.isDocumentAdmin) {
this.addManualRedaction();
} else {
this.suggestManualRedaction()
async ngOnInit() {
this.isDocumentAdmin =
(this._appStateService.isActiveProjectOwner ||
this._appStateService.isActiveFileDocumentReviewer) &&
this._userService.user.isManager;
const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required];
this.redactionForm = this._formBuilder.group({
addToDictionary: [false],
reason: [null, Validators.required],
dictionary: [null, Validators.required],
comment: commentField
});
this.dictionaries = this._dictionaryControllerService
.getAllTypes()
.pipe(map((r) => r.types));
}
}
suggestManualRedaction() {
const mre = Object.assign({}, this.addRedactionRequest);
this._enhanceManualRedaction(mre);
this._manualRedactionControllerService.requestAddRedaction(mre, this._appStateService.activeProject.project.projectId, this._appStateService.activeFile.fileId).subscribe(ok => {
this._appStateService.reanalyseActiveFile();
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.dialog.add-redaction.success.label'), null, NotificationType.SUCCESS);
this.dialogRef.close();
}, (err) => {
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.dialog.add-redaction.failed.label', err), null, NotificationType.ERROR);
});
}
handleAddRedaction() {
if (this.isDocumentAdmin) {
this.addManualRedaction();
} else {
this.suggestManualRedaction();
}
}
addManualRedaction() {
const mre = Object.assign({}, this.addRedactionRequest);
this._enhanceManualRedaction(mre);
this._manualRedactionControllerService.addRedaction(mre, this._appStateService.activeProject.project.projectId, this._appStateService.activeFile.fileId).subscribe(ok => {
this._appStateService.reanalyseActiveFile();
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.dialog.add-redaction.success.label'), null, NotificationType.SUCCESS);
this.dialogRef.close();
}, (err) => {
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.dialog.add-redaction.failed.label', err), null, NotificationType.ERROR);
});
}
suggestManualRedaction() {
const mre = Object.assign({}, this.addRedactionRequest);
this._enhanceManualRedaction(mre);
this._manualRedactionControllerService
.requestAddRedaction(
mre,
this._appStateService.activeProject.project.projectId,
this._appStateService.activeFile.fileId
)
.subscribe(
(ok) => {
this._appStateService.reanalyseActiveFile();
this._notificationService.showToastNotification(
this._translateService.instant(
'manual-redaction.dialog.add-redaction.success.label'
),
null,
NotificationType.SUCCESS
);
this.dialogRef.close();
},
(err) => {
this._notificationService.showToastNotification(
this._translateService.instant(
'manual-redaction.dialog.add-redaction.failed.label',
err
),
null,
NotificationType.ERROR
);
}
);
}
private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) {
addRedactionRequest.type = this.redactionForm.get('dictionary').value;
addRedactionRequest.addToDictionary = this.redactionForm.get('addToDictionary').value;
addRedactionRequest.reason = this.redactionForm.get('reason').value;
const commentValue = this.redactionForm.get('comment').value;
addRedactionRequest.comment = commentValue ? {text: commentValue} : null;
}
addManualRedaction() {
const mre = Object.assign({}, this.addRedactionRequest);
this._enhanceManualRedaction(mre);
this._manualRedactionControllerService
.addRedaction(
mre,
this._appStateService.activeProject.project.projectId,
this._appStateService.activeFile.fileId
)
.subscribe(
(ok) => {
this._appStateService.reanalyseActiveFile();
this._notificationService.showToastNotification(
this._translateService.instant(
'manual-redaction.dialog.add-redaction.success.label'
),
null,
NotificationType.SUCCESS
);
this.dialogRef.close();
},
(err) => {
this._notificationService.showToastNotification(
this._translateService.instant(
'manual-redaction.dialog.add-redaction.failed.label',
err
),
null,
NotificationType.ERROR
);
}
);
}
private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) {
addRedactionRequest.type = this.redactionForm.get('dictionary').value;
addRedactionRequest.addToDictionary = this.redactionForm.get('addToDictionary').value;
addRedactionRequest.reason = this.redactionForm.get('reason').value;
const commentValue = this.redactionForm.get('comment').value;
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
}
}

View File

@ -1,36 +1,43 @@
<section class="dialog">
<div [translate]="'project-details.dialog.title.label'" class="dialog-header heading-l"></div>
<div [translate]="'project-details.dialog.title.label'"
class="dialog-header heading-l">
</div>
<div class="dialog-content">
<div class="file-details">
<div class="detail-row">
{{projectDetails.project.projectName}}
</div>
<div class="detail-row">
{{projectDetails.project.description}}
</div>
<div class="detail-row">
{{projectDetails.project.date | date:'short'}}
</div>
<div class="detail-row">
{{'project-details.dialog.info.file-count.label'| translate:{fileCount: projectDetails.files ? projectDetails.files.length : 0} }}
</div>
<div class="dialog-content">
<div class="file-details">
<div class="detail-row">
{{ projectDetails.project.projectName }}
</div>
<div class="detail-row">
{{ projectDetails.project.description }}
</div>
<div class="detail-row">
{{ projectDetails.project.date | date: 'short' }}
</div>
<div class="detail-row">
{{
'project-details.dialog.info.file-count.label'
| translate
: { fileCount: projectDetails.files ? projectDetails.files.length : 0 }
}}
</div>
</div>
</div>
</div>
<div class="dialog-actions">
<button (click)="downloadRedactionReport()" color="primary" mat-flat-button
translate="project-details.dialog.actions.download-redaction-report.label"></button>
<button (click)="reanalyseProject()" color="primary" mat-flat-button
translate="project-details.dialog.actions.reanalyse-project.label"></button>
</div>
<div class="dialog-actions">
<button
(click)="downloadRedactionReport()"
color="primary"
mat-flat-button
translate="project-details.dialog.actions.download-redaction-report.label"
></button>
<button
(click)="reanalyseProject()"
color="primary"
mat-flat-button
translate="project-details.dialog.actions.reanalyse-project.label"
></button>
</div>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</section>

View File

@ -1,35 +1,44 @@
import {Component, Inject, OnInit} from '@angular/core';
import {FileUploadControllerService, ReanalysisControllerService} from "@redaction/red-ui-http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {download} from "../../utils/file-download-utils";
import {ProjectWrapper} from "../../state/app-state.service";
import { Component, Inject, OnInit } from '@angular/core';
import { FileUploadControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { download } from '../../utils/file-download-utils';
import { ProjectWrapper } from '../../state/app-state.service';
@Component({
selector: 'redaction-project-details-dialog',
templateUrl: './project-details-dialog.component.html',
styleUrls: ['./project-details-dialog.component.scss']
selector: 'redaction-project-details-dialog',
templateUrl: './project-details-dialog.component.html',
styleUrls: ['./project-details-dialog.component.scss']
})
export class ProjectDetailsDialogComponent implements OnInit {
constructor(
private readonly _fileUploadControllerService: FileUploadControllerService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
public dialogRef: MatDialogRef<ProjectDetailsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public projectDetails: ProjectWrapper
) {}
constructor(
private readonly _fileUploadControllerService: FileUploadControllerService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
public dialogRef: MatDialogRef<ProjectDetailsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public projectDetails: ProjectWrapper) {
}
ngOnInit(): void {}
ngOnInit(): void {
}
downloadRedactionReport() {
this._fileUploadControllerService
.downloadRedactionReportForProject(
this.projectDetails.project.projectId,
true,
'response'
)
.subscribe((data) => {
download(
data,
'redaction-report-' + this.projectDetails.project.projectName + '.docx'
);
});
}
downloadRedactionReport() {
this._fileUploadControllerService.downloadRedactionReportForProject(this.projectDetails.project.projectId, true, 'response').subscribe(data => {
download(data, 'redaction-report-' + this.projectDetails.project.projectName + ".docx");
});
}
reanalyseProject() {
this._reanalysisControllerService.reanalyzeProject(this.projectDetails.project.projectId).subscribe(() => {
this.dialogRef.close();
});
}
reanalyseProject() {
this._reanalysisControllerService
.reanalyzeProject(this.projectDetails.project.projectId)
.subscribe(() => {
this.dialogRef.close();
});
}
}

View File

@ -1,5 +1,5 @@
import {LanguageService} from "./language.service";
import { LanguageService } from './language.service';
export function languageInitializer(languageService: LanguageService) {
return () => languageService.chooseAndSetInitialLanguage();
return () => languageService.chooseAndSetInitialLanguage();
}

View File

@ -1,43 +1,39 @@
import {Injectable} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: "root"
providedIn: 'root'
})
export class LanguageService {
constructor(
private translateService: TranslateService
) {
translateService.addLangs(['en', 'de']);
}
get currentLanguage() {
return this.translateService.currentLang;
}
chooseAndSetInitialLanguage() {
let defaultLang: string;
const localStorageLang = localStorage.getItem('redaction.language');
const browserLang = this.translateService.getBrowserLang();
// @ts-ignore
if (this.translateService.getLangs().includes(localStorageLang)) {
defaultLang = localStorageLang;
// @ts-ignore
} else if (this.translateService.getLangs().includes(browserLang)) {
defaultLang = browserLang;
} else {
defaultLang = 'en';
constructor(private translateService: TranslateService) {
translateService.addLangs(['en', 'de']);
}
document.documentElement.lang = defaultLang;
this.translateService.setDefaultLang(defaultLang);
this.translateService.use(defaultLang).subscribe(() => {
});
}
changeLanguage(language: string) {
localStorage.setItem('redaction.language', language);
document.documentElement.lang = language;
this.translateService.use(language).subscribe(() => {
});
}
get currentLanguage() {
return this.translateService.currentLang;
}
chooseAndSetInitialLanguage() {
let defaultLang: string;
const localStorageLang = localStorage.getItem('redaction.language');
const browserLang = this.translateService.getBrowserLang();
// @ts-ignore
if (this.translateService.getLangs().includes(localStorageLang)) {
defaultLang = localStorageLang;
// @ts-ignore
} else if (this.translateService.getLangs().includes(browserLang)) {
defaultLang = browserLang;
} else {
defaultLang = 'en';
}
document.documentElement.lang = defaultLang;
this.translateService.setDefaultLang(defaultLang);
this.translateService.use(defaultLang).subscribe(() => {});
}
changeLanguage(language: string) {
localStorage.setItem('redaction.language', language);
document.documentElement.lang = language;
this.translateService.use(language).subscribe(() => {});
}
}

View File

@ -1,15 +1,13 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {AppConfigService} from "../app-config/app-config.service";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AppConfigService } from '../app-config/app-config.service';
@Injectable()
export class ApiPathInterceptorService implements HttpInterceptor {
constructor(private readonly _appConfigService: AppConfigService) {}
constructor(private readonly _appConfigService: AppConfigService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req);
}
}

View File

@ -1,4 +1,4 @@
<div class="redacto-logo">
<div class="line-1"></div>
<div class="line-2"></div>
<div class="line-1"></div>
<div class="line-2"></div>
</div>

View File

@ -1,15 +1,12 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'redaction-logo',
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.scss']
selector: 'redaction-logo',
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.scss']
})
export class LogoComponent implements OnInit {
constructor() {}
constructor() { }
ngOnInit(): void {
}
ngOnInit(): void {}
}

View File

@ -1,36 +1,38 @@
import {Injectable} from "@angular/core";
import {MatSnackBar} from "@angular/material/snack-bar";
import {ToastrService} from "ngx-toastr";
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ToastrService } from 'ngx-toastr';
export enum NotificationType {
SUCCESS = 'SUCCESS', WARNING = 'WARNING', ERROR = 'ERROR', INFO = 'INFO',
SUCCESS = 'SUCCESS',
WARNING = 'WARNING',
ERROR = 'ERROR',
INFO = 'INFO'
}
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class NotificationService {
constructor(private readonly _snackBar: MatSnackBar, private readonly _toastr: ToastrService) {}
constructor(private readonly _snackBar: MatSnackBar,
private readonly _toastr: ToastrService) {
}
showToastNotification(message: string, title?: string, notificationType: NotificationType = NotificationType.INFO) {
switch (notificationType) {
case NotificationType.ERROR:
this._toastr.error(message, title);
break;
case NotificationType.SUCCESS:
this._toastr.success(message, title);
break;
case NotificationType.WARNING:
this._toastr.warning(message, title);
break
case NotificationType.INFO:
this._toastr.info(message, title);
break
showToastNotification(
message: string,
title?: string,
notificationType: NotificationType = NotificationType.INFO
) {
switch (notificationType) {
case NotificationType.ERROR:
this._toastr.error(message, title);
break;
case NotificationType.SUCCESS:
this._toastr.success(message, title);
break;
case NotificationType.WARNING:
this._toastr.warning(message, title);
break;
case NotificationType.INFO:
this._toastr.info(message, title);
break;
}
}
}
}

View File

@ -1,12 +1,30 @@
<section>
<p class="heading-xl" [translate]="'auth-error.heading.label'"
*ngIf="!configuredAdminName && !configuredAdminUrl"></p>
<p class="heading-xl"
[innerHTML]="'auth-error.heading-with-name-and-link.label' | translate:{adminName: configuredAdminName, adminUrl: configuredAdminUrl}"
*ngIf="configuredAdminName && configuredAdminUrl"></p>
<p class="heading-xl" [innerHTML]="'auth-error.heading-with-name.label' | translate:{adminName: configuredAdminName }"
*ngIf="configuredAdminName && !configuredAdminUrl"></p>
<p class="heading-xl" [innerHTML]="'auth-error.heading-with-link.label' | translate:{adminName: configuredAdminName }"
*ngIf="!configuredAdminName && configuredAdminUrl"></p>
<a (click)="logout()" [translate]="'auth-error.logout.label'"></a>
<p
class="heading-xl"
[translate]="'auth-error.heading.label'"
*ngIf="!configuredAdminName && !configuredAdminUrl"
></p>
<p
class="heading-xl"
[innerHTML]="
'auth-error.heading-with-name-and-link.label'
| translate: { adminName: configuredAdminName, adminUrl: configuredAdminUrl }
"
*ngIf="configuredAdminName && configuredAdminUrl"
></p>
<p
class="heading-xl"
[innerHTML]="
'auth-error.heading-with-name.label' | translate: { adminName: configuredAdminName }
"
*ngIf="configuredAdminName && !configuredAdminUrl"
></p>
<p
class="heading-xl"
[innerHTML]="
'auth-error.heading-with-link.label' | translate: { adminName: configuredAdminName }
"
*ngIf="!configuredAdminName && configuredAdminUrl"
></p>
<a (click)="logout()" [translate]="'auth-error.logout.label'"></a>
</section>

View File

@ -1,3 +1,3 @@
section {
padding: 24px;
padding: 24px;
}

View File

@ -1,28 +1,29 @@
import {Component, OnInit} from '@angular/core';
import {UserService} from "../../user/user.service";
import {AppConfigKey, AppConfigService} from "../../app-config/app-config.service";
import { Component, OnInit } from '@angular/core';
import { UserService } from '../../user/user.service';
import { AppConfigKey, AppConfigService } from '../../app-config/app-config.service';
@Component({
selector: 'redaction-auth-error',
templateUrl: './auth-error.component.html',
styleUrls: ['./auth-error.component.scss']
selector: 'redaction-auth-error',
templateUrl: './auth-error.component.html',
styleUrls: ['./auth-error.component.scss']
})
export class AuthErrorComponent implements OnInit {
configuredAdminName: string;
configuredAdminUrl: string;
configuredAdminName: string;
configuredAdminUrl: string;
constructor(
private readonly _userService: UserService,
private readonly _appConfigService: AppConfigService
) {}
constructor(private readonly _userService: UserService, private readonly _appConfigService: AppConfigService) {
}
ngOnInit(): void {
this.configuredAdminName = this._appConfigService.getConfig(
AppConfigKey.ADMIN_CONTACT_NAME
);
this.configuredAdminUrl = this._appConfigService.getConfig(AppConfigKey.ADMIN_CONTACT_URL);
}
ngOnInit(): void {
this.configuredAdminName = this._appConfigService.getConfig(AppConfigKey.ADMIN_CONTACT_NAME);
this.configuredAdminUrl = this._appConfigService.getConfig(AppConfigKey.ADMIN_CONTACT_URL);
}
logout() {
this._userService.logout();
}
logout() {
this._userService.logout();
}
}

View File

@ -1,31 +1,29 @@
import {Component} from '@angular/core';
import {UserService} from "../../user/user.service";
import {AppStateService} from "../../state/app-state.service";
import {LanguageService} from "../../i18n/language.service";
import { Component } from '@angular/core';
import { UserService } from '../../user/user.service';
import { AppStateService } from '../../state/app-state.service';
import { LanguageService } from '../../i18n/language.service';
@Component({
selector: 'redaction-base-screen',
templateUrl: './base-screen.component.html',
styleUrls: ['./base-screen.component.scss']
selector: 'redaction-base-screen',
templateUrl: './base-screen.component.html',
styleUrls: ['./base-screen.component.scss']
})
export class BaseScreenComponent {
constructor(
public readonly appStateService: AppStateService,
private readonly _languageService: LanguageService,
private readonly _userService: UserService
) {}
constructor(
public readonly appStateService: AppStateService,
private readonly _languageService: LanguageService,
private readonly _userService: UserService) {
get user() {
return this._userService.user;
}
}
logout() {
this._userService.logout();
}
get user() {
return this._userService.user;
}
logout() {
this._userService.logout();
}
changeLanguage(language: string) {
this._languageService.changeLanguage(language);
}
changeLanguage(language: string) {
this._languageService.changeLanguage(language);
}
}

View File

@ -1,5 +1,5 @@
export enum FileType {
ORIGINAL = 'ORIGINAL',
ANNOTATED = 'ANNOTATED',
REDACTED = 'REDACTED'
ORIGINAL = 'ORIGINAL',
ANNOTATED = 'ANNOTATED',
REDACTED = 'REDACTED'
}

View File

@ -1,3 +1,3 @@
<div class="page">
<div #viewer [id]="fileStatus.fileId" class="viewer"></div>
<div #viewer [id]="fileStatus.fileId" class="viewer"></div>
</div>

View File

@ -1,10 +1,10 @@
.page {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.viewer {
height: 100%;
height: 100%;
}

View File

@ -1,38 +1,58 @@
import {Injectable} from '@angular/core';
import {Observable, of} from "rxjs";
import {tap} from "rxjs/operators";
import {FileUploadControllerService} from '@redaction/red-ui-http';
import {FileType} from "../model/file-type";
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { FileUploadControllerService } from '@redaction/red-ui-http';
import { FileType } from '../model/file-type';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class FileDownloadService {
constructor(private readonly _fileUploadControllerService: FileUploadControllerService) {
}
constructor(private readonly _fileUploadControllerService: FileUploadControllerService) {}
loadFile(fileType: FileType | string, fileId: string, saveTo: (fileData) => void = () => null, fetch: () => any = () => null): Observable<any> {
let fileObs$: Observable<any>;
switch (fileType) {
case FileType.ANNOTATED:
fileObs$ = fetch() ? of(fetch()) : this._fileUploadControllerService.downloadAnnotatedFile(fileId, true, 'body').pipe(tap(data => {
saveTo(data);
}));
break;
case FileType.REDACTED:
fileObs$ = fetch() ? of(fetch()) : this._fileUploadControllerService.downloadRedactedFile(fileId, true, 'body').pipe(tap(data => {
saveTo(data);
}));
break;
case FileType.ORIGINAL:
default:
fileObs$ = fetch() ? of(fetch()) : this._fileUploadControllerService.downloadOriginalFile(fileId, true, 'body')
.pipe(tap(data => {
saveTo(data);
}));
break;
loadFile(
fileType: FileType | string,
fileId: string,
saveTo: (fileData) => void = () => null,
fetch: () => any = () => null
): Observable<any> {
let fileObs$: Observable<any>;
switch (fileType) {
case FileType.ANNOTATED:
fileObs$ = fetch()
? of(fetch())
: this._fileUploadControllerService
.downloadAnnotatedFile(fileId, true, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
case FileType.REDACTED:
fileObs$ = fetch()
? of(fetch())
: this._fileUploadControllerService
.downloadRedactedFile(fileId, true, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
case FileType.ORIGINAL:
default:
fileObs$ = fetch()
? of(fetch())
: this._fileUploadControllerService
.downloadOriginalFile(fileId, true, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
}
return fileObs$;
}
return fileObs$;
}
}

View File

@ -2,25 +2,24 @@ import { Injectable } from '@angular/core';
import { AnnotationFilters } from '../../../utils/types';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class FiltersService {
constructor() {
}
constructor() {}
private _filters: AnnotationFilters = {
hint: {
hint_only: false,
vertebrate: false,
names: false,
},
redaction: false,
comment: false,
suggestion: false,
ignore: false,
}
private _filters: AnnotationFilters = {
hint: {
hint_only: false,
vertebrate: false,
names: false
},
redaction: false,
comment: false,
suggestion: false,
ignore: false
};
public get filters(): AnnotationFilters {
return JSON.parse(JSON.stringify(this._filters));
}
public get filters(): AnnotationFilters {
return JSON.parse(JSON.stringify(this._filters));
}
}

View File

@ -1,23 +1,21 @@
import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from "@angular/router";
import {AppStateService} from "./app-state.service";
import {UserService} from "../user/user.service";
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { AppStateService } from './app-state.service';
import { UserService } from '../user/user.service';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class AppStateGuard implements CanActivate {
constructor(
private readonly _appStateService: AppStateService,
private readonly _userService: UserService
) {}
constructor(private readonly _appStateService: AppStateService, private readonly _userService: UserService) {
}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
await this._userService.loadAllUsersIfNecessary()
await this._appStateService.loadAllProjectsIfNecessary();
return true;
}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
await this._userService.loadAllUsersIfNecessary();
await this._appStateService.loadAllProjectsIfNecessary();
return true;
}
}

View File

@ -1,11 +1,12 @@
<section>
<ngx-dropzone (change)="handleFileInput($event.addedFiles)"
class="file-drop-zone">
<ngx-dropzone-label>{{"project-overview.upload-files.label"| translate}}</ngx-dropzone-label>
</ngx-dropzone>
<input type="file" multiple hidden/>
<ngx-dropzone (change)="handleFileInput($event.addedFiles)" class="file-drop-zone">
<ngx-dropzone-label>{{
'project-overview.upload-files.label' | translate
}}</ngx-dropzone-label>
</ngx-dropzone>
<input type="file" multiple hidden />
<button (click)="close()" class="close-icon" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<button (click)="close()" class="close-icon" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</section>

View File

@ -1,26 +1,25 @@
section {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1000;
padding: 12px;
background: white;
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1000;
padding: 12px;
background: white;
}
.file-drop-zone {
background: white;
height: calc(100% - 26px);
width: calc(100% - 26px);
background: white;
height: calc(100% - 26px);
width: calc(100% - 26px);
}
.close-icon {
position: absolute;
z-index: 1100;
top: 20px;
right: 40px;
position: absolute;
z-index: 1100;
top: 20px;
right: 40px;
}

View File

@ -1,46 +1,46 @@
import {Component, HostListener, OnInit} from '@angular/core';
import {FileUploadService} from "../file-upload.service";
import {FileUploadModel} from "../model/file-upload.model";
import {OverlayRef} from "@angular/cdk/overlay";
import {UploadStatusOverlayService} from "../upload-status-dialog/service/upload-status-overlay.service";
import { Component, HostListener, OnInit } from '@angular/core';
import { FileUploadService } from '../file-upload.service';
import { FileUploadModel } from '../model/file-upload.model';
import { OverlayRef } from '@angular/cdk/overlay';
import { UploadStatusOverlayService } from '../upload-status-dialog/service/upload-status-overlay.service';
@Component({
selector: 'redaction-file-drop',
templateUrl: './file-drop.component.html',
styleUrls: ['./file-drop.component.scss']
selector: 'redaction-file-drop',
templateUrl: './file-drop.component.html',
styleUrls: ['./file-drop.component.scss']
})
export class FileDropComponent implements OnInit {
constructor(
private readonly _dialogRef: OverlayRef,
private readonly _fileUploadService: FileUploadService,
private _uploadStatusOverlayService: UploadStatusOverlayService
) {}
constructor(private readonly _dialogRef: OverlayRef, private readonly _fileUploadService: FileUploadService, private _uploadStatusOverlayService: UploadStatusOverlayService) {
}
ngOnInit() {}
ngOnInit() {
}
close() {
this._dialogRef.detach();
}
@HostListener('document:keydown.escape', ['$event'])
onKeydownHandler(evt: KeyboardEvent) {
this.close();
}
handleFileInput(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
})
close() {
this._dialogRef.detach();
}
this._fileUploadService.uploadFiles(uploadFiles);
this._uploadStatusOverlayService.openStatusOverlay();
this._dialogRef.detach();
}
@HostListener('document:keydown.escape', ['$event'])
onKeydownHandler(evt: KeyboardEvent) {
this.close();
}
handleFileInput(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();
this._dialogRef.detach();
}
}

View File

@ -1,69 +1,64 @@
import {Injectable, Injector} from "@angular/core";
import {Overlay} from "@angular/cdk/overlay";
import {FileDropComponent} from "../file-drop.component";
import {ComponentPortal} from "@angular/cdk/portal";
import {OverlayRef} from "@angular/cdk/overlay";
import { Injectable, Injector } from '@angular/core';
import { Overlay } from '@angular/cdk/overlay';
import { FileDropComponent } from '../file-drop.component';
import { ComponentPortal } from '@angular/cdk/portal';
import { OverlayRef } from '@angular/cdk/overlay';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class FileDropOverlayService {
private readonly _dropOverlayRef: OverlayRef;
private readonly _dropOverlayRef: OverlayRef;
constructor(private overlay: Overlay, private readonly _injector: Injector) {
this._dropOverlayRef = this.overlay.create({
height: '100vh',
width: '100vw',
});
}
dragListener = () => {
this.openFileDropOverlay();
};
mouseOut = e => {
if (e.toElement == null && e.relatedTarget == null) {
this.closeFileDropOverlay();
constructor(private overlay: Overlay, private readonly _injector: Injector) {
this._dropOverlayRef = this.overlay.create({
height: '100vh',
width: '100vw'
});
}
};
initFileDropHandling() {
document
.getElementsByTagName('body')[0]
.addEventListener('dragenter', this.dragListener, false);
document.getElementsByTagName('body')[0].addEventListener('mouseout', this.mouseOut, false);
}
dragListener = () => {
this.openFileDropOverlay();
};
mouseOut = (e) => {
if (e.toElement == null && e.relatedTarget == null) {
this.closeFileDropOverlay();
}
};
cleanupFileDropHandling() {
document
.getElementsByTagName('body')[0]
.removeEventListener('dragenter', this.dragListener, false);
document
.getElementsByTagName('body')[0]
.removeEventListener('mouseout', this.mouseOut, false);
}
private _createInjector() {
return Injector.create({
providers: [{provide: OverlayRef, useValue: this._dropOverlayRef}],
parent: this._injector,
})
}
openFileDropOverlay() {
const component = new ComponentPortal(FileDropComponent, null, this._createInjector());
if (!this._dropOverlayRef.hasAttached()) {
this._dropOverlayRef.attach(component);
initFileDropHandling() {
document
.getElementsByTagName('body')[0]
.addEventListener('dragenter', this.dragListener, false);
document.getElementsByTagName('body')[0].addEventListener('mouseout', this.mouseOut, false);
}
}
closeFileDropOverlay() {
if (this._dropOverlayRef) {
this._dropOverlayRef.detach();
cleanupFileDropHandling() {
document
.getElementsByTagName('body')[0]
.removeEventListener('dragenter', this.dragListener, false);
document
.getElementsByTagName('body')[0]
.removeEventListener('mouseout', this.mouseOut, false);
}
}
private _createInjector() {
return Injector.create({
providers: [{ provide: OverlayRef, useValue: this._dropOverlayRef }],
parent: this._injector
});
}
openFileDropOverlay() {
const component = new ComponentPortal(FileDropComponent, null, this._createInjector());
if (!this._dropOverlayRef.hasAttached()) {
this._dropOverlayRef.attach(component);
}
}
closeFileDropOverlay() {
if (this._dropOverlayRef) {
this._dropOverlayRef.detach();
}
}
}

View File

@ -1,32 +1,31 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {FileDropComponent} from './file-drop/file-drop.component';
import {OverlayModule} from '@angular/cdk/overlay';
import {UploadStatusOverlay} from './upload-status-dialog/upload-status-overlay.component';
import {NgxDropzoneModule} from "ngx-dropzone";
import {TranslateModule} from "@ngx-translate/core";
import {MatButtonModule} from "@angular/material/button";
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FileDropComponent } from './file-drop/file-drop.component';
import { OverlayModule } from '@angular/cdk/overlay';
import { UploadStatusOverlay } from './upload-status-dialog/upload-status-overlay.component';
import { NgxDropzoneModule } from 'ngx-dropzone';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
@NgModule({
imports: [
CommonModule,
MatIconModule,
MatTooltipModule,
TranslateModule,
MatListModule,
NgxDropzoneModule,
MatProgressBarModule,
OverlayModule,
MatButtonModule,
],
declarations: [FileDropComponent, UploadStatusOverlay],
providers: [],
entryComponents: [FileDropComponent, UploadStatusOverlay],
exports: [FileDropComponent, UploadStatusOverlay]
imports: [
CommonModule,
MatIconModule,
MatTooltipModule,
TranslateModule,
MatListModule,
NgxDropzoneModule,
MatProgressBarModule,
OverlayModule,
MatButtonModule
],
declarations: [FileDropComponent, UploadStatusOverlay],
providers: [],
entryComponents: [FileDropComponent, UploadStatusOverlay],
exports: [FileDropComponent, UploadStatusOverlay]
})
export class FileUploadModule {
}
export class FileUploadModule {}

View File

@ -1,47 +1,56 @@
import {Injectable} from "@angular/core";
import {FileUploadModel} from "./model/file-upload.model";
import {FileUploadControllerService} from "@redaction/red-ui-http";
import {AppStateService} from "../state/app-state.service";
import {HttpEventType} from "@angular/common/http";
import { Injectable } from '@angular/core';
import { FileUploadModel } from './model/file-upload.model';
import { FileUploadControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '../state/app-state.service';
import { HttpEventType } from '@angular/common/http';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class FileUploadService {
files: FileUploadModel[] = [];
files: FileUploadModel[] = [];
constructor(
private readonly _appStateService: AppStateService,
private readonly _fileUploadControllerService: FileUploadControllerService
) {}
constructor(
private readonly _appStateService: AppStateService,
private readonly _fileUploadControllerService: FileUploadControllerService,
) {
}
uploadFiles(files: FileUploadModel[]) {
this.files.push(...files);
files.forEach((newFile) => {
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
);
}
if (event.type === HttpEventType.Response) {
if (event.status < 300) {
newFile.progress = 100;
newFile.completed = true;
} else {
newFile.completed = true;
newFile.error = event.body;
}
}
},
(error) => {
newFile.completed = true;
newFile.error = error;
}
);
});
}
uploadFiles(files: FileUploadModel[]) {
this.files.push(...files);
files.forEach(newFile => {
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));
}
if (event.type === HttpEventType.Response) {
if (event.status < 300) {
newFile.progress = 100;
newFile.completed = true;
} else {
newFile.completed = true;
newFile.error = event.body;
}
}
}, (error) => {
newFile.completed = true;
newFile.error = error;
});
});
}
stopAllUploads() {
this.files = [];
}
stopAllUploads() {
this.files = [];
}
}

View File

@ -1,8 +1,6 @@
export interface FileUploadModel {
file: File;
progress: number;
completed: boolean;
error: any;
file: File;
progress: number;
completed: boolean;
error: any;
}

View File

@ -1,41 +1,38 @@
import {Injectable, Injector} from "@angular/core";
import {Overlay, OverlayRef} from "@angular/cdk/overlay";
import {ComponentPortal} from "@angular/cdk/portal";
import {UploadStatusOverlay} from "../upload-status-overlay.component";
import { Injectable, Injector } from '@angular/core';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { UploadStatusOverlay } from '../upload-status-overlay.component';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class UploadStatusOverlayService {
private readonly _statusOverlayRef: OverlayRef;
private readonly _statusOverlayRef: OverlayRef;
constructor(private overlay: Overlay, private readonly _injector: Injector) {
this._statusOverlayRef = this.overlay.create({
height: '500px',
width: '300px',
});
}
private _createInjector() {
return Injector.create({
providers: [{provide: OverlayRef, useValue: this._statusOverlayRef}],
parent: this._injector,
})
}
openStatusOverlay() {
const component = new ComponentPortal(UploadStatusOverlay, null, this._createInjector());
if (!this._statusOverlayRef.hasAttached()) {
this._statusOverlayRef.attach(component);
constructor(private overlay: Overlay, private readonly _injector: Injector) {
this._statusOverlayRef = this.overlay.create({
height: '500px',
width: '300px'
});
}
}
closeStatusOverlay() {
if (this._statusOverlayRef) {
this._statusOverlayRef.detach();
private _createInjector() {
return Injector.create({
providers: [{ provide: OverlayRef, useValue: this._statusOverlayRef }],
parent: this._injector
});
}
}
openStatusOverlay() {
const component = new ComponentPortal(UploadStatusOverlay, null, this._createInjector());
if (!this._statusOverlayRef.hasAttached()) {
this._statusOverlayRef.attach(component);
}
}
closeStatusOverlay() {
if (this._statusOverlayRef) {
this._statusOverlayRef.detach();
}
}
}

View File

@ -1,77 +1,68 @@
<section class="red-upload-overlay mat-elevation-z4">
<div class="red-upload-header" (click)="collapsed = !collapsed">
<div class="title">
{{ 'upload-status.dialog.title.label' | translate: {p1: uploadService.files.length} }}
</div>
<div class="collapse-icon" *ngIf="!collapsed">
<mat-icon svgIcon="red:arrow-down"></mat-icon>
</div>
<div class="collapse-icon" *ngIf="collapsed">
<mat-icon svgIcon="red:arrow-up"></mat-icon>
</div>
<div (click)="closeDialog()" class="close-icon">
<mat-icon svgIcon="red:close"></mat-icon>
</div>
</div>
<div [hidden]="collapsed">
<div class="upload-list">
<div
*ngFor="let model of uploadService.files"
class="upload-list-item"
>
<div class="upload-line">
<div
class="upload-file-name"
[matTooltip]="model.file?.name"
>
{{ model.file?.name }}
</div>
<div class="upload-progress" *ngIf="!model.completed && model.progress < 100">
{{ model.progress }}%
</div>
<div class="upload-progress error" *ngIf="model.completed && model.error">
<mat-icon svgIcon="red:error"></mat-icon>
</div>
<div class="upload-progress ok" *ngIf="model.completed && !model.error">
<mat-icon svgIcon="red:check"></mat-icon>
</div>
<div class="red-upload-header" (click)="collapsed = !collapsed">
<div class="title">
{{ 'upload-status.dialog.title.label' | translate: { p1: uploadService.files.length } }}
</div>
<div class="upload-line" *ngIf="model.completed && model.error">
<div
class="upload-file-name error"
[matTooltip]="model.error.message"
>
{{ model.error.message }}
</div>
<div class="collapse-icon" *ngIf="!collapsed">
<mat-icon svgIcon="red:arrow-down"></mat-icon>
</div>
<div class="collapse-icon" *ngIf="collapsed">
<mat-icon svgIcon="red:arrow-up"></mat-icon>
</div>
<div (click)="closeDialog()" class="close-icon">
<mat-icon svgIcon="red:close"></mat-icon>
</div>
</div>
<div [hidden]="collapsed">
<div class="upload-list">
<div *ngFor="let model of uploadService.files" class="upload-list-item">
<div class="upload-line">
<div class="upload-file-name" [matTooltip]="model.file?.name">
{{ model.file?.name }}
</div>
<div class="upload-progress" *ngIf="!model.completed && model.progress < 100">
{{ model.progress }}%
</div>
<div class="upload-progress error" *ngIf="model.completed && model.error">
<mat-icon svgIcon="red:error"></mat-icon>
</div>
<div class="upload-progress ok" *ngIf="model.completed && !model.error">
<mat-icon svgIcon="red:check"></mat-icon>
</div>
</div>
<div class="upload-line" *ngIf="model.completed && model.error">
<div class="upload-file-name error" [matTooltip]="model.error.message">
{{ model.error.message }}
</div>
<div class="upload-progress">
<div
class="error-action"
(click)="uploadItem(model)"
[matTooltip]="'upload-status.dialog.actions.re-upload.label' | translate"
>
<mat-icon svgIcon="red:refresh"></mat-icon>key
<div class="upload-progress">
<div
class="error-action"
(click)="uploadItem(model)"
[matTooltip]="
'upload-status.dialog.actions.re-upload.label' | translate
"
>
<mat-icon svgIcon="red:refresh"></mat-icon>key
</div>
<div
class="error-action"
(click)="cancelItem(model)"
[matTooltip]="'upload-status.dialog.actions.cancel.label' | translate"
>
<mat-icon svgIcon="red:close"></mat-icon>
</div>
</div>
</div>
<div mat-line *ngIf="!model.completed" class="upload-progress">
<mat-progress-bar
mode="determinate"
color="primary"
[value]="model.progress"
*ngIf="model.progress !== 100"
></mat-progress-bar>
</div>
</div>
<div
class="error-action"
(click)="cancelItem(model)"
[matTooltip]="'upload-status.dialog.actions.cancel.label' | translate"
>
<mat-icon svgIcon="red:close"></mat-icon>
</div>
</div>
</div>
<div mat-line *ngIf="!model.completed" class="upload-progress">
<mat-progress-bar
mode="determinate"
color="primary"
[value]="model.progress"
*ngIf="model.progress !== 100"
></mat-progress-bar>
</div>
</div>
</div>
</div>
</section>

View File

@ -1,91 +1,89 @@
@import "../../../assets/styles/red-variables";
@import '../../../assets/styles/red-variables';
section {
background: white;
position: fixed;
bottom: 10px;
right: 10px;
border: 2px solid $grey-1;
background: white;
position: fixed;
bottom: 10px;
right: 10px;
border: 2px solid $grey-1;
}
.upload-list {
max-height: 400px;
max-width: 400px;
overflow: auto;
max-height: 400px;
max-width: 400px;
overflow: auto;
}
.red-upload-header {
display: flex;
flex-direction: row;
align-items: center;
position: relative;
padding: 10px;
background: $grey-1;
color: $white;
width: 380px;
cursor: pointer;
mat-icon {
display: flex;
flex-direction: row;
align-items: center;
position: relative;
padding: 10px;
background: $grey-1;
color: $white;
}
width: 380px;
cursor: pointer;
mat-icon {
color: $white;
}
}
.collapse-icon {
padding-left: 8px;
padding-left: 8px;
mat-icon {
width: 16px;
}
mat-icon {
width: 16px;
}
}
.close-icon {
position: absolute;
right: 10px;
color: $white;
position: absolute;
right: 10px;
color: $white;
}
.upload-list-item {
padding: 8px;
padding: 8px;
mat-icon {
width: 16px;
}
.upload-line {
display: flex !important;
height: 20px;
position: relative;
justify-content: flex-start;
.upload-file-name {
text-overflow: ellipsis;
overflow: hidden;
display: block;
white-space: nowrap;
padding-right: 50px;
&.error {
color: $red-1;
padding-right: 60px;
}
mat-icon {
width: 16px;
}
.upload-progress {
position: absolute;
right: 0;
width: 50px;
display: flex;
justify-content: space-evenly;
.upload-line {
display: flex !important;
height: 20px;
position: relative;
justify-content: flex-start;
&.error {
color: $red-1;
}
.upload-file-name {
text-overflow: ellipsis;
overflow: hidden;
display: block;
white-space: nowrap;
padding-right: 50px;
&.ok {
color: $blue-1;
}
&.error {
color: $red-1;
padding-right: 60px;
}
}
.upload-progress {
position: absolute;
right: 0;
width: 50px;
display: flex;
justify-content: space-evenly;
&.error {
color: $red-1;
}
&.ok {
color: $blue-1;
}
}
}
}
}

View File

@ -1,39 +1,32 @@
import {Component, OnInit} from '@angular/core';
import {FileUploadModel} from "../model/file-upload.model";
import {FileUploadService} from "../file-upload.service";
import {OverlayRef} from "@angular/cdk/overlay";
import { Component, OnInit } from '@angular/core';
import { FileUploadModel } from '../model/file-upload.model';
import { FileUploadService } from '../file-upload.service';
import { OverlayRef } from '@angular/cdk/overlay';
@Component({
selector: 'redaction-upload-status-overlay',
templateUrl: './upload-status-overlay.component.html',
styleUrls: ['./upload-status-overlay.component.scss']
selector: 'redaction-upload-status-overlay',
templateUrl: './upload-status-overlay.component.html',
styleUrls: ['./upload-status-overlay.component.scss']
})
export class UploadStatusOverlay implements OnInit {
collapsed = false;
collapsed = false;
constructor(
public readonly uploadService: FileUploadService,
private readonly _overlayRef: OverlayRef
) {}
ngOnInit() {}
constructor(public readonly uploadService: FileUploadService, private readonly _overlayRef: OverlayRef) {
}
cancelItem(item: FileUploadModel) {}
uploadItem(item: FileUploadModel) {
item.completed = false;
item.error = null;
}
ngOnInit() {
}
cancelItem(item: FileUploadModel) {
}
uploadItem(item: FileUploadModel) {
item.completed = false;
item.error = null;
}
closeDialog() {
this.uploadService.stopAllUploads();
this._overlayRef.detach();
}
closeDialog() {
this.uploadService.stopAllUploads();
this._overlayRef.detach();
}
}

View File

@ -1,111 +1,117 @@
import {Injectable} from '@angular/core';
import {KeycloakService} from 'keycloak-angular';
import {KeycloakProfile} from 'keycloak-js';
import { Injectable } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { KeycloakProfile } from 'keycloak-js';
import jwt_decode from 'jwt-decode';
import {User, UserControllerService} from '@redaction/red-ui-http';
import { User, UserControllerService } from '@redaction/red-ui-http';
export class UserWrapper {
constructor(private _currentUser: KeycloakProfile, public roles: string[], public id: string) {}
constructor(private _currentUser: KeycloakProfile, public roles: string[], public id: string) {
}
get name() {
return this._currentUser.firstName + ' ' + this._currentUser.lastName;
}
get name() {
return this._currentUser.firstName + ' ' + this._currentUser.lastName;
}
get isManager() {
return this.roles.indexOf('RED_MANAGER') >= 0;
}
get isManager() {
return this.roles.indexOf('RED_MANAGER') >= 0;
}
get isUser() {
return this.roles.indexOf('RED_USER') >= 0;
}
get isAdmin() {
return this.roles.indexOf('RED_ADMIN') >= 0;
}
get isUser() {
return this.roles.indexOf('RED_USER') >= 0;
}
get isAdmin() {
return this.roles.indexOf('RED_ADMIN') >= 0;
}
get hasAnyREDRoles() {
return this.isUser || this.isManager || this.isAdmin;
}
get hasAnyREDRoles() {
return this.isUser || this.isManager || this.isAdmin;
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class UserService {
private _currentUser: UserWrapper;
private _allUsers: User[];
private _currentUser: UserWrapper;
private _allUsers: User[];
constructor(private readonly _keycloakService: KeycloakService,
private readonly _userControllerService: UserControllerService) {
}
constructor(
private readonly _keycloakService: KeycloakService,
private readonly _userControllerService: UserControllerService
) {}
logout() {
this._keycloakService.logout(window.location.origin);
}
get userId() {
return this._currentUser.id;
}
get allUsers() {
return this._allUsers;
}
get managerUsers() {
return this._allUsers.filter(u => u.roles.indexOf('RED_MANAGER') >= 0);
}
async loadAllUsersIfNecessary() {
if (!this._allUsers) {
await this.loadAllUsers();
logout() {
this._keycloakService.logout(window.location.origin);
}
}
async loadAllUsers() {
const allUsers = await this._userControllerService.getUsers({}, 0, 100).toPromise();
this._allUsers = allUsers.users.filter(u => this._hasAnyRedRole(u));
return allUsers;
}
get userId() {
return this._currentUser.id;
}
async loadCurrentUser() {
const token = await this._keycloakService.getToken();
const decoded = jwt_decode(token);
const userId = decoded.sub;
this._currentUser = new UserWrapper(await this._keycloakService.loadUserProfile(false), this._keycloakService.getUserRoles(true), userId);
}
get allUsers() {
return this._allUsers;
}
get user() {
return this._currentUser;
}
get managerUsers() {
return this._allUsers.filter((u) => u.roles.indexOf('RED_MANAGER') >= 0);
}
getUserById(id: string) {
return this._allUsers.find(u => u.userId === id);
}
async loadAllUsersIfNecessary() {
if (!this._allUsers) {
await this.loadAllUsers();
}
}
getNameForId(userId: string) {
return this.getName(this.getUserById(userId));
}
async loadAllUsers() {
const allUsers = await this._userControllerService.getUsers({}, 0, 100).toPromise();
this._allUsers = allUsers.users.filter((u) => this._hasAnyRedRole(u));
return allUsers;
}
getName(user?: User) {
return user ?
(user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.username) :
undefined;
}
async loadCurrentUser() {
const token = await this._keycloakService.getToken();
const decoded = jwt_decode(token);
const userId = decoded.sub;
this._currentUser = new UserWrapper(
await this._keycloakService.loadUserProfile(false),
this._keycloakService.getUserRoles(true),
userId
);
}
isManager(user: User) {
return user.roles.indexOf('RED_MANAGER') >= 0;
}
get user() {
return this._currentUser;
}
isUser(user: User) {
return user.roles.indexOf('RED_USER') >= 0;
}
getUserById(id: string) {
return this._allUsers.find((u) => u.userId === id);
}
private _hasAnyRedRole(u: User) {
return u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0 || u.roles.indexOf('RED_ADMIN') >= 0;
}
getNameForId(userId: string) {
return this.getName(this.getUserById(userId));
}
getName(user?: User) {
return user
? user.firstName && user.lastName
? `${user.firstName} ${user.lastName}`
: user.username
: undefined;
}
isManager(user: User) {
return user.roles.indexOf('RED_MANAGER') >= 0;
}
isUser(user: User) {
return user.roles.indexOf('RED_USER') >= 0;
}
private _hasAnyRedRole(u: User) {
return (
u.roles.indexOf('RED_USER') >= 0 ||
u.roles.indexOf('RED_MANAGER') >= 0 ||
u.roles.indexOf('RED_ADMIN') >= 0
);
}
}

View File

@ -1,107 +1,114 @@
import {Annotations} from '@pdftron/webviewer';
import {AnnotationFilters} from './types';
import { Annotations } from '@pdftron/webviewer';
import { AnnotationFilters } from './types';
export class AnnotationUtils {
public static sortAnnotations(annotations: Annotations.Annotation[]): Annotations.Annotation[] {
return annotations.sort((ann1, ann2) => {
if (ann1.getPageNumber() === ann2.getPageNumber()) {
if (ann1.getY() === ann2.getY()) {
if (ann1.getX() === ann2.getY()) {
return 0;
}
return ann1.getX() < ann2.getX() ? -1 : 1;
}
return ann1.getY() < ann2.getY() ? -1 : 1;
}
return ann1.getPageNumber() < ann2.getPageNumber() ? -1 : 1;
});
}
public static hasSubsections(filter: AnnotationFilters | boolean) {
return filter instanceof Object;
}
public static checkedSubkeys(filter: AnnotationFilters | boolean) {
return Object.keys(filter).filter(subkey => this.isChecked(filter[subkey])).length;
}
// Only some of the sub-items are selected
public static isIndeterminate(filter: AnnotationFilters | boolean): boolean {
return this.hasSubsections(filter) ? AnnotationUtils.checkedSubkeys(filter) > 0 && !this.isChecked(filter) : false;
}
// All sub-items are selected
public static isChecked(filter: AnnotationFilters | boolean): boolean {
return this.hasSubsections(filter) ?
AnnotationUtils.checkedSubkeys(filter) === Object.keys(filter).length :
filter as boolean;
}
public static hasActiveFilters(filter: AnnotationFilters): boolean {
const activeFilters = Object.keys(filter).filter(key => {
return this.isChecked(filter[key]) || this.isIndeterminate(filter[key]);
});
return activeFilters.length > 0;
}
public static parseAnnotations(annotations: Annotations.Annotation[], filters: AnnotationFilters):
{ [key: number]: { annotations: Annotations.Annotation[] } } {
const obj = {};
for (const ann of annotations) {
const pageNumber = ann.getPageNumber();
const type = this.getType(ann);
const dictionary = this.getDictionary(ann);
if (this.hasActiveFilters(filters)) {
if (!this.hasSubsections(filters[type]) && !filters[type]) {
continue;
}
if (this.hasSubsections(filters[type]) && !filters[type][dictionary]) {
continue;
}
}
if (!obj[pageNumber]) {
obj[pageNumber] = {
annotations: [],
hint: 0,
redaction: 0,
comment: 0,
suggestion: 0,
ignore: 0
};
}
obj[pageNumber].annotations.push(ann);
obj[pageNumber][type]++;
public static sortAnnotations(annotations: Annotations.Annotation[]): Annotations.Annotation[] {
return annotations.sort((ann1, ann2) => {
if (ann1.getPageNumber() === ann2.getPageNumber()) {
if (ann1.getY() === ann2.getY()) {
if (ann1.getX() === ann2.getY()) {
return 0;
}
return ann1.getX() < ann2.getX() ? -1 : 1;
}
return ann1.getY() < ann2.getY() ? -1 : 1;
}
return ann1.getPageNumber() < ann2.getPageNumber() ? -1 : 1;
});
}
Object.keys(obj).map(page => {
obj[page].annotations = this.sortAnnotations(obj[page].annotations);
});
return obj;
}
public static addAnnotations(initialAnnotations: Annotations.Annotation[], addedAnnotations: Annotations.Annotation[]) {
for (const annotation of addedAnnotations) {
if (annotation.Id.indexOf(':') > 0) {
const found = initialAnnotations.find(a => a.Id === annotation.Id);
if(!found) {
initialAnnotations.push(annotation);
}
}
public static hasSubsections(filter: AnnotationFilters | boolean) {
return filter instanceof Object;
}
}
public static getType(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 1 ? parts[0] : 'n/a';
}
public static checkedSubkeys(filter: AnnotationFilters | boolean) {
return Object.keys(filter).filter((subkey) => this.isChecked(filter[subkey])).length;
}
public static getDictionary(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 2 ? parts[1] : 'n/a';
}
// Only some of the sub-items are selected
public static isIndeterminate(filter: AnnotationFilters | boolean): boolean {
return this.hasSubsections(filter)
? AnnotationUtils.checkedSubkeys(filter) > 0 && !this.isChecked(filter)
: false;
}
// All sub-items are selected
public static isChecked(filter: AnnotationFilters | boolean): boolean {
return this.hasSubsections(filter)
? AnnotationUtils.checkedSubkeys(filter) === Object.keys(filter).length
: (filter as boolean);
}
public static hasActiveFilters(filter: AnnotationFilters): boolean {
const activeFilters = Object.keys(filter).filter((key) => {
return this.isChecked(filter[key]) || this.isIndeterminate(filter[key]);
});
return activeFilters.length > 0;
}
public static parseAnnotations(
annotations: Annotations.Annotation[],
filters: AnnotationFilters
): { [key: number]: { annotations: Annotations.Annotation[] } } {
const obj = {};
for (const ann of annotations) {
const pageNumber = ann.getPageNumber();
const type = this.getType(ann);
const dictionary = this.getDictionary(ann);
if (this.hasActiveFilters(filters)) {
if (!this.hasSubsections(filters[type]) && !filters[type]) {
continue;
}
if (this.hasSubsections(filters[type]) && !filters[type][dictionary]) {
continue;
}
}
if (!obj[pageNumber]) {
obj[pageNumber] = {
annotations: [],
hint: 0,
redaction: 0,
comment: 0,
suggestion: 0,
ignore: 0
};
}
obj[pageNumber].annotations.push(ann);
obj[pageNumber][type]++;
}
Object.keys(obj).map((page) => {
obj[page].annotations = this.sortAnnotations(obj[page].annotations);
});
return obj;
}
public static addAnnotations(
initialAnnotations: Annotations.Annotation[],
addedAnnotations: Annotations.Annotation[]
) {
for (const annotation of addedAnnotations) {
if (annotation.Id.indexOf(':') > 0) {
const found = initialAnnotations.find((a) => a.Id === annotation.Id);
if (!found) {
initialAnnotations.push(annotation);
}
}
}
}
public static getType(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 1 ? parts[0] : 'n/a';
}
public static getDictionary(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 2 ? parts[1] : 'n/a';
}
}

View File

@ -2,7 +2,7 @@ import { EventEmitter, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class AppLoadStateService {
private _loadingEvent = new EventEmitter();

View File

@ -1,57 +1,69 @@
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import {Injectable, Injector} from '@angular/core';
import {from, Observable, of} from 'rxjs';
import {catchError, map, mergeMap, tap} from 'rxjs/operators';
import {AppLoadStateService} from "./app-load-state.service";
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree
} from '@angular/router';
import { Injectable, Injector } from '@angular/core';
import { from, Observable, of } from 'rxjs';
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { AppLoadStateService } from './app-load-state.service';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class CompositeRouteGuard implements CanActivate {
constructor(
protected readonly _router: Router,
protected readonly _injector: Injector,
private readonly _appLoadStateService: AppLoadStateService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
this._appLoadStateService.pushLoadingEvent(true);
let compositeCanActivateObservable: Observable<boolean> = of(true);
constructor(protected readonly _router: Router, protected readonly _injector: Injector, private readonly _appLoadStateService: AppLoadStateService) {
}
const routeGuards = route.data.routeGuards;
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<CanActivate>(routeGuards[i]);
let canActivateResult = routeGuard.canActivate(route, state);
if (canActivateResult instanceof Promise) {
canActivateResult = from(canActivateResult);
}
if (typeof canActivateResult === 'boolean' || canActivateResult instanceof UrlTree) {
canActivateResult = of(canActivateResult);
}
const canActivatePipe: Observable<boolean> = canActivateResult.pipe(map(m => !!m));
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
mergeMap(bool => {
if (!bool) {
return of(false);
} else {
return canActivatePipe;
if (routeGuards) {
for (let i = 0; i < routeGuards.length; i++) {
const routeGuard = this._injector.get<CanActivate>(routeGuards[i]);
let canActivateResult = routeGuard.canActivate(route, state);
if (canActivateResult instanceof Promise) {
canActivateResult = from(canActivateResult);
}
if (
typeof canActivateResult === 'boolean' ||
canActivateResult instanceof UrlTree
) {
canActivateResult = of(canActivateResult);
}
const canActivatePipe: Observable<boolean> = canActivateResult.pipe(
map((m) => !!m)
);
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
mergeMap((bool) => {
if (!bool) {
return of(false);
} else {
return canActivatePipe;
}
})
);
}
})
}
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
tap(() => {
this._appLoadStateService.pushLoadingEvent(false);
}),
catchError(() => {
this._appLoadStateService.pushLoadingEvent(false);
return of(false);
})
);
}
return compositeCanActivateObservable;
}
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
tap(() => {
this._appLoadStateService.pushLoadingEvent(false);
}),
catchError(() => {
this._appLoadStateService.pushLoadingEvent(false);
return of(false);
})
);
return compositeCanActivateObservable;
}
}

View File

@ -1,10 +1,10 @@
export function debounce(delay: number = 300): MethodDecorator {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let timeout = null;
const original = descriptor.value;
descriptor.value = function(...args) {
descriptor.value = function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => original.apply(this, args), delay);
};

View File

@ -1,13 +1,15 @@
import {HttpResponse} from '@angular/common/http';
import {saveAs} from 'file-saver';
import { HttpResponse } from '@angular/common/http';
import { saveAs } from 'file-saver';
export function download(event: HttpResponse<Blob>, altName?: string) {
const contentDisposition = event.headers.get('Content-Disposition');
let fileName;
try {
fileName = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : undefined;
} catch (e) {
console.log('failed to parse content-disposition: ', contentDisposition);
}
saveAs(event.body, fileName ? fileName : altName);
const contentDisposition = event.headers.get('Content-Disposition');
let fileName;
try {
fileName = contentDisposition
? contentDisposition.split('filename=')[1].replace(/"/g, '')
: undefined;
} catch (e) {
console.log('failed to parse content-disposition: ', contentDisposition);
}
saveAs(event.body, fileName ? fileName : altName);
}

View File

@ -1,5 +1,4 @@
<section class="full-page-load-section" *ngIf="displayed">
</section>
<section class="full-page-load-section" *ngIf="displayed"></section>
<section class="full-page-load-spinner" *ngIf="displayed">
<mat-spinner diameter="40"></mat-spinner>
<mat-spinner diameter="40"></mat-spinner>
</section>

View File

@ -1,24 +1,23 @@
@import "../../../assets/styles/red-variables";
@import '../../../assets/styles/red-variables';
.full-page-load-section, .full-page-load-spinner {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.full-page-load-section,
.full-page-load-spinner {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.full-page-load-section {
opacity: 0.7;
background: $white;
z-index: 900;
opacity: 0.7;
background: $white;
z-index: 900;
}
.full-page-load-spinner {
z-index: 1000;
justify-content: center;
align-items: center;
display: flex;
z-index: 1000;
justify-content: center;
align-items: center;
display: flex;
}

View File

@ -1,12 +1,10 @@
import {Component, Input, OnInit} from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'redaction-full-page-loading-indicator',
templateUrl: './full-page-loading-indicator.component.html',
styleUrls: ['./full-page-loading-indicator.component.scss']
selector: 'redaction-full-page-loading-indicator',
templateUrl: './full-page-loading-indicator.component.html',
styleUrls: ['./full-page-loading-indicator.component.scss']
})
export class FullPageLoadingIndicatorComponent {
@Input() displayed = false;
export class FullPageLoadingIndicatorComponent {
@Input() displayed = false;
}

View File

@ -1,6 +1,6 @@
export function groupBy(xs: any[], key: string) {
return xs.reduce((rv, x) => {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
return xs.reduce((rv, x) => {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
}

View File

@ -1,20 +1,15 @@
import {FileStatus} from "@redaction/red-ui-http";
import { FileStatus } from '@redaction/red-ui-http';
export type Color = FileStatus.StatusEnum | ProjectStatus.StatusEnum;
export type AnnotationType =
'hint' |
'redaction' |
'suggestion' |
'comment' |
'ignore'
export type AnnotationType = 'hint' | 'redaction' | 'suggestion' | 'comment' | 'ignore';
export class SortingOption {
label: string;
order: string;
column: string;
label: string;
order: string;
column: string;
}
export class AnnotationFilters {
[key: AnnotationType]: boolean
[key: AnnotationType]: boolean;
}

View File

@ -1,6 +1,6 @@
{
"OAUTH_URL": "https://keycloak-dev.iqser.cloud/auth/realms/redaction",
"OAUTH_CLIENT_ID": "redaction",
"API_URL": "",
"PDFTRON_LICENSE": ""
"OAUTH_URL": "https://keycloak-dev.iqser.cloud/auth/realms/redaction",
"OAUTH_CLIENT_ID": "redaction",
"API_URL": "",
"PDFTRON_LICENSE": ""
}

View File

@ -1,466 +1,466 @@
{
"auth-error": {
"heading": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie Ihren Administrator für den Zugriff!"
},
"heading-with-name-and-link": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie <a href={{adminUrl}} target=_blank >{{adminName}}</a> für den Zugriff!"
},
"heading-with-name": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie {{adminName}} für den Zugriff!"
},
"heading-with-link": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie <a href={{adminUrl}} target=_blank >Ihren Administrator</a> für den Zugriff!"
},
"logout": {
"label": "Ausloggen"
}
},
"manual-redaction": {
"remove-annotation": {
"success": {
"label": "Anmerkung Zum Entfernen empfohlen!"
},
"failed": {
"label": "Fehler beim Anfordern der Entfernung von Anmerkungen!"
}
},
"dialog": {
"header": {
"label": "Manuelle Redaktion hinzufügen"
},
"add-redaction": {
"success": {
"label": "Redaktionsvorschlag hinzugefügt!"
"auth-error": {
"heading": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie Ihren Administrator für den Zugriff!"
},
"failed": {
"label": "Manuelle Redaktion konnte nicht hinzugefügt werden: {{message}}"
}
},
"actions": {
"save": {
"label": "Manuelle Redaktion speichern"
}
},
"content": {
"text": {
"label": "<strong>Ausgewählter Text:</strong> {{value}}"
"heading-with-name-and-link": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie <a href={{adminUrl}} target=_blank >{{adminName}}</a> für den Zugriff!"
},
"dictionary": {
"add": {
"label": "Zum Wörterbuch hinzufügen"
},
"label": "Wörterbuch"
"heading-with-name": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie {{adminName}} für den Zugriff!"
},
"reason": {
"label": "Grund"
}
}
}
},
"app-name": {
"label": "Redacto"
},
"upload-status": {
"dialog": {
"title": {
"label": "Datei-Upload"
},
"actions": {
"re-upload": {
"label": "Wiederholen Sie den Upload"
"heading-with-link": {
"label": "Ihr Benutzer verfügt nicht über die erforderlichen RED- * -Rollen, um auf diese Anwendung zuzugreifen. Bitte kontaktieren Sie <a href={{adminUrl}} target=_blank >Ihren Administrator</a> für den Zugriff!"
},
"cancel": {
"label": "Upload abbrechen"
}
}
}
},
"pdf-viewer": {
"text-popup": {
"actions": {
"suggestion-redaction": {
"label": "Redaktion vorschlagen"
}
}
}
},
"common": {
"dialog": {
"close": {
"label": "Dialog schließen"
}
},
"confirmation-dialog": {
"title": {
"label": "Aktion bestätigen"
},
"description": {
"label": "Diese Aktion muss bestätigt werden. Möchten Sie fortfahren?"
},
"confirm": {
"label": "Ja"
},
"deny": {
"label": "Nein"
}
}
},
"top-bar": {
"navigation-items": {
"projects": {
"label": "Projekte"
},
"my-account": {
"label": "Mein Konto",
"children": {
"language": {
"label": "Sprache",
"english": {
"label": "Englisch"
},
"german": {
"label": "Deutsche"
}
},
"logout": {
"logout": {
"label": "Ausloggen"
}
}
}
}
},
"filters": {
"filter-by": {
"label": "Filtern nach:"
},
"status": {
"label": "Status"
},
"people": {
"label": "Menschen"
},
"due-date": {
"label": "Geburtstermin"
},
"created-on": {
"label": "Erstellt am"
},
"project": {
"label": "Projekt"
},
"document": {
"label": "Dokument"
}
},
"project-listing": {
"table-header": {
"title": {
"label": "{{length}} aktive Projekte"
},
"bulk-select": {
"label": "Massenauswahl"
},
"recent": {
"label": "Kürzlich"
}
},
"stats": {
"analyzed-pages": {
"label": "Analysierte Seiten"
},
"total-people": {
"label": "Insgesamt Menschen"
},
"charts": {
"projects": {
"label": "Projekte"
"manual-redaction": {
"remove-annotation": {
"success": {
"label": "Anmerkung Zum Entfernen empfohlen!"
},
"failed": {
"label": "Fehler beim Anfordern der Entfernung von Anmerkungen!"
}
},
"total-documents": {
"label": "Dokumente insgesamt"
"dialog": {
"header": {
"label": "Manuelle Redaktion hinzufügen"
},
"add-redaction": {
"success": {
"label": "Redaktionsvorschlag hinzugefügt!"
},
"failed": {
"label": "Manuelle Redaktion konnte nicht hinzugefügt werden: {{message}}"
}
},
"actions": {
"save": {
"label": "Manuelle Redaktion speichern"
}
},
"content": {
"text": {
"label": "<strong>Ausgewählter Text:</strong> {{value}}"
},
"dictionary": {
"add": {
"label": "Zum Wörterbuch hinzufügen"
},
"label": "Wörterbuch"
},
"reason": {
"label": "Grund"
}
}
}
}
},
"add-edit-dialog": {
"header-new": {
"label": "Neues Projekt"
},
"header-edit": {
"label": "Projekt bearbeiten"
},
"form": {
"description": {
"label": "Beschreibung"
"app-name": {
"label": "Redacto"
},
"upload-status": {
"dialog": {
"title": {
"label": "Datei-Upload"
},
"actions": {
"re-upload": {
"label": "Wiederholen Sie den Upload"
},
"cancel": {
"label": "Upload abbrechen"
}
}
}
},
"pdf-viewer": {
"text-popup": {
"actions": {
"suggestion-redaction": {
"label": "Redaktion vorschlagen"
}
}
}
},
"common": {
"dialog": {
"close": {
"label": "Dialog schließen"
}
},
"name": {
"label": "Name"
"confirmation-dialog": {
"title": {
"label": "Aktion bestätigen"
},
"description": {
"label": "Diese Aktion muss bestätigt werden. Möchten Sie fortfahren?"
},
"confirm": {
"label": "Ja"
},
"deny": {
"label": "Nein"
}
}
},
"actions": {
"save": {
"label": "Projekt speichern"
},
"top-bar": {
"navigation-items": {
"projects": {
"label": "Projekte"
},
"my-account": {
"label": "Mein Konto",
"children": {
"language": {
"label": "Sprache",
"english": {
"label": "Englisch"
},
"german": {
"label": "Deutsche"
}
},
"logout": {
"label": "Ausloggen"
}
}
}
}
}
},
"header": {
"label": "Projekte"
},
"edit": {
"action": {
"label": "Projekt bearbeiten"
}
},
"delete": {
"action": {
"label": "Projekt löschen"
},
"delete-failed": {
"label": "Projekt konnte nicht gelöscht werden: {{projectName}}"
}
},
"add-new": {
"label": "Neues Projekt"
},
"no-projects": {
"label": "Sie haben derzeit keine Projekte. Sie können Ihre Arbeit beginnen, indem Sie eine neue erstellen!"
},
"sorting": {
"recent": {
"label": "Kürzlich"
},
"alphabetically": {
"label": "Alphabetisch"
}
}
},
"file-details": {
"dialog": {
"title": {
"label": "Dateidetails"
},
"actions": {
"download-redaction-report": {
"label": "Redaktionsbericht herunterladen"
}
}
}
},
"project-details": {
"dialog": {
"title": {
"label": "Projekt Details"
},
"info": {
"file-count": {
"label": "Anzahl der Dateien: {{fileCount}}"
}
},
"actions": {
"download-redaction-report": {
"label": "Redaktionsbericht herunterladen"
"filters": {
"filter-by": {
"label": "Filtern nach:"
},
"reanalyse-project": {
"label": "Projekt erneut analysieren"
}
}
}
},
"project-overview": {
"table-header": {
"title": {
"label": "{{length}} Dokumente"
},
"bulk-select": {
"label": "Massenauswahl"
},
"recent": {
"label": "Kürzlich"
}
},
"table-col-names": {
"name": {
"label": "Name"
},
"added-on": {
"label": "Hinzugefügt zu"
},
"needs-work": {
"label": "Braucht Arbeit"
},
"assigned-to": {
"label": "Zugewiesen an"
},
"status": {
"label": "Status"
}
},
"sorting": {
"recent": {
"label": "Kürzlich"
},
"alphabetically": {
"label": "Alphabetisch"
},
"number-of-pages": {
"label": "Seitenzahl"
},
"number-of-analyses": {
"label": "Anzahl der Analysen"
}
},
"upload-error": {
"label": "Datei konnte nicht hochgeladen werden: {{name}}"
},
"delete-file-error": {
"label": "Fehler beim Löschen der Datei: {{filename}}"
},
"reanalyse": {
"action": {
"label": "Datei erneut analysieren"
}
},
"delete": {
"action": {
"label": "Datei löschen"
}
},
"file-listing": {
"file-entry": {
"status": {
"label": "Status: {{status}}"
"label": "Status"
},
"number-of-pages": {
"label": "Anzahl der Seiten: {{numberOfPages}}"
"people": {
"label": "Menschen"
},
"number-of-analyses": {
"label": "Anzahl der Analysen: {{numberOfAnalyses}}"
"due-date": {
"label": "Geburtstermin"
},
"added": {
"label": "Datum hinzugefügt: {{added}}"
"created-on": {
"label": "Erstellt am"
},
"last-updated": {
"label": "Letzte Aktualisierung: {{lastUpdated}}"
"project": {
"label": "Projekt"
},
"document": {
"label": "Dokument"
}
},
"project-listing": {
"table-header": {
"title": {
"label": "{{length}} aktive Projekte"
},
"bulk-select": {
"label": "Massenauswahl"
},
"recent": {
"label": "Kürzlich"
}
},
"stats": {
"analyzed-pages": {
"label": "Analysierte Seiten"
},
"total-people": {
"label": "Insgesamt Menschen"
},
"charts": {
"projects": {
"label": "Projekte"
},
"total-documents": {
"label": "Dokumente insgesamt"
}
}
},
"add-edit-dialog": {
"header-new": {
"label": "Neues Projekt"
},
"header-edit": {
"label": "Projekt bearbeiten"
},
"form": {
"description": {
"label": "Beschreibung"
},
"name": {
"label": "Name"
}
},
"actions": {
"save": {
"label": "Projekt speichern"
}
}
},
"header": {
"label": "Projekte"
},
"edit": {
"action": {
"label": "Projekt bearbeiten"
}
},
"delete": {
"action": {
"label": "Projekt löschen"
},
"delete-failed": {
"label": "Projekt konnte nicht gelöscht werden: {{projectName}}"
}
},
"add-new": {
"label": "Neues Projekt"
},
"no-projects": {
"label": "Sie haben derzeit keine Projekte. Sie können Ihre Arbeit beginnen, indem Sie eine neue erstellen!"
},
"sorting": {
"recent": {
"label": "Kürzlich"
},
"alphabetically": {
"label": "Alphabetisch"
}
}
},
"file-details": {
"dialog": {
"title": {
"label": "Dateidetails"
},
"actions": {
"download-redaction-report": {
"label": "Redaktionsbericht herunterladen"
}
}
}
}
},
"project-details": {
"project-team": {
"label": "Projektteam"
},
"charts": {
"total-documents": {
"label": "Dokumente insgesamt"
"dialog": {
"title": {
"label": "Projekt Details"
},
"info": {
"file-count": {
"label": "Anzahl der Dateien: {{fileCount}}"
}
},
"actions": {
"download-redaction-report": {
"label": "Redaktionsbericht herunterladen"
},
"reanalyse-project": {
"label": "Projekt erneut analysieren"
}
}
}
}
},
"header": {
"label": "Projektübersicht"
},
"upload-document": {
"label": "Dokument hochladen"
},
"no-project": {
"label": "Angefordertes Projekt: {{projectId}} existiert nicht! <a href='/ui/projects'>Zurück zur Projektliste.</a>"
}
},
"file-preview": {
"view-toggle": {
"label": "Redigierte Ansicht"
},
"filter-menu": {
"label": "Filter",
"all": {
"label": "Alle"
},
"none": {
"label": "Keiner"
},
"filter-types": {
"label": "Filtertypen"
},
"hint": {
"label": "Hinweis Anmerkung",
"hint_only": {
"label": "Nur Hinweis"
"project-overview": {
"table-header": {
"title": {
"label": "{{length}} Dokumente"
},
"bulk-select": {
"label": "Massenauswahl"
},
"recent": {
"label": "Kürzlich"
}
},
"vertebrate": {
"label": "Wirbeltier"
"table-col-names": {
"name": {
"label": "Name"
},
"added-on": {
"label": "Hinzugefügt zu"
},
"needs-work": {
"label": "Braucht Arbeit"
},
"assigned-to": {
"label": "Zugewiesen an"
},
"status": {
"label": "Status"
}
},
"names": {
"label": "Namen"
"sorting": {
"recent": {
"label": "Kürzlich"
},
"alphabetically": {
"label": "Alphabetisch"
},
"number-of-pages": {
"label": "Seitenzahl"
},
"number-of-analyses": {
"label": "Anzahl der Analysen"
}
},
"upload-error": {
"label": "Datei konnte nicht hochgeladen werden: {{name}}"
},
"delete-file-error": {
"label": "Fehler beim Löschen der Datei: {{filename}}"
},
"reanalyse": {
"action": {
"label": "Datei erneut analysieren"
}
},
"delete": {
"action": {
"label": "Datei löschen"
}
},
"file-listing": {
"file-entry": {
"status": {
"label": "Status: {{status}}"
},
"number-of-pages": {
"label": "Anzahl der Seiten: {{numberOfPages}}"
},
"number-of-analyses": {
"label": "Anzahl der Analysen: {{numberOfAnalyses}}"
},
"added": {
"label": "Datum hinzugefügt: {{added}}"
},
"last-updated": {
"label": "Letzte Aktualisierung: {{lastUpdated}}"
}
}
},
"project-details": {
"project-team": {
"label": "Projektteam"
},
"charts": {
"total-documents": {
"label": "Dokumente insgesamt"
}
}
},
"header": {
"label": "Projektübersicht"
},
"upload-document": {
"label": "Dokument hochladen"
},
"no-project": {
"label": "Angefordertes Projekt: {{projectId}} existiert nicht! <a href='/ui/projects'>Zurück zur Projektliste.</a>"
}
},
"redaction": {
"label": "Redaktion"
},
"comment": {
"label": "Kommentar Annotation"
},
"suggestion": {
"label": "Vorgeschlagene Redaktion"
},
"ignore": {
"label": "Redaktion ignoriert"
}
},
"tabs": {
"quick-navigation": {
"label": "Schnelle Navigation"
},
"annotations": {
"label": "Anmerkungen"
},
"info": {
"label": "Die Info",
"assign-reviewer": {
"label": "Prüfer zuweisen"
"file-preview": {
"view-toggle": {
"label": "Redigierte Ansicht"
},
"added-on": {
"label": "Hinzugefügt zu"
"filter-menu": {
"label": "Filter",
"all": {
"label": "Alle"
},
"none": {
"label": "Keiner"
},
"filter-types": {
"label": "Filtertypen"
},
"hint": {
"label": "Hinweis Anmerkung",
"hint_only": {
"label": "Nur Hinweis"
},
"vertebrate": {
"label": "Wirbeltier"
},
"names": {
"label": "Namen"
}
},
"redaction": {
"label": "Redaktion"
},
"comment": {
"label": "Kommentar Annotation"
},
"suggestion": {
"label": "Vorgeschlagene Redaktion"
},
"ignore": {
"label": "Redaktion ignoriert"
}
},
"added-by": {
"label": "Hinzugefügt von"
"tabs": {
"quick-navigation": {
"label": "Schnelle Navigation"
},
"annotations": {
"label": "Anmerkungen"
},
"info": {
"label": "Die Info",
"assign-reviewer": {
"label": "Prüfer zuweisen"
},
"added-on": {
"label": "Hinzugefügt zu"
},
"added-by": {
"label": "Hinzugefügt von"
},
"last-modified-on": {
"label": "Zuletzt geändert am"
}
}
},
"last-modified-on": {
"label": "Zuletzt geändert am"
"download": {
"label": "Herunterladen",
"dropdown": {
"original": {
"label": "Original"
},
"annotated": {
"label": "Kommentiert"
},
"redacted": {
"label": "Redigiert"
}
}
}
}
},
"download": {
"label": "Herunterladen",
"dropdown": {
"original": {
"label": "Original"
},
"annotated": {
"label": "Kommentiert"
},
"redacted": {
"label": "Redigiert"
"initials-avatar": {
"unassigned": {
"label": "Nicht zugewiesen"
}
}
}
},
"initials-avatar": {
"unassigned": {
"label": "Nicht zugewiesen"
}
},
"unassigned": "Nicht zugewiesen",
"under-review": "Wird überprüft",
"under-approval": "In Genehmigung",
"efsa": "EFSA-Zulassung",
"finished": "Fertig",
"approved": "Genehmigt",
"submitted": "Eingereicht",
"active": "Aktiv",
"archived": "Archiviert",
"hint": "Hinweis",
"ignore": "Ignorieren",
"redaction": "Redaktion",
"comment": "Kommentar",
"suggestion": "Redaktionsvorschlag",
"dictionary": "Wörterbuch",
"content": "Inhalt",
"page": "Seite"
},
"unassigned": "Nicht zugewiesen",
"under-review": "Wird überprüft",
"under-approval": "In Genehmigung",
"efsa": "EFSA-Zulassung",
"finished": "Fertig",
"approved": "Genehmigt",
"submitted": "Eingereicht",
"active": "Aktiv",
"archived": "Archiviert",
"hint": "Hinweis",
"ignore": "Ignorieren",
"redaction": "Redaktion",
"comment": "Kommentar",
"suggestion": "Redaktionsvorschlag",
"dictionary": "Wörterbuch",
"content": "Inhalt",
"page": "Seite"
}

View File

@ -1,7 +1,7 @@
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>

View File

@ -1,41 +1,45 @@
@import "red-variables";
@import 'red-variables';
.mat-button, .mat-flat-button {
font-family: Inter, sans-serif !important;
border-radius: 17px !important;
font-size: 13px !important;
height: 34px;
display: flex !important;
align-items: center;
&.mat-primary {
font-weight: 500 !important;
}
&:not(.mat-primary) {
font-weight: 400 !important;
}
.mat-button-wrapper {
display: flex;
.mat-button,
.mat-flat-button {
font-family: Inter, sans-serif !important;
border-radius: 17px !important;
font-size: 13px !important;
height: 34px;
display: flex !important;
align-items: center;
&.mat-primary {
font-weight: 500 !important;
}
&:not(.mat-primary) {
font-weight: 400 !important;
}
.mat-button-wrapper {
display: flex;
align-items: center;
gap: 6px;
}
}
.mat-button[aria-expanded='true'],
.mat-button.overlay {
background: rgba($primary, 0.1);
}
.mat-button,
.mat-flat-button,
.mat-icon-button {
gap: 6px;
}
mat-icon {
width: 14px;
}
}
.mat-button[aria-expanded="true"], .mat-button.overlay {
background: rgba($primary, 0.1);
}
.mat-button, .mat-flat-button, .mat-icon-button {
gap: 6px;
mat-icon {
width: 14px;
}
}
.arrow-button mat-icon {
.arrow-button mat-icon {
width: 9px;
margin-left: 3px;
}

View File

@ -1,14 +1,15 @@
@import "red-variables";
@import 'red-variables';
.mat-checkbox .mat-checkbox-frame {
border: 1px solid $grey-5;
border: 1px solid $grey-5;
}
.mat-checkbox-indeterminate.mat-accent .mat-checkbox-background, .mat-checkbox-checked.mat-accent .mat-checkbox-background {
margin-top: 1px;
margin-left: 1px;
width: 18px;
height: 18px;
.mat-checkbox-indeterminate.mat-accent .mat-checkbox-background,
.mat-checkbox-checked.mat-accent .mat-checkbox-background {
margin-top: 1px;
margin-left: 1px;
width: 18px;
height: 18px;
}
.mat-checkbox-layout {

View File

@ -1,70 +1,71 @@
@import "red-variables";
@import 'red-variables';
.oval, .square {
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
height: 24px;
width: 24px;
font-size: 10px;
line-height: 12px;
text-align: center;
text-transform: uppercase;
border: none;
&.large {
height: 32px;
width: 32px;
font-size: 13px;
}
&.lightgray-dark {
background-color: $grey-4;
}
&.lightgray-red {
background-color: $grey-4;
color: $red-1;
}
&.darkgray-white {
background-color: $grey-1;
color: $white;
}
&.lightgray-white {
background-color: $grey-5;
color: $white;
}
&.red-white {
background-color: $red-1;
color: $white;
}
&.white-dark {
border: 1px solid #E2E4E9;
}
}
.oval {
border-radius: 50%;
}
.stats-subtitle {
display: flex;
> div {
.oval,
.square {
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
}
height: 24px;
width: 24px;
font-size: 10px;
line-height: 12px;
text-align: center;
text-transform: uppercase;
border: none;
gap: 12px;
&.large {
height: 32px;
width: 32px;
font-size: 13px;
}
mat-icon {
width: 10px;
margin-right: 4px;
}
&.lightgray-dark {
background-color: $grey-4;
}
&.lightgray-red {
background-color: $grey-4;
color: $red-1;
}
&.darkgray-white {
background-color: $grey-1;
color: $white;
}
&.lightgray-white {
background-color: $grey-5;
color: $white;
}
&.red-white {
background-color: $red-1;
color: $white;
}
&.white-dark {
border: 1px solid #e2e4e9;
}
}
.oval {
border-radius: 50%;
}
.stats-subtitle {
display: flex;
> div {
display: flex;
justify-content: center;
align-items: center;
}
gap: 12px;
mat-icon {
width: 10px;
margin-right: 4px;
}
}

View File

@ -1,40 +1,38 @@
@import "red-variables";
@import 'red-variables';
.btn-group {
display: flex;
flex-direction: row;
display: flex;
flex-direction: row;
.btn-group-btn {
cursor: pointer;
color: $accent;
background: $white;
font-family: Inter, sans-serif;
font-size: 13px;
line-height: 14px;
padding: 10px 14px;
transition: color 0.25s ease-in-out;
outline: none;
border: none;
.btn-group-btn {
cursor: pointer;
color: $accent;
background: $white;
font-family: Inter, sans-serif;
font-size: 13px;
line-height: 14px;
padding: 10px 14px;
transition: color 0.25s ease-in-out;
outline: none;
border: none;
&:hover {
color: $black;
&:hover {
color: $black;
}
&.active {
color: $light;
background: $primary;
border-radius: 17px;
}
&.active:hover {
color: $grey-3;
}
}
&.active {
color: $light;
background: $primary;
border-radius: 17px;
}
&.active:hover {
color: $grey-3;
}
}
}
.icon-10 {
width: 10px;
height: 10px;
width: 10px;
height: 10px;
}

View File

@ -3,33 +3,33 @@
}
.dialog {
position: relative;
min-height: 80px;
position: relative;
min-height: 80px;
.dialog-close {
position: absolute;
top: -15px;
right: -10px;
.dialog-close {
position: absolute;
top: -15px;
right: -10px;
mat-icon {
width: 12px;
mat-icon {
width: 12px;
}
}
}
.dialog-header {
padding-bottom: 12px;
}
.dialog-content {
padding-top: 12px;
padding-bottom: 12px;
}
.dialog-actions {
padding-top: 12px;
display: flex;
> * {
margin-right: 16px
.dialog-header {
padding-bottom: 12px;
}
.dialog-content {
padding-top: 12px;
padding-bottom: 12px;
}
.dialog-actions {
padding-top: 12px;
display: flex;
> * {
margin-right: 16px;
}
}
}
}

View File

@ -1,13 +1,13 @@
@import "red-variables";
@import 'red-variables';
.mat-list-item {
font-family: Inter, sans-serif;
color: $grey-1 !important;
font-size: 13px !important;
line-height: 16px !important;
font-family: Inter, sans-serif;
color: $grey-1 !important;
font-size: 13px !important;
line-height: 16px !important;
}
.list-50vh {
overflow-y: scroll;
max-height: 50vh;
overflow-y: scroll;
max-height: 50vh;
}

View File

@ -1,24 +1,24 @@
@import "red-variables";
@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: $primary;
}
.line-2 {
height: 6px;
height: 14px;
width: 22px;
border-radius: 6px;
background-color: $primary;
}
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-direction: column;
.line-1 {
height: 6px;
width: 16px;
border-radius: 3px;
background-color: $primary;
}
.line-2 {
height: 6px;
width: 22px;
border-radius: 6px;
background-color: $primary;
}
}

View File

@ -4,56 +4,57 @@
@include mat-core();
$primary-palette: (
default: $red-1,
lighter: lighten($red-1, 30%),
darker: darken($red-1, 30%),
text: $red-1,
contrast: (
default: $light,
lighter: $light,
darker: $light
)
default: $red-1,
lighter: lighten($red-1, 30%),
darker: darken($red-1, 30%),
text: $red-1,
contrast: (
default: $light,
lighter: $light,
darker: $light
)
);
$secondary-palette: (
default: $grey-1,
lighter: lighten($grey-1, 30%),
darker: darken($grey-1, 30%),
text: $grey-1,
contrast: (
default: $light,
lighter: $light,
darker: $light
)
default: $grey-1,
lighter: lighten($grey-1, 30%),
darker: darken($grey-1, 30%),
text: $grey-1,
contrast: (
default: $light,
lighter: $light,
darker: $light
)
);
$red-palette: (
default: $red-1,
lighter: lighten($red-1, 30%),
darker: darken($red-1, 30%),
text: $red-1,
contrast: (
default: $light,
lighter: $light,
darker: $light
)
default: $red-1,
lighter: lighten($red-1, 30%),
darker: darken($red-1, 30%),
text: $red-1,
contrast: (
default: $light,
lighter: $light,
darker: $light
)
);
$gn-next-primary: mat-palette($primary-palette, default, lighter, darker, text);
$gn-next-secondary: mat-palette($secondary-palette, default, lighter, darker, text);
$gn-next-warning: mat-palette($red-palette, default, lighter, darker, text);
$gn-next-mat-theme: mat-light-theme((
color: (
primary: $gn-next-primary,
accent: $gn-next-secondary,
warn: $gn-next-warning,
)
));
$gn-next-mat-theme: mat-light-theme(
(
color: (
primary: $gn-next-primary,
accent: $gn-next-secondary,
warn: $gn-next-warning
)
)
);
@include angular-material-theme($gn-next-mat-theme);
.mat-flat-button {
min-width: unset !important;
min-width: unset !important;
}

View File

@ -1,17 +1,15 @@
@media only screen and (max-width: 800px) {
.visible-lg {
display: none !important;
}
.visible-lg {
display: none !important;
}
}
.visible-lt-lg {
display: none !important;
display: none !important;
}
@media only screen and (max-width: 800px) {
.visible-lt-lg {
display: flex !important;
}
.visible-lt-lg {
display: flex !important;
}
}

View File

@ -1,12 +1,12 @@
@import 'red-variables';
@mixin line-clamp($lines) {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin no-scroll-bar {

View File

@ -1,52 +1,52 @@
@import "red-variables";
@import 'red-variables';
.red-select {
.mat-select-value-text {
font-family: Inter, sans-serif;
color: $grey-1;
font-size: 13px;
line-height: 14px;
}
.mat-form-field-wrapper {
padding-bottom: 0;
}
.mat-form-field-infix {
padding: 0;
border: none;
width: fit-content;
}
.mat-select-value {
max-width: none;
}
.mat-select-arrow-wrapper {
padding-left: 5px;
.mat-select-arrow {
color: $grey-1 !important;
.mat-select-value-text {
font-family: Inter, sans-serif;
color: $grey-1;
font-size: 13px;
line-height: 14px;
}
.mat-form-field-wrapper {
padding-bottom: 0;
}
.mat-form-field-infix {
padding: 0;
border: none;
width: fit-content;
}
.mat-select-value {
max-width: none;
}
.mat-select-arrow-wrapper {
padding-left: 5px;
.mat-select-arrow {
color: $grey-1 !important;
}
}
}
}
.red-select-panel {
border-radius: 0 !important;
border-radius: 0 !important;
.mat-option {
background: $white !important;
font-family: Inter, sans-serif;
color: $grey-1;
font-size: 13px;
line-height: 14px;
.mat-option {
background: $white !important;
font-family: Inter, sans-serif;
color: $grey-1;
font-size: 13px;
line-height: 14px;
&.mat-selected.mat-active {
color: $primary;
&.mat-selected.mat-active {
color: $primary;
}
&:hover {
background: $grey-2 !important;
}
}
&:hover {
background: $grey-2 !important;
}
}
}

View File

@ -1,71 +1,71 @@
@import "red-variables";
@import "red-mixins";
@import 'red-variables';
@import 'red-mixins';
a {
color: $primary;
color: $primary;
&:hover {
color: lighten($primary, 10%)
}
&:hover {
color: lighten($primary, 10%);
}
cursor: pointer;
cursor: pointer;
}
.heading-xl {
font-size: 24px;
font-weight: 600;
line-height: 29px;
font-size: 24px;
font-weight: 600;
line-height: 29px;
}
.heading-l {
font-size: 18px;
font-weight: 600;
line-height: 22px;
font-size: 18px;
font-weight: 600;
line-height: 22px;
}
.heading {
font-size: 16px;
line-height: 20px;
font-weight: 600;
font-size: 16px;
line-height: 20px;
font-weight: 600;
}
.page-title {
font-size: 13px;
font-weight: 600;
line-height: 18px;
text-align: center;
font-size: 13px;
font-weight: 600;
line-height: 18px;
text-align: center;
}
.all-caps-label {
text-transform: uppercase;
opacity: 0.7;
font-size: 11px;
font-weight: 600;
letter-spacing: 0;
line-height: 14px;
text-transform: uppercase;
opacity: 0.7;
font-size: 11px;
font-weight: 600;
letter-spacing: 0;
line-height: 14px;
}
.small-label {
opacity: 0.7;
font-size: 11px;
line-height: 14px;
opacity: 0.7;
font-size: 11px;
line-height: 14px;
}
.medium-label {
font-size: 11px;
font-weight: 500;
line-height: 14px;
font-size: 11px;
font-weight: 500;
line-height: 14px;
}
.clamp-1 {
@include line-clamp(1);
@include line-clamp(1);
}
.clamp-2 {
@include line-clamp(2);
@include line-clamp(2);
}
.primary {
color: $primary;
opacity: 1;
color: $primary;
opacity: 1;
}

View File

@ -1,19 +1,19 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@300;400;500;600;700&display=swap');
@import '~ngx-toastr/toastr';
@import "red-material-theme";
@import "red-page-layout";
@import "red-text-styles";
@import "red-dialog";
@import "red-input";
@import "red-button";
@import "red-select";
@import "red-list";
@import "red-checkbox";
@import "red-toggle";
@import "red-menu";
@import "red-media-queries";
@import "red-tables";
@import "red-components";
@import "red-controls";
@import "red-logo";
@import 'red-material-theme';
@import 'red-page-layout';
@import 'red-text-styles';
@import 'red-dialog';
@import 'red-input';
@import 'red-button';
@import 'red-select';
@import 'red-list';
@import 'red-checkbox';
@import 'red-toggle';
@import 'red-menu';
@import 'red-media-queries';
@import 'red-tables';
@import 'red-components';
@import 'red-controls';
@import 'red-logo';

View File

@ -1,19 +1,19 @@
mat-slide-toggle {
.mat-slide-toggle-bar {
height: 16px !important;
width: 30px !important;
border-radius: 16px !important;
}
.mat-slide-toggle-bar {
height: 16px !important;
width: 30px !important;
border-radius: 16px !important;
}
.mat-slide-toggle-thumb-container {
top: 2px !important;
left: 2px !important;
height: 12px !important;
width: 12px !important;
}
.mat-slide-toggle-thumb-container {
top: 2px !important;
left: 2px !important;
height: 12px !important;
width: 12px !important;
}
.mat-slide-toggle-thumb {
height: 12px !important;
width: 12px !important;
}
.mat-slide-toggle-thumb {
height: 12px !important;
width: 12px !important;
}
}

View File

@ -1,31 +1,33 @@
$white: #FFF;
$white: #fff;
$black: #000;
$grey-1: #283241;
$grey-2: #F4F5F7;
$grey-3: #AAACB3;
$grey-4: #E2E4E9;
$grey-5: #D3D5DA;
$grey-6: #F0F1F4;
$grey-2: #f4f5f7;
$grey-3: #aaacb3;
$grey-4: #e2e4e9;
$grey-5: #d3d5da;
$grey-6: #f0f1f4;
$blue-1: #4875F7;
$blue-2: #48C9F7;
$blue-3: #5B97DB;
$blue-4: #374C81;
$red-1: #DD4D50;
$yellow-1: #FFB83B;
$yellow-2: #FFFF02;
$green-1: #00FF00;
$green-2: #5CE594;
$orange-1: #FF801A;
$blue-1: #4875f7;
$blue-2: #48c9f7;
$blue-3: #5b97db;
$blue-4: #374c81;
$red-1: #dd4d50;
$yellow-1: #ffb83b;
$yellow-2: #ffff02;
$green-1: #00ff00;
$green-2: #5ce594;
$orange-1: #ff801a;
$primary: $red-1;
$accent: $grey-1;
$light: $white;
$dark: $black;
$separator: rgba(226,228,233,0.9);
$separator: rgba(226, 228, 233, 0.9);
$right-container-inside-width: 340px;
$right-container-padding: 16px;
$right-container-width: calc(#{$right-container-inside-width} + 2*#{$right-container-padding} + 1px);
$right-container-width: calc(
#{$right-container-inside-width} + 2 *#{$right-container-padding} + 1px
);

View File

@ -1,3 +1,3 @@
export const environment = {
production: true,
production: true
};

View File

@ -3,7 +3,7 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
production: false
};
/*

View File

@ -1,16 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Redacto</title>
<base href="/"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link href="favicon.ico" rel="icon" type="image/x-icon"/>
<link href="manifest.webmanifest" rel="manifest">
<meta content="#1976d2" name="theme-color">
</head>
<body>
<redaction-root></redaction-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
<head>
<meta charset="utf-8" />
<title>Redacto</title>
<base href="/" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link href="favicon.ico" rel="icon" type="image/x-icon" />
<link href="manifest.webmanifest" rel="manifest" />
<meta content="#1976d2" name="theme-color" />
</head>
<body>
<redaction-root></redaction-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>

View File

@ -1,13 +1,13 @@
import {enableProdMode} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module';
import {environment} from './environments/environment';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
.bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@ -1,59 +1,59 @@
{
"name": "red-ui",
"short_name": "red-ui",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
"name": "red-ui",
"short_name": "red-ui",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}

View File

@ -1,2 +1,2 @@
/* You can add global styles to this file, and also import other style files */
@import "./assets/styles/red-theme";
@import './assets/styles/red-theme';

View File

@ -1,8 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
},
"files": ["src/main.ts", "src/polyfills.ts"]
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
},
"files": ["src/main.ts", "src/polyfills.ts"]
}

View File

@ -1,13 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -1,10 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
}

View File

@ -1,10 +1,10 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [true, "attribute", "redaction", "camelCase"],
"component-selector": [true, "element", "redaction", "kebab-case"]
},
"linterOptions": {
"exclude": ["!**/*"]
}
"extends": "../../tslint.json",
"rules": {
"directive-selector": [true, "attribute", "redaction", "camelCase"],
"component-selector": [true, "element", "redaction", "kebab-case"]
},
"linterOptions": {
"exclude": ["!**/*"]
}
}

Some files were not shown because too many files have changed in this diff Show More