Pull request #186: Updates

Merge in RED/ui from updates to master

* commit 'b3ce5d7914cf0e9bed1bce12008cb52b829163ac':
  other deps updates
  nx updates
  update nx & eslint plugins
  update other dependencies
  update angular material and fix update errors
  migrate to ng12
  nx updates
  set max line length to 100 and set indent size for .json files to 2
This commit is contained in:
Timo Bejan 2021-05-18 14:06:27 +02:00
commit 61e454fa72
223 changed files with 10729 additions and 6760 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,205 @@
{
"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": [
"@redaction/red-ui-http",
"@redaction/red-cache",
"@services/**",
"@components/**",
"@guards/**",
"@i18n/**",
"@state/**",
"@utils/**",
"@models/**",
"@environments/**",
"@shared/**",
"@app-config/**",
"@upload-download/**"
],
"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",
"@angular-eslint/template/eqeqeq": "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,200 @@
{
"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",
"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"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"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() {
@ -195,6 +204,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]="['..']"
@ -93,11 +100,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

@ -3,7 +3,9 @@ import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Title } from '@angular/platform-browser';
import { version } from '../../../../../../package.json';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import packageInfo from '../../../../../../package.json';
import { CacheApiService, wipeCaches } from '@redaction/red-cache';
export enum AppConfigKey {
@ -34,23 +36,37 @@ 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 ',
this.version
);
if (lastVersion !== this.version) {
console.log('[REDACTION] Version-missmatch - wiping caches!');
await wipeCaches();
}
await this._cacheApiService.cacheValue(
AppConfigKey.FRONTEND_APP_VERSION,
this.version
);
});
return this._httpClient.get<any>('/assets/config/config.json').pipe(
tap((config) => {
console.log('[REDACTION] Started with config: ', config);
this._config = config;
this._config[AppConfigKey.FRONTEND_APP_VERSION] = version;
this._config[AppConfigKey.FRONTEND_APP_VERSION] = this.version;
this._titleService.setTitle(this.getConfig(AppConfigKey.APP_NAME, 'DDA-R'));
})
);
@ -59,4 +75,8 @@ export class AppConfigService {
getConfig(key: AppConfigKey | string, defaultValue?: any) {
return this._config[key] ? this._config[key] : defaultValue;
}
get version(): string {
return packageInfo.version;
}
}

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

@ -7,9 +7,21 @@
type="dark-bg"
></redaction-circle-button>
<redaction-circle-button (action)="assign()" *ngIf="canAssign" icon="red:assign" [tooltip]="assignTooltip" type="dark-bg"></redaction-circle-button>
<redaction-circle-button
(action)="assign()"
*ngIf="canAssign"
icon="red:assign"
[tooltip]="assignTooltip"
type="dark-bg"
></redaction-circle-button>
<redaction-circle-button (action)="assignToMe()" *ngIf="canAssignToSelf" icon="red:assign-me" tooltip="project-overview.assign-me" type="dark-bg">
<redaction-circle-button
(action)="assignToMe()"
*ngIf="canAssignToSelf"
icon="red:assign-me"
tooltip="project-overview.assign-me"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
@ -21,10 +33,19 @@
>
</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>
<redaction-file-download-btn [file]="selectedFiles" [project]="project"></redaction-file-download-btn>
<redaction-file-download-btn
[file]="selectedFiles"
[project]="project"
></redaction-file-download-btn>
<!-- Approved-->
<redaction-circle-button
@ -38,10 +59,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() {
@ -53,8 +64,14 @@ export class ProjectOverviewBulkActionsComponent {
get allSelectedFilesCanBeAssignedIntoSameState() {
if (this.areSomeFilesSelected) {
const selectedFiles = this.selectedFiles;
const allFilesAreUnderReviewOrUnassigned = selectedFiles.reduce((acc, file) => acc && (file.isUnderReview || file.isUnassigned), true);
const allFilesAreUnderApproval = selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
const allFilesAreUnderReviewOrUnassigned = selectedFiles.reduce(
(acc, file) => acc && (file.isUnderReview || file.isUnassigned),
true
);
const allFilesAreUnderApproval = selectedFiles.reduce(
(acc, file) => acc && file.isUnderApproval,
true
);
return allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval;
}
return false;
@ -63,27 +80,42 @@ export class ProjectOverviewBulkActionsComponent {
get canAssignToSelf() {
return (
this.allSelectedFilesCanBeAssignedIntoSameState &&
this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canAssignToSelf(file), true)
this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canAssignToSelf(file),
true
)
);
}
get canAssign() {
return (
this.allSelectedFilesCanBeAssignedIntoSameState &&
this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canAssignUser(file), true)
this.selectedFiles.reduce(
(acc, file) => acc && this._permissionsService.canAssignUser(file),
true
)
);
}
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 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() {
@ -92,47 +124,74 @@ 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
);
}
get assignTooltip() {
const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
return allFilesAreUnderApproval ? 'project-overview.assign-approver' : 'project-overview.assign-reviewer';
const allFilesAreUnderApproval = this.selectedFiles.reduce(
(acc, file) => acc && file.isUnderApproval,
true
);
return allFilesAreUnderApproval
? 'project-overview.assign-approver'
: 'project-overview.assign-reviewer';
}
delete() {
this.loading = true;
this._dialogService.openDeleteFilesDialog(null, this._appStateService.activeProjectId, this.selectedFileIds, () => {
this.reload.emit();
this.loading = false;
this.selectedFileIds.splice(0, this.selectedFileIds.length);
});
this._dialogService.openDeleteFilesDialog(
null,
this._appStateService.activeProjectId,
this.selectedFileIds,
() => {
this.reload.emit();
this.loading = false;
this.selectedFileIds.splice(0, this.selectedFileIds.length);
}
);
}
setToUnderApproval() {
// If more than 1 approver - show dialog and ask who to assign
if (this._appStateService.activeProject.approverIds.length > 1) {
this.loading = true;
const files = this.selectedFileIds.map((fileId) => this._appStateService.getFileById(this._appStateService.activeProjectId, fileId));
const files = this.selectedFileIds.map((fileId) =>
this._appStateService.getFileById(this._appStateService.activeProjectId, fileId)
);
this._dialogService.openAssignFileToUserDialog(
files,
@ -144,13 +203,25 @@ export class ProjectOverviewBulkActionsComponent {
true
);
} else {
this._performBulkAction(this._fileActionService.setFileUnderApproval(this.selectedFiles, this._appStateService.activeProject.approverIds[0]));
this._performBulkAction(
this._fileActionService.setFileUnderApproval(
this.selectedFiles,
this._appStateService.activeProject.approverIds[0]
)
);
}
}
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() {
@ -175,7 +246,9 @@ export class ProjectOverviewBulkActionsComponent {
assign() {
this.loading = true;
const files = this.selectedFileIds.map((fileId) => this._appStateService.getFileById(this._appStateService.activeProjectId, fileId));
const files = this.selectedFileIds.map((fileId) =>
this._appStateService.getFileById(this._appStateService.activeProjectId, fileId)
);
const mode = files[0].isUnderApproval ? 'approver' : 'reviewer';

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

@ -94,7 +94,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';
}
get canAssignToSelf() {
@ -86,7 +88,9 @@ export class FileActionsComponent implements OnInit {
}
get assignTooltip() {
return this.fileStatus.isUnderApproval ? 'project-overview.assign-approver' : 'project-overview.assign-reviewer';
return this.fileStatus.isUnderApproval
? 'project-overview.assign-approver'
: 'project-overview.assign-reviewer';
}
ngOnInit(): void {
@ -108,9 +112,14 @@ export class FileActionsComponent implements OnInit {
}
openDeleteFileDialog($event: MouseEvent) {
this._dialogService.openDeleteFilesDialog($event, this.fileStatus.projectId, [this.fileStatus.fileId], () => {
this.actionPerformed.emit('delete');
});
this._dialogService.openDeleteFilesDialog(
$event,
this.fileStatus.projectId,
[this.fileStatus.fileId],
() => {
this.actionPerformed.emit('delete');
}
);
}
assign($event: MouseEvent) {
@ -137,7 +146,11 @@ export class FileActionsComponent implements OnInit {
setFileUnderApproval($event: MouseEvent) {
$event.stopPropagation();
if (this.appStateService.activeProject.approverIds.length > 1) {
this._fileActionService.assignProjectApprover(this.fileStatus, () => this.actionPerformed.emit('assign-reviewer'), true);
this._fileActionService.assignProjectApprover(
this.fileStatus,
() => this.actionPerformed.emit('assign-reviewer'),
true
);
} else {
this._fileActionService.setFileUnderApproval(this.fileStatus).subscribe(() => {
this.reloadProjects('set-under-approval');
@ -161,7 +174,11 @@ export class FileActionsComponent implements OnInit {
setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) {
$event.stopPropagation();
this._fileActionService.assignProjectReviewer(this.fileStatus, () => this.actionPerformed.emit('assign-reviewer'), ignoreDialogChanges);
this._fileActionService.assignProjectReviewer(
this.fileStatus,
() => this.actionPerformed.emit('assign-reviewer'),
ignoreDialogChanges
);
}
reloadProjects(action: string) {
@ -174,6 +191,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>();
@ -37,7 +49,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[];
@ -66,7 +81,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',
@ -86,7 +104,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() {
@ -101,7 +121,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._changeDetectorRef.markForCheck();
}
@ -114,13 +138,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;
}
@ -131,7 +162,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();
@ -140,7 +172,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);
}
@ -159,7 +192,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);
}
@ -168,13 +203,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]);
@ -194,7 +234,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();
}
}
@ -213,39 +256,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) {
@ -253,7 +310,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]]);
@ -264,10 +322,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]
]);
}
}
}
@ -329,7 +390,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

@ -1,4 +1,4 @@
@import '/apps/red-ui/src/assets/styles/red-variables';
@import 'apps/red-ui/src/assets/styles/red-variables';
.needs-work {
display: flex;

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();
}
}

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