set max line length to 100 and set indent size for .json files to 2

This commit is contained in:
Dan Percic 2021-05-13 15:50:07 +03:00
parent 6bf6ca70d0
commit 17979c0448
213 changed files with 8149 additions and 4430 deletions

View File

@ -8,7 +8,11 @@ indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
ij_html_quote_style = double
max_line_length = 100
[*.md]
max_line_length = off
trim_trailing_whitespace = false
[{*.json, .prettierrc, .eslintrc}]
indent_size = 2

View File

@ -1,182 +1,190 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nrwl/nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nrwl/nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nrwl/nx/typescript"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nrwl/nx/javascript"],
"rules": {}
},
{
"files": ["*.ts"],
"rules": {
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "error",
"@angular-eslint/no-input-rename": "error",
"@angular-eslint/no-inputs-metadata-property": "error",
"@angular-eslint/no-output-native": "error",
"@angular-eslint/no-output-on-prefix": "error",
"@angular-eslint/no-output-rename": "error",
"@angular-eslint/no-outputs-metadata-property": "error",
"@angular-eslint/use-lifecycle-interface": "error",
"@angular-eslint/use-pipe-transform-interface": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/explicit-member-accessibility": [
"warn",
{
"accessibility": "no-public"
}
],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "memberLike",
"modifiers": ["readonly"],
"format": ["UPPER_CASE", "camelCase"]
},
{
"selector": "enumMember",
"format": ["UPPER_CASE"]
},
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "memberLike",
"modifiers": ["protected"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "memberLike",
"modifiers": ["private", "readonly"],
"format": ["UPPER_CASE", "camelCase"],
"leadingUnderscore": "require"
}
],
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-inferrable-types": [
"error",
{
"ignoreParameters": true
}
],
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-shadow": [
"error",
{
"hoist": "all"
}
],
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "error",
"constructor-super": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"id-blacklist": "off",
"id-match": "off",
"import/no-deprecated": "warn",
"no-bitwise": "error",
"no-caller": "error",
"no-console": [
"error",
{
"allow": [
"log",
"warn",
"dir",
"timeLog",
"assert",
"clear",
"count",
"countReset",
"group",
"groupEnd",
"table",
"dirxml",
"error",
"groupCollapsed",
"Console",
"profile",
"profileEnd",
"timeStamp",
"context"
]
}
],
"no-debugger": "error",
"no-empty": "off",
"no-eval": "error",
"no-fallthrough": "error",
"no-new-wrappers": "error",
"no-restricted-imports": ["error", "rxjs/Rx"],
"no-throw-literal": "error",
"no-undef-init": "error",
"no-underscore-dangle": "off",
"no-var": "error",
"prefer-const": "error",
"radix": "error"
},
"plugins": ["eslint-plugin-import", "@angular-eslint/eslint-plugin", "@typescript-eslint"]
},
{
"files": ["*.html"],
"rules": {
"@angular-eslint/template/banana-in-box": "error",
"@angular-eslint/template/no-negated-async": "error"
},
"plugins": ["@angular-eslint/eslint-plugin-template"]
}
]
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nrwl/nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nrwl/nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nrwl/nx/typescript"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nrwl/nx/javascript"],
"rules": {}
},
{
"files": ["*.ts"],
"rules": {
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "error",
"@angular-eslint/no-input-rename": "error",
"@angular-eslint/no-inputs-metadata-property": "error",
"@angular-eslint/no-output-native": "error",
"@angular-eslint/no-output-on-prefix": "error",
"@angular-eslint/no-output-rename": "error",
"@angular-eslint/no-outputs-metadata-property": "error",
"@angular-eslint/use-lifecycle-interface": "error",
"@angular-eslint/use-pipe-transform-interface": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/explicit-member-accessibility": [
"warn",
{
"accessibility": "no-public"
}
],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "memberLike",
"modifiers": ["readonly"],
"format": ["UPPER_CASE", "camelCase"]
},
{
"selector": "enumMember",
"format": ["UPPER_CASE"]
},
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "memberLike",
"modifiers": ["protected"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": "memberLike",
"modifiers": ["private", "readonly"],
"format": ["UPPER_CASE", "camelCase"],
"leadingUnderscore": "require"
}
],
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-inferrable-types": [
"error",
{
"ignoreParameters": true
}
],
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-shadow": [
"error",
{
"hoist": "all"
}
],
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "error",
"constructor-super": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"id-blacklist": "off",
"id-match": "off",
"import/no-deprecated": "warn",
"no-bitwise": "error",
"no-caller": "error",
"no-console": [
"error",
{
"allow": [
"log",
"warn",
"dir",
"timeLog",
"assert",
"clear",
"count",
"countReset",
"group",
"groupEnd",
"table",
"dirxml",
"error",
"groupCollapsed",
"Console",
"profile",
"profileEnd",
"timeStamp",
"context"
]
}
],
"no-debugger": "error",
"no-empty": "off",
"no-eval": "error",
"no-fallthrough": "error",
"no-new-wrappers": "error",
"no-restricted-imports": ["error", "rxjs/Rx"],
"no-throw-literal": "error",
"no-undef-init": "error",
"no-underscore-dangle": "off",
"no-var": "error",
"prefer-const": "error",
"radix": "error",
"max-len": [
"error",
{
"code": 100,
"tabWidth": 4,
"ignorePattern": "^import .*"
}
]
},
"plugins": ["eslint-plugin-import", "@angular-eslint/eslint-plugin", "@typescript-eslint"]
},
{
"files": ["*.html"],
"rules": {
"@angular-eslint/template/banana-in-box": "error",
"@angular-eslint/template/no-negated-async": "error"
},
"plugins": ["@angular-eslint/eslint-plugin-template"]
}
]
}

View File

@ -2,3 +2,5 @@
/dist
/coverage
/node_modules
/bamboo-specs

View File

@ -1,6 +1,6 @@
{
"useTabs": false,
"printWidth": 160,
"printWidth": 100,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "none",
@ -10,6 +10,12 @@
"options": {
"parser": "angular"
}
},
{
"files": ["*.json"],
"options": {
"tabWidth": 2
}
}
]
}

View File

@ -1,194 +1,195 @@
{
"version": 1,
"cli": {
"defaultCollection": "@nrwl/angular",
"analytics": false,
"packageManager": "yarn"
"$schema": "node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"defaultCollection": "@nrwl/angular",
"analytics": false,
"packageManager": "yarn"
},
"defaultProject": "red-ui",
"schematics": {
"@nrwl/angular:application": {
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"defaultProject": "red-ui",
"schematics": {
"@nrwl/angular:application": {
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"@nrwl/angular:library": {
"unitTestRunner": "jest"
}
},
"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/"
},
{
"glob": "**/*",
"input": "node_modules/ace-builds/src-min/",
"output": "/assets/ace-builds"
},
"apps/red-ui/src/manifest.webmanifest"
],
"styles": ["apps/red-ui/src/styles.scss"],
"scripts": [
"node_modules/@pdftron/webviewer/webviewer.min.js",
"node_modules/ace-builds/src-min/ace.js",
"node_modules/ace-builds/src-min/mode-java.js",
"node_modules/ace-builds/src-min/theme-eclipse.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,
"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"
}
},
"outputs": ["{options.outputPath}"]
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "red-ui:build"
},
"configurations": {
"production": {
"browserTarget": "red-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "red-ui:build"
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["apps/red-ui/src/**/*.ts", "apps/red-ui/src/**/*.html"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "apps/red-ui/jest.config.js",
"passWithNoTests": true
},
"outputs": ["coverage/apps/red-ui"]
}
}
},
"red-ui-http": {
"projectType": "library",
"root": "libs/red-ui-http",
"sourceRoot": "libs/red-ui-http/src",
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "libs/red-ui-http/tsconfig.lib.json",
"project": "libs/red-ui-http/ng-package.json"
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["libs/red-ui-http/src/**/*.ts", "libs/red-ui-http/src/**/*.html"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/red-ui-http/jest.config.js",
"passWithNoTests": true
},
"outputs": ["coverage/libs/red-ui-http"]
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
},
"red-cache": {
"projectType": "library",
"root": "libs/red-cache",
"sourceRoot": "libs/red-cache/src",
"prefix": "redaction",
"architect": {
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["libs/red-cache/src/**/*.ts", "libs/red-cache/src/**/*.html"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/red-cache/jest.config.js",
"passWithNoTests": true
},
"outputs": ["coverage/libs/red-cache"]
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
}
"@nrwl/angular:library": {
"unitTestRunner": "jest"
}
},
"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/"
},
{
"glob": "**/*",
"input": "node_modules/ace-builds/src-min/",
"output": "/assets/ace-builds"
},
"apps/red-ui/src/manifest.webmanifest"
],
"styles": ["apps/red-ui/src/styles.scss"],
"scripts": [
"node_modules/@pdftron/webviewer/webviewer.min.js",
"node_modules/ace-builds/src-min/ace.js",
"node_modules/ace-builds/src-min/mode-java.js",
"node_modules/ace-builds/src-min/theme-eclipse.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,
"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"
}
},
"outputs": ["{options.outputPath}"]
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "red-ui:build"
},
"configurations": {
"production": {
"browserTarget": "red-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "red-ui:build"
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["apps/red-ui/src/**/*.ts", "apps/red-ui/src/**/*.html"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "apps/red-ui/jest.config.js",
"passWithNoTests": true
},
"outputs": ["coverage/apps/red-ui"]
}
}
},
"red-ui-http": {
"projectType": "library",
"root": "libs/red-ui-http",
"sourceRoot": "libs/red-ui-http/src",
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "libs/red-ui-http/tsconfig.lib.json",
"project": "libs/red-ui-http/ng-package.json"
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["libs/red-ui-http/src/**/*.ts", "libs/red-ui-http/src/**/*.html"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/red-ui-http/jest.config.js",
"passWithNoTests": true
},
"outputs": ["coverage/libs/red-ui-http"]
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
},
"red-cache": {
"projectType": "library",
"root": "libs/red-cache",
"sourceRoot": "libs/red-cache/src",
"prefix": "redaction",
"architect": {
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["libs/red-cache/src/**/*.ts", "libs/red-cache/src/**/*.html"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/red-cache/jest.config.js",
"passWithNoTests": true
},
"outputs": ["coverage/libs/red-cache"]
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
}
}
}

View File

@ -1,37 +1,40 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
"parserOptions": {
"project": ["apps/red-ui/tsconfig.*?.json"]
},
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "redaction",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "redaction",
"style": "kebab-case"
}
]
},
"plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"]
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nrwl/nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"parserOptions": {
"project": ["apps/red-ui/tsconfig.*?.json"]
},
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "redaction",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "redaction",
"style": "kebab-case"
}
]
},
"plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"]
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
}

View File

@ -4,7 +4,12 @@ module.exports = {
globals: {
'ts-jest': {
stringifyContentPathRegex: '\\.(html|svg)$',
astTransformers: { before: ['jest-preset-angular/build/InlineFilesTransformer', 'jest-preset-angular/build/StripStylesTransformer'] },
astTransformers: {
before: [
'jest-preset-angular/build/InlineFilesTransformer',
'jest-preset-angular/build/StripStylesTransformer'
]
},
tsconfig: '<rootDir>/tsconfig.spec.json'
}
},

View File

@ -1,21 +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

@ -43,7 +43,8 @@ const routes = [
{
path: 'main/projects',
component: BaseScreenComponent,
loadChildren: () => import('./modules/projects/projects.module').then((m) => m.ProjectsModule)
loadChildren: () =>
import('./modules/projects/projects.module').then((m) => m.ProjectsModule)
},
{
path: 'main/downloads',

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

@ -8,5 +8,8 @@ import { RouterHistoryService } from '@services/router-history.service';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(public appLoadStateService: AppLoadStateService, private readonly _routerHistoryService: RouterHistoryService) {}
constructor(
public appLoadStateService: AppLoadStateService,
private readonly _routerHistoryService: RouterHistoryService
) {}
}

View File

@ -47,7 +47,14 @@ function cleanupBaseUrl(baseUrl: string) {
const screens = [BaseScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastComponent, NotificationsComponent, ...screens];
const components = [
AppComponent,
LogoComponent,
AuthErrorComponent,
ToastComponent,
NotificationsComponent,
...screens
];
@NgModule({
declarations: [...components],
@ -113,7 +120,11 @@ export class AppModule {
private _configureKeyCloakRouteHandling() {
this._route.queryParamMap.subscribe((queryParams) => {
if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
if (
queryParams.has('code') ||
queryParams.has('state') ||
queryParams.has('session_state')
) {
this._router.navigate([], {
queryParams: {
state: null,

View File

@ -1,8 +1,15 @@
<section>
<p *ngIf="!configuredAdminName && !configuredAdminUrl" [translate]="'auth-error.heading'" class="heading-xl"></p>
<p
*ngIf="!configuredAdminName && !configuredAdminUrl"
[translate]="'auth-error.heading'"
class="heading-xl"
></p>
<p
*ngIf="configuredAdminName && configuredAdminUrl"
[innerHTML]="'auth-error.heading-with-name-and-link' | translate: { adminName: configuredAdminName, adminUrl: configuredAdminUrl }"
[innerHTML]="
'auth-error.heading-with-name-and-link'
| translate: { adminName: configuredAdminName, adminUrl: configuredAdminUrl }
"
class="heading-xl"
></p>
<p

View File

@ -11,10 +11,15 @@ export class AuthErrorComponent implements OnInit {
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.configuredAdminName = this._appConfigService.getConfig(
AppConfigKey.ADMIN_CONTACT_NAME
);
this.configuredAdminUrl = this._appConfigService.getConfig(AppConfigKey.ADMIN_CONTACT_URL);
}

View File

@ -6,20 +6,36 @@
<mat-icon svgIcon="red:menu"></mat-icon>
</button>
<mat-menu #menuNav="matMenu">
<button mat-menu-item routerLink="/main/projects" translate="top-bar.navigation-items.projects"></button>
<button *ngIf="appStateService.activeProject" [routerLink]="'/main/projects/' + appStateService.activeProjectId" mat-menu-item>
<button
mat-menu-item
routerLink="/main/projects"
translate="top-bar.navigation-items.projects"
></button>
<button
*ngIf="appStateService.activeProject"
[routerLink]="'/main/projects/' + appStateService.activeProjectId"
mat-menu-item
>
{{ appStateService.activeProject.project.projectName }}
</button>
<button
*ngIf="appStateService.activeFile"
[routerLink]="'/main/projects/' + appStateService.activeProjectId + '/file/' + appStateService.activeFile.fileId"
[routerLink]="
'/main/projects/' +
appStateService.activeProjectId +
'/file/' +
appStateService.activeFile.fileId
"
mat-menu-item
>
{{ appStateService.activeFile.filename }}
</button>
</mat-menu>
</div>
<div *ngIf="permissionsService.isUser()" class="menu flex-2 visible-lg breadcrumbs-container">
<div
*ngIf="permissionsService.isUser()"
class="menu flex-2 visible-lg breadcrumbs-container"
>
<a
*ngIf="projectsView"
[routerLinkActiveOptions]="{ exact: true }"
@ -33,8 +49,15 @@
{{ 'top-bar.navigation-items.back' | translate }}
</a>
<ng-container *ngIf="projectsView">
<mat-icon *ngIf="!appStateService.activeProject" class="primary" svgIcon="red:arrow-down"></mat-icon>
<mat-icon *ngIf="appStateService.activeProject" svgIcon="red:arrow-right"></mat-icon>
<mat-icon
*ngIf="!appStateService.activeProject"
class="primary"
svgIcon="red:arrow-down"
></mat-icon>
<mat-icon
*ngIf="appStateService.activeProject"
svgIcon="red:arrow-right"
></mat-icon>
<a
*ngIf="appStateService.activeProject"
[routerLinkActiveOptions]="{ exact: true }"
@ -47,7 +70,12 @@
<mat-icon *ngIf="appStateService.activeFile" svgIcon="red:arrow-right"></mat-icon>
<a
*ngIf="appStateService.activeFile"
[routerLink]="'/main/projects/' + appStateService.activeProjectId + '/file/' + appStateService.activeFile.fileId"
[routerLink]="
'/main/projects/' +
appStateService.activeProjectId +
'/file/' +
appStateService.activeFile.fileId
"
class="breadcrumb"
routerLinkActive="active"
>
@ -60,13 +88,28 @@
<redaction-logo></redaction-logo>
</redaction-hidden-action>
<div class="app-name">{{ titleService.getTitle() }}</div>
<span *ngIf="userPreferenceService.areDevFeaturesEnabled" class="dev-mode" translate="dev-mode"></span>
<span
*ngIf="userPreferenceService.areDevFeaturesEnabled"
class="dev-mode"
translate="dev-mode"
></span>
</div>
<div class="menu right flex-2">
<redaction-notifications *ngIf="userPreferenceService.areDevFeaturesEnabled" class="mr-8"></redaction-notifications>
<redaction-user-button [matMenuTriggerFor]="userMenu" [showDot]="showPendingDownloadsDot" [user]="user"></redaction-user-button>
<redaction-notifications
*ngIf="userPreferenceService.areDevFeaturesEnabled"
class="mr-8"
></redaction-notifications>
<redaction-user-button
[matMenuTriggerFor]="userMenu"
[showDot]="showPendingDownloadsDot"
[user]="user"
></redaction-user-button>
<mat-menu #userMenu="matMenu" class="user-menu" xPosition="before">
<button [routerLink]="'/main/my-profile'" mat-menu-item translate="top-bar.navigation-items.my-account.children.my-profile"></button>
<button
[routerLink]="'/main/my-profile'"
mat-menu-item
translate="top-bar.navigation-items.my-account.children.my-profile"
></button>
<button
(click)="appStateService.reset()"
*ngIf="permissionsService.isManager() || permissionsService.isUserAdmin()"

View File

@ -14,26 +14,49 @@
<div class="content-container">
<div class="header-item">
<span class="all-caps-label">
{{ 'downloads-list.table-header.title' | translate: { length: fileDownloadService.downloads.length } }}
{{
'downloads-list.table-header.title'
| translate: { length: fileDownloadService.downloads.length }
}}
</span>
</div>
<div [class.no-data]="noData" class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name label="downloads-list.table-col-names.name"></redaction-table-col-name>
<redaction-table-col-name label="downloads-list.table-col-names.size"></redaction-table-col-name>
<redaction-table-col-name label="downloads-list.table-col-names.date"></redaction-table-col-name>
<redaction-table-col-name label="downloads-list.table-col-names.status"></redaction-table-col-name>
<redaction-table-col-name
label="downloads-list.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name
label="downloads-list.table-col-names.size"
></redaction-table-col-name>
<redaction-table-col-name
label="downloads-list.table-col-names.date"
></redaction-table-col-name>
<redaction-table-col-name
label="downloads-list.table-col-names.status"
></redaction-table-col-name>
<div></div>
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="noData" icon="red:download" screen="downloads-list"></redaction-empty-state>
<redaction-empty-state
*ngIf="noData"
icon="red:download"
screen="downloads-list"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div *cdkVirtualFor="let download of fileDownloadService.downloads" class="table-item">
<div
*cdkVirtualFor="let download of fileDownloadService.downloads"
class="table-item"
>
<div>
<div [class.no-bold]="download.lastDownload" class="table-item-title heading">{{ download.filename }}</div>
<div
[class.no-bold]="download.lastDownload"
class="table-item-title heading"
>
{{ download.filename }}
</div>
</div>
<div>
<div class="small-label">
@ -61,7 +84,12 @@
>
</redaction-circle-button>
<redaction-circle-button (action)="deleteItem(download)" icon="red:trash" tooltip="downloads-list.actions.delete" type="dark-bg">
<redaction-circle-button
(action)="deleteItem(download)"
icon="red:trash"
tooltip="downloads-list.actions.delete"
type="dark-bg"
>
</redaction-circle-button>
<mat-spinner *ngIf="download.inProgress" diameter="15"></mat-spinner>

View File

@ -9,7 +9,10 @@ import { DownloadControllerService } from '@redaction/red-ui-http';
styleUrls: ['./downloads-list-screen.component.scss']
})
export class DownloadsListScreenComponent implements OnInit {
constructor(readonly fileDownloadService: FileDownloadService, private readonly _downloadControllerService: DownloadControllerService) {}
constructor(
readonly fileDownloadService: FileDownloadService,
private readonly _downloadControllerService: DownloadControllerService
) {}
get noData(): boolean {
return this.fileDownloadService.downloads.length === 0;

View File

@ -1,5 +1,14 @@
<redaction-circle-button [matMenuTriggerFor]="overlay" [showDot]="hasUnread" icon="red:notification"></redaction-circle-button>
<mat-menu #overlay="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
<redaction-circle-button
[matMenuTriggerFor]="overlay"
[showDot]="hasUnread"
icon="red:notification"
></redaction-circle-button>
<mat-menu
#overlay="matMenu"
backdropClass="notifications-backdrop"
class="notifications-menu"
xPosition="before"
>
<div *ngFor="let group of groupedNotifications | sortBy: 'desc':'dateString'">
<div class="all-caps-label">{{ day(group) }}</div>
<div
@ -15,7 +24,10 @@
</div>
<div
(click)="toggleRead(notification, $event)"
[matTooltip]="(notification.read ? 'notifications.mark-unread' : 'notifications.mark-read') | translate"
[matTooltip]="
(notification.read ? 'notifications.mark-unread' : 'notifications.mark-read')
| translate
"
class="dot"
matTooltipPosition="before"
></div>

View File

@ -2,8 +2,20 @@
<div *ngIf="title" [attr.aria-label]="title" [class]="options.titleClass">
{{ title }}
</div>
<div *ngIf="message && options.enableHtml" [class]="options.messageClass" [innerHTML]="message" aria-live="polite" role="alert"></div>
<div *ngIf="message && !options.enableHtml" [attr.aria-label]="message" [class]="options.messageClass" aria-live="polite" role="alert">
<div
*ngIf="message && options.enableHtml"
[class]="options.messageClass"
[innerHTML]="message"
aria-live="polite"
role="alert"
></div>
<div
*ngIf="message && !options.enableHtml"
[attr.aria-label]="message"
[class]="options.messageClass"
aria-live="polite"
role="alert"
>
{{ message }}
</div>

View File

@ -7,7 +7,10 @@ import { Toast, ToastPackage, ToastrService } from 'ngx-toastr';
styleUrls: ['./toast.component.scss']
})
export class ToastComponent extends Toast {
constructor(protected readonly _toastrService: ToastrService, readonly toastPackage: ToastPackage) {
constructor(
protected readonly _toastrService: ToastrService,
readonly toastPackage: ToastPackage
) {
super(_toastrService, toastPackage);
}

View File

@ -23,17 +23,29 @@
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="red-input-group">
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
<label
[translate]="
'top-bar.navigation-items.my-account.children.language.label'
"
></label>
<mat-select formControlName="language">
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'top-bar.navigation-items.my-account.children.language.' + language | translate }}
{{
'top-bar.navigation-items.my-account.children.language.' +
language | translate
}}
</mat-option>
</mat-select>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="formGroup.invalid || !(profileChanged || languageChanged)" color="primary" mat-flat-button type="submit">
<button
[disabled]="formGroup.invalid || !(profileChanged || languageChanged)"
color="primary"
mat-flat-button
type="submit"
>
{{ 'user-profile.actions.save' | translate }}
</button>
</div>
@ -42,4 +54,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -30,8 +30,10 @@ export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate
// if there are no pending changes, just allow deactivation; else confirm first
return !component.hasChanges
? true
: // NOTE: this warning message will only be shown when navigating elsewhere within your angular app;
// when navigating away from your angular app, the browser will show a generic warning message
: // NOTE: this warning message will only be shown when navigating elsewhere
// within your angular app;
// when navigating away from your angular app,
// the browser will show a generic warning message
// see http://stackoverflow.com/a/42207299/7307355
confirm(this._translateService.instant('pending-changes-guard'));
}

View File

@ -1,4 +1,10 @@
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree
} from '@angular/router';
import { Injectable, Injector } from '@angular/core';
import { from, of } from 'rxjs';
import { AppLoadStateService } from '@services/app-load-state.service';
@ -7,7 +13,11 @@ import { AppLoadStateService } from '@services/app-load-state.service';
providedIn: 'root'
})
export class CompositeRouteGuard implements CanActivate {
constructor(protected readonly _router: Router, protected readonly _injector: Injector, private readonly _appLoadStateService: AppLoadStateService) {}
constructor(
protected readonly _router: Router,
protected readonly _injector: Injector,
private readonly _appLoadStateService: AppLoadStateService
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
this._appLoadStateService.pushLoadingEvent(true);
@ -21,7 +31,10 @@ export class CompositeRouteGuard implements CanActivate {
if (canActivateResult instanceof Promise) {
canActivateResult = from(canActivateResult);
}
if (typeof canActivateResult === 'boolean' || canActivateResult instanceof UrlTree) {
if (
typeof canActivateResult === 'boolean' ||
canActivateResult instanceof UrlTree
) {
canActivateResult = of(canActivateResult);
}

View File

@ -30,22 +30,34 @@ export class AnnotationPermissions {
const permissions: AnnotationPermissions = new AnnotationPermissions();
permissions.canUndo =
annotation.isUndoableSuperType && (annotation.userId === user.id || (annotation.userId && isApprover && !annotation.isSuggestion));
annotation.isUndoableSuperType &&
(annotation.userId === user.id ||
(annotation.userId && isApprover && !annotation.isSuggestion));
permissions.canForceRedaction = annotation.isSkipped && !permissions.canUndo;
permissions.canAcceptRecommendation = annotation.isRecommendation;
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && !annotation.force;
permissions.canMarkTextOnlyAsFalsePositive = annotation.canBeMarkedAsFalsePositiveWithTextOnly && !annotation.force;
permissions.canMarkAsFalsePositive =
annotation.canBeMarkedAsFalsePositive && !annotation.force;
permissions.canMarkTextOnlyAsFalsePositive =
annotation.canBeMarkedAsFalsePositiveWithTextOnly && !annotation.force;
permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted && !annotation.force;
permissions.canRemoveOrSuggestToRemoveFromDictionary =
annotation.isRedacted && !annotation.isManualRedaction && annotation.isModifyDictionary && !annotation.force;
annotation.isRedacted &&
!annotation.isManualRedaction &&
annotation.isModifyDictionary &&
!annotation.force;
permissions.canAcceptSuggestion = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
permissions.canAcceptSuggestion =
isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
permissions.canRejectSuggestion =
isApprover && (annotation.isSuggestion || (annotation.isReadyForAnalysis && !permissions.canUndo && annotation.superType !== 'pending-analysis'));
isApprover &&
(annotation.isSuggestion ||
(annotation.isReadyForAnalysis &&
!permissions.canUndo &&
annotation.superType !== 'pending-analysis'));
return permissions;
}

View File

@ -79,15 +79,28 @@ export class AnnotationWrapper {
}
get canBeMarkedAsFalsePositive() {
return (this.isRecommendation || this.superType === 'redaction') && (this.hasTextAfter || this.hasTextBefore) && !this.isImage;
return (
(this.isRecommendation || this.superType === 'redaction') &&
(this.hasTextAfter || this.hasTextBefore) &&
!this.isImage
);
}
get canBeMarkedAsFalsePositiveWithTextOnly() {
return !this.canBeMarkedAsFalsePositive && (this.isRecommendation || this.superType === 'redaction') && !this.isImage;
return (
!this.canBeMarkedAsFalsePositive &&
(this.isRecommendation || this.superType === 'redaction') &&
!this.isImage
);
}
get isSuperTypeBasedColor() {
return this.isSkipped || this.isSuggestion || this.isReadyForAnalysis || this.isDeclinedSuggestion;
return (
this.isSkipped ||
this.isSuggestion ||
this.isReadyForAnalysis ||
this.isDeclinedSuggestion
);
}
get isSkipped() {
@ -104,7 +117,10 @@ export class AnnotationWrapper {
get isFalsePositive() {
return (
this.dictionary?.toLowerCase() === 'false_positive' && (this.superType === 'skipped' || this.superType === 'hint' || this.superType === 'redaction')
this.dictionary?.toLowerCase() === 'false_positive' &&
(this.superType === 'skipped' ||
this.superType === 'hint' ||
this.superType === 'redaction')
);
}
@ -142,11 +158,18 @@ export class AnnotationWrapper {
}
get isSuggestionAdd() {
return this.superType === 'suggestion-add' || this.superType === 'suggestion-add-dictionary' || this.superType === 'suggestion-force-redaction';
return (
this.superType === 'suggestion-add' ||
this.superType === 'suggestion-add-dictionary' ||
this.superType === 'suggestion-force-redaction'
);
}
get isSuggestionRemove() {
return this.superType === 'suggestion-remove' || this.superType === 'suggestion-remove-dictionary';
return (
this.superType === 'suggestion-remove' ||
this.superType === 'suggestion-remove-dictionary'
);
}
get isModifyDictionary() {
@ -154,7 +177,10 @@ export class AnnotationWrapper {
}
get isConvertedRecommendation() {
return this.isRecommendation && (this.superType === 'suggestion-add-dictionary' || this.superType === 'add-dictionary');
return (
this.isRecommendation &&
(this.superType === 'suggestion-add-dictionary' || this.superType === 'add-dictionary')
);
}
get isRecommendation() {
@ -203,13 +229,21 @@ export class AnnotationWrapper {
return annotationWrapper;
}
private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntryWrapper) {
private static _handleRecommendations(
annotationWrapper: AnnotationWrapper,
redactionLogEntry: RedactionLogEntryWrapper
) {
if (annotationWrapper.superType === 'recommendation') {
annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length);
annotationWrapper.recommendationType = redactionLogEntry.type.substr(
'recommendation_'.length
);
}
}
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
private static _setSuperType(
annotationWrapper: AnnotationWrapper,
redactionLogEntryWrapper: RedactionLogEntryWrapper
) {
if (redactionLogEntryWrapper.recommendation) {
if (redactionLogEntryWrapper.redacted) {
annotationWrapper.superType = 'recommendation';
@ -235,23 +269,40 @@ export class AnnotationWrapper {
} else {
if (redactionLogEntryWrapper.redacted) {
if (redactionLogEntryWrapper.force) {
annotationWrapper.superType = redactionLogEntryWrapper.status === 'REQUESTED' ? 'suggestion-add' : 'redaction';
annotationWrapper.superType =
redactionLogEntryWrapper.status === 'REQUESTED'
? 'suggestion-add'
: 'redaction';
} else if (redactionLogEntryWrapper.type === 'manual') {
annotationWrapper.superType = redactionLogEntryWrapper.status === 'REQUESTED' ? 'suggestion-add' : 'manual-redaction';
annotationWrapper.superType =
redactionLogEntryWrapper.status === 'REQUESTED'
? 'suggestion-add'
: 'manual-redaction';
} else {
if (redactionLogEntryWrapper.status === 'REQUESTED') {
if (redactionLogEntryWrapper.dictionaryEntry) {
annotationWrapper.superType =
redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'suggestion-add-dictionary' : 'suggestion-remove-dictionary';
redactionLogEntryWrapper.manualRedactionType === 'ADD'
? 'suggestion-add-dictionary'
: 'suggestion-remove-dictionary';
} else {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'suggestion-add' : 'suggestion-remove';
annotationWrapper.superType =
redactionLogEntryWrapper.manualRedactionType === 'ADD'
? 'suggestion-add'
: 'suggestion-remove';
}
}
if (redactionLogEntryWrapper.status === 'APPROVED') {
if (redactionLogEntryWrapper.dictionaryEntry) {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'add-dictionary' : 'remove-dictionary';
annotationWrapper.superType =
redactionLogEntryWrapper.manualRedactionType === 'ADD'
? 'add-dictionary'
: 'remove-dictionary';
} else {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'manual-redaction' : 'remove-only-here';
annotationWrapper.superType =
redactionLogEntryWrapper.manualRedactionType === 'ADD'
? 'manual-redaction'
: 'remove-only-here';
}
}
}
@ -259,14 +310,24 @@ export class AnnotationWrapper {
}
if (!annotationWrapper.superType) {
annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'skipped';
annotationWrapper.superType = annotationWrapper.redaction
? 'redaction'
: annotationWrapper.hint
? 'hint'
: 'skipped';
}
}
private static _handleSkippedState(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
private static _handleSkippedState(
annotationWrapper: AnnotationWrapper,
redactionLogEntryWrapper: RedactionLogEntryWrapper
) {
if (annotationWrapper.superType === 'skipped') {
if (!annotationWrapper.userId) {
if (redactionLogEntryWrapper.manualRedactionType === 'REMOVE' || redactionLogEntryWrapper.manualRedactionType === 'UNDO') {
if (
redactionLogEntryWrapper.manualRedactionType === 'REMOVE' ||
redactionLogEntryWrapper.manualRedactionType === 'UNDO'
) {
annotationWrapper.superType = 'pending-analysis';
return;
}
@ -283,7 +344,10 @@ export class AnnotationWrapper {
}
}
private static _createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntryWrapper) {
private static _createContent(
annotationWrapper: AnnotationWrapper,
entry: RedactionLogEntryWrapper
) {
let content = '';
if (entry.matchedRule) {
content += 'Rule ' + entry.matchedRule + ' matched \n\n';

View File

@ -33,7 +33,12 @@ export class FileDataModel {
return this.redactionLog.redactionLogEntry;
}
getAnnotations(dictionaryData: { [p: string]: TypeValue }, currentUser: UserWrapper, viewMode: ViewMode, areDevFeaturesEnabled: boolean): AnnotationData {
getAnnotations(
dictionaryData: { [p: string]: TypeValue },
currentUser: UserWrapper,
viewMode: ViewMode,
areDevFeaturesEnabled: boolean
): AnnotationData {
const entries: RedactionLogEntryWrapper[] = this._convertData(dictionaryData);
let allAnnotations = entries.map((entry) => AnnotationWrapper.fromData(entry));
@ -70,11 +75,14 @@ export class FileDataModel {
return;
}
const redactionLogEntryWrapper: RedactionLogEntryWrapper = { actionPendingReanalysis: false };
const redactionLogEntryWrapper: RedactionLogEntryWrapper = {
actionPendingReanalysis: false
};
Object.assign(redactionLogEntryWrapper, changeLogEntry);
redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id];
redactionLogEntryWrapper.comments =
this.manualRedactions.comments[redactionLogEntryWrapper.id];
redactionLogEntryWrapper.isChangeLogEntry = true;
redactionLogEntryWrapper.changeLogType = changeLogEntry.changeType;
redactionLogEntryWrapper.id = 'changed-log-removed-' + redactionLogEntryWrapper.id;
@ -88,12 +96,17 @@ export class FileDataModel {
return;
}
const existingChangeLogEntry = this.redactionChangeLog?.redactionLogEntry?.find((rle) => rle.id === redactionLogEntry.id);
const existingChangeLogEntry = this.redactionChangeLog?.redactionLogEntry?.find(
(rle) => rle.id === redactionLogEntry.id
);
// copy the redactionLog Entry
const redactionLogEntryWrapper: RedactionLogEntryWrapper = { actionPendingReanalysis: false };
const redactionLogEntryWrapper: RedactionLogEntryWrapper = {
actionPendingReanalysis: false
};
Object.assign(redactionLogEntryWrapper, redactionLogEntry);
redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id];
redactionLogEntryWrapper.comments =
this.manualRedactions.comments[redactionLogEntryWrapper.id];
redactionLogEntryWrapper.isChangeLogEntry = !!existingChangeLogEntry;
redactionLogEntryWrapper.changeLogType = 'ADDED';
result.push(redactionLogEntryWrapper);
@ -117,7 +130,10 @@ export class FileDataModel {
relevantRedactionLogEntry.force = true;
// if statuses differ
if (!forceRedaction.processedDate || forceRedaction.status !== relevantRedactionLogEntry.status) {
if (
!forceRedaction.processedDate ||
forceRedaction.status !== relevantRedactionLogEntry.status
) {
relevantRedactionLogEntry.actionPendingReanalysis = true;
relevantRedactionLogEntry.status = forceRedaction.status;
}
@ -152,7 +168,8 @@ export class FileDataModel {
relevantRedactionLogEntry.status = manual.status;
}
} else {
// dictionary modifying requests that have been processed already updated the dictionary and should not be drawn
// dictionary modifying requests that have been processed already updated
// the dictionary and should not be drawn
if (manual.addToDictionary && this._hasAlreadyBeenProcessed(manual)) {
return;
}
@ -175,9 +192,11 @@ export class FileDataModel {
redactionLogEntryWrapper.hint = dictionary.hint;
redactionLogEntryWrapper.manualRedactionType = 'ADD';
redactionLogEntryWrapper.manual = true;
redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id];
redactionLogEntryWrapper.comments =
this.manualRedactions.comments[redactionLogEntryWrapper.id];
if (markedAsReasonRedactionLogEntry) {
// cleanup reason if the reason is another annotationId - it is not needed for drawing
// cleanup reason if the reason is another annotationId
// it is not needed for drawing
redactionLogEntryWrapper.reason = null;
}
@ -212,14 +231,18 @@ export class FileDataModel {
result.forEach((redactionLogEntry) => {
if (redactionLogEntry.manual) {
if (redactionLogEntry.manualRedactionType === 'ADD') {
const foundManualEntry = this.manualRedactions.entriesToAdd.find((me) => me.id === redactionLogEntry.id);
const foundManualEntry = this.manualRedactions.entriesToAdd.find(
(me) => me.id === redactionLogEntry.id
);
// ADD has been undone - not yet processed
if (!foundManualEntry) {
redactionLogEntry.hidden = true;
}
}
if (redactionLogEntry.manualRedactionType === 'REMOVE') {
const foundManualEntry = this.manualRedactions.idsToRemove.find((me) => me.id === redactionLogEntry.id);
const foundManualEntry = this.manualRedactions.idsToRemove.find(
(me) => me.id === redactionLogEntry.id
);
// REMOVE has been undone - not yet processed
if (!foundManualEntry) {
redactionLogEntry.manual = false;
@ -236,6 +259,9 @@ export class FileDataModel {
}
private _hasAlreadyBeenProcessed(entry: ManualRedactionEntry | IdRemoval): boolean {
return !entry.processedDate ? false : new Date(entry.processedDate).getTime() < new Date(this.fileStatus.lastProcessed).getTime();
return !entry.processedDate
? false
: new Date(entry.processedDate).getTime() <
new Date(this.fileStatus.lastProcessed).getTime();
}
}

View File

@ -5,11 +5,18 @@ export class FileStatusWrapper {
primaryAttribute: string;
searchField: string;
constructor(public fileStatus: FileStatus, public reviewerName: string, public ruleSetId: string, fileAttributesConfig?: FileAttributesConfig) {
constructor(
public fileStatus: FileStatus,
public reviewerName: string,
public ruleSetId: string,
fileAttributesConfig?: FileAttributesConfig
) {
this.searchField = fileStatus.filename;
if (fileAttributesConfig) {
const primary = fileAttributesConfig.fileAttributeConfigs?.find((c) => c.primaryAttribute);
const primary = fileAttributesConfig.fileAttributeConfigs?.find(
(c) => c.primaryAttribute
);
if (primary && fileStatus.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = fileStatus.fileAttributes?.attributeIdToValue[primary.id];
this.searchField += ' ' + this.primaryAttribute;
@ -125,7 +132,9 @@ export class FileStatusWrapper {
}
get status() {
return this.fileStatus.status === 'REPROCESS' || this.fileStatus.status === 'FULLREPROCESS' ? 'PROCESSING' : this.fileStatus.status;
return this.fileStatus.status === 'REPROCESS' || this.fileStatus.status === 'FULLREPROCESS'
? 'PROCESSING'
: this.fileStatus.status;
}
get numberOfPages() {
@ -187,6 +196,8 @@ export class FileStatusWrapper {
get newestDate() {
const uploadedDate = new Date(this.fileStatus.lastUploaded);
const updatedDate = new Date(this.fileStatus.lastUpdated);
return updatedDate && updatedDate.getTime() > uploadedDate.getTime() ? updatedDate : uploadedDate;
return updatedDate && updatedDate.getTime() > uploadedDate.getTime()
? updatedDate
: uploadedDate;
}
}

View File

@ -5,9 +5,16 @@ export class ManualAnnotationResponse {
annotationId;
commentId;
constructor(public manualRedactionEntryWrapper: ManualRedactionEntryWrapper, public manualAddResponse: ManualAddResponse) {
this.annotationId = manualAddResponse?.annotationId ? manualAddResponse.annotationId : new Date().getTime();
this.commentId = manualAddResponse?.commentId ? manualAddResponse.commentId : new Date().getTime();
constructor(
public manualRedactionEntryWrapper: ManualRedactionEntryWrapper,
public manualAddResponse: ManualAddResponse
) {
this.annotationId = manualAddResponse?.annotationId
? manualAddResponse.annotationId
: new Date().getTime();
this.commentId = manualAddResponse?.commentId
? manualAddResponse.commentId
: new Date().getTime();
}
get dictionary() {

View File

@ -77,6 +77,13 @@ const components = [
@NgModule({
declarations: [...components],
providers: [AdminDialogService],
imports: [CommonModule, SharedModule, AdminRoutingModule, AceEditorModule, NgxChartsModule, ColorPickerModule]
imports: [
CommonModule,
SharedModule,
AdminRoutingModule,
AceEditorModule,
NgxChartsModule,
ColorPickerModule
]
})
export class AdminModule {}

View File

@ -1,5 +1,10 @@
<div class="menu flex-2 visible-lg breadcrumbs-container">
<a *ngIf="root || !!appStateService.activeRuleSet" [routerLink]="'/main/admin/project-templates'" class="breadcrumb" translate="project-templates"></a>
<a
*ngIf="root || !!appStateService.activeRuleSet"
[routerLink]="'/main/admin/project-templates'"
class="breadcrumb"
translate="project-templates"
></a>
<ng-container *ngIf="appStateService.activeRuleSet">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
@ -15,7 +20,12 @@
<ng-container *ngIf="appStateService.activeDictionary">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a
[routerLink]="'/main/admin/project-templates/' + appStateService.activeRuleSetId + '/dictionaries/' + appStateService.activeDictionaryType"
[routerLink]="
'/main/admin/project-templates/' +
appStateService.activeRuleSetId +
'/dictionaries/' +
appStateService.activeDictionaryType
"
class="breadcrumb ml-0"
routerLinkActive="active"
>

View File

@ -1,8 +1,24 @@
import { Component, ContentChild, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import {
Component,
ContentChild,
EventEmitter,
HostListener,
Input,
Output,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { curveLinear } from 'd3-shape';
import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
import { BaseChartComponent, calculateViewDimensions, ColorHelper, LineSeriesComponent, ViewDimensions } from '@swimlane/ngx-charts';
import {
BaseChartComponent,
calculateViewDimensions,
ColorHelper,
LineSeriesComponent,
ViewDimensions
} from '@swimlane/ngx-charts';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
@ -261,7 +277,9 @@ export class ComboChartComponent extends BaseChartComponent {
if (this.bandwidth === undefined) {
this.bandwidth = width - this.barPadding;
}
const offset = Math.floor((width + this.barPadding - (this.bandwidth + this.barPadding) * domain.length) / 2);
const offset = Math.floor(
(width + this.barPadding - (this.bandwidth + this.barPadding) * domain.length) / 2
);
if (this.scaleType === 'time') {
scale = scaleTime().range([0, width]).domain(domain);
@ -326,7 +344,12 @@ export class ComboChartComponent extends BaseChartComponent {
domain = this.yDomain;
}
this.colors = new ColorHelper(this.scheme, this.schemeType, domain, this.customColors);
this.colorsLine = new ColorHelper(this.colorSchemeLine, this.schemeType, domain, this.customColors);
this.colorsLine = new ColorHelper(
this.colorSchemeLine,
this.schemeType,
domain,
this.customColors
);
}
getLegendOptions() {
@ -364,7 +387,9 @@ export class ComboChartComponent extends BaseChartComponent {
}
onActivate(item) {
const idx = this.activeEntries.findIndex((d) => d.name === item.name && d.value === item.value && d.series === item.series);
const idx = this.activeEntries.findIndex(
(d) => d.name === item.name && d.value === item.value && d.series === item.series
);
if (idx > -1) {
return;
}
@ -374,7 +399,9 @@ export class ComboChartComponent extends BaseChartComponent {
}
onDeactivate(item) {
const idx = this.activeEntries.findIndex((d) => d.name === item.name && d.value === item.value && d.series === item.series);
const idx = this.activeEntries.findIndex(
(d) => d.name === item.name && d.value === item.value && d.series === item.series
);
this.activeEntries.splice(idx, 1);
this.activeEntries = [...this.activeEntries];

View File

@ -1,4 +1,11 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output
} from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations';
import { formatLabel } from '@swimlane/ngx-charts';
@ -151,7 +158,10 @@ export class ComboSeriesVerticalComponent implements OnChanges {
bar.gradientStops = this.colors.getLinearGradientStops(value);
} else {
bar.color = this.colors.getColor(bar.offset1);
bar.gradientStops = this.colors.getLinearGradientStops(bar.offset1, bar.offset0);
bar.gradientStops = this.colors.getLinearGradientStops(
bar.offset1,
bar.offset0
);
}
}
@ -164,7 +174,9 @@ export class ComboSeriesVerticalComponent implements OnChanges {
const lineValue = this.seriesLine[0].series[index].value;
bar.tooltipText = `
<span class='tooltip-label'>${tooltipLabel}</span>
<span class='tooltip-val'> Y1 - ${value.toLocaleString()} Y2 - ${lineValue.toLocaleString()}%</span>
<span class='tooltip-val'>
Y1 - ${value.toLocaleString()} Y2 - ${lineValue.toLocaleString()}%
</span>
`;
return bar;
@ -177,7 +189,9 @@ export class ComboSeriesVerticalComponent implements OnChanges {
isActive(entry): boolean {
if (!this.activeEntries) return false;
const item = this.activeEntries.find((d) => entry.name === d.name && entry.series === d.series);
const item = this.activeEntries.find(
(d) => entry.name === d.name && entry.series === d.series
);
return item !== undefined;
}

View File

@ -11,7 +11,15 @@ import { AppStateService } from '@state/app-state.service';
export class SideNavComponent {
@Input() type: 'settings' | 'project-templates';
items: { [key: string]: { screen: string; onlyDevMode?: boolean; onlyAdmin?: boolean; userManagerOnly?: boolean; label?: string }[] } = {
items: {
[key: string]: {
screen: string;
onlyDevMode?: boolean;
onlyAdmin?: boolean;
userManagerOnly?: boolean;
label?: string;
}[];
} = {
settings: [
{ screen: 'project-templates', onlyAdmin: true },
{ screen: 'digital-signature', onlyAdmin: true },

View File

@ -1,5 +1,10 @@
<div class="collapsed-wrapper">
<redaction-circle-button (action)="toggleCollapse.emit()" icon="red:expand" tooltip="user-stats.expand" tooltipPosition="before"></redaction-circle-button>
<redaction-circle-button
(action)="toggleCollapse.emit()"
icon="red:expand"
tooltip="user-stats.expand"
tooltipPosition="before"
></redaction-circle-button>
<div class="all-caps-label" translate="user-stats.title"></div>
</div>

View File

@ -1,6 +1,9 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ (dictionary ? 'add-edit-dictionary.title.edit' : 'add-edit-dictionary.title.new') | translate: { name: dictionary?.type | humanize } }}
{{
(dictionary ? 'add-edit-dictionary.title.edit' : 'add-edit-dictionary.title.new')
| translate: { name: dictionary?.type | humanize }
}}
</div>
<form (submit)="saveDictionary()" [formGroup]="dictionaryForm">
@ -15,13 +18,23 @@
<div class="first-row">
<div *ngIf="!dictionary" class="red-input-group required">
<label translate="add-edit-dictionary.form.name"></label>
<input formControlName="type" name="type" placeholder="{{ 'add-edit-dictionary.form.name-placeholder' | translate }}" type="text" />
<input
formControlName="type"
name="type"
placeholder="{{ 'add-edit-dictionary.form.name-placeholder' | translate }}"
type="text"
/>
<span class="hint" translate="add-edit-dictionary.form.name-hint"></span>
</div>
<div class="red-input-group required w-75">
<label translate="add-edit-dictionary.form.rank"></label>
<input formControlName="rank" name="rank" placeholder="{{ 'add-edit-dictionary.form.rank-placeholder' | translate }}" type="number" />
<input
formControlName="rank"
name="rank"
placeholder="{{ 'add-edit-dictionary.form.rank-placeholder' | translate }}"
type="number"
/>
</div>
<div class="red-input-group required">
@ -41,7 +54,10 @@
class="input-icon"
>
<mat-icon
*ngIf="!dictionaryForm.get('hexColor').value || dictionaryForm.get('hexColor').value?.length === 0"
*ngIf="
!dictionaryForm.get('hexColor').value ||
dictionaryForm.get('hexColor').value?.length === 0
"
svgIcon="red:color-picker"
></mat-icon>
</div>
@ -53,7 +69,9 @@
<textarea
formControlName="description"
name="description"
placeholder="{{ 'add-edit-dictionary.form.description-placeholder' | translate }}"
placeholder="{{
'add-edit-dictionary.form.description-placeholder' | translate
}}"
redactionHasScrollbar
rows="4"
type="text"
@ -62,8 +80,12 @@
<div class="red-input-group slider-row">
<mat-button-toggle-group appearance="legacy" formControlName="hint" name="hint">
<mat-button-toggle [value]="false"> {{ 'add-edit-dictionary.form.redaction' | translate }}</mat-button-toggle>
<mat-button-toggle [value]="true"> {{ 'add-edit-dictionary.form.hint' | translate }}</mat-button-toggle>
<mat-button-toggle [value]="false">
{{ 'add-edit-dictionary.form.redaction' | translate }}</mat-button-toggle
>
<mat-button-toggle [value]="true">
{{ 'add-edit-dictionary.form.hint' | translate }}</mat-button-toggle
>
</mat-button-toggle-group>
</div>
@ -74,18 +96,31 @@
</div>
<div class="red-input-group">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
<mat-checkbox
color="primary"
formControlName="addToDictionaryAction"
name="addToDictionaryAction"
>
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }}
</mat-checkbox>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="dictionaryForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button
[disabled]="dictionaryForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{ 'add-edit-dictionary.save' | translate }}
</button>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -65,7 +65,11 @@ export class AddEditDictionaryDialogComponent {
let observable: Observable<any>;
if (this.dictionary) {
// edit mode
observable = this._dictionaryControllerService.updateType(typeValue, typeValue.type, this._ruleSetId);
observable = this._dictionaryControllerService.updateType(
typeValue,
typeValue.type,
this._ruleSetId
);
} else {
// create mode
typeValue.ruleSetId = this._ruleSetId;
@ -89,7 +93,11 @@ export class AddEditDictionaryDialogComponent {
}
private _notifyError(message: string) {
this._notificationService.showToastNotification(this._translateService.instant(message), null, NotificationType.ERROR);
this._notificationService.showToastNotification(
this._translateService.instant(message),
null,
NotificationType.ERROR
);
}
private _formToObject(): TypeValue {

View File

@ -1,13 +1,23 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ (fileAttribute ? 'add-edit-file-attribute.title.edit' : 'add-edit-file-attribute.title.new') | translate: { name: fileAttribute?.label } }}
{{
(fileAttribute
? 'add-edit-file-attribute.title.edit'
: 'add-edit-file-attribute.title.new'
) | translate: { name: fileAttribute?.label }
}}
</div>
<form (submit)="saveFileAttribute()" [formGroup]="fileAttributeForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="add-edit-file-attribute.form.name"></label>
<input formControlName="label" name="label" placeholder="{{ 'add-edit-file-attribute.form.name-placeholder' | translate }}" type="text" />
<input
formControlName="label"
name="label"
placeholder="{{ 'add-edit-file-attribute.form.name-placeholder' | translate }}"
type="text"
/>
</div>
<div class="red-input-group required w-300">
@ -15,7 +25,9 @@
<input
formControlName="csvColumnHeader"
name="csvColumnHeader"
placeholder="{{ 'add-edit-file-attribute.form.column-header-placeholder' | translate }}"
placeholder="{{
'add-edit-file-attribute.form.column-header-placeholder' | translate
}}"
type="text"
/>
</div>
@ -31,22 +43,37 @@
<div class="options-wrapper">
<div class="red-input-group">
<mat-slide-toggle color="primary" formControlName="readonly">{{ 'add-edit-file-attribute.form.read-only' | translate }}</mat-slide-toggle>
<mat-slide-toggle color="primary" formControlName="readonly">{{
'add-edit-file-attribute.form.read-only' | translate
}}</mat-slide-toggle>
</div>
<div class="red-input-group mt-0">
<mat-checkbox color="primary" formControlName="primaryAttribute" name="primaryAttribute">
<mat-checkbox
color="primary"
formControlName="primaryAttribute"
name="primaryAttribute"
>
{{ 'add-edit-file-attribute.form.primary' | translate }}
</mat-checkbox>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="fileAttributeForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button
[disabled]="fileAttributeForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{ 'add-edit-file-attribute.save' | translate }}
</button>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -13,13 +13,18 @@ export class AddEditFileAttributeDialogComponent {
fileAttributeForm: FormGroup;
fileAttribute: FileAttributeConfig;
ruleSetId: string;
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
readonly typeOptions = [
FileAttributeConfig.TypeEnum.TEXT,
FileAttributeConfig.TypeEnum.NUMBER,
FileAttributeConfig.TypeEnum.DATE
];
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { fileAttribute: FileAttributeConfig; ruleSetId: string }
@Inject(MAT_DIALOG_DATA)
public data: { fileAttribute: FileAttributeConfig; ruleSetId: string }
) {
this.fileAttribute = data.fileAttribute;
this.ruleSetId = data.ruleSetId;
@ -27,7 +32,10 @@ export class AddEditFileAttributeDialogComponent {
this.fileAttributeForm = this._formBuilder.group({
label: [this.fileAttribute?.label, Validators.required],
csvColumnHeader: [this.fileAttribute?.csvColumnHeader, Validators.required],
type: [this.fileAttribute?.type || FileAttributeConfig.TypeEnum.TEXT, Validators.required],
type: [
this.fileAttribute?.type || FileAttributeConfig.TypeEnum.TEXT,
Validators.required
],
readonly: [this.fileAttribute ? !this.fileAttribute.editable : false],
primaryAttribute: [this.fileAttribute?.primaryAttribute]
});

View File

@ -1,13 +1,25 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ (ruleSet ? 'add-edit-project-template.title.edit' : 'add-edit-project-template.title.new') | translate: { name: ruleSet?.name } }}
{{
(ruleSet
? 'add-edit-project-template.title.edit'
: 'add-edit-project-template.title.new'
) | translate: { name: ruleSet?.name }
}}
</div>
<form (submit)="saveRuleSet()" [formGroup]="ruleSetForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="add-edit-project-template.form.name"></label>
<input formControlName="name" name="name" placeholder="{{ 'add-edit-project-template.form.name-placeholder' | translate }}" type="text" />
<input
formControlName="name"
name="name"
placeholder="{{
'add-edit-project-template.form.name-placeholder' | translate
}}"
type="text"
/>
</div>
<div class="red-input-group w-400">
@ -15,7 +27,9 @@
<textarea
formControlName="description"
name="description"
placeholder="{{ 'add-edit-project-template.form.description-placeholder' | translate }}"
placeholder="{{
'add-edit-project-template.form.description-placeholder' | translate
}}"
rows="4"
type="text"
></textarea>
@ -23,11 +37,21 @@
<div class="validity">
<div>
<mat-checkbox (change)="hasValidFrom = !hasValidFrom" [checked]="hasValidFrom" class="filter-menu-checkbox" color="primary">
<mat-checkbox
(change)="hasValidFrom = !hasValidFrom"
[checked]="hasValidFrom"
class="filter-menu-checkbox"
color="primary"
>
{{ 'add-edit-project-template.form.valid-from' | translate }}
</mat-checkbox>
<mat-checkbox (change)="hasValidTo = !hasValidTo" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
<mat-checkbox
(change)="hasValidTo = !hasValidTo"
[checked]="hasValidTo"
class="filter-menu-checkbox"
color="primary"
>
{{ 'add-edit-project-template.form.valid-to' | translate }}
</mat-checkbox>
</div>
@ -35,7 +59,11 @@
<div>
<div class="red-input-group datepicker-wrapper">
<ng-container *ngIf="hasValidFrom">
<input [matDatepicker]="fromPicker" formControlName="validFrom" placeholder="dd/mm/yy" />
<input
[matDatepicker]="fromPicker"
formControlName="validFrom"
placeholder="dd/mm/yy"
/>
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
@ -45,7 +73,11 @@
<div class="red-input-group datepicker-wrapper">
<ng-container *ngIf="hasValidTo">
<input [matDatepicker]="toPicker" formControlName="validTo" placeholder="dd/mm/yy" />
<input
[matDatepicker]="toPicker"
formControlName="validTo"
placeholder="dd/mm/yy"
/>
<mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
@ -58,14 +90,25 @@
<p class="download-includes">{{ 'download-includes' | translate }}</p>
<div class="space-between">
<redaction-select
[label]="'report-type.label' | translate: { length: this.ruleSetForm.controls['reportTypes'].value.length }"
[label]="
'report-type.label'
| translate
: { length: this.ruleSetForm.controls['reportTypes'].value.length }
"
[options]="reportTypesEnum"
[translatePrefix]="'report-type.'"
class="w-410"
formControlName="reportTypes"
></redaction-select>
<redaction-select
[label]="'download-type.label' | translate: { length: this.ruleSetForm.controls['downloadFileTypes'].value.length }"
[label]="
'download-type.label'
| translate
: {
length: this.ruleSetForm.controls['downloadFileTypes'].value
.length
}
"
[options]="downloadTypesEnum"
[translatePrefix]="'download-type.'"
class="w-410"
@ -75,11 +118,20 @@
</div>
<div class="dialog-actions">
<button [disabled]="ruleSetForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button
[disabled]="ruleSetForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{ 'add-edit-project-template.save' | translate }}
</button>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -32,13 +32,25 @@ export class AddEditRuleSetDialogComponent {
this.ruleSetForm = this._formBuilder.group({
name: [this.ruleSet?.name, Validators.required],
description: [this.ruleSet?.description],
validFrom: [this.ruleSet?.validFrom ? moment(this.ruleSet?.validFrom) : null, this._requiredIfValidator(() => this.hasValidFrom)],
validTo: [this.ruleSet?.validTo ? moment(this.ruleSet?.validTo) : null, this._requiredIfValidator(() => this.hasValidTo)],
downloadFileTypes: [this.ruleSet ? this.ruleSet.downloadFileTypes : ['PREVIEW', 'REDACTED']],
validFrom: [
this.ruleSet?.validFrom ? moment(this.ruleSet?.validFrom) : null,
this._requiredIfValidator(() => this.hasValidFrom)
],
validTo: [
this.ruleSet?.validTo ? moment(this.ruleSet?.validTo) : null,
this._requiredIfValidator(() => this.hasValidTo)
],
downloadFileTypes: [
this.ruleSet ? this.ruleSet.downloadFileTypes : ['PREVIEW', 'REDACTED']
],
reportTypes: [
this.ruleSet
? this.ruleSet.reportTypes
: ['WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE', 'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE', 'EXCEL_MULTI_FILE'],
: [
'WORD_SINGLE_FILE_APPENDIX_A1_TEMPLATE',
'WORD_SINGLE_FILE_APPENDIX_A2_TEMPLATE',
'EXCEL_MULTI_FILE'
],
Validators.required
]
});
@ -61,14 +73,24 @@ export class AddEditRuleSetDialogComponent {
if (this.hasValidFrom !== !!this.ruleSet.validFrom) {
return true;
}
if (this.hasValidFrom && !moment(this.ruleSet.validFrom).isSame(moment(this.ruleSetForm.get('validFrom').value))) {
if (
this.hasValidFrom &&
!moment(this.ruleSet.validFrom).isSame(
moment(this.ruleSetForm.get('validFrom').value)
)
) {
return true;
}
} else if (key === 'validTo') {
if (this.hasValidTo !== !!this.ruleSet.validTo) {
return true;
}
if (this.hasValidTo && !moment(this.ruleSet.validTo).isSame(moment(this.ruleSetForm.get('validTo').value))) {
if (
this.hasValidTo &&
!moment(this.ruleSet.validTo).isSame(
moment(this.ruleSetForm.get('validTo').value)
)
) {
return true;
}
} else if (this.ruleSet[key] !== this.ruleSetForm.get(key).value) {
@ -93,7 +115,16 @@ export class AddEditRuleSetDialogComponent {
}
private _applyValidityIntervalConstraints(value): boolean {
if (applyIntervalConstraints(value, this._previousValidFrom, this._previousValidTo, this.ruleSetForm, 'validFrom', 'validTo')) {
if (
applyIntervalConstraints(
value,
this._previousValidFrom,
this._previousValidTo,
this.ruleSetForm,
'validFrom',
'validTo'
)
) {
return true;
}

View File

@ -28,7 +28,11 @@
<div class="red-input-group">
<label translate="add-edit-user.form.role"></label>
<div class="roles-wrapper">
<mat-checkbox *ngFor="let role of ROLES" [formControlName]="role" color="primary">
<mat-checkbox
*ngFor="let role of ROLES"
[formControlName]="role"
color="primary"
>
{{ 'roles.' + role | translate }}
</mat-checkbox>
</div>
@ -36,15 +40,37 @@
</div>
<div class="dialog-actions">
<button [disabled]="userForm.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ (user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate }}
<button
[disabled]="userForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{
(user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save')
| translate
}}
</button>
<redaction-icon-button (action)="delete()" *ngIf="user" icon="red:trash" text="add-edit-user.actions.delete" type="show-bg"></redaction-icon-button>
<redaction-icon-button
(action)="delete()"
*ngIf="user"
icon="red:trash"
text="add-edit-user.actions.delete"
type="show-bg"
></redaction-icon-button>
<div class="all-caps-label cancel" mat-dialog-close translate="add-edit-user.actions.cancel"></div>
<div
class="all-caps-label cancel"
mat-dialog-close
translate="add-edit-user.actions.cancel"
></div>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -29,7 +29,10 @@ export class AddEditUserDialogComponent {
disabled:
this.user &&
Object.keys(this._ROLE_REQUIREMENTS).reduce(
(value, key) => value || (role === this._ROLE_REQUIREMENTS[key] && this.user.roles.indexOf(key) !== -1),
(value, key) =>
value ||
(role === this._ROLE_REQUIREMENTS[key] &&
this.user.roles.indexOf(key) !== -1),
false
)
}
@ -40,7 +43,10 @@ export class AddEditUserDialogComponent {
this.userForm = this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required],
email: [{ value: this.user?.email, disabled: !!user }, [Validators.required, Validators.email]],
email: [
{ value: this.user?.email, disabled: !!user },
[Validators.required, Validators.email]
],
// password: [this.user?.password, Validators.required],
...rolesControls
});

View File

@ -1,6 +1,9 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ 'confirm-delete-file-attribute.title.' + type | translate: { name: fileAttribute?.label } }}
{{
'confirm-delete-file-attribute.title.' + type
| translate: { name: fileAttribute?.label }
}}
</div>
<div *ngIf="showToast" class="inline-dialog-toast toast-error">
@ -27,7 +30,15 @@
<button (click)="deleteFileAttribute()" color="primary" mat-flat-button>
{{ 'confirm-delete-file-attribute.delete.' + type | translate }}
</button>
<div (click)="cancel()" [translate]="'confirm-delete-file-attribute.cancel.' + type" class="all-caps-label cancel"></div>
<div
(click)="cancel()"
[translate]="'confirm-delete-file-attribute.cancel.' + type"
class="all-caps-label cancel"
></div>
</div>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -17,7 +17,10 @@
[class.error]="!checkbox.value && showToast"
color="primary"
>
{{ 'confirm-delete-users.' + checkbox.label | translate: { projectsCount: projectsCount } }}
{{
'confirm-delete-users.' + checkbox.label
| translate: { projectsCount: projectsCount }
}}
</mat-checkbox>
</div>
@ -25,7 +28,15 @@
<button (click)="deleteUser()" color="primary" mat-flat-button>
{{ 'confirm-delete-users.delete.' + type | translate }}
</button>
<div (click)="cancel()" [translate]="'confirm-delete-users.cancel.' + type" class="all-caps-label cancel"></div>
<div
(click)="cancel()"
[translate]="'confirm-delete-users.cancel.' + type"
class="all-caps-label cancel"
></div>
</div>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -1,5 +1,8 @@
<section class="dialog">
<div [translate]="'default-colors-screen.types.' + this.colorKey" class="dialog-header heading-l"></div>
<div
[translate]="'default-colors-screen.types.' + this.colorKey"
class="dialog-header heading-l"
></div>
<form (submit)="saveColors()" [formGroup]="colorForm">
<div class="dialog-content">
@ -19,17 +22,32 @@
[style.background]="colorForm.get('color').value"
class="input-icon"
>
<mat-icon *ngIf="!colorForm.get('color').value || colorForm.get('color').value?.length === 0" svgIcon="red:color-picker"></mat-icon>
<mat-icon
*ngIf="
!colorForm.get('color').value ||
colorForm.get('color').value?.length === 0
"
svgIcon="red:color-picker"
></mat-icon>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="colorForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button
[disabled]="colorForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{ 'edit-color-dialog.save' | translate }}
</button>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -23,7 +23,8 @@ export class EditColorDialogComponent {
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService,
public dialogRef: MatDialogRef<EditColorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { colors: Colors; colorKey: string; ruleSetId: string }
@Inject(MAT_DIALOG_DATA)
public data: { colors: Colors; colorKey: string; ruleSetId: string }
) {
this.colors = data.colors;
this.colorKey = data.colorKey;
@ -50,11 +51,17 @@ export class EditColorDialogComponent {
this.dialogRef.close(true);
this._notificationService.showToastNotification(
this._translateService.instant('edit-color-dialog.success', {
color: this._translateService.instant('default-colors-screen.types.' + this.colorKey)
color: this._translateService.instant(
'default-colors-screen.types.' + this.colorKey
)
})
);
} catch (e) {
this._notificationService.showToastNotification(this._translateService.instant('edit-color-dialog.error'), null, NotificationType.ERROR);
this._notificationService.showToastNotification(
this._translateService.instant('edit-color-dialog.error'),
null,
NotificationType.ERROR
);
}
}
}

View File

@ -7,7 +7,10 @@
></redaction-round-checkbox>
</div>
<span class="all-caps-label">
{{ 'file-attributes-csv-import.table-header.title' | translate: { length: allEntities.length } }}
{{
'file-attributes-csv-import.table-header.title'
| translate: { length: allEntities.length }
}}
</span>
<ng-container *ngIf="areSomeEntitiesSelected">
@ -29,7 +32,10 @@
<div class="separator"></div>
<redaction-chevron-button [matMenuTriggerFor]="typeMenu" text="file-attributes-csv-import.table-header.actions.type"></redaction-chevron-button>
<redaction-chevron-button
[matMenuTriggerFor]="typeMenu"
text="file-attributes-csv-import.table-header.actions.type"
></redaction-chevron-button>
<mat-menu #readOnlyMenu="matMenu" class="no-padding-bottom">
<button
@ -45,7 +51,11 @@
</mat-menu>
<mat-menu #typeMenu="matMenu" class="no-padding-bottom">
<button (click)="setAttributeForSelection('type', type)" *ngFor="let type of typeOptions" mat-menu-item>
<button
(click)="setAttributeForSelection('type', type)"
*ngFor="let type of typeOptions"
mat-menu-item
>
{{ 'file-attribute-types.' + type | translate }}
</button>
</mat-menu>
@ -55,9 +65,14 @@
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name class="name" label="file-attributes-csv-import.table-col-names.name"></redaction-table-col-name>
<redaction-table-col-name
class="name"
label="file-attributes-csv-import.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name label="file-attributes-csv-import.table-col-names.type"></redaction-table-col-name>
<redaction-table-col-name
label="file-attributes-csv-import.table-col-names.type"
></redaction-table-col-name>
<redaction-table-col-name
class="flex-center"
@ -76,7 +91,11 @@
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!allEntities.length" icon="red:attribute" screen="file-attributes-csv-import"></redaction-empty-state>
<redaction-empty-state
*ngIf="!allEntities.length"
icon="red:attribute"
screen="file-attributes-csv-import"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="50" redactionHasScrollbar>
<!-- Table lines -->
@ -93,7 +112,10 @@
<div *ngIf="!field.editingName">
{{ field.name }}
</div>
<form (submit)="field.editingName = false; field.name = field.temporaryName" *ngIf="field.editingName">
<form
(submit)="field.editingName = false; field.name = field.temporaryName"
*ngIf="field.editingName"
>
<div class="red-input-group w-200">
<input [(ngModel)]="field.temporaryName" name="name" />
</div>
@ -139,7 +161,10 @@
<mat-slide-toggle [(ngModel)]="field.readonly" color="primary"></mat-slide-toggle>
</div>
<div class="center">
<redaction-round-checkbox (click)="togglePrimary(field)" [active]="field.primaryAttribute"></redaction-round-checkbox>
<redaction-round-checkbox
(click)="togglePrimary(field)"
[active]="field.primaryAttribute"
></redaction-round-checkbox>
</div>
<div class="actions-container">
<div class="action-buttons">

View File

@ -1,4 +1,12 @@
import { Component, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import {
Component,
EventEmitter,
Injector,
Input,
OnChanges,
Output,
SimpleChanges
} from '@angular/core';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { Field } from '../file-attributes-csv-import-dialog.component';
import { FileAttributeConfig } from '@redaction/red-ui-http';
@ -14,7 +22,11 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
@Output() setHoveredColumn = new EventEmitter<string>();
@Output() toggleFieldActive = new EventEmitter<Field>();
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
readonly typeOptions = [
FileAttributeConfig.TypeEnum.TEXT,
FileAttributeConfig.TypeEnum.NUMBER,
FileAttributeConfig.TypeEnum.DATE
];
protected readonly _selectionKey = 'csvColumn';
@ -30,7 +42,9 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
}
deactivateSelection() {
this.allEntities.filter((field) => this.isEntitySelected(field)).forEach((field) => (field.primaryAttribute = false));
this.allEntities
.filter((field) => this.isEntitySelected(field))
.forEach((field) => (field.primaryAttribute = false));
this.allEntities = [...this.allEntities.filter((field) => !this.isEntitySelected(field))];
this.allEntitiesChange.emit(this.allEntities);
this.selectedEntitiesIds = [];

View File

@ -4,25 +4,37 @@
<div class="dialog-content">
<div class="sub-header">
<div class="left">
<div class="info"><span translate="file-attributes-csv-import.file"> </span> {{ csvFile.name }}</div>
<div class="info">
<span translate="file-attributes-csv-import.file"> </span> {{ csvFile.name }}
</div>
<div class="large-label">
{{ 'file-attributes-csv-import.total-rows' | translate: { rows: parseResult?.data?.length } }}
{{
'file-attributes-csv-import.total-rows'
| translate: { rows: parseResult?.data?.length }
}}
</div>
</div>
<div class="right">
<form (submit)="changedParseConfig && readFile()" [formGroup]="baseConfigForm">
<div class="red-input-group required w-250">
<mat-form-field floatLabel="always">
<mat-label>{{ 'file-attributes-csv-import.key-column' | translate }}</mat-label>
<mat-label>{{
'file-attributes-csv-import.key-column' | translate
}}</mat-label>
<input
[matAutocomplete]="auto"
[placeholder]="'file-attributes-csv-import.key-column-placeholder' | translate"
[placeholder]="
'file-attributes-csv-import.key-column-placeholder' | translate
"
formControlName="filenameMappingColumnHeaderName"
matInput
type="text"
/>
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption>
<mat-option *ngFor="let field of filteredKeyOptions | async" [value]="field">
<mat-option
*ngFor="let field of filteredKeyOptions | async"
[value]="field"
>
{{ field }}
</mat-option>
</mat-autocomplete>
@ -32,7 +44,9 @@
<div class="red-input-group required w-110">
<label translate="file-attributes-csv-import.delimiter"></label>
<input
[placeholder]="'file-attributes-csv-import.delimiter-placeholder' | translate"
[placeholder]="
'file-attributes-csv-import.delimiter-placeholder' | translate
"
formControlName="delimiter"
name="delimiter"
type="text"
@ -42,7 +56,9 @@
<div class="red-input-group required w-160">
<label translate="file-attributes-csv-import.encoding"></label>
<input
[placeholder]="'file-attributes-csv-import.encoding-placeholder' | translate"
[placeholder]="
'file-attributes-csv-import.encoding-placeholder' | translate
"
formControlName="encoding"
name="encoding"
type="text"
@ -62,9 +78,13 @@
<div class="csv-part-header">
<div>
<span class="all-caps-label">{{
'file-attributes-csv-import.available' | translate: { value: parseResult?.meta?.fields.length }
'file-attributes-csv-import.available'
| translate: { value: parseResult?.meta?.fields.length }
}}</span>
<span class="all-caps-label">{{
'file-attributes-csv-import.selected'
| translate: { value: activeFields.length }
}}</span>
<span class="all-caps-label">{{ 'file-attributes-csv-import.selected' | translate: { value: activeFields.length } }}</span>
</div>
<div class="actions">
<redaction-circle-button
@ -106,17 +126,29 @@
{{ field.csvColumn }}
</div>
<div class="secondary">
<div class="entry-count small-label">{{ getEntries(field.csvColumn) }} entries</div>
<div class="sample small-label">Sample: {{ getSample(field.csvColumn) }}</div>
<div class="entry-count small-label">
{{ getEntries(field.csvColumn) }} entries
</div>
<div class="sample small-label">
Sample: {{ getSample(field.csvColumn) }}
</div>
</div>
</div>
</div>
</div>
</div>
<div (mouseenter)="keepPreview = true" (mouseleave)="keepPreview = false; setHoveredColumn()" [class.collapsed]="!previewExpanded" class="center">
<div
(mouseenter)="keepPreview = true"
(mouseleave)="keepPreview = false; setHoveredColumn()"
[class.collapsed]="!previewExpanded"
class="center"
>
<div class="csv-part-header">
<div class="all-caps-label">
{{ 'file-attributes-csv-import.csv-column' + (previewExpanded ? '' : '-preview') | translate }}
{{
'file-attributes-csv-import.csv-column' +
(previewExpanded ? '' : '-preview') | translate
}}
</div>
<redaction-circle-button
(click)="previewExpanded = !previewExpanded"
@ -124,9 +156,16 @@
></redaction-circle-button>
</div>
<div [class.hidden]="!previewExpanded" class="csv-part-content">
<div *ngIf="!hoveredColumn" class="no-column-data" translate="file-attributes-csv-import.no-hovered-column"></div>
<div
*ngIf="!hoveredColumn"
class="no-column-data"
translate="file-attributes-csv-import.no-hovered-column"
></div>
<div *ngIf="hoveredColumn && !columnSample.length" class="no-column-data">
{{ 'file-attributes-csv-import.no-sample-data-for' | translate: { column: hoveredColumn } }}
{{
'file-attributes-csv-import.no-sample-data-for'
| translate: { column: hoveredColumn }
}}
</div>
<div *ngFor="let row of columnSample">
{{ row }}
@ -144,12 +183,23 @@
</div>
<div class="dialog-actions">
<button (click)="save()" [disabled]="changedParseConfig || baseConfigForm.invalid" color="primary" mat-flat-button>
<button
(click)="save()"
[disabled]="changedParseConfig || baseConfigForm.invalid"
color="primary"
mat-flat-button
>
{{ 'file-attributes-csv-import.save.label' | translate }}
</button>
<div (click)="dialogRef.close()" class="all-caps-label cancel">{{ 'file-attributes-csv-import.cancel' | translate }}</div>
<div (click)="dialogRef.close()" class="all-caps-label cancel">
{{ 'file-attributes-csv-import.cancel' | translate }}
</div>
</div>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -3,7 +3,11 @@ import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/fo
import { AppStateService } from '@state/app-state.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as Papa from 'papaparse';
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import {
FileAttributeConfig,
FileAttributesConfig,
FileAttributesControllerService
} from '@redaction/red-ui-http';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
@ -40,7 +44,8 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
keepPreview = false;
columnSample = [];
initialParseConfig: { delimiter?: string; encoding?: string } = {};
@ViewChild(CdkVirtualScrollViewport, { static: false }) cdkVirtualScrollViewport: CdkVirtualScrollViewport;
@ViewChild(CdkVirtualScrollViewport, { static: false })
cdkVirtualScrollViewport: CdkVirtualScrollViewport;
protected readonly _searchKey = 'csvColumn';
constructor(
@ -50,14 +55,18 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
private readonly _notificationService: NotificationService,
public dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
protected readonly _injector: Injector,
@Inject(MAT_DIALOG_DATA) public data: { csv: File; ruleSetId: string; existingConfiguration: FileAttributesConfig }
@Inject(MAT_DIALOG_DATA)
public data: { csv: File; ruleSetId: string; existingConfiguration: FileAttributesConfig }
) {
super(_injector);
this.csvFile = data.csv;
this.ruleSetId = data.ruleSetId;
this.baseConfigForm = this._formBuilder.group({
filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]],
filenameMappingColumnHeaderName: [
'',
[Validators.required, this._autocompleteStringValidator()]
],
delimiter: [undefined, Validators.required],
encoding: ['UTF-8', Validators.required]
});
@ -87,12 +96,16 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
this.parseResult.meta.fields = Object.keys(this.parseResult.data[0]);
}
this.allEntities = this.parseResult.meta.fields.map((field) => this._buildAttribute(field));
this.allEntities = this.parseResult.meta.fields.map((field) =>
this._buildAttribute(field)
);
this.displayedEntities = [...this.allEntities];
this.activeFields = [];
for (const entity of this.allEntities) {
const existing = this.data.existingConfiguration.fileAttributeConfigs.find((a) => a.csvColumnHeader === entity.csvColumn);
const existing = this.data.existingConfiguration.fileAttributeConfigs.find(
(a) => a.csvColumnHeader === entity.csvColumn
);
if (existing) {
entity.id = existing.id;
entity.name = existing.label;
@ -104,18 +117,35 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
}
}
this.filteredKeyOptions = this.baseConfigForm.get('filenameMappingColumnHeaderName').valueChanges.pipe(
startWith(this.baseConfigForm.get('filenameMappingColumnHeaderName').value as string),
map((value: string) =>
this.allEntities.filter((field) => field.csvColumn.toLowerCase().indexOf(value.toLowerCase()) !== -1).map((field) => field.csvColumn)
)
);
this.filteredKeyOptions = this.baseConfigForm
.get('filenameMappingColumnHeaderName')
.valueChanges.pipe(
startWith(
this.baseConfigForm.get('filenameMappingColumnHeaderName').value as string
),
map((value: string) =>
this.allEntities
.filter(
(field) =>
field.csvColumn.toLowerCase().indexOf(value.toLowerCase()) !==
-1
)
.map((field) => field.csvColumn)
)
);
if (
this.data.existingConfiguration &&
this.allEntities.find((entity) => entity.csvColumn === this.data.existingConfiguration.filenameMappingColumnHeaderName)
this.allEntities.find(
(entity) =>
entity.csvColumn ===
this.data.existingConfiguration.filenameMappingColumnHeaderName
)
) {
this.baseConfigForm.patchValue({ filenameMappingColumnHeaderName: this.data.existingConfiguration.filenameMappingColumnHeaderName });
this.baseConfigForm.patchValue({
filenameMappingColumnHeaderName:
this.data.existingConfiguration.filenameMappingColumnHeaderName
});
}
this.initialParseConfig = {
@ -169,14 +199,17 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
const newPrimary = !!this.activeFields.find((attr) => attr.primaryAttribute);
if (newPrimary) {
this.data.existingConfiguration.fileAttributeConfigs.forEach((attr) => (attr.primaryAttribute = false));
this.data.existingConfiguration.fileAttributeConfigs.forEach(
(attr) => (attr.primaryAttribute = false)
);
}
const fileAttributes = {
...this.baseConfigForm.getRawValue(),
fileAttributeConfigs: [
...this.data.existingConfiguration.fileAttributeConfigs.filter(
(a) => !this.allEntities.find((entity) => entity.csvColumn === a.csvColumnHeader)
(a) =>
!this.allEntities.find((entity) => entity.csvColumn === a.csvColumnHeader)
),
...this.activeFields.map((field) => ({
id: field.id,
@ -190,9 +223,13 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
};
try {
await this._fileAttributesControllerService.setFileAttributesConfig(fileAttributes, this.ruleSetId).toPromise();
await this._fileAttributesControllerService
.setFileAttributesConfig(fileAttributes, this.ruleSetId)
.toPromise();
this._notificationService.showToastNotification(
this._translateService.instant('file-attributes-csv-import.save.success', { count: this.activeFields.length }),
this._translateService.instant('file-attributes-csv-import.save.success', {
count: this.activeFields.length
}),
null,
NotificationType.SUCCESS
);
@ -217,7 +254,9 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
if (!column) {
this.columnSample = [];
} else {
this.columnSample = this.parseResult.data.filter((row) => !!row[column]).map((row) => row[column]);
this.columnSample = this.parseResult.data
.filter((row) => !!row[column])
.map((row) => row[column]);
}
}, 0);
}
@ -238,7 +277,9 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
csvColumn,
name: csvColumn,
temporaryName: csvColumn,
type: isNumber ? FileAttributeConfig.TypeEnum.NUMBER : FileAttributeConfig.TypeEnum.TEXT,
type: isNumber
? FileAttributeConfig.TypeEnum.NUMBER
: FileAttributeConfig.TypeEnum.TEXT,
readonly: false,
primaryAttribute: false
};

View File

@ -5,7 +5,12 @@
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="smtp-auth-config.form.username"></label>
<input formControlName="user" name="user" placeholder="{{ 'smtp-auth-config.form.username-placeholder' | translate }}" type="text" />
<input
formControlName="user"
name="user"
placeholder="{{ 'smtp-auth-config.form.username-placeholder' | translate }}"
type="text"
/>
</div>
<div class="red-input-group required w-300">
@ -18,9 +23,17 @@
<button [disabled]="authForm.invalid" color="primary" mat-flat-button type="submit">
{{ 'smtp-auth-config.actions.save' | translate }}
</button>
<div class="all-caps-label cancel" mat-dialog-close translate="smtp-auth-config.actions.cancel"></div>
<div
class="all-caps-label cancel"
mat-dialog-close
translate="smtp-auth-config.actions.cancel"
></div>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
<redaction-circle-button
class="dialog-close"
icon="red:close"
mat-dialog-close
></redaction-circle-button>
</section>

View File

@ -22,7 +22,10 @@
<div class="content-container">
<div class="header-item">
<span class="all-caps-label">
{{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }}
{{
'audit-screen.table-header.title'
| translate: { length: logs?.totalHits || 0 }
}}
</span>
<div class="actions-wrapper">
<redaction-pagination
@ -34,7 +37,10 @@
<div class="red-input-group w-150 mr-20">
<mat-form-field class="no-label">
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
<mat-option
*ngFor="let category of categories"
[value]="category"
>
{{ category | translate }}
</mat-option>
</mat-select>
@ -50,7 +56,10 @@
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
<div
*ngIf="filterForm.get('userId').value === ALL_USERS"
[translate]="ALL_USERS"
></div>
</mat-select-trigger>
<mat-option *ngFor="let userId of userIds" [value]="userId">
<redaction-initials-avatar
@ -59,16 +68,26 @@
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
<div
*ngIf="userId === ALL_USERS"
[translate]="ALL_USERS"
></div>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="separator">·</div>
<div class="red-input-group datepicker-wrapper mr-20">
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
<input
[matDatepicker]="fromPicker"
formControlName="from"
placeholder="dd/mm/yy"
/>
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
<mat-icon
matDatepickerToggleIcon
svgIcon="red:calendar"
></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</div>
@ -76,9 +95,16 @@
<div class="mr-20" translate="audit-screen.to"></div>
<div class="red-input-group datepicker-wrapper">
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
<input
[matDatepicker]="toPicker"
formControlName="to"
placeholder="dd/mm/yy"
/>
<mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
<mat-icon
matDatepickerToggleIcon
svgIcon="red:calendar"
></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</div>
@ -87,14 +113,31 @@
</div>
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name column="message" label="audit-screen.table-col-names.message"></redaction-table-col-name>
<redaction-table-col-name column="date" label="audit-screen.table-col-names.date"></redaction-table-col-name>
<redaction-table-col-name class="user-column" column="user" label="audit-screen.table-col-names.user"></redaction-table-col-name>
<redaction-table-col-name column="category" label="audit-screen.table-col-names.category"></redaction-table-col-name>
<redaction-table-col-name
column="message"
label="audit-screen.table-col-names.message"
></redaction-table-col-name>
<redaction-table-col-name
column="date"
label="audit-screen.table-col-names.date"
></redaction-table-col-name>
<redaction-table-col-name
class="user-column"
column="user"
label="audit-screen.table-col-names.user"
></redaction-table-col-name>
<redaction-table-col-name
column="category"
label="audit-screen.table-col-names.category"
></redaction-table-col-name>
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!logs?.totalHits" icon="red:document" screen="audit-screen"></redaction-empty-state>
<redaction-empty-state
*ngIf="!logs?.totalHits"
icon="red:document"
screen="audit-screen"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div *cdkVirtualFor="let log of logs?.data" class="table-item">
@ -105,7 +148,11 @@
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
</div>
<div class="user-column">
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
<redaction-initials-avatar
[userId]="log.userId"
[withName]="true"
size="small"
></redaction-initials-avatar>
</div>
<div>
{{ log.category }}
@ -118,4 +165,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -61,7 +61,16 @@ export class AuditScreenComponent {
}
private _updateDateFilters(value): boolean {
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
if (
applyIntervalConstraints(
value,
this._previousFrom,
this._previousTo,
this.filterForm,
'from',
'to'
)
) {
return true;
}

View File

@ -5,7 +5,12 @@
<div class="flex-1 actions">
<redaction-rule-set-actions></redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
<redaction-circle-button
[routerLink]="['../..']"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
@ -17,7 +22,10 @@
<div class="content-container">
<div class="header-item">
<span class="all-caps-label">
{{ 'default-colors-screen.table-header.title' | translate: { length: allEntities.length } }}
{{
'default-colors-screen.table-header.title'
| translate: { length: allEntities.length }
}}
</span>
</div>
@ -30,7 +38,10 @@
label="default-colors-screen.table-col-names.key"
></redaction-table-col-name>
<redaction-table-col-name class="flex-center" label="default-colors-screen.table-col-names.color"></redaction-table-col-name>
<redaction-table-col-name
class="flex-center"
label="default-colors-screen.table-col-names.color"
></redaction-table-col-name>
<div></div>
<div class="scrollbar-placeholder"></div>
@ -38,13 +49,24 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div *cdkVirtualFor="let color of allEntities | sortBy: sortingOption.order:sortingOption.column" class="table-item">
<div
*cdkVirtualFor="
let color of allEntities | sortBy: sortingOption.order:sortingOption.column
"
class="table-item"
>
<div>
<div [translate]="'default-colors-screen.types.' + color.key" class="table-item-title heading"></div>
<div
[translate]="'default-colors-screen.types.' + color.key"
class="table-item-title heading"
></div>
</div>
<div class="color-wrapper">
<div [ngStyle]="{ 'background-color': color.value }" class="color-square"></div>
<div
[ngStyle]="{ 'background-color': color.value }"
class="color-square"
></div>
</div>
<div class="actions-container">
@ -66,4 +88,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -11,7 +11,10 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
templateUrl: './default-colors-screen.component.html',
styleUrls: ['./default-colors-screen.component.scss']
})
export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: string; value: string }> {
export class DefaultColorsScreenComponent extends BaseListingComponent<{
key: string;
value: string;
}> {
viewReady = false;
protected readonly _sortKey = 'default-colors';
private _colorsObj: Colors;
@ -35,9 +38,14 @@ export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: st
openEditColorDialog($event: any, color: { key: string; value: string }) {
$event.stopPropagation();
this._dialogService.openEditColorsDialog(this._colorsObj, color.key, this._appStateService.activeRuleSetId, async () => {
this._loadColors();
});
this._dialogService.openEditColorsDialog(
this._colorsObj,
color.key,
this._appStateService.activeRuleSetId,
async () => {
this._loadColors();
}
);
}
private _loadColors() {

View File

@ -5,7 +5,12 @@
<div class="flex-1 actions">
<redaction-rule-set-actions></redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
<redaction-circle-button
[routerLink]="['../..']"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
@ -25,11 +30,17 @@
</div>
<span class="all-caps-label">
{{ 'dictionary-listing.table-header.title' | translate: { length: displayedEntities.length } }}
{{
'dictionary-listing.table-header.title'
| translate: { length: displayedEntities.length }
}}
</span>
<div class="attributes-actions-container">
<redaction-search-input [form]="searchForm" [placeholder]="'dictionary-listing.search'"></redaction-search-input>
<redaction-search-input
[form]="searchForm"
[placeholder]="'dictionary-listing.search'"
></redaction-search-input>
<div class="actions">
<redaction-icon-button
(action)="openAddEditDictionaryDialog()"
@ -42,7 +53,11 @@
</div>
</div>
<div [class.no-data]="!allEntities.length" class="table-header" redactionSyncWidth="table-item">
<div
[class.no-data]="!allEntities.length"
class="table-header"
redactionSyncWidth="table-item"
>
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
@ -62,7 +77,10 @@
label="dictionary-listing.table-col-names.order-of-importance"
></redaction-table-col-name>
<redaction-table-col-name class="flex-center" label="dictionary-listing.table-col-names.hint-redaction"></redaction-table-col-name>
<redaction-table-col-name
class="flex-center"
label="dictionary-listing.table-col-names.hint-redaction"
></redaction-table-col-name>
<div></div>
<div class="scrollbar-placeholder"></div>
</div>
@ -75,20 +93,32 @@
screen="dictionary-listing"
></redaction-empty-state>
<redaction-empty-state *ngIf="allEntities.length && !displayedEntities.length" screen="dictionary-listing" type="no-match"></redaction-empty-state>
<redaction-empty-state
*ngIf="allEntities.length && !displayedEntities.length"
screen="dictionary-listing"
type="no-match"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div
*cdkVirtualFor="let dict of displayedEntities | sortBy: sortingOption.order:sortingOption.column"
*cdkVirtualFor="
let dict of displayedEntities
| sortBy: sortingOption.order:sortingOption.column
"
[routerLink]="[dict.type]"
class="table-item pointer"
>
<div (click)="toggleEntitySelected($event, dict)" class="selection-column">
<redaction-round-checkbox [active]="isEntitySelected(dict)"></redaction-round-checkbox>
<redaction-round-checkbox
[active]="isEntitySelected(dict)"
></redaction-round-checkbox>
</div>
<div>
<div [ngStyle]="{ 'background-color': dict.hexColor }" class="color-square"></div>
<div
[ngStyle]="{ 'background-color': dict.hexColor }"
class="color-square"
></div>
<div class="dict-name">
<div class="table-item-title heading">
{{ dict.label }}
@ -111,7 +141,10 @@
</div>
<div class="center">
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
<redaction-annotation-icon
[dictType]="dict"
[type]="dict.hint ? 'circle' : 'square'"
></redaction-annotation-icon>
</div>
<div class="actions-container">
@ -154,4 +187,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -14,7 +14,10 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
templateUrl: './dictionary-listing-screen.component.html',
styleUrls: ['./dictionary-listing-screen.component.scss']
})
export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValue> implements OnInit {
export class DictionaryListingScreenComponent
extends BaseListingComponent<TypeValue>
implements OnInit
{
viewReady = false;
chartData: DoughnutChartConfig[] = [];
protected readonly _searchKey = 'label';
@ -39,38 +42,50 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
openAddEditDictionaryDialog($event?: MouseEvent, dict?: TypeValue) {
$event?.stopPropagation();
this._dialogService.openAddEditDictionaryDialog(dict, this._appStateService.activeRuleSetId, async (newDictionary) => {
if (newDictionary) {
await this._appStateService.loadDictionaryData();
this._loadDictionaryData();
this._dialogService.openAddEditDictionaryDialog(
dict,
this._appStateService.activeRuleSetId,
async (newDictionary) => {
if (newDictionary) {
await this._appStateService.loadDictionaryData();
this._loadDictionaryData();
}
}
});
);
}
openDeleteDictionaryDialog($event: any, dict: TypeValue) {
this._dialogService.openDeleteDictionaryDialog($event, dict, this._appStateService.activeRuleSetId, async () => {
await this._appStateService.loadDictionaryData();
this._loadDictionaryData();
});
this._dialogService.openDeleteDictionaryDialog(
$event,
dict,
this._appStateService.activeRuleSetId,
async () => {
await this._appStateService.loadDictionaryData();
this._loadDictionaryData();
}
);
}
private _loadDictionaryData() {
const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeRuleSetId];
const appStateDictionaryData =
this._appStateService.dictionaryData[this._appStateService.activeRuleSetId];
this.allEntities = Object.keys(appStateDictionaryData)
.map((key) => appStateDictionaryData[key])
.filter((d) => !d.virtual);
this.displayedEntities = [...this.allEntities];
const dataObs = this.allEntities.map((dict) =>
this._dictionaryControllerService.getDictionaryForType(this._appStateService.activeRuleSetId, dict.type).pipe(
tap((values) => {
dict.entries = values.entries ? values.entries : [];
}),
catchError(() => {
console.log('error');
dict.entries = [];
return of({});
})
)
this._dictionaryControllerService
.getDictionaryForType(this._appStateService.activeRuleSetId, dict.type)
.pipe(
tap((values) => {
dict.entries = values.entries ? values.entries : [];
}),
catchError(() => {
console.log('error');
dict.entries = [];
return of({});
})
)
);
forkJoin(dataObs)
.pipe(defaultIfEmpty(null))

View File

@ -38,7 +38,14 @@
tooltipPosition="below"
></redaction-circle-button>
<input #fileInput (change)="upload($event)" accept="text/plain" class="file-upload-input" hidden type="file" />
<input
#fileInput
(change)="upload($event)"
accept="text/plain"
class="file-upload-input"
hidden
type="file"
/>
<redaction-circle-button
[routerLink]="['..']"
@ -92,11 +99,16 @@
</div>
<div *ngIf="!!dictionary.description" class="pb-32 mt-20">
<div class="heading" translate="dictionary-overview.dictionary-details.description"></div>
<div
class="heading"
translate="dictionary-overview.dictionary-details.description"
></div>
<div class="mt-8">{{ dictionary.description }}</div>
</div>
</div>
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="processing"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="processing"
></redaction-full-page-loading-indicator>

View File

@ -21,7 +21,8 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
processing = false;
entries: string[] = [];
@ViewChild('dictionaryManager', { static: false }) private _dictionaryManager: DictionaryManagerComponent;
@ViewChild('dictionaryManager', { static: false })
private _dictionaryManager: DictionaryManagerComponent;
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor(
@ -37,7 +38,10 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
private readonly _formBuilder: FormBuilder
) {
super(_translateService);
this._appStateService.activateDictionary(this._activatedRoute.snapshot.params.type, this._activatedRoute.snapshot.params.ruleSetId);
this._appStateService.activateDictionary(
this._activatedRoute.snapshot.params.type,
this._activatedRoute.snapshot.params.ruleSetId
);
}
ngOnInit(): void {
@ -54,16 +58,25 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
openEditDictionaryDialog($event: any) {
$event.stopPropagation();
this._dialogService.openAddEditDictionaryDialog(this.dictionary, this.dictionary.ruleSetId, async () => {
await this._appStateService.loadDictionaryData();
});
this._dialogService.openAddEditDictionaryDialog(
this.dictionary,
this.dictionary.ruleSetId,
async () => {
await this._appStateService.loadDictionaryData();
}
);
}
openDeleteDictionaryDialog($event: any) {
this._dialogService.openDeleteDictionaryDialog($event, this.dictionary, this.dictionary.ruleSetId, async () => {
await this._appStateService.loadDictionaryData();
this._router.navigate(['..']);
});
this._dialogService.openDeleteDictionaryDialog(
$event,
this.dictionary,
this.dictionary.ruleSetId,
async () => {
await this._appStateService.loadDictionaryData();
this._router.navigate(['..']);
}
);
}
download(): void {
@ -89,28 +102,40 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
saveEntries(entries: string[]) {
this.processing = true;
this._dictionarySaveService.saveEntries(entries, this.entries, this.dictionary.ruleSetId, this.dictionary.type, null).subscribe(
() => {
this.processing = false;
this._loadEntries();
},
() => {
this.processing = false;
}
);
this._dictionarySaveService
.saveEntries(
entries,
this.entries,
this.dictionary.ruleSetId,
this.dictionary.type,
null
)
.subscribe(
() => {
this.processing = false;
this._loadEntries();
},
() => {
this.processing = false;
}
);
}
private _loadEntries() {
this.processing = true;
this._dictionaryControllerService.getDictionaryForType(this.dictionary.ruleSetId, this.dictionary.type).subscribe(
(data) => {
this.processing = false;
this.entries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
},
() => {
this.processing = false;
this.entries = [];
}
);
this._dictionaryControllerService
.getDictionaryForType(this.dictionary.ruleSetId, this.dictionary.type)
.subscribe(
(data) => {
this.processing = false;
this.entries = data.entries.sort((str1, str2) =>
str1.localeCompare(str2, undefined, { sensitivity: 'accent' })
);
},
() => {
this.processing = false;
this.entries = [];
}
);
}
}

View File

@ -22,8 +22,19 @@
<div class="red-content-inner">
<div class="content-container">
<div class="content-container-content">
<form (keyup)="formChanged()" *ngIf="digitalSignatureForm" [formGroup]="digitalSignatureForm" autocomplete="off">
<input #fileInput (change)="fileChanged($event, fileInput)" class="file-upload-input" hidden type="file" />
<form
(keyup)="formChanged()"
*ngIf="digitalSignatureForm"
[formGroup]="digitalSignatureForm"
autocomplete="off"
>
<input
#fileInput
(change)="fileChanged($event, fileInput)"
class="file-upload-input"
hidden
type="file"
/>
<redaction-empty-state
(action)="fileInput.click()"
@ -32,34 +43,66 @@
screen="digital-signature-screen"
></redaction-empty-state>
<div [class.hidden]="!hasDigitalSignatureSet" class="red-input-group required w-300">
<label translate="digital-signature-screen.certificate-name.label"></label>
<div
[class.hidden]="!hasDigitalSignatureSet"
class="red-input-group required w-300"
>
<label
translate="digital-signature-screen.certificate-name.label"
></label>
<input
[placeholder]="'digital-signature-screen.certificate-name.placeholder' | translate"
[placeholder]="
'digital-signature-screen.certificate-name.placeholder'
| translate
"
formControlName="certificateName"
name="certificateName"
/>
</div>
<div *ngIf="!digitalSignatureExists" [class.hidden]="!hasDigitalSignatureSet" class="red-input-group required w-300">
<div
*ngIf="!digitalSignatureExists"
[class.hidden]="!hasDigitalSignatureSet"
class="red-input-group required w-300"
>
<label translate="digital-signature-screen.password.label"></label>
<input [placeholder]="'digital-signature-screen.password.placeholder' | translate" formControlName="keySecret" name="keySecret" />
<input
[placeholder]="
'digital-signature-screen.password.placeholder' | translate
"
formControlName="keySecret"
name="keySecret"
/>
</div>
<div [class.hidden]="!hasDigitalSignatureSet" class="red-input-group w-300">
<label translate="digital-signature-screen.reason.label"></label>
<input [placeholder]="'digital-signature-screen.reason.placeholder' | translate" formControlName="reason" name="reason" />
<input
[placeholder]="
'digital-signature-screen.reason.placeholder' | translate
"
formControlName="reason"
name="reason"
/>
</div>
<div [class.hidden]="!hasDigitalSignatureSet" class="red-input-group w-300">
<label translate="digital-signature-screen.location.label"></label>
<input [placeholder]="'digital-signature-screen.location.placeholder' | translate" formControlName="location" name="location" />
<input
[placeholder]="
'digital-signature-screen.location.placeholder' | translate
"
formControlName="location"
name="location"
/>
</div>
<div [class.hidden]="!hasDigitalSignatureSet" class="red-input-group w-300">
<label translate="digital-signature-screen.contact-info.label"></label>
<input
[placeholder]="'digital-signature-screen.contact-info.placeholder' | translate"
[placeholder]="
'digital-signature-screen.contact-info.placeholder' | translate
"
formControlName="contactInfo"
name="contactInfo"
/>
@ -96,4 +139,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -29,7 +29,10 @@ export class DigitalSignatureScreenComponent {
}
get hasDigitalSignatureSet() {
return this.digitalSignatureExists || !!this.digitalSignatureForm.get('base64EncodedPrivateKey').value;
return (
this.digitalSignatureExists ||
!!this.digitalSignatureForm.get('base64EncodedPrivateKey').value
);
}
saveDigitalSignature() {
@ -55,13 +58,17 @@ export class DigitalSignatureScreenComponent {
(error) => {
if (error.status === 400) {
this._notificationService.showToastNotification(
this._translateService.instant('digital-signature-screen.action.certificate-not-valid-error'),
this._translateService.instant(
'digital-signature-screen.action.certificate-not-valid-error'
),
null,
NotificationType.ERROR
);
} else {
this._notificationService.showToastNotification(
this._translateService.instant('digital-signature-screen.action.save-error'),
this._translateService.instant(
'digital-signature-screen.action.save-error'
),
null,
NotificationType.ERROR
);
@ -75,7 +82,9 @@ export class DigitalSignatureScreenComponent {
() => {
this.loadDigitalSignatureAndInitializeForm();
this._notificationService.showToastNotification(
this._translateService.instant('digital-signature-screen.action.delete-success'),
this._translateService.instant(
'digital-signature-screen.action.delete-success'
),
null,
NotificationType.SUCCESS
);
@ -130,9 +139,13 @@ export class DigitalSignatureScreenComponent {
certificateName: [this.digitalSignature.certificateName, Validators.required],
contactInfo: this.digitalSignature.contactInfo,
location: this.digitalSignature.location,
keySecret: this.digitalSignatureExists ? null : [this.digitalSignature.password, Validators.required],
keySecret: this.digitalSignatureExists
? null
: [this.digitalSignature.password, Validators.required],
reason: this.digitalSignature.reason,
base64EncodedPrivateKey: this.digitalSignatureExists ? null : [this.digitalSignature.base64EncodedPrivateKey, Validators.required]
base64EncodedPrivateKey: this.digitalSignatureExists
? null
: [this.digitalSignature.base64EncodedPrivateKey, Validators.required]
});
}
}

View File

@ -3,7 +3,12 @@
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions flex-1">
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
<redaction-circle-button
[routerLink]="['../..']"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
@ -23,7 +28,10 @@
</div>
<span class="all-caps-label">
{{ 'file-attributes-listing.table-header.title' | translate: { length: displayedEntities.length } }}
{{
'file-attributes-listing.table-header.title'
| translate: { length: displayedEntities.length }
}}
</span>
<redaction-circle-button
@ -38,8 +46,17 @@
<mat-spinner *ngIf="loading" diameter="15"></mat-spinner>
<div class="attributes-actions-container">
<redaction-search-input [form]="searchForm" [placeholder]="'file-attributes-listing.search'"></redaction-search-input>
<input #fileInput (change)="importCSV($event.target['files'])" accept=".csv" class="csv-input" type="file" />
<redaction-search-input
[form]="searchForm"
[placeholder]="'file-attributes-listing.search'"
></redaction-search-input>
<input
#fileInput
(change)="importCSV($event.target['files'])"
accept=".csv"
class="csv-input"
type="file"
/>
<redaction-circle-button
(action)="fileInput.click()"
@ -58,7 +75,11 @@
</div>
</div>
<div [class.no-data]="!allEntities.length" class="table-header" redactionSyncWidth="table-item">
<div
[class.no-data]="!allEntities.length"
class="table-header"
redactionSyncWidth="table-item"
>
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
@ -86,7 +107,9 @@
label="file-attributes-listing.table-col-names.read-only"
></redaction-table-col-name>
<redaction-table-col-name label="file-attributes-listing.table-col-names.csv-column"></redaction-table-col-name>
<redaction-table-col-name
label="file-attributes-listing.table-col-names.csv-column"
></redaction-table-col-name>
<redaction-table-col-name
class="flex-center"
@ -100,7 +123,11 @@
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!allEntities.length" icon="red:attribute" screen="file-attributes-listing"></redaction-empty-state>
<redaction-empty-state
*ngIf="!allEntities.length"
icon="red:attribute"
screen="file-attributes-listing"
></redaction-empty-state>
<redaction-empty-state
*ngIf="allEntities.length && !displayedEntities.length"
@ -110,16 +137,27 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div *cdkVirtualFor="let attribute of displayedEntities | sortBy: sortingOption.order:sortingOption.column" class="table-item">
<div
*cdkVirtualFor="
let attribute of displayedEntities
| sortBy: sortingOption.order:sortingOption.column
"
class="table-item"
>
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column">
<redaction-round-checkbox [active]="isEntitySelected(attribute)"></redaction-round-checkbox>
<redaction-round-checkbox
[active]="isEntitySelected(attribute)"
></redaction-round-checkbox>
</div>
<div class="label">
<span>{{ attribute.label }}</span>
</div>
<div [translate]="'file-attribute-types.' + attribute.type" class="small-label"></div>
<div
[translate]="'file-attribute-types.' + attribute.type"
class="small-label"
></div>
<div class="center read-only">
<mat-icon
@ -133,7 +171,11 @@
{{ attribute.csvColumnHeader }}
</div>
<div class="center">
<redaction-round-checkbox *ngIf="attribute.primaryAttribute" [active]="true" [size]="18"></redaction-round-checkbox>
<redaction-round-checkbox
*ngIf="attribute.primaryAttribute"
[active]="true"
[size]="18"
></redaction-round-checkbox>
</div>
<div class="actions-container">
<div class="action-buttons">
@ -162,4 +204,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -1,6 +1,10 @@
import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import {
FileAttributeConfig,
FileAttributesConfig,
FileAttributesControllerService
} from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
@ -11,7 +15,10 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
templateUrl: './file-attributes-listing-screen.component.html',
styleUrls: ['./file-attributes-listing-screen.component.scss']
})
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit {
export class FileAttributesListingScreenComponent
extends BaseListingComponent<FileAttributeConfig>
implements OnInit
{
viewReady = false;
loading = false;
protected readonly _searchKey = 'label';
@ -39,38 +46,65 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
$event.stopPropagation();
this._dialogService.openAddEditFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async (newValue: FileAttributeConfig) => {
this.loading = true;
await this._fileAttributesService.setFileAttributesConfiguration(newValue, this._appStateService.activeRuleSetId).toPromise();
await this._loadData();
});
this._dialogService.openAddEditFileAttributeDialog(
fileAttribute,
this._appStateService.activeRuleSetId,
async (newValue: FileAttributeConfig) => {
this.loading = true;
await this._fileAttributesService
.setFileAttributesConfiguration(newValue, this._appStateService.activeRuleSetId)
.toPromise();
await this._loadData();
}
);
}
openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
$event.stopPropagation();
this._dialogService.openConfirmDeleteFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async () => {
this.loading = true;
if (fileAttribute) {
await this._fileAttributesService.deleteFileAttribute(this._appStateService.activeRuleSetId, fileAttribute.id).toPromise();
} else {
await this._fileAttributesService.deleteFileAttributes(this.selectedEntitiesIds, this._appStateService.activeRuleSetId).toPromise();
this._dialogService.openConfirmDeleteFileAttributeDialog(
fileAttribute,
this._appStateService.activeRuleSetId,
async () => {
this.loading = true;
if (fileAttribute) {
await this._fileAttributesService
.deleteFileAttribute(
this._appStateService.activeRuleSetId,
fileAttribute.id
)
.toPromise();
} else {
await this._fileAttributesService
.deleteFileAttributes(
this.selectedEntitiesIds,
this._appStateService.activeRuleSetId
)
.toPromise();
}
await this._loadData();
}
await this._loadData();
});
);
}
importCSV(files: FileList | File[]) {
const csvFile = files[0];
this._fileInput.nativeElement.value = null;
this._dialogService.openImportFileAttributeCSVDialog(csvFile, this._appStateService.activeRuleSetId, this._existingConfiguration, async () => {
await this._loadData();
});
this._dialogService.openImportFileAttributeCSVDialog(
csvFile,
this._appStateService.activeRuleSetId,
this._existingConfiguration,
async () => {
await this._loadData();
}
);
}
private async _loadData() {
try {
const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise();
const response = await this._fileAttributesService
.getFileAttributesConfiguration(this._appStateService.activeRuleSetId)
.toPromise();
this._existingConfiguration = response;
this.allEntities = response?.fileAttributeConfigs || [];
} catch (e) {

View File

@ -8,7 +8,12 @@
<div class="breadcrumb" translate="license-information"></div>
<div class="actions">
<button (click)="sendMail()" color="primary" mat-flat-button translate="license-info-screen.email-report"></button>
<button
(click)="sendMail()"
color="primary"
mat-flat-button
translate="license-info-screen.email-report"
></button>
<redaction-circle-button
*ngIf="permissionsService.isUser()"
class="ml-6"
@ -40,7 +45,12 @@
<div class="row">
<div translate="license-info-screen.copyright-claim-title"></div>
<div>{{ 'license-info-screen.copyright-claim-text' | translate: { currentYear: currentYear } }}</div>
<div>
{{
'license-info-screen.copyright-claim-text'
| translate: { currentYear: currentYear }
}}
</div>
</div>
<div class="row">
@ -54,7 +64,10 @@
<!-- <div>Future feature: we will provide that with the /info endpoint</div>-->
<!-- </div>-->
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
<div
class="section-title all-caps-label"
translate="license-info-screen.licensing-details"
></div>
<div class="row">
<div translate="license-info-screen.licensed-to"></div>
@ -63,7 +76,10 @@
<div class="row">
<div translate="license-info-screen.licensing-period"></div>
<div>{{ appConfigService.getConfig('LICENSE_START', '-') }} / {{ appConfigService.getConfig('LICENSE_END', '-') }}</div>
<div>
{{ appConfigService.getConfig('LICENSE_START', '-') }} /
{{ appConfigService.getConfig('LICENSE_END', '-') }}
</div>
</div>
<div class="row">
@ -76,16 +92,28 @@
<div>{{ currentInfo.numberOfAnalyzedPages }}</div>
</div>
<div class="section-title all-caps-label" translate="license-info-screen.usage-details"></div>
<div
class="section-title all-caps-label"
translate="license-info-screen.usage-details"
></div>
<div class="row">
<div>{{ 'license-info-screen.total-analyzed' | translate: { date: totalInfo.startDate | date: 'longDate' } }}</div>
<div>
{{
'license-info-screen.total-analyzed'
| translate: { date: totalInfo.startDate | date: 'longDate' }
}}
</div>
<div>{{ totalInfo.numberOfAnalyzedPages }}</div>
</div>
<div class="row">
<div translate="license-info-screen.current-analyzed"></div>
<div>{{ currentInfo.numberOfAnalyzedPages }} ({{ analysisPercentageOfLicense | number: '1.0-2' }}%)</div>
<div>
{{ currentInfo.numberOfAnalyzedPages }} ({{
analysisPercentageOfLicense | number: '1.0-2'
}}%)
</div>
</div>
<div *ngIf="!!unlicensedInfo" class="row">
@ -123,4 +151,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -54,31 +54,47 @@ export class LicenseInformationScreenComponent implements OnInit {
startDate: startDate.toDate(),
endDate: endDate.toDate()
};
const promises = [this._licenseReportController.licenseReport(currentConfig).toPromise(), this._licenseReportController.licenseReport({}).toPromise()];
const promises = [
this._licenseReportController.licenseReport(currentConfig).toPromise(),
this._licenseReportController.licenseReport({}).toPromise()
];
if (endDate.isBefore(moment())) {
const unlicensedConfig = {
startDate: endDate.toDate()
};
promises.push(this._licenseReportController.licenseReport(unlicensedConfig).toPromise());
promises.push(
this._licenseReportController.licenseReport(unlicensedConfig).toPromise()
);
}
Promise.all(promises).then((reports) => {
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports;
this.viewReady = true;
this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0 ? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100 : 100;
this.totalLicensedNumberOfPages > 0
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) *
100
: 100;
});
}
sendMail(): void {
const licenseCustomer = this.appConfigService.getConfig('LICENSE_CUSTOMER');
const subject = this._translateService.instant('license-info-screen.email.title', { licenseCustomer });
const subject = this._translateService.instant('license-info-screen.email.title', {
licenseCustomer
});
const body = [
this._translateService.instant('license-info-screen.email.body.analyzed', { pages: this.currentInfo.numberOfAnalyzedPages }),
this._translateService.instant('license-info-screen.email.body.licensed', { pages: this.totalLicensedNumberOfPages })
this._translateService.instant('license-info-screen.email.body.analyzed', {
pages: this.currentInfo.numberOfAnalyzedPages
}),
this._translateService.instant('license-info-screen.email.body.licensed', {
pages: this.totalLicensedNumberOfPages
})
].join('%0D%0A');
window.location.href = `mailto:${this.appConfigService.getConfig('LICENSE_EMAIL')}?subject=${subject}&body=${body}`;
window.location.href = `mailto:${this.appConfigService.getConfig(
'LICENSE_EMAIL'
)}?subject=${subject}&body=${body}`;
}
private async _setMonthlyStats(startDate: moment.Moment, endDate: moment.Moment) {

View File

@ -28,15 +28,24 @@
</div>
<span class="all-caps-label">
{{ 'project-templates-listing.table-header.title' | translate: { length: displayedEntities.length } }}
{{
'project-templates-listing.table-header.title'
| translate: { length: displayedEntities.length }
}}
</span>
<div class="actions flex-1">
<redaction-search-input [form]="searchForm" [placeholder]="'project-templates-listing.search'"></redaction-search-input>
<redaction-search-input
[form]="searchForm"
[placeholder]="'project-templates-listing.search'"
></redaction-search-input>
<redaction-icon-button
(action)="openAddRuleSetDialog()"
*ngIf="permissionsService.isAdmin() && userPreferenceService.areDevFeaturesEnabled"
*ngIf="
permissionsService.isAdmin() &&
userPreferenceService.areDevFeaturesEnabled
"
icon="red:plus"
text="project-templates-listing.add-new"
type="primary"
@ -44,7 +53,11 @@
</div>
</div>
<div [class.no-data]="!allEntities.length" class="table-header" redactionSyncWidth="table-item">
<div
[class.no-data]="!allEntities.length"
class="table-header"
redactionSyncWidth="table-item"
>
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
@ -54,7 +67,10 @@
column="name"
label="project-templates-listing.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name class="user-column" label="project-templates-listing.table-col-names.created-by"></redaction-table-col-name>
<redaction-table-col-name
class="user-column"
label="project-templates-listing.table-col-names.created-by"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
@ -72,7 +88,11 @@
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!allEntities.length" icon="red:template" screen="project-templates-listing"></redaction-empty-state>
<redaction-empty-state
*ngIf="!allEntities.length"
icon="red:template"
screen="project-templates-listing"
></redaction-empty-state>
<redaction-empty-state
*ngIf="allEntities.length && !displayedEntities.length"
@ -82,12 +102,20 @@
<cdk-virtual-scroll-viewport [itemSize]="100" redactionHasScrollbar>
<div
*cdkVirtualFor="let ruleSet of displayedEntities | sortBy: sortingOption.order:sortingOption.column"
*cdkVirtualFor="
let ruleSet of displayedEntities
| sortBy: sortingOption.order:sortingOption.column
"
[routerLink]="[ruleSet.ruleSetId, 'dictionaries']"
class="table-item pointer"
>
<div (click)="toggleEntitySelected($event, ruleSet)" class="selection-column">
<redaction-round-checkbox [active]="isEntitySelected(ruleSet)"></redaction-round-checkbox>
<div
(click)="toggleEntitySelected($event, ruleSet)"
class="selection-column"
>
<redaction-round-checkbox
[active]="isEntitySelected(ruleSet)"
></redaction-round-checkbox>
</div>
<div>
@ -97,13 +125,19 @@
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:dictionary"></mat-icon>
{{ 'project-templates-listing.dictionaries' | translate: { length: ruleSet.dictionariesCount } }}
{{
'project-templates-listing.dictionaries'
| translate: { length: ruleSet.dictionariesCount }
}}
</div>
</div>
</div>
<div class="user-column">
<redaction-initials-avatar [userId]="ruleSet.createdBy" [withName]="true"></redaction-initials-avatar>
<redaction-initials-avatar
[userId]="ruleSet.createdBy"
[withName]="true"
></redaction-initials-avatar>
</div>
<div class="small-label">
{{ ruleSet.dateAdded | date: 'd MMM. yyyy' }}

View File

@ -11,7 +11,10 @@ import { RuleSetModel } from '@redaction/red-ui-http';
templateUrl: './rule-sets-listing-screen.component.html',
styleUrls: ['./rule-sets-listing-screen.component.scss']
})
export class RuleSetsListingScreenComponent extends BaseListingComponent<RuleSetModel> implements OnInit {
export class RuleSetsListingScreenComponent
extends BaseListingComponent<RuleSetModel>
implements OnInit
{
protected readonly _searchKey = 'name';
protected readonly _selectionKey = 'ruleSetId';
protected readonly _sortKey = 'rule-sets-listing';

View File

@ -5,7 +5,12 @@
<div class="flex-1 actions">
<redaction-rule-set-actions></redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
<redaction-circle-button
[routerLink]="['../..']"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
@ -29,10 +34,21 @@
</ace-editor>
</div>
<div *ngIf="hasChanges && permissionsService.isAdmin()" class="changes-box">
<redaction-icon-button (action)="save()" icon="red:check" text="rules-screen.save-changes" type="primary"></redaction-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="rules-screen.revert-changes"></div>
<redaction-icon-button
(action)="save()"
icon="red:check"
text="rules-screen.save-changes"
type="primary"
></redaction-icon-button>
<div
(click)="revert()"
class="all-caps-label cancel"
translate="rules-screen.revert-changes"
></div>
</div>
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="processing"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="processing"
></redaction-full-page-loading-indicator>

View File

@ -69,7 +69,12 @@ export class RulesScreenComponent extends ComponentHasChanges {
const entry = this.currentLines[i];
if (entry?.trim().length > 0) {
// only mark non-empty lines
this.activeEditMarkers.push(this.editorComponent.getEditor().getSession().addMarker(new range(i, 0, i, 1), 'changed-row-marker', 'fullLine'));
this.activeEditMarkers.push(
this.editorComponent
.getEditor()
.getSession()
.addMarker(new range(i, 0, i, 1), 'changed-row-marker', 'fullLine')
);
}
}
}
@ -92,7 +97,11 @@ export class RulesScreenComponent extends ComponentHasChanges {
},
() => {
this.processing = false;
this._notificationService.showToastNotification(this._translateService.instant('rules-screen.error.generic'), null, NotificationType.ERROR);
this._notificationService.showToastNotification(
this._translateService.instant('rules-screen.error.generic'),
null,
NotificationType.ERROR
);
}
);
}

View File

@ -34,7 +34,9 @@
<input
formControlName="host"
name="host"
placeholder="{{ 'smtp-config-screen.form.host-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.host-placeholder' | translate
}}"
type="text"
/>
</div>
@ -44,7 +46,9 @@
<input
formControlName="port"
name="port"
placeholder="{{ 'smtp-config-screen.form.port-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.port-placeholder' | translate
}}"
type="number"
/>
</div>
@ -54,20 +58,30 @@
<input
formControlName="from"
name="from"
placeholder="{{ 'smtp-config-screen.form.from-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.from-placeholder' | translate
}}"
type="email"
/>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.from-display-name"></label>
<label
translate="smtp-config-screen.form.from-display-name"
></label>
<input
formControlName="fromDisplayName"
name="fromDisplayName"
placeholder="{{ 'smtp-config-screen.form.from-display-name-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.from-display-name-placeholder'
| translate
}}"
type="text"
/>
<span class="hint" translate="smtp-config-screen.form.from-display-name-hint"></span>
<span
class="hint"
translate="smtp-config-screen.form.from-display-name-hint"
></span>
</div>
<div class="red-input-group">
@ -75,44 +89,69 @@
<input
formControlName="replyTo"
name="replyTo"
placeholder="{{ 'smtp-config-screen.form.reply-to-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.reply-to-placeholder'
| translate
}}"
type="text"
/>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.reply-to-display-name"></label>
<label
translate="smtp-config-screen.form.reply-to-display-name"
></label>
<input
formControlName="replyToDisplayName"
name="replyToDisplayName"
placeholder="{{ 'smtp-config-screen.form.reply-to-display-name-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.reply-to-display-name-placeholder'
| translate
}}"
type="text"
/>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.envelope-from"></label>
<label
translate="smtp-config-screen.form.envelope-from"
></label>
<input
formControlName="envelopeFrom"
name="envelopeFrom"
placeholder="{{ 'smtp-config-screen.form.envelope-from-placeholder' | translate }}"
placeholder="{{
'smtp-config-screen.form.envelope-from-placeholder'
| translate
}}"
type="text"
/>
<span class="hint" translate="smtp-config-screen.form.envelope-from-hint"></span>
<span
class="hint"
translate="smtp-config-screen.form.envelope-from-hint"
></span>
</div>
</div>
<div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.ssl"></label>
<mat-slide-toggle color="primary" formControlName="ssl"></mat-slide-toggle>
<mat-slide-toggle
color="primary"
formControlName="ssl"
></mat-slide-toggle>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.starttls"></label>
<mat-slide-toggle color="primary" formControlName="starttls"></mat-slide-toggle>
<mat-slide-toggle
color="primary"
formControlName="starttls"
></mat-slide-toggle>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.auth"></label>
<mat-slide-toggle color="primary" formControlName="auth"></mat-slide-toggle>
<mat-slide-toggle
color="primary"
formControlName="auth"
></mat-slide-toggle>
</div>
<div
(click)="openAuthConfigDialog(true)"
@ -123,7 +162,12 @@
</div>
</div>
<div class="dialog-actions">
<button [disabled]="configForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button
[disabled]="configForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{ 'smtp-config-screen.actions.save' | translate }}
</button>
@ -141,4 +185,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -65,32 +65,43 @@ export class SmtpConfigScreenComponent implements OnInit {
async save() {
this.viewReady = false;
await this._smtpConfigService.updateSMTPConfiguration(this.configForm.getRawValue()).toPromise();
await this._smtpConfigService
.updateSMTPConfiguration(this.configForm.getRawValue())
.toPromise();
this._initialValue = this.configForm.getRawValue();
this.viewReady = true;
}
openAuthConfigDialog(skipDisableOnCancel?: boolean) {
this._dialogService.openSMTPAuthConfigDialog(this.configForm.getRawValue(), (authConfig) => {
if (authConfig) {
this.configForm.patchValue(authConfig);
} else if (!skipDisableOnCancel) {
this.configForm.patchValue({ auth: false }, { emitEvent: false });
this._dialogService.openSMTPAuthConfigDialog(
this.configForm.getRawValue(),
(authConfig) => {
if (authConfig) {
this.configForm.patchValue(authConfig);
} else if (!skipDisableOnCancel) {
this.configForm.patchValue({ auth: false }, { emitEvent: false });
}
}
});
);
}
async testConnection() {
this.viewReady = false;
try {
await this._smtpConfigService.testSMTPConfiguration(this.configForm.getRawValue()).toPromise();
await this._smtpConfigService
.testSMTPConfiguration(this.configForm.getRawValue())
.toPromise();
this._notificationService.showToastNotification(
this._translateService.instant('smtp-config-screen.test.success'),
undefined,
NotificationType.SUCCESS
);
} catch (e) {
this._notificationService.showToastNotification(this._translateService.instant('smtp-config-screen.test.error'), undefined, NotificationType.ERROR);
this._notificationService.showToastNotification(
this._translateService.instant('smtp-config-screen.test.error'),
undefined,
NotificationType.ERROR
);
} finally {
this.viewReady = true;
}
@ -98,7 +109,9 @@ export class SmtpConfigScreenComponent implements OnInit {
private async _loadData() {
try {
this._initialValue = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise();
this._initialValue = await this._smtpConfigService
.getCurrentSMTPConfiguration()
.toPromise();
this.configForm.patchValue(this._initialValue, { emitEvent: false });
} catch (e) {
} finally {

View File

@ -8,7 +8,10 @@
<div class="breadcrumb" translate="user-management"></div>
<div class="actions">
<redaction-search-input [form]="searchForm" [placeholder]="'user-listing.search'"></redaction-search-input>
<redaction-search-input
[form]="searchForm"
[placeholder]="'user-listing.search'"
></redaction-search-input>
<redaction-icon-button
(action)="openAddEditUserDialog($event)"
*ngIf="permissionsService.isUserAdmin()"
@ -39,14 +42,21 @@
</div>
<span class="all-caps-label">
{{ 'user-listing.table-header.title' | translate: { length: displayedEntities.length } }}
{{
'user-listing.table-header.title'
| translate: { length: displayedEntities.length }
}}
</span>
<ng-container *ngIf="areSomeEntitiesSelected && !loading">
<redaction-circle-button
(action)="bulkDelete()"
[disabled]="!canDeleteSelected"
[tooltip]="canDeleteSelected ? 'user-listing.bulk.delete' : 'user-listing.bulk.delete-disabled'"
[tooltip]="
canDeleteSelected
? 'user-listing.bulk.delete'
: 'user-listing.bulk.delete-disabled'
"
icon="red:trash"
tooltipPosition="after"
type="dark-bg"
@ -59,33 +69,56 @@
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name label="user-listing.table-col-names.name"></redaction-table-col-name>
<redaction-table-col-name
label="user-listing.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name label="user-listing.table-col-names.email"></redaction-table-col-name>
<redaction-table-col-name
label="user-listing.table-col-names.email"
></redaction-table-col-name>
<redaction-table-col-name class="flex-center" label="user-listing.table-col-names.active"></redaction-table-col-name>
<redaction-table-col-name
class="flex-center"
label="user-listing.table-col-names.active"
></redaction-table-col-name>
<redaction-table-col-name label="user-listing.table-col-names.roles"></redaction-table-col-name>
<redaction-table-col-name
label="user-listing.table-col-names.roles"
></redaction-table-col-name>
<div></div>
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!displayedEntities.length" screen="user-listing" type="no-match"></redaction-empty-state>
<redaction-empty-state
*ngIf="!displayedEntities.length"
screen="user-listing"
type="no-match"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div *cdkVirtualFor="let user of displayedEntities" class="table-item">
<div (click)="toggleEntitySelected($event, user)" class="selection-column">
<redaction-round-checkbox [active]="isEntitySelected(user)"></redaction-round-checkbox>
<redaction-round-checkbox
[active]="isEntitySelected(user)"
></redaction-round-checkbox>
</div>
<div>
<redaction-initials-avatar [showYou]="true" [user]="user" [withName]="true"></redaction-initials-avatar>
<redaction-initials-avatar
[showYou]="true"
[user]="user"
[withName]="true"
></redaction-initials-avatar>
</div>
<div class="small-label">{{ user.email || '-' }}</div>
<div class="center">
<mat-slide-toggle (toggleChange)="toggleActive(user)" [checked]="userService.isActive(user)" color="primary"></mat-slide-toggle>
<mat-slide-toggle
(toggleChange)="toggleActive(user)"
[checked]="userService.isActive(user)"
color="primary"
></mat-slide-toggle>
</div>
<div class="small-label">{{ getDisplayRoles(user) }}</div>
<div class="actions-container">
@ -113,10 +146,15 @@
</div>
<div [class.collapsed]="collapsedDetails" class="right-container" redactionHasScrollbar>
<redaction-users-stats (toggleCollapse)="toggleCollapsedDetails()" [chartData]="chartData"></redaction-users-stats>
<redaction-users-stats
(toggleCollapse)="toggleCollapsedDetails()"
[chartData]="chartData"
></redaction-users-stats>
</div>
</div>
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -50,7 +50,9 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
if (result.action === 'CREATE') {
await this._userControllerService.createUser(result.user).toPromise();
} else if (result.action === 'UPDATE') {
await this._userControllerService.updateProfile(result.user, user.userId).toPromise();
await this._userControllerService
.updateProfile(result.user, user.userId)
.toPromise();
}
await this._loadData();
}
@ -67,7 +69,10 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
}
getDisplayRoles(user: User) {
return user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') || this._translateService.instant('roles.NO_ROLE');
return (
user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') ||
this._translateService.instant('roles.NO_ROLE')
);
}
async toggleActive(user: User) {
@ -101,32 +106,46 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
this.chartData = this._translateChartService.translateRoles(
[
{
value: this.allEntities.filter((user) => !this.userService.isActive(user)).length,
value: this.allEntities.filter((user) => !this.userService.isActive(user))
.length,
color: 'INACTIVE',
label: 'INACTIVE'
},
{
value: this.allEntities.filter((user) => user.roles.length === 1 && user.roles[0] === 'RED_USER').length,
value: this.allEntities.filter(
(user) => user.roles.length === 1 && user.roles[0] === 'RED_USER'
).length,
color: 'REGULAR',
label: 'REGULAR'
},
{
value: this.allEntities.filter((user) => this.userService.isManager(user) && !this.userService.isAdmin(user)).length,
value: this.allEntities.filter(
(user) =>
this.userService.isManager(user) && !this.userService.isAdmin(user)
).length,
color: 'MANAGER',
label: 'RED_MANAGER'
},
{
value: this.allEntities.filter((user) => this.userService.isManager(user) && this.userService.isAdmin(user)).length,
value: this.allEntities.filter(
(user) => this.userService.isManager(user) && this.userService.isAdmin(user)
).length,
color: 'MANAGER_ADMIN',
label: 'MANAGER_ADMIN'
},
{
value: this.allEntities.filter((user) => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length,
value: this.allEntities.filter(
(user) =>
this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)
).length,
color: 'USER_ADMIN',
label: 'RED_USER_ADMIN'
},
{
value: this.allEntities.filter((user) => this.userService.isAdmin(user) && !this.userService.isManager(user)).length,
value: this.allEntities.filter(
(user) =>
this.userService.isAdmin(user) && !this.userService.isManager(user)
).length,
color: 'ADMIN',
label: 'RED_ADMIN'
}

View File

@ -3,7 +3,12 @@
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions flex-1">
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
<redaction-circle-button
[routerLink]="['../..']"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
@ -22,7 +27,11 @@
text="watermark-screen.action.save"
type="primary"
></redaction-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="watermark-screen.action.revert"></div>
<div
(click)="revert()"
class="all-caps-label cancel"
translate="watermark-screen.action.revert"
></div>
</div>
</div>
@ -43,7 +52,10 @@
</div>
<div class="red-input-group">
<label class="all-caps-label mb-8" translate="watermark-screen.form.orientation"></label>
<label
class="all-caps-label mb-8"
translate="watermark-screen.form.orientation"
></label>
<div class="square-options">
<div
(click)="setValue('orientation', option)"
@ -58,17 +70,34 @@
</div>
<div class="red-input-group">
<label class="all-caps-label" translate="watermark-screen.form.font-size"></label>
<mat-slider (change)="configChanged()" color="primary" formControlName="fontSize" max="50" min="5"></mat-slider>
<label
class="all-caps-label"
translate="watermark-screen.form.font-size"
></label>
<mat-slider
(change)="configChanged()"
color="primary"
formControlName="fontSize"
max="50"
min="5"
></mat-slider>
</div>
<div class="red-input-group">
<label class="all-caps-label" translate="watermark-screen.form.opacity"></label>
<mat-slider (change)="configChanged()" color="primary" formControlName="opacity" min="1"></mat-slider>
<mat-slider
(change)="configChanged()"
color="primary"
formControlName="opacity"
min="1"
></mat-slider>
</div>
<div class="red-input-group w-150">
<label class="all-caps-label mb-5" translate="watermark-screen.form.color"></label>
<label
class="all-caps-label mb-5"
translate="watermark-screen.form.color"
></label>
<input
class="hex-color-input"
formControlName="hexColor"
@ -88,14 +117,20 @@
class="input-icon"
>
<mat-icon
*ngIf="!configForm.get('hexColor')?.value || configForm.get('hexColor').value?.length === 0"
*ngIf="
!configForm.get('hexColor')?.value ||
configForm.get('hexColor').value?.length === 0
"
svgIcon="red:color-picker"
></mat-icon>
</div>
</div>
<div class="red-input-group">
<label class="all-caps-label mb-8" translate="watermark-screen.form.font-type"></label>
<label
class="all-caps-label mb-8"
translate="watermark-screen.form.font-type"
></label>
<div class="square-options">
<div
(click)="setValue('fontType', option.value)"
@ -119,4 +154,6 @@
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -78,20 +78,33 @@ export class WatermarkScreenComponent implements OnInit {
};
const observable = watermark.text
? this._watermarkControllerService.saveWatermark(watermark, this.appStateService.activeRuleSetId)
: this._watermarkControllerService.deleteWatermark(this.appStateService.activeRuleSetId);
? this._watermarkControllerService.saveWatermark(
watermark,
this.appStateService.activeRuleSetId
)
: this._watermarkControllerService.deleteWatermark(
this.appStateService.activeRuleSetId
);
observable.subscribe(
() => {
this._loadWatermark();
this._notificationService.showToastNotification(
this._translateService.instant(watermark.text ? 'watermark-screen.action.change-success' : 'watermark-screen.action.delete-success'),
this._translateService.instant(
watermark.text
? 'watermark-screen.action.change-success'
: 'watermark-screen.action.delete-success'
),
null,
NotificationType.SUCCESS
);
},
() => {
this._notificationService.showToastNotification(this._translateService.instant('watermark-screen.action.error'), null, NotificationType.ERROR);
this._notificationService.showToastNotification(
this._translateService.instant('watermark-screen.action.error'),
null,
NotificationType.ERROR
);
}
);
}
@ -111,18 +124,20 @@ export class WatermarkScreenComponent implements OnInit {
}
private _loadWatermark() {
this._watermarkControllerService.getWatermark(this.appStateService.activeRuleSetId).subscribe(
(watermark) => {
this._watermark = watermark;
this.configForm.setValue({ ...this._watermark });
this._loadViewer();
},
() => {
this._watermark = DEFAULT_WATERMARK;
this.configForm.setValue({ ...this._watermark });
this._loadViewer();
}
);
this._watermarkControllerService
.getWatermark(this.appStateService.activeRuleSetId)
.subscribe(
(watermark) => {
this._watermark = watermark;
this.configForm.setValue({ ...this._watermark });
this._loadViewer();
},
() => {
this._watermark = DEFAULT_WATERMARK;
this.configForm.setValue({ ...this._watermark });
this._loadViewer();
}
);
}
private _loadViewer() {
@ -177,14 +192,17 @@ export class WatermarkScreenComponent implements OnInit {
const text = this.configForm.get('text').value || '';
const fontSize = this.configForm.get('fontSize').value;
const fontType = this.configForm.get('fontType').value;
const orientation: WatermarkModel.WatermarkOrientationEnum = this.configForm.get('orientation').value;
const orientation: WatermarkModel.WatermarkOrientationEnum =
this.configForm.get('orientation').value;
const opacity = this.configForm.get('opacity').value;
const color = this.configForm.get('hexColor').value;
const rgbColor = hexToRgb(color);
const stamper = await pdfNet.Stamper.create(3, fontSize, 0);
await stamper.setFontColor(await pdfNet.ColorPt.init(rgbColor.r / 255, rgbColor.g / 255, rgbColor.b / 255));
await stamper.setFontColor(
await pdfNet.ColorPt.init(rgbColor.r / 255, rgbColor.g / 255, rgbColor.b / 255)
);
await stamper.setOpacity(opacity / 100);
switch (orientation) {
@ -200,7 +218,10 @@ export class WatermarkScreenComponent implements OnInit {
await stamper.setRotation(-45);
}
const font = await pdfNet.Font.createAndEmbed(document, this._convertFont(fontType));
const font = await pdfNet.Font.createAndEmbed(
document,
this._convertFont(fontType)
);
await stamper.setFont(font);
await stamper.setTextAlignment(0);
await stamper.stampText(document, text, pageSet);
@ -216,11 +237,26 @@ export class WatermarkScreenComponent implements OnInit {
private _initForm() {
this.configForm = this._formBuilder.group({
text: [{ value: null, disabled: !this.permissionsService.isAdmin() }],
hexColor: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
opacity: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
fontSize: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
fontType: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
orientation: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required]
hexColor: [
{ value: null, disabled: !this.permissionsService.isAdmin() },
Validators.required
],
opacity: [
{ value: null, disabled: !this.permissionsService.isAdmin() },
Validators.required
],
fontSize: [
{ value: null, disabled: !this.permissionsService.isAdmin() },
Validators.required
],
fontType: [
{ value: null, disabled: !this.permissionsService.isAdmin() },
Validators.required
],
orientation: [
{ value: null, disabled: !this.permissionsService.isAdmin() },
Validators.required
]
});
}

View File

@ -53,19 +53,30 @@ export class AdminDialogService {
private readonly _manualRedactionControllerService: ManualRedactionControllerService
) {}
openDeleteDictionaryDialog($event: MouseEvent, dictionary: TypeValue, ruleSetId: string, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
openDeleteDictionaryDialog(
$event: MouseEvent,
dictionary: TypeValue,
ruleSetId: string,
cb?: Function
): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async (result) => {
if (result) {
await this._dictionaryControllerService.deleteType(dictionary.type, ruleSetId).toPromise();
await this._dictionaryControllerService
.deleteType(dictionary.type, ruleSetId)
.toPromise();
if (cb) cb();
}
});
return ref;
}
openDeleteRuleSetDialog($event: MouseEvent, ruleSet: RuleSetModel, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
openDeleteRuleSetDialog(
$event: MouseEvent,
ruleSet: RuleSetModel,
cb?: Function
): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async (result) => {
@ -77,7 +88,11 @@ export class AdminDialogService {
return ref;
}
openAddEditDictionaryDialog(dictionary: TypeValue, ruleSetId: string, cb?: Function): MatDialogRef<AddEditDictionaryDialogComponent> {
openAddEditDictionaryDialog(
dictionary: TypeValue,
ruleSetId: string,
cb?: Function
): MatDialogRef<AddEditDictionaryDialogComponent> {
const ref = this._dialog.open(AddEditDictionaryDialogComponent, {
...dialogConfig,
data: { dictionary, ruleSetId },
@ -93,7 +108,12 @@ export class AdminDialogService {
return ref;
}
openEditColorsDialog(colors: Colors, colorKey: string, ruleSetId: string, cb?: Function): MatDialogRef<EditColorDialogComponent> {
openEditColorsDialog(
colors: Colors,
colorKey: string,
ruleSetId: string,
cb?: Function
): MatDialogRef<EditColorDialogComponent> {
const ref = this._dialog.open(EditColorDialogComponent, {
...dialogConfig,
data: { colors, colorKey, ruleSetId },
@ -109,7 +129,10 @@ export class AdminDialogService {
return ref;
}
openAddEditRuleSetDialog(ruleSet: RuleSetModel, cb?: Function): MatDialogRef<AddEditRuleSetDialogComponent> {
openAddEditRuleSetDialog(
ruleSet: RuleSetModel,
cb?: Function
): MatDialogRef<AddEditRuleSetDialogComponent> {
const ref = this._dialog.open(AddEditRuleSetDialogComponent, {
...dialogConfig,
width: '900px',
@ -146,7 +169,11 @@ export class AdminDialogService {
return ref;
}
openAddEditFileAttributeDialog(fileAttribute: FileAttributeConfig, ruleSetId: string, cb?: Function): MatDialogRef<AddEditFileAttributeDialogComponent> {
openAddEditFileAttributeDialog(
fileAttribute: FileAttributeConfig,
ruleSetId: string,
cb?: Function
): MatDialogRef<AddEditFileAttributeDialogComponent> {
const ref = this._dialog.open(AddEditFileAttributeDialogComponent, {
...dialogConfig,
data: { fileAttribute, ruleSetId },
@ -182,7 +209,10 @@ export class AdminDialogService {
return ref;
}
openSMTPAuthConfigDialog(smtpConfig: SMTPConfigurationModel, cb?: Function): MatDialogRef<SmtpAuthDialogComponent> {
openSMTPAuthConfigDialog(
smtpConfig: SMTPConfigurationModel,
cb?: Function
): MatDialogRef<SmtpAuthDialogComponent> {
const ref = this._dialog.open(SmtpAuthDialogComponent, {
...dialogConfig,
data: smtpConfig,
@ -214,7 +244,10 @@ export class AdminDialogService {
return ref;
}
openConfirmDeleteUsersDialog(users: User[], cb?: Function): MatDialogRef<ConfirmDeleteUsersDialogComponent> {
openConfirmDeleteUsersDialog(
users: User[],
cb?: Function
): MatDialogRef<ConfirmDeleteUsersDialogComponent> {
const ref = this._dialog.open(ConfirmDeleteUsersDialogComponent, {
...dialogConfig,
data: users,

View File

@ -34,17 +34,28 @@ export enum AppConfigKey {
export class AppConfigService {
private _config: { [key in AppConfigKey]?: any } = {};
constructor(private readonly _httpClient: HttpClient, private readonly _cacheApiService: CacheApiService, private readonly _titleService: Title) {}
constructor(
private readonly _httpClient: HttpClient,
private readonly _cacheApiService: CacheApiService,
private readonly _titleService: Title
) {}
loadAppConfig(): Observable<any> {
this._cacheApiService.getCachedValue(AppConfigKey.FRONTEND_APP_VERSION).then(async (lastVersion) => {
console.log('[REDACTION] Last app version: ', lastVersion, ' current version ', version);
if (lastVersion !== version) {
console.log('[REDACTION] Version-missmatch - wiping caches!');
await wipeCaches();
}
await this._cacheApiService.cacheValue(AppConfigKey.FRONTEND_APP_VERSION, version);
});
this._cacheApiService
.getCachedValue(AppConfigKey.FRONTEND_APP_VERSION)
.then(async (lastVersion) => {
console.log(
'[REDACTION] Last app version: ',
lastVersion,
' current version ',
version
);
if (lastVersion !== version) {
console.log('[REDACTION] Version-missmatch - wiping caches!');
await wipeCaches();
}
await this._cacheApiService.cacheValue(AppConfigKey.FRONTEND_APP_VERSION, version);
});
return this._httpClient.get<any>('/assets/config/config.json').pipe(
tap((config) => {

View File

@ -7,7 +7,11 @@ import { KeycloakAngularModule, KeycloakOptions, KeycloakService } from 'keycloa
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
import { BASE_HREF } from '../../tokens';
export function keycloakInitializer(keycloak: KeycloakService, appConfigService: AppConfigService, baseUrl) {
export function keycloakInitializer(
keycloak: KeycloakService,
appConfigService: AppConfigService,
baseUrl
) {
return () =>
appConfigService
.loadAppConfig()
@ -26,12 +30,15 @@ export function keycloakInitializer(keycloak: KeycloakService, appConfigService:
initOptions: {
checkLoginIframe: false,
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + baseUrl + '/assets/oauth/silent-refresh.html',
silentCheckSsoRedirectUri:
window.location.origin + baseUrl + '/assets/oauth/silent-refresh.html',
flow: 'standard'
},
enableBearerInterceptor: true
};
return keycloak.init(options).then(() => configureAutomaticRedirectToLoginScreen(keycloak));
return keycloak
.init(options)
.then(() => configureAutomaticRedirectToLoginScreen(keycloak));
});
}

View File

@ -8,7 +8,11 @@ import { Observable } from 'rxjs';
providedIn: 'root'
})
export class RedRoleGuard implements CanActivate {
constructor(protected readonly _router: Router, private readonly _appLoadStateService: AppLoadStateService, private readonly _userService: UserService) {}
constructor(
protected readonly _router: Router,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return new Observable((obs) => {
@ -20,7 +24,11 @@ export class RedRoleGuard implements CanActivate {
} else {
// we have at least 1 RED Role -> if it's not user he must be admin
if (this._userService.user.isUserAdmin && !this._userService.user.isAdmin && !state.url.startsWith('/main/admin/users')) {
if (
this._userService.user.isUserAdmin &&
!this._userService.user.isAdmin &&
!state.url.startsWith('/main/admin/users')
) {
this._router.navigate(['/main/admin/users']);
obs.next(false);
obs.complete();

View File

@ -9,7 +9,10 @@ import { DomSanitizer } from '@angular/platform-browser';
exports: [MatIconModule]
})
export class IconsModule {
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
constructor(
private readonly _iconRegistry: MatIconRegistry,
private readonly _sanitizer: DomSanitizer
) {
const icons = [
'add',
'analyse',
@ -84,7 +87,11 @@ export class IconsModule {
];
for (const icon of icons) {
_iconRegistry.addSvgIconInNamespace('red', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${icon}.svg`));
_iconRegistry.addSvgIconInNamespace(
'red',
icon,
_sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${icon}.svg`)
);
}
}
}

View File

@ -10,7 +10,13 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, [annotation], annotationsChanged)"
(action)="
annotationActionsService.convertRecommendationToAnnotation(
$event,
[annotation],
annotationsChanged
)
"
*ngIf="annotationPermissions.canAcceptRecommendation"
icon="red:check"
tooltip="annotation-actions.accept-recommendation.label"
@ -20,8 +26,13 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.markAsFalsePositive($event, [annotation], annotationsChanged)"
*ngIf="annotationPermissions.canMarkTextOnlyAsFalsePositive && !annotationPermissions.canPerformMultipleRemoveActions"
(action)="
annotationActionsService.markAsFalsePositive($event, [annotation], annotationsChanged)
"
*ngIf="
annotationPermissions.canMarkTextOnlyAsFalsePositive &&
!annotationPermissions.canPerformMultipleRemoveActions
"
icon="red:thumb-down"
tooltip="annotation-actions.remove-annotation.false-positive"
tooltipPosition="before"
@ -30,7 +41,9 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.acceptSuggestion($event, [annotation], annotationsChanged)"
(action)="
annotationActionsService.acceptSuggestion($event, [annotation], annotationsChanged)
"
*ngIf="annotationPermissions.canAcceptSuggestion"
icon="red:check"
tooltip="annotation-actions.accept-suggestion.label"
@ -40,7 +53,9 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.undoDirectAction($event, [annotation], annotationsChanged)"
(action)="
annotationActionsService.undoDirectAction($event, [annotation], annotationsChanged)
"
*ngIf="annotationPermissions.canUndo"
icon="red:undo"
tooltip="annotation-actions.undo"
@ -70,7 +85,9 @@
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.rejectSuggestion($event, [annotation], annotationsChanged)"
(action)="
annotationActionsService.rejectSuggestion($event, [annotation], annotationsChanged)
"
*ngIf="annotationPermissions.canRejectSuggestion"
icon="red:close"
tooltip="annotation-actions.reject-suggestion"

View File

@ -1,6 +1,9 @@
<redaction-circle-button
(action)="suggestRemoveAnnotations($event, false)"
*ngIf="permissions.canRemoveOrSuggestToRemoveOnlyHere && permissions.canNotPerformMultipleRemoveActions"
*ngIf="
permissions.canRemoveOrSuggestToRemoveOnlyHere &&
permissions.canNotPerformMultipleRemoveActions
"
[tooltipPosition]="tooltipPosition"
[type]="btnType"
icon="red:trash"
@ -21,16 +24,36 @@
</redaction-circle-button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="suggestRemoveAnnotations($event, true)" *ngIf="permissions.canRemoveOrSuggestToRemoveFromDictionary" mat-menu-item>
<redaction-annotation-icon [color]="dictionaryColor" [label]="'S'" [type]="'rhombus'"></redaction-annotation-icon>
<div
(click)="suggestRemoveAnnotations($event, true)"
*ngIf="permissions.canRemoveOrSuggestToRemoveFromDictionary"
mat-menu-item
>
<redaction-annotation-icon
[color]="dictionaryColor"
[label]="'S'"
[type]="'rhombus'"
></redaction-annotation-icon>
<div [translate]="'annotation-actions.remove-annotation.remove-from-dict'"></div>
</div>
<div (click)="suggestRemoveAnnotations($event, false)" *ngIf="permissions.canRemoveOrSuggestToRemoveOnlyHere" mat-menu-item>
<redaction-annotation-icon [color]="suggestionColor" [label]="'S'" [type]="'rhombus'"></redaction-annotation-icon>
<div
(click)="suggestRemoveAnnotations($event, false)"
*ngIf="permissions.canRemoveOrSuggestToRemoveOnlyHere"
mat-menu-item
>
<redaction-annotation-icon
[color]="suggestionColor"
[label]="'S'"
[type]="'rhombus'"
></redaction-annotation-icon>
<div translate="annotation-actions.remove-annotation.only-here"></div>
</div>
<div (click)="markAsFalsePositive($event)" *ngIf="permissions.canMarkAsFalsePositive" mat-menu-item>
<div
(click)="markAsFalsePositive($event)"
*ngIf="permissions.canMarkAsFalsePositive"
mat-menu-item
>
<mat-icon class="false-positive-icon" svgIcon="red:thumb-down"></mat-icon>
<div translate="annotation-actions.remove-annotation.false-positive"></div>
</div>

View File

@ -65,20 +65,44 @@ export class AnnotationRemoveActionsComponent {
suggestRemoveAnnotations($event, removeFromDict: boolean) {
$event.stopPropagation();
this._annotationActionsService.suggestRemoveAnnotation($event, this.annotations, removeFromDict, this.annotationsChanged);
this._annotationActionsService.suggestRemoveAnnotation(
$event,
this.annotations,
removeFromDict,
this.annotationsChanged
);
}
markAsFalsePositive($event) {
this._annotationActionsService.markAsFalsePositive($event, this.annotations, this.annotationsChanged);
this._annotationActionsService.markAsFalsePositive(
$event,
this.annotations,
this.annotationsChanged
);
}
private _setPermissions() {
this.permissions = {
canRemoveOrSuggestToRemoveOnlyHere: this._annotationsPermissions(['canRemoveOrSuggestToRemoveOnlyHere'], true),
canPerformMultipleRemoveActions: this._annotationsPermissions(['canPerformMultipleRemoveActions'], true),
canNotPerformMultipleRemoveActions: this._annotationsPermissions(['canPerformMultipleRemoveActions'], false),
canRemoveOrSuggestToRemoveFromDictionary: this._annotationsPermissions(['canRemoveOrSuggestToRemoveFromDictionary'], true),
canMarkAsFalsePositive: this._annotationsPermissions(['canMarkAsFalsePositive', 'canMarkTextOnlyAsFalsePositive'], true)
canRemoveOrSuggestToRemoveOnlyHere: this._annotationsPermissions(
['canRemoveOrSuggestToRemoveOnlyHere'],
true
),
canPerformMultipleRemoveActions: this._annotationsPermissions(
['canPerformMultipleRemoveActions'],
true
),
canNotPerformMultipleRemoveActions: this._annotationsPermissions(
['canPerformMultipleRemoveActions'],
false
),
canRemoveOrSuggestToRemoveFromDictionary: this._annotationsPermissions(
['canRemoveOrSuggestToRemoveFromDictionary'],
true
),
canMarkAsFalsePositive: this._annotationsPermissions(
['canMarkAsFalsePositive', 'canMarkTextOnlyAsFalsePositive'],
true
)
};
}
@ -89,7 +113,10 @@ export class AnnotationRemoveActionsComponent {
this._permissionsService.currentUser,
annotation
);
const hasAtLeastOnePermission = keys.reduce((acc, key) => acc || annotationPermissions[key] === expectedValue, false);
const hasAtLeastOnePermission = keys.reduce(
(acc, key) => acc || annotationPermissions[key] === expectedValue,
false
);
return prevValue && hasAtLeastOnePermission;
}, true);
}

View File

@ -15,7 +15,10 @@
type="dark-bg"
></redaction-circle-button>
<redaction-file-download-btn [file]="selectedFiles" [project]="project"></redaction-file-download-btn>
<redaction-file-download-btn
[file]="selectedFiles"
[project]="project"
></redaction-file-download-btn>
<redaction-circle-button
(action)="setToUnderApproval()"
@ -26,7 +29,13 @@
>
</redaction-circle-button>
<redaction-circle-button (action)="setToUnderReview()" *ngIf="canSetToUnderReview" icon="red:undo" tooltip="project-overview.under-review" type="dark-bg">
<redaction-circle-button
(action)="setToUnderReview()"
*ngIf="canSetToUnderReview"
icon="red:undo"
tooltip="project-overview.under-review"
type="dark-bg"
>
</redaction-circle-button>
<!-- Approved-->
@ -41,10 +50,22 @@
</redaction-circle-button>
<!-- Back to approval -->
<redaction-circle-button (action)="setToUnderApproval()" *ngIf="canUndoApproval" icon="red:undo" tooltip="project-overview.under-approval" type="dark-bg">
<redaction-circle-button
(action)="setToUnderApproval()"
*ngIf="canUndoApproval"
icon="red:undo"
tooltip="project-overview.under-approval"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button (action)="ocr()" *ngIf="canOcr" icon="red:ocr" tooltip="project-overview.ocr-file" type="dark-bg"></redaction-circle-button>
<redaction-circle-button
(action)="ocr()"
*ngIf="canOcr"
icon="red:ocr"
tooltip="project-overview.ocr-file"
type="dark-bg"
></redaction-circle-button>
<redaction-circle-button
(action)="reanalyse()"

View File

@ -1,7 +1,10 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
import {
FileManagementControllerService,
ReanalysisControllerService
} from '@redaction/red-ui-http';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { FileActionService } from '../../services/file-action.service';
@ -39,11 +42,19 @@ export class ProjectOverviewBulkActionsComponent {
}
get selectedFiles(): FileStatusWrapper[] {
return this.selectedFileIds.map((fileId) => this._appStateService.getFileById(this._appStateService.activeProject.project.projectId, fileId));
return this.selectedFileIds.map((fileId) =>
this._appStateService.getFileById(
this._appStateService.activeProject.project.projectId,
fileId
)
);
}
get areAllFilesSelected() {
return this._appStateService.activeProject.files.length !== 0 && this.selectedFileIds.length === this._appStateService.activeProject.files.length;
return (
this._appStateService.activeProject.files.length !== 0 &&
this.selectedFileIds.length === this._appStateService.activeProject.files.length
);
}
get areSomeFilesSelected() {
@ -51,19 +62,31 @@ export class ProjectOverviewBulkActionsComponent {
}
get canDelete() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canDeleteFile(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canDeleteFile(file),
true
);
}
get canAssign() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canAssignReviewer(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canAssignReviewer(file),
true
);
}
get canReanalyse() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canReanalyseFile(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canReanalyseFile(file),
true
);
}
get canOcr() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canOcrFile(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canOcrFile(file),
true
);
}
get fileStatuses() {
@ -72,35 +95,55 @@ export class ProjectOverviewBulkActionsComponent {
// Under review
get canSetToUnderReview() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderReview(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canSetUnderReview(file),
true
);
}
// Under approval
get canSetToUnderApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderApproval(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canSetUnderApproval(file),
true
);
}
// Approve
get isReadyForApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.isReadyForApproval(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.isReadyForApproval(file),
true
);
}
get canApprove() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canApprove(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canApprove(file),
true
);
}
// Undo approval
get canUndoApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canUndoApproval(file), true);
return this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canUndoApproval(file),
true
);
}
delete() {
this.loading = true;
this._dialogService.openDeleteFilesDialog(null, this._appStateService.activeProject.project.projectId, this.selectedFileIds, () => {
this.reload.emit();
this.loading = false;
this.selectedFileIds.splice(0, this.selectedFileIds.length);
});
this._dialogService.openDeleteFilesDialog(
null,
this._appStateService.activeProject.project.projectId,
this.selectedFileIds,
() => {
this.reload.emit();
this.loading = false;
this.selectedFileIds.splice(0, this.selectedFileIds.length);
}
);
}
assign() {
@ -112,8 +155,15 @@ export class ProjectOverviewBulkActionsComponent {
}
async reanalyse() {
const fileIds = this.selectedFiles.filter((file) => this._permissionsService.fileRequiresReanalysis(file)).map((file) => file.fileId);
this._performBulkAction(this._reanalysisControllerService.reanalyzeFilesForProject(fileIds, this._appStateService.activeProject.projectId));
const fileIds = this.selectedFiles
.filter((file) => this._permissionsService.fileRequiresReanalysis(file))
.map((file) => file.fileId);
this._performBulkAction(
this._reanalysisControllerService.reanalyzeFilesForProject(
fileIds,
this._appStateService.activeProject.projectId
)
);
}
ocr() {

View File

@ -1,10 +1,20 @@
<div class="wrapper">
<ng-container *ngIf="expanded">
<div *ngFor="let comment of annotation.comments; let idx = index" class="comment">
<div [class.comment-owner]="isCommentOwner(comment)" [class.red]="isCommentOwner(comment)" class="comment-icon">
<mat-icon [svgIcon]="isCommentOwner(comment) ? 'red:comment-fill' : 'red:comment'"></mat-icon>
<div
[class.comment-owner]="isCommentOwner(comment)"
[class.red]="isCommentOwner(comment)"
class="comment-icon"
>
<mat-icon
[svgIcon]="isCommentOwner(comment) ? 'red:comment-fill' : 'red:comment'"
></mat-icon>
</div>
<div (click)="deleteComment(comment)" [class.comment-owner]="isCommentOwner(comment)" class="trash-icon red">
<div
(click)="deleteComment(comment)"
[class.comment-owner]="isCommentOwner(comment)"
class="trash-icon red"
>
<mat-icon svgIcon="red:trash"></mat-icon>
</div>
@ -20,9 +30,14 @@
{{
expanded
? translateService.instant('comments.hide-comments')
: translateService.instant(annotation.comments.length === 1 ? 'comments.comment' : 'comments.comments', {
count: annotation.comments.length
})
: translateService.instant(
annotation.comments.length === 1
? 'comments.comment'
: 'comments.comments',
{
count: annotation.comments.length
}
)
}}
</div>
<div
@ -32,14 +47,33 @@
></div>
</div>
<form (submit)="addComment()" *ngIf="addingComment && permissionsService.canAddComment()" [formGroup]="commentForm">
<form
(submit)="addComment()"
*ngIf="addingComment && permissionsService.canAddComment()"
[formGroup]="commentForm"
>
<div class="red-input-group">
<input [placeholder]="translateService.instant('comments.add-comment')" class="w-full" formControlName="comment" name="comment" type="text" />
<input
[placeholder]="translateService.instant('comments.add-comment')"
class="w-full"
formControlName="comment"
name="comment"
type="text"
/>
</div>
</form>
<div *ngIf="addingComment" class="comment-actions-container">
<redaction-circle-button (action)="addComment()" [disabled]="!commentForm.value.comment" icon="red:check" type="primary"></redaction-circle-button>
<div (click)="toggleAddingComment($event)" class="all-caps-label cancel" translate="comments.cancel"></div>
<redaction-circle-button
(action)="addComment()"
[disabled]="!commentForm.value.comment"
icon="red:check"
type="primary"
></redaction-circle-button>
<div
(click)="toggleAddingComment($event)"
class="all-caps-label cancel"
translate="comments.cancel"
></div>
</div>
</div>

View File

@ -58,25 +58,29 @@ export class CommentsComponent {
addComment(): void {
const value = this.commentForm.value.comment;
if (value) {
this._manualAnnotationService.addComment(value, this.annotation.id).subscribe((commentResponse) => {
this.annotation.comments.push({
text: value,
id: commentResponse.commentId,
user: this._userService.userId
this._manualAnnotationService
.addComment(value, this.annotation.id)
.subscribe((commentResponse) => {
this.annotation.comments.push({
text: value,
id: commentResponse.commentId,
user: this._userService.userId
});
});
});
this.commentForm.reset();
this.toggleAddingComment();
}
}
deleteComment(comment: Comment): void {
this._manualAnnotationService.deleteComment(comment.id, this.annotation.id).subscribe(() => {
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
if (!this.annotation.comments.length) {
this.expanded = false;
}
});
this._manualAnnotationService
.deleteComment(comment.id, this.annotation.id)
.subscribe(() => {
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
if (!this.annotation.comments.length) {
this.expanded = false;
}
});
}
isCommentOwner(comment: Comment): boolean {

View File

@ -26,19 +26,31 @@
<div class="section small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:folder"></mat-icon>
<span>{{ 'file-preview.tabs.document-info.details.project' | translate: { projectName: project.name } }}</span>
<span>{{
'file-preview.tabs.document-info.details.project'
| translate: { projectName: project.name }
}}</span>
</div>
<div>
<mat-icon svgIcon="red:document"></mat-icon>
<span>{{ 'file-preview.tabs.document-info.details.pages' | translate: { pages: file.numberOfPages } }}</span>
<span>{{
'file-preview.tabs.document-info.details.pages'
| translate: { pages: file.numberOfPages }
}}</span>
</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>
<span>{{ 'file-preview.tabs.document-info.details.created-on' | translate: { date: file.added | date: 'mediumDate' } }}</span>
<span>{{
'file-preview.tabs.document-info.details.created-on'
| translate: { date: file.added | date: 'mediumDate' }
}}</span>
</div>
<div *ngIf="project.project.dueDate">
<mat-icon svgIcon="red:lightning"></mat-icon>
<span>{{ 'file-preview.tabs.document-info.details.due' | translate: { date: project.project.dueDate | date: 'mediumDate' } }}</span>
<span>{{
'file-preview.tabs.document-info.details.due'
| translate: { date: project.project.dueDate | date: 'mediumDate' }
}}</span>
</div>
<div>
<mat-icon svgIcon="red:template"></mat-icon>

View File

@ -14,7 +14,10 @@ export class DocumentInfoComponent {
fileAttributesConfig: FileAttributesConfig;
constructor(private readonly _appStateService: AppStateService, private readonly _dialogService: ProjectsDialogService) {
constructor(
private readonly _appStateService: AppStateService,
private readonly _dialogService: ProjectsDialogService
) {
this.fileAttributesConfig = this._appStateService.activeFileAttributesConfig;
}

View File

@ -31,10 +31,16 @@
<!-- assign-->
<redaction-circle-button
(action)="assignReviewer($event, fileStatus)"
*ngIf="permissionsService.canAssignReviewer(fileStatus) && screen === 'project-overview'"
*ngIf="
permissionsService.canAssignReviewer(fileStatus) && screen === 'project-overview'
"
[icon]="permissionsService.isOwner() ? 'red:assign' : 'red:assign-me'"
[tooltipPosition]="tooltipPosition"
[tooltip]="permissionsService.isApprover() ? 'project-overview.assign' : 'project-overview.assign-me'"
[tooltip]="
permissionsService.isApprover()
? 'project-overview.assign'
: 'project-overview.assign-me'
"
[type]="buttonType"
>
</redaction-circle-button>
@ -86,7 +92,11 @@
*ngIf="permissionsService.isReadyForApproval(fileStatus)"
[disabled]="!permissionsService.canApprove(fileStatus)"
[tooltipPosition]="tooltipPosition"
[tooltip]="permissionsService.canApprove(fileStatus) ? 'project-overview.approve' : 'project-overview.approve-disabled'"
[tooltip]="
permissionsService.canApprove(fileStatus)
? 'project-overview.approve'
: 'project-overview.approve-disabled'
"
[type]="buttonType"
icon="red:approved"
>

View File

@ -38,7 +38,9 @@ export class FileActionsComponent implements OnInit {
return 'file-preview.toggle-analysis.only-managers';
}
return this.fileStatus?.isExcluded ? 'file-preview.toggle-analysis.enable' : 'file-preview.toggle-analysis.disable';
return this.fileStatus?.isExcluded
? 'file-preview.toggle-analysis.enable'
: 'file-preview.toggle-analysis.disable';
}
ngOnInit(): void {
@ -60,14 +62,21 @@ export class FileActionsComponent implements OnInit {
}
openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
this._dialogService.openDeleteFilesDialog($event, fileStatusWrapper.projectId, [fileStatusWrapper.fileId], () => {
this.actionPerformed.emit('delete');
});
this._dialogService.openDeleteFilesDialog(
$event,
fileStatusWrapper.projectId,
[fileStatusWrapper.fileId],
() => {
this.actionPerformed.emit('delete');
}
);
}
async assignReviewer($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
await this._fileActionService.assignProjectReviewerFromOverview(file, () => this.actionPerformed.emit('assign-reviewer'));
await this._fileActionService.assignProjectReviewerFromOverview(file, () =>
this.actionPerformed.emit('assign-reviewer')
);
}
reanalyseFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper, priority = -1) {
@ -98,12 +107,20 @@ export class FileActionsComponent implements OnInit {
});
}
setFileUnderReview($event: MouseEvent, fileStatus: FileStatusWrapper, ignoreDialogChanges = false) {
setFileUnderReview(
$event: MouseEvent,
fileStatus: FileStatusWrapper,
ignoreDialogChanges = false
) {
$event.stopPropagation();
// this._fileActionService.setFileUnderReview(fileStatus).subscribe(() => {
// this.reloadProjects('set-review');
// });
this._fileActionService.assignProjectReviewer(fileStatus, () => this.actionPerformed.emit('assign-reviewer'), ignoreDialogChanges);
this._fileActionService.assignProjectReviewer(
fileStatus,
() => this.actionPerformed.emit('assign-reviewer'),
ignoreDialogChanges
);
}
reloadProjects(action: string) {
@ -116,6 +133,8 @@ export class FileActionsComponent implements OnInit {
$event.stopPropagation();
await this._fileActionService.toggleAnalysis(this.fileStatus).toPromise();
await this.appStateService.getFiles();
this.actionPerformed.emit(this.fileStatus?.isExcluded ? 'enable-analysis' : 'disable-analysis');
this.actionPerformed.emit(
this.fileStatus?.isExcluded ? 'enable-analysis' : 'disable-analysis'
);
}
}

View File

@ -33,7 +33,11 @@
tooltipPosition="above"
></redaction-annotation-remove-actions>
</div>
<redaction-circle-button (action)="multiSelectActive = false" icon="red:close" type="primary"></redaction-circle-button>
<redaction-circle-button
(action)="multiSelectActive = false"
icon="red:close"
type="primary"
></redaction-circle-button>
</div>
<div [class.multi-select-active]="multiSelectActive" class="annotations-wrapper">
<div
@ -78,13 +82,24 @@
<div style="overflow: hidden; width: 100%">
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
<span *ngIf="!!activeViewerPage" class="all-caps-label"
><span translate="page"></span> {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }}
<span [translate]="displayedAnnotations[activeViewerPage]?.annotations?.length === 1 ? 'annotation' : 'annotations'"></span
><span translate="page"></span> {{ activeViewerPage }} -
{{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }}
<span
[translate]="
displayedAnnotations[activeViewerPage]?.annotations?.length === 1
? 'annotation'
: 'annotations'
"
></span
></span>
<div *ngIf="multiSelectActive">
<div (click)="selectAllOnActivePage()" class="all-caps-label primary pointer">All</div>
<div (click)="deselectAllOnActivePage()" class="all-caps-label primary pointer">None</div>
<div (click)="selectAllOnActivePage()" class="all-caps-label primary pointer">
All
</div>
<div (click)="deselectAllOnActivePage()" class="all-caps-label primary pointer">
None
</div>
</div>
</div>
@ -98,7 +113,12 @@
tabindex="1"
>
<ng-container *ngIf="!displayedAnnotations[activeViewerPage]">
<redaction-empty-state [horizontalPadding]="24" [verticalPadding]="40" icon="red:document" screen="file-preview"></redaction-empty-state>
<redaction-empty-state
[horizontalPadding]="24"
[verticalPadding]="40"
icon="red:document"
screen="file-preview"
></redaction-empty-state>
<div class="no-annotations-buttons-container mt-32">
<redaction-icon-button
(action)="jumpToPreviousWithAnnotations()"
@ -109,7 +129,9 @@
></redaction-icon-button>
<redaction-icon-button
(action)="jumpToNextWithAnnotations()"
[disabled]="activeViewerPage >= displayedPages[displayedPages.length - 1]"
[disabled]="
activeViewerPage >= displayedPages[displayedPages.length - 1]
"
class="mt-8"
icon="red:nav-next"
text="file-preview.tabs.annotations.jump-to-next"
@ -129,21 +151,32 @@
>
<div class="active-bar-marker"></div>
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
<redaction-hidden-action (action)="logAnnotation(annotation)" [requiredClicks]="2">
<redaction-hidden-action
(action)="logAnnotation(annotation)"
[requiredClicks]="2"
>
<div class="details">
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
<redaction-type-annotation-icon
[annotation]="annotation"
></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation.dictionary && annotation.dictionary !== 'manual'">
<div
*ngIf="
annotation.dictionary &&
annotation.dictionary !== 'manual'
"
>
<strong
><span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.dictionary | humanize: false }}
</div>
<div *ngIf="annotation.content && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.content }}
<strong><span translate="content"></span>: </strong
>{{ annotation.content }}
</div>
{{ annotation.id }}
</div>
@ -155,7 +188,9 @@
</ng-container>
<div class="active-icon-marker-container">
<redaction-round-checkbox
*ngIf="multiSelectActive && annotationIsSelected(annotation)"
*ngIf="
multiSelectActive && annotationIsSelected(annotation)
"
[active]="true"
></redaction-round-checkbox>
</div>
@ -170,16 +205,25 @@
</div>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter *ngIf="_(filter).topLevelFilter" [filter]="filter"></redaction-type-filter>
<redaction-type-filter
*ngIf="_(filter).topLevelFilter"
[filter]="filter"
></redaction-type-filter>
<ng-container *ngIf="!_(filter).topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
<redaction-dictionary-annotation-icon
[dictionaryKey]="filter.key"
></redaction-dictionary-annotation-icon>
{{ filter.key | humanize: false }}
</ng-container>
</ng-template>
<ng-template #annotationFilterActionTemplate let-filter="filter">
<ng-container *ngIf="filter.key === 'skipped'">
<redaction-circle-button (action)="toggleSkipped.emit($event)" [icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'" type="dark-bg">
<redaction-circle-button
(action)="toggleSkipped.emit($event)"
[icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'"
type="dark-bg"
>
</redaction-circle-button>
</ng-container>
</ng-template>

View File

@ -1,4 +1,14 @@
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild } from '@angular/core';
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import { FilterModel } from '@shared/components/filter/model/filter.model';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
@ -27,7 +37,9 @@ export class FileWorkloadComponent {
@Input() hideSkipped: boolean;
@Input() annotationActionsTemplate: TemplateRef<any>;
@Output() shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter<boolean>();
@Output() selectAnnotations = new EventEmitter<AnnotationWrapper[] | { annotations: AnnotationWrapper[]; multiSelect: boolean }>();
@Output() selectAnnotations = new EventEmitter<
AnnotationWrapper[] | { annotations: AnnotationWrapper[]; multiSelect: boolean }
>();
@Output() deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
@Output() selectPage = new EventEmitter<number>();
@Output() toggleSkipped = new EventEmitter<any>();
@ -39,7 +51,10 @@ export class FileWorkloadComponent {
@ViewChild('annotationsElement') private _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
constructor(private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _annotationProcessingService: AnnotationProcessingService) {}
constructor(
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _annotationProcessingService: AnnotationProcessingService
) {}
private _annotations: AnnotationWrapper[];
@ -68,7 +83,10 @@ export class FileWorkloadComponent {
return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null;
}
private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
private static _scrollToFirstElement(
elements: HTMLElement[],
mode: 'always' | 'if-needed' = 'if-needed'
) {
if (elements.length > 0) {
scrollIntoView(elements[0], {
behavior: 'smooth',
@ -88,7 +106,9 @@ export class FileWorkloadComponent {
}
pageHasSelection(page: number) {
return this.multiSelectActive && !!this.selectedAnnotations?.find((a) => a.pageNumber === page);
return (
this.multiSelectActive && !!this.selectedAnnotations?.find((a) => a.pageNumber === page)
);
}
selectAllOnActivePage() {
@ -103,7 +123,11 @@ export class FileWorkloadComponent {
@debounce(0)
filtersChanged(filters: { primary: FilterModel[]; secondary?: FilterModel[] }) {
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters.primary, filters.secondary);
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(
this._annotations,
filters.primary,
filters.secondary
);
this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key));
this.computeQuickNavButtonsState();
this._changeDetectorRef.markForCheck();
@ -111,7 +135,8 @@ export class FileWorkloadComponent {
computeQuickNavButtonsState() {
setTimeout(() => {
const element: HTMLElement = this._quickNavigationElement.nativeElement.querySelector(`#pages`);
const element: HTMLElement =
this._quickNavigationElement.nativeElement.querySelector(`#pages`);
const { scrollTop, scrollHeight, clientHeight } = element;
this.quickScrollFirstEnabled = scrollTop !== 0;
this.quickScrollLastEnabled = scrollHeight !== scrollTop + clientHeight;
@ -126,13 +151,20 @@ export class FileWorkloadComponent {
if (($event.ctrlKey || $event.metaKey) && this.selectedAnnotations.length > 0) {
this.multiSelectActive = true;
}
this.selectAnnotations.emit({ annotations: [annotation], multiSelect: this.multiSelectActive });
this.selectAnnotations.emit({
annotations: [annotation],
multiSelect: this.multiSelectActive
});
}
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
if (!ALL_HOTKEY_ARRAY.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN || ($event.target as any).localName === 'input') {
if (
!ALL_HOTKEY_ARRAY.includes($event.key) ||
this.dialogRef?.getState() === MatDialogState.OPEN ||
($event.target as any).localName === 'input'
) {
return;
}
@ -143,7 +175,8 @@ export class FileWorkloadComponent {
if ($event.key === 'ArrowRight') {
this.pagesPanelActive = false;
// if we activated annotationsPanel - select first annotation from this page in case there is no
// if we activated annotationsPanel -
// select first annotation from this page in case there is no
// selected annotation on this page and not in multi select mode
if (!this.pagesPanelActive && !this.multiSelectActive) {
this._selectFirstAnnotationOnCurrentPageIfNecessary();
@ -152,7 +185,8 @@ export class FileWorkloadComponent {
}
if (!this.pagesPanelActive) {
// Disable annotation navigation in multi select mode => TODO: maybe implement selection on enter?
// Disable annotation navigation in multi select mode
// => TODO: maybe implement selection on enter?
if (!this.multiSelectActive) {
this._navigateAnnotations($event);
}
@ -171,7 +205,9 @@ export class FileWorkloadComponent {
}
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') {
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
`div[anotation-page-header="${page}"]`
);
FileWorkloadComponent._scrollToFirstElement(elements, mode);
}
@ -180,13 +216,18 @@ export class FileWorkloadComponent {
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) {
return;
}
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`);
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
`div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`
);
FileWorkloadComponent._scrollToFirstElement(elements);
}
scrollQuickNavigation() {
let quickNavPageIndex = this.displayedPages.findIndex((p) => p >= this.activeViewerPage);
if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== this.activeViewerPage) {
if (
quickNavPageIndex === -1 ||
this.displayedPages[quickNavPageIndex] !== this.activeViewerPage
) {
quickNavPageIndex = Math.max(0, quickNavPageIndex - 1);
}
this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]);
@ -210,7 +251,10 @@ export class FileWorkloadComponent {
}
preventKeyDefault($event: KeyboardEvent) {
if (COMMAND_KEY_ARRAY.includes($event.key) && !(($event.target as any).localName === 'input')) {
if (
COMMAND_KEY_ARRAY.includes($event.key) &&
!(($event.target as any).localName === 'input')
) {
$event.preventDefault();
}
}
@ -229,39 +273,53 @@ export class FileWorkloadComponent {
private _selectFirstAnnotationOnCurrentPageIfNecessary() {
if (
(!this._firstSelectedAnnotation || this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber) &&
(!this._firstSelectedAnnotation ||
this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber) &&
this.displayedPages.indexOf(this.activeViewerPage) >= 0
) {
this.selectAnnotations.emit([this.displayedAnnotations[this.activeViewerPage].annotations[0]]);
this.selectAnnotations.emit([
this.displayedAnnotations[this.activeViewerPage].annotations[0]
]);
}
}
private _navigateAnnotations($event: KeyboardEvent) {
if (!this._firstSelectedAnnotation || this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber) {
if (
!this._firstSelectedAnnotation ||
this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber
) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if (pageIdx !== -1) {
// Displayed page has annotations
this.selectAnnotations.emit([this.displayedAnnotations[this.activeViewerPage].annotations[0]]);
this.selectAnnotations.emit([
this.displayedAnnotations[this.activeViewerPage].annotations[0]
]);
} else {
// Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
const nextPage = this._nextPageWithAnnotations();
this.shouldDeselectAnnotationsOnPageChange = false;
this.shouldDeselectAnnotationsOnPageChangeChange.emit(false);
this.selectAnnotations.emit([this.displayedAnnotations[nextPage].annotations[0]]);
this.selectAnnotations.emit([
this.displayedAnnotations[nextPage].annotations[0]
]);
} else {
const prevPage = this._prevPageWithAnnotations();
this.shouldDeselectAnnotationsOnPageChange = false;
this.shouldDeselectAnnotationsOnPageChangeChange.emit(false);
const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations;
this.selectAnnotations.emit([prevPageAnnotations[prevPageAnnotations.length - 1]]);
this.selectAnnotations.emit([
prevPageAnnotations[prevPageAnnotations.length - 1]
]);
}
}
} else {
const page = this._firstSelectedAnnotation.pageNumber;
const pageIdx = this.displayedPages.indexOf(page);
const annotationsOnPage = this.displayedAnnotations[page].annotations;
const idx = annotationsOnPage.findIndex((a) => a.id === this._firstSelectedAnnotation.id);
const idx = annotationsOnPage.findIndex(
(a) => a.id === this._firstSelectedAnnotation.id
);
if ($event.key === 'ArrowDown') {
if (idx + 1 !== annotationsOnPage.length) {
@ -269,7 +327,8 @@ export class FileWorkloadComponent {
this.selectAnnotations.emit([annotationsOnPage[idx + 1]]);
} else if (pageIdx + 1 < this.displayedPages.length) {
// If not last page
const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations;
const nextPageAnnotations =
this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations;
this.shouldDeselectAnnotationsOnPageChange = false;
this.shouldDeselectAnnotationsOnPageChangeChange.emit(false);
this.selectAnnotations.emit([nextPageAnnotations[0]]);
@ -280,10 +339,13 @@ export class FileWorkloadComponent {
this.selectAnnotations.emit([annotationsOnPage[idx - 1]]);
} else if (pageIdx) {
// If not first page
const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations;
const prevPageAnnotations =
this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations;
this.shouldDeselectAnnotationsOnPageChange = false;
this.shouldDeselectAnnotationsOnPageChangeChange.emit(false);
this.selectAnnotations.emit([prevPageAnnotations[prevPageAnnotations.length - 1]]);
this.selectAnnotations.emit([
prevPageAnnotations[prevPageAnnotations.length - 1]
]);
}
}
}
@ -345,7 +407,9 @@ export class FileWorkloadComponent {
}
private _scrollQuickNavigationToPage(page: number) {
const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(
`#quick-nav-page-${page}`
);
FileWorkloadComponent._scrollToFirstElement(elements);
}
}

View File

@ -1,9 +1,39 @@
<div class="needs-work">
<redaction-annotation-icon *ngIf="reanalysisRequired()" [color]="analysisColor" label="A" type="square"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="hasUpdates" [color]="updatedColor" label="U" type="square"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hasRedactions" [color]="redactionColor" label="R" type="square"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="hasImages" [color]="imageColor" label="I" type="square"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hintsOnly" [color]="hintColor" label="H" type="circle"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hasRequests" [color]="suggestionColor" label="S" type="rhombus"></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="reanalysisRequired()"
[color]="analysisColor"
label="A"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="hasUpdates"
[color]="updatedColor"
label="U"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="needsWorkInput.hasRedactions"
[color]="redactionColor"
label="R"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="hasImages"
[color]="imageColor"
label="I"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="needsWorkInput.hintsOnly"
[color]="hintColor"
label="H"
type="circle"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="needsWorkInput.hasRequests"
[color]="suggestionColor"
label="S"
type="rhombus"
></redaction-annotation-icon>
<mat-icon *ngIf="hasAnnotationComments" svgIcon="red:comment"></mat-icon>
</div>

View File

@ -12,7 +12,10 @@ import { ProjectWrapper } from '@state/model/project.wrapper';
export class NeedsWorkBadgeComponent {
@Input() needsWorkInput: FileStatusWrapper | ProjectWrapper;
constructor(private readonly _appStateService: AppStateService, private readonly _permissionsService: PermissionsService) {}
constructor(
private readonly _appStateService: AppStateService,
private readonly _permissionsService: PermissionsService
) {}
get suggestionColor() {
return this._getDictionaryColor('suggestion');
@ -47,7 +50,10 @@ export class NeedsWorkBadgeComponent {
}
get hasAnnotationComments(): boolean {
return this.needsWorkInput instanceof FileStatusWrapper && (<any>this.needsWorkInput).hasAnnotationComments;
return (
this.needsWorkInput instanceof FileStatusWrapper &&
(<any>this.needsWorkInput).hasAnnotationComments
);
}
reanalysisRequired() {

View File

@ -1,4 +1,13 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import {
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import { ViewedPages, ViewedPagesControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
@ -95,15 +104,25 @@ export class PageIndicatorComponent implements OnChanges, OnInit, OnDestroy {
private _markPageRead() {
this._viewedPagesControllerService
.addPage({ page: this.number }, this._appStateService.activeProjectId, this._appStateService.activeFileId)
.addPage(
{ page: this.number },
this._appStateService.activeProjectId,
this._appStateService.activeFileId
)
.subscribe(() => {
this.viewedPages?.pages?.push(this.number);
});
}
private _markPageUnread() {
this._viewedPagesControllerService.removePage(this._appStateService.activeProjectId, this._appStateService.activeFileId, this.number).subscribe(() => {
this.viewedPages?.pages?.splice(this.viewedPages?.pages?.indexOf(this.number), 1);
});
this._viewedPagesControllerService
.removePage(
this._appStateService.activeProjectId,
this._appStateService.activeFileId,
this.number
)
.subscribe(() => {
this.viewedPages?.pages?.splice(this.viewedPages?.pages?.indexOf(this.number), 1);
});
}
}

View File

@ -1,4 +1,17 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, NgZone, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Inject,
Input,
NgZone,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild
} from '@angular/core';
import { ManualRedactionEntry, Rectangle } from '@redaction/red-ui-http';
import WebViewer, { Annotations, Tools, WebViewerInstance } from '@pdftron/webviewer';
import { TranslateService } from '@ngx-translate/core';
@ -72,7 +85,9 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this.instance.annotManager.deselectAllAnnotations();
}
selectAnnotations($event: AnnotationWrapper[] | { annotations: AnnotationWrapper[]; multiSelect: boolean }) {
selectAnnotations(
$event: AnnotationWrapper[] | { annotations: AnnotationWrapper[]; multiSelect: boolean }
) {
let annotations: AnnotationWrapper[];
let multiSelect: boolean;
if ($event instanceof Array) {
@ -87,14 +102,18 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this.deselectAllAnnotations();
}
const annotationsFromViewer = annotations.map((ann) => this.instance.annotManager.getAnnotationById(ann.id));
const annotationsFromViewer = annotations.map((ann) =>
this.instance.annotManager.getAnnotationById(ann.id)
);
this.instance.annotManager.selectAnnotations(annotationsFromViewer);
this.navigateToPage(annotations[0].pageNumber);
this.instance.annotManager.jumpToAnnotation(annotationsFromViewer[0]);
}
deselectAnnotations(annotations: AnnotationWrapper[]) {
this.instance.annotManager.deselectAnnotations(annotations.map((ann) => this.instance.annotManager.getAnnotationById(ann.id)));
this.instance.annotManager.deselectAnnotations(
annotations.map((ann) => this.instance.annotManager.getAnnotationById(ann.id))
);
}
navigateToPage(pageNumber: number) {
@ -107,7 +126,9 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
setInitialViewerState() {
// viewer init
this.instance.setFitMode('FitPage');
const instanceDisplayMode = this.instance.docViewer.getDisplayModeManager().getDisplayMode();
const instanceDisplayMode = this.instance.docViewer
.getDisplayModeManager()
.getDisplayMode();
instanceDisplayMode.mode = 'Single';
this.instance.docViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode);
}
@ -127,20 +148,28 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this._configureTextPopup();
instance.annotManager.on('annotationSelected', (annotations, action) => {
this.annotationSelected.emit(instance.annotManager.getSelectedAnnotations().map((ann) => ann.Id));
this.annotationSelected.emit(
instance.annotManager.getSelectedAnnotations().map((ann) => ann.Id)
);
if (action === 'deselected') {
this._toggleRectangleAnnotationAction(true);
} else {
this._configureAnnotationSpecificActions(annotations);
this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly);
this._toggleRectangleAnnotationAction(
annotations.length === 1 && annotations[0].ReadOnly
);
// this.annotationSelected.emit(annotations.map((a) => a.Id));
}
});
instance.annotManager.on('annotationChanged', (annotations) => {
// when a rectangle is drawn, it returns one annotation with tool name 'AnnotationCreateRectangle;
// when a rectangle is drawn,
// it returns one annotation with tool name 'AnnotationCreateRectangle;
// this will auto select rectangle after drawing
if (annotations.length === 1 && annotations[0].ToolName === 'AnnotationCreateRectangle') {
if (
annotations.length === 1 &&
annotations[0].ToolName === 'AnnotationCreateRectangle'
) {
instance.annotManager.selectAnnotations(annotations);
}
});
@ -183,7 +212,9 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
instance.iframeWindow.addEventListener('visibilityChanged', (event: any) => {
if (event.detail.element === 'searchPanel') {
const inputElement = instance.iframeWindow.document.getElementById('SearchPanel__input') as HTMLInputElement;
const inputElement = instance.iframeWindow.document.getElementById(
'SearchPanel__input'
) as HTMLInputElement;
setTimeout(() => {
inputElement.value = '';
}, 0);
@ -253,43 +284,58 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _configureAnnotationSpecificActions(viewerAnnotations: Annotations.Annotation[]) {
console.log('configure actions', viewerAnnotations);
if (this.canPerformActions) {
const annotationWrappers = viewerAnnotations.map((va) => this.annotations.find((a) => a.id === va.Id)).filter((va) => !!va);
this.instance.annotationPopup.update([]);
if (annotationWrappers.length === 0) {
this._configureRectangleAnnotationPopup();
return;
}
// Add hide action as last item
const allAnnotationsHaveImageAction = annotationWrappers.reduce((acc, next) => acc && next.isImage, true);
if (allAnnotationsHaveImageAction) {
const allAreVisible = viewerAnnotations.reduce((acc, next) => next.isVisible() && acc, true);
this.instance.annotationPopup.add([
{
type: 'actionButton',
img: allAreVisible
? this._convertPath('/assets/icons/general/visibility-off.svg')
: this._convertPath('/assets/icons/general/visibility.svg'),
title: this._translateService.instant('annotation-actions.hide'),
onClick: () => {
this._ngZone.run(() => {
if (allAreVisible) {
this.instance.annotManager.hideAnnotations(viewerAnnotations);
} else {
this.instance.annotManager.showAnnotations(viewerAnnotations);
}
this.instance.annotManager.deselectAllAnnotations();
});
}
}
]);
}
this.instance.annotationPopup.add(this._annotationActionsService.getViewerAvailableActions(annotationWrappers, this.annotationsChanged));
if (!this.canPerformActions) {
return;
}
const annotationWrappers = viewerAnnotations
.map((va) => this.annotations.find((a) => a.id === va.Id))
.filter((va) => !!va);
this.instance.annotationPopup.update([]);
if (annotationWrappers.length === 0) {
this._configureRectangleAnnotationPopup();
return;
}
// Add hide action as last item
const allAnnotationsHaveImageAction = annotationWrappers.reduce(
(acc, next) => acc && next.isImage,
true
);
if (allAnnotationsHaveImageAction) {
const allAreVisible = viewerAnnotations.reduce(
(acc, next) => next.isVisible() && acc,
true
);
this.instance.annotationPopup.add([
{
type: 'actionButton',
img: allAreVisible
? this._convertPath('/assets/icons/general/visibility-off.svg')
: this._convertPath('/assets/icons/general/visibility.svg'),
title: this._translateService.instant('annotation-actions.hide'),
onClick: () => {
this._ngZone.run(() => {
if (allAreVisible) {
this.instance.annotManager.hideAnnotations(viewerAnnotations);
} else {
this.instance.annotManager.showAnnotations(viewerAnnotations);
}
this.instance.annotManager.deselectAllAnnotations();
});
}
}
]);
}
this.instance.annotationPopup.add(
this._annotationActionsService.getViewerAvailableActions(
annotationWrappers,
this.annotationsChanged
)
);
}
private _configureRectangleAnnotationPopup() {
@ -297,12 +343,17 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
type: 'actionButton',
dataElement: 'add-rectangle',
img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'),
title: this._translateService.instant(this._manualAnnotationService.getTitle('REDACTION')),
title: this._translateService.instant(
this._manualAnnotationService.getTitle('REDACTION')
),
onClick: () => {
const selectedAnnotations = this.instance.annotManager.getSelectedAnnotations();
const activeAnnotation = selectedAnnotations[0];
const activePage = selectedAnnotations[0].getPageNumber();
const quad = this._annotationDrawService.annotationToQuads(activeAnnotation, this.instance);
const quad = this._annotationDrawService.annotationToQuads(
activeAnnotation,
this.instance
);
const quadsObject = {};
quadsObject[activePage] = [quad];
const mre = this._getManualRedactionEntry(quadsObject, 'Rectangle');
@ -311,7 +362,15 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this.instance.disableElements(['shapeToolGroupButton']);
this.instance.enableElements(['shapeToolGroupButton']);
// dispatch event
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper([quad], mre, 'REDACTION', 'RECTANGLE', activeAnnotation.Id));
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(
[quad],
mre,
'REDACTION',
'RECTANGLE',
activeAnnotation.Id
)
);
}
});
}
@ -344,12 +403,20 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
type: 'actionButton',
dataElement: 'add-false-positive',
img: this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg'),
title: this._translateService.instant(this._manualAnnotationService.getTitle('FALSE_POSITIVE')),
title: this._translateService.instant(
this._manualAnnotationService.getTitle('FALSE_POSITIVE')
),
onClick: () => {
const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
const text = this.instance.docViewer.getSelectedText();
const mre = this._getManualRedactionEntry(selectedQuads, text, true);
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'FALSE_POSITIVE'));
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(
this.instance.docViewer.getSelectedTextQuads(),
mre,
'FALSE_POSITIVE'
)
);
}
});
}
@ -358,12 +425,20 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
type: 'actionButton',
dataElement: 'add-dictionary',
img: this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg'),
title: this._translateService.instant(this._manualAnnotationService.getTitle('DICTIONARY')),
title: this._translateService.instant(
this._manualAnnotationService.getTitle('DICTIONARY')
),
onClick: () => {
const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
const text = this.instance.docViewer.getSelectedText();
const mre = this._getManualRedactionEntry(selectedQuads, text, true);
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'DICTIONARY'));
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(
this.instance.docViewer.getSelectedTextQuads(),
mre,
'DICTIONARY'
)
);
}
});
@ -371,12 +446,20 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
type: 'actionButton',
dataElement: 'add-redaction',
img: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg'),
title: this._translateService.instant(this._manualAnnotationService.getTitle('REDACTION')),
title: this._translateService.instant(
this._manualAnnotationService.getTitle('REDACTION')
),
onClick: () => {
const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
const text = this.instance.docViewer.getSelectedText();
const mre = this._getManualRedactionEntry(selectedQuads, text, true);
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'REDACTION'));
this.manualAnnotationRequested.emit(
new ManualRedactionEntryWrapper(
this.instance.docViewer.getSelectedTextQuads(),
mre,
'REDACTION'
)
);
}
});
this._handleCustomActions();
@ -386,7 +469,13 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
this.instance.setToolMode('AnnotationEdit');
if (this.canPerformActions) {
this.instance.enableTools(['AnnotationCreateRectangle']);
this.instance.enableElements(['add-redaction', 'add-rectangle', 'add-false-positive', 'shapeToolGroupButton', 'annotationPopup']);
this.instance.enableElements([
'add-redaction',
'add-rectangle',
'add-false-positive',
'shapeToolGroupButton',
'annotationPopup'
]);
if (this._selectedText.length > 2) {
this.instance.enableElements(['add-dictionary', 'add-false-positive']);
}
@ -403,13 +492,19 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
}
}
private _getManualRedactionEntry(quads: any, text: string, convertQuads: boolean = false): ManualRedactionEntry {
private _getManualRedactionEntry(
quads: any,
text: string,
convertQuads: boolean = false
): ManualRedactionEntry {
text = text.replace(/-\n/gi, '');
const entry: ManualRedactionEntry = { positions: [] };
for (const key of Object.keys(quads)) {
for (const quad of quads[key]) {
const page = parseInt(key, 10);
entry.positions.push(this._toPosition(page, convertQuads ? this._translateQuads(page, quad) : quad));
entry.positions.push(
this._toPosition(page, convertQuads ? this._translateQuads(page, quad) : quad)
);
}
}
entry.value = text;

View File

@ -53,7 +53,11 @@
</div>
<div *ngIf="hasFiles" class="mt-24 legend pb-32">
<div (click)="toggleFilter('needsWorkFilters', filter.key)" *ngFor="let filter of filters.needsWorkFilters" [class.active]="filter.checked">
<div
(click)="toggleFilter('needsWorkFilters', filter.key)"
*ngFor="let filter of filters.needsWorkFilters"
[class.active]="filter.checked"
>
<redaction-type-filter [filter]="filter"></redaction-type-filter>
</div>
</div>
@ -61,37 +65,63 @@
<div [class.mt-24]="!hasFiles" class="pb-32 small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:document"></mat-icon>
<span>{{ 'project-overview.project-details.stats.documents' | translate: { count: appStateService.activeProject.files.length } }}</span>
<span>{{
'project-overview.project-details.stats.documents'
| translate: { count: appStateService.activeProject.files.length }
}}</span>
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
<span>{{ 'project-overview.project-details.stats.people' | translate: { count: appStateService.activeProject.memberCount } }}</span>
<span>{{
'project-overview.project-details.stats.people'
| translate: { count: appStateService.activeProject.memberCount }
}}</span>
</div>
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
<span>{{
'project-overview.project-details.stats.analysed-pages' | translate: { count: appStateService.activeProject.totalNumberOfPages | number }
'project-overview.project-details.stats.analysed-pages'
| translate
: { count: appStateService.activeProject.totalNumberOfPages | number }
}}</span>
</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>
<span
>{{
'project-overview.project-details.stats.created-on' | translate: { date: appStateService.activeProject.project.date | date: 'd MMM. yyyy' }
'project-overview.project-details.stats.created-on'
| translate
: {
date:
appStateService.activeProject.project.date
| date: 'd MMM. yyyy'
}
}}
</span>
</div>
<div *ngIf="appStateService.activeProject.project.dueDate">
<mat-icon svgIcon="red:lightning"></mat-icon>
<span>{{
'project-overview.project-details.stats.due-date' | translate: { date: appStateService.activeProject.project.dueDate | date: 'd MMM. yyyy' }
'project-overview.project-details.stats.due-date'
| translate
: {
date:
appStateService.activeProject.project.dueDate
| date: 'd MMM. yyyy'
}
}}</span>
</div>
<div>
<mat-icon svgIcon="red:template"></mat-icon>
<span>{{ appStateService.getRuleSetById(appStateService.activeProject.ruleSetId)?.name }} </span>
<span
>{{ appStateService.getRuleSetById(appStateService.activeProject.ruleSetId)?.name }}
</span>
</div>
<div *ngIf="appStateService.activeProject.type" class="pointer" (click)="openDossierDictionaryDialog.emit()">
<div
*ngIf="appStateService.activeProject.type"
class="pointer"
(click)="openDossierDictionaryDialog.emit()"
>
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span>{{ 'project-overview.project-details.dictionary' | translate }} </span>
</div>

View File

@ -51,10 +51,17 @@ export class ProjectDetailsComponent implements OnInit {
const groups = groupBy(this.appStateService.activeProject?.files, 'status');
this.documentsChartData = [];
for (const key of Object.keys(groups)) {
this.documentsChartData.push({ value: groups[key].length, color: key, label: key, key: key });
this.documentsChartData.push({
value: groups[key].length,
color: key,
label: key,
key: key
});
}
this.documentsChartData.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]);
this.documentsChartData = this.translateChartService.translateStatus(this.documentsChartData);
this.documentsChartData = this.translateChartService.translateStatus(
this.documentsChartData
);
this._changeDetectorRef.detectChanges();
}
}

View File

@ -36,5 +36,9 @@
>
</redaction-circle-button>
<redaction-file-download-btn [file]="project.files" [project]="project" type="dark-bg"></redaction-file-download-btn>
<redaction-file-download-btn
[file]="project.files"
[project]="project"
type="dark-bg"
></redaction-file-download-btn>
</div>

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