Pull request #182: Updates

Merge in RED/ui from updates to master

* commit '579ef2957f2bc21eae9613464424d6347501b4a9':
  fix version
  fix conflicts, add nrwl webpack peer dependencie, reformat code
  update dependencies, fix eslint & husky
  fix not implemented
  rearrange entries
  update paths & refactor imports
  minor fixes
  undo abstract members
  fix eslint errors
  migrate from tslint to eslint
  remove gitkeep and package-lock.json
  update dependencies with backward-incompatible changes
  update dependencies with backward-compatible features
  update dependencies with backward-compatible updates
  add yarn as default package manager
  nx updates
This commit is contained in:
Timo Bejan 2021-05-10 12:08:41 +02:00
commit 542085b920
340 changed files with 7525 additions and 26362 deletions

182
.eslintrc.json Normal file
View File

@ -0,0 +1,182 @@
{
"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"]
}
]
}

View File

@ -1,5 +1,20 @@
{
"version": 1,
"cli": {
"defaultCollection": "@nrwl/angular",
"analytics": false,
"packageManager": "yarn"
},
"defaultProject": "red-ui",
"schematics": {
"@nrwl/angular:application": {
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"@nrwl/angular:library": {
"unitTestRunner": "jest"
}
},
"projects": {
"red-ui": {
"projectType": "application",
@ -59,7 +74,6 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
@ -79,7 +93,8 @@
"serviceWorker": true,
"ngswConfigPath": "apps/red-ui/ngsw-config.json"
}
}
},
"outputs": ["{options.outputPath}"]
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
@ -99,10 +114,9 @@
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@nrwl/linter:eslint",
"options": {
"tsConfig": ["apps/red-ui/tsconfig.app.json"],
"exclude": ["**/node_modules/**", "!apps/red-ui/**/*"]
"lintFilePatterns": ["apps/red-ui/src/**/*.ts", "apps/red-ui/src/**/*.html"]
}
},
"test": {
@ -110,7 +124,8 @@
"options": {
"jestConfig": "apps/red-ui/jest.config.js",
"passWithNoTests": true
}
},
"outputs": ["coverage/apps/red-ui"]
}
}
},
@ -121,17 +136,16 @@
"prefix": "redaction",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr: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": "@angular-devkit/build-angular:tslint",
"builder": "@nrwl/linter:eslint",
"options": {
"tsConfig": ["libs/red-ui-http/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/red-ui-http/**/*"]
"lintFilePatterns": ["libs/red-ui-http/src/**/*.ts", "libs/red-ui-http/src/**/*.html"]
}
},
"test": {
@ -139,7 +153,8 @@
"options": {
"jestConfig": "libs/red-ui-http/jest.config.js",
"passWithNoTests": true
}
},
"outputs": ["coverage/libs/red-ui-http"]
}
},
"schematics": {
@ -155,10 +170,9 @@
"prefix": "redaction",
"architect": {
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@nrwl/linter:eslint",
"options": {
"tsConfig": ["libs/red-cache/tsconfig.lib.json", "libs/red-cache/tsconfig.spec.json"],
"exclude": ["**/node_modules/**", "!libs/red-cache/**/*"]
"lintFilePatterns": ["libs/red-cache/src/**/*.ts", "libs/red-cache/src/**/*.html"]
}
},
"test": {
@ -166,7 +180,8 @@
"options": {
"jestConfig": "libs/red-cache/jest.config.js",
"passWithNoTests": true
}
},
"outputs": ["coverage/libs/red-cache"]
}
},
"schematics": {
@ -175,19 +190,5 @@
}
}
}
},
"cli": {
"defaultCollection": "@nrwl/angular",
"analytics": false
},
"schematics": {
"@nrwl/angular:application": {
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"@nrwl/angular:library": {
"unitTestRunner": "jest"
}
},
"defaultProject": "red-ui"
}
}

View File

View File

@ -0,0 +1,37 @@
{
"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

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

View File

@ -1,14 +1,14 @@
import { AuthErrorComponent } from './components/auth-error/auth-error.component';
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
import { AuthGuard } from './modules/auth/auth.guard';
import { CompositeRouteGuard } from './guards/composite-route.guard';
import { CompositeRouteGuard } from '@guards/composite-route.guard';
import { RedRoleGuard } from './modules/auth/red-role.guard';
import { BaseScreenComponent } from './components/base-screen/base-screen.component';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
import { RouteReuseStrategy, RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { DownloadsListScreenComponent } from './components/downloads-list-screen/downloads-list-screen.component';
import { AppStateGuard } from './state/app-state.guard';
import { UserProfileScreenComponent } from './components/user-profile/user-profile-screen.component';
import { CustomRouteReuseStrategy } from './utils/custom-route-reuse.strategy';
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
import { AppStateGuard } from '@state/app-state.guard';
import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component';
import { CustomRouteReuseStrategy } from '@utils/custom-route-reuse.strategy';
const routes = [
{

View File

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

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { AppLoadStateService } from './services/app-load-state.service';
import { RouterHistoryService } from './services/router-history.service';
import { AppLoadStateService } from '@services/app-load-state.service';
import { RouterHistoryService } from '@services/router-history.service';
@Component({
selector: 'redaction-root',

View File

@ -4,34 +4,34 @@ import { AppComponent } from './app.component';
import { ActivatedRoute, Router } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { BaseScreenComponent } from './components/base-screen/base-screen.component';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
import { ApiModule } from '@redaction/red-ui-http';
import { ApiPathInterceptor } from './utils/api-path-interceptor';
import { ApiPathInterceptor } from '@utils/api-path-interceptor';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { languageInitializer } from './i18n/language.initializer';
import { LanguageService } from './i18n/language.service';
import { languageInitializer } from '@i18n/language.initializer';
import { LanguageService } from '@i18n/language.service';
import { ToastrModule } from 'ngx-toastr';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { environment } from '@environments/environment';
import { AuthModule } from './modules/auth/auth.module';
import { LogoComponent } from './components/logo/logo.component';
import { AuthErrorComponent } from './components/auth-error/auth-error.component';
import { ToastComponent } from './components/toast/toast.component';
import { LogoComponent } from '@components/logo/logo.component';
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
import { ToastComponent } from '@components/toast/toast.component';
import { HttpCacheInterceptor } from '@redaction/red-cache';
import { NotificationsComponent } from './components/notifications/notifications.component';
import { NotificationsComponent } from '@components/notifications/notifications.component';
import { KeycloakService } from 'keycloak-angular';
import { DownloadsListScreenComponent } from './components/downloads-list-screen/downloads-list-screen.component';
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
import { AppRoutingModule } from './app-routing.module';
import { SharedModule } from './modules/shared/shared.module';
import { FileUploadDownloadModule } from './modules/upload-download/file-upload-download.module';
import { UserProfileScreenComponent } from './components/user-profile/user-profile-screen.component';
import { SharedModule } from '@shared/shared.module';
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component';
import { PlatformLocation } from '@angular/common';
import { BASE_HREF } from './tokens';
declare var ace;
declare let ace;
export function HttpLoaderFactory(httpClient: HttpClient) {
export function httpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
}
@ -68,7 +68,7 @@ const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastCompon
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
useFactory: httpLoaderFactory,
deps: [HttpClient]
}
}),

View File

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

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { UserService } from '../../services/user.service';
import { AppConfigKey, AppConfigService } from '../../modules/app-config/app-config.service';
import { UserService } from '@services/user.service';
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
@Component({
selector: 'redaction-auth-error',

View File

@ -1,7 +1,7 @@
<div class="red-top-bar">
<div class="top-bar-row">
<div class="menu-placeholder" *ngIf="!permissionsService.isUser()"></div>
<div class="menu visible-lt-lg" *ngIf="permissionsService.isUser()">
<div *ngIf="!permissionsService.isUser()" class="menu-placeholder"></div>
<div *ngIf="permissionsService.isUser()" class="menu visible-lt-lg">
<button [matMenuTriggerFor]="menuNav" mat-flat-button>
<mat-icon svgIcon="red:menu"></mat-icon>
</button>
@ -19,36 +19,36 @@
</button>
</mat-menu>
</div>
<div class="menu flex-2 visible-lg breadcrumbs-container" *ngIf="permissionsService.isUser()">
<div *ngIf="permissionsService.isUser()" class="menu flex-2 visible-lg breadcrumbs-container">
<a
class="breadcrumb"
routerLink="/main/projects"
translate="top-bar.navigation-items.projects"
routerLinkActive="active"
*ngIf="projectsView"
[routerLinkActiveOptions]="{ exact: true }"
class="breadcrumb"
routerLink="/main/projects"
routerLinkActive="active"
translate="top-bar.navigation-items.projects"
></a>
<a class="breadcrumb back" redactionNavigateLastProjectsScreen *ngIf="!projectsView">
<a *ngIf="!projectsView" class="breadcrumb back" redactionNavigateLastProjectsScreen>
<mat-icon svgIcon="red:expand"></mat-icon>
{{ 'top-bar.navigation-items.back' | translate }}
</a>
<ng-container *ngIf="projectsView">
<mat-icon class="primary" *ngIf="!appStateService.activeProject" svgIcon="red:arrow-down"></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"
class="breadcrumb"
[routerLink]="'/main/projects/' + appStateService.activeProjectId"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
[routerLink]="'/main/projects/' + appStateService.activeProjectId"
class="breadcrumb"
routerLinkActive="active"
>
{{ appStateService.activeProject.project.projectName }}
</a>
<mat-icon svgIcon="red:arrow-right" *ngIf="appStateService.activeFile"></mat-icon>
<mat-icon *ngIf="appStateService.activeFile" svgIcon="red:arrow-right"></mat-icon>
<a
*ngIf="appStateService.activeFile"
class="breadcrumb"
[routerLink]="'/main/projects/' + appStateService.activeProjectId + '/file/' + appStateService.activeFile.fileId"
class="breadcrumb"
routerLinkActive="active"
>
{{ appStateService.activeFile.filename }}
@ -60,16 +60,16 @@
<redaction-logo></redaction-logo>
</redaction-hidden-action>
<div class="app-name">{{ titleService.getTitle() }}</div>
<span class="dev-mode" *ngIf="userPreferenceService.areDevFeaturesEnabled" translate="dev-mode"></span>
<span *ngIf="userPreferenceService.areDevFeaturesEnabled" class="dev-mode" translate="dev-mode"></span>
</div>
<div class="menu right flex-2">
<redaction-notifications class="mr-8" *ngIf="userPreferenceService.areDevFeaturesEnabled"></redaction-notifications>
<redaction-user-button [user]="user" [matMenuTriggerFor]="userMenu" [showDot]="showPendingDownloadsDot"></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
*ngIf="permissionsService.isManager() || permissionsService.isUserAdmin()"
(click)="appStateService.reset()"
*ngIf="permissionsService.isManager() || permissionsService.isUserAdmin()"
[routerLink]="'/main/admin'"
mat-menu-item
translate="top-bar.navigation-items.my-account.children.admin"
@ -82,7 +82,7 @@
></button>
<button [matMenuTriggerFor]="language" mat-menu-item translate="top-bar.navigation-items.my-account.children.language.label"></button>
<mat-menu #language="matMenu">
<button *ngFor="let lang of languages" (click)="changeLanguage(lang)" mat-menu-item translate>
<button (click)="changeLanguage(lang)" *ngFor="let lang of languages" mat-menu-item translate>
top-bar.navigation-items.my-account.children.language.{{ lang }}
</button>
</mat-menu>

View File

@ -1,14 +1,14 @@
import { Component } from '@angular/core';
import { UserService } from '../../services/user.service';
import { AppStateService } from '../../state/app-state.service';
import { LanguageService } from '../../i18n/language.service';
import { PermissionsService } from '../../services/permissions.service';
import { UserPreferenceService } from '../../services/user-preference.service';
import { UserService } from '@services/user.service';
import { AppStateService } from '@state/app-state.service';
import { LanguageService } from '@i18n/language.service';
import { PermissionsService } from '@services/permissions.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { Router } from '@angular/router';
import { AppConfigService } from '../../modules/app-config/app-config.service';
import { AppConfigService } from '@app-config/app-config.service';
import { Title } from '@angular/platform-browser';
import { FileDownloadService } from '../../modules/upload-download/services/file-download.service';
import { StatusOverlayService } from '../../modules/upload-download/services/status-overlay.service';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
import { TranslateService } from '@ngx-translate/core';
@Component({
@ -17,18 +17,12 @@ import { TranslateService } from '@ngx-translate/core';
styleUrls: ['./base-screen.component.scss']
})
export class BaseScreenComponent {
private _projectsView: boolean;
get user() {
return this._userService.user;
}
constructor(
public readonly appStateService: AppStateService,
public readonly permissionsService: PermissionsService,
public readonly userPreferenceService: UserPreferenceService,
public readonly titleService: Title,
public readonly fileDownloadService: FileDownloadService,
readonly appStateService: AppStateService,
readonly permissionsService: PermissionsService,
readonly userPreferenceService: UserPreferenceService,
readonly titleService: Title,
readonly fileDownloadService: FileDownloadService,
private readonly _statusOverlayService: StatusOverlayService,
private readonly _appConfigService: AppConfigService,
private readonly _router: Router,
@ -41,10 +35,16 @@ export class BaseScreenComponent {
});
}
private _projectsView: boolean;
get projectsView() {
return this._projectsView;
}
get user() {
return this._userService.user;
}
get showPendingDownloadsDot() {
return this.fileDownloadService.hasPendingDownloads;
}

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { FileDownloadService } from '../../modules/upload-download/services/file-download.service';
import { DownloadStatusWrapper } from '../../modules/upload-download/model/download-status.wrapper';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { DownloadStatusWrapper } from '@upload-download/model/download-status.wrapper';
import { DownloadControllerService } from '@redaction/red-ui-http';
@Component({
@ -9,17 +9,17 @@ import { DownloadControllerService } from '@redaction/red-ui-http';
styleUrls: ['./downloads-list-screen.component.scss']
})
export class DownloadsListScreenComponent implements OnInit {
constructor(public readonly fileDownloadService: FileDownloadService, private readonly _downloadControllerService: DownloadControllerService) {}
constructor(readonly fileDownloadService: FileDownloadService, private readonly _downloadControllerService: DownloadControllerService) {}
get noData(): boolean {
return this.fileDownloadService.downloads.length === 0;
}
ngOnInit(): void {
this.fileDownloadService.getDownloadStatus().subscribe();
}
public get noData(): boolean {
return this.fileDownloadService.downloads.length === 0;
}
public async downloadItem(download: DownloadStatusWrapper) {
async downloadItem(download: DownloadStatusWrapper) {
await this.fileDownloadService.performDownload(download);
}

View File

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

View File

@ -1,12 +1,12 @@
<redaction-circle-button [matMenuTriggerFor]="overlay" icon="red:notification" [showDot]="hasUnread"></redaction-circle-button>
<mat-menu #overlay="matMenu" class="notifications-menu" backdropClass="notifications-backdrop" 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
class="notification"
mat-menu-item
*ngFor="let notification of group.notifications | sortBy: 'desc':'eventTime'"
[class.unread]="!notification.read"
class="notification"
mat-menu-item
>
<redaction-initials-avatar></redaction-initials-avatar>
<div class="notification-content">
@ -14,9 +14,9 @@
<div class="small-label mt-2">{{ eventTime(notification.eventTime) }}</div>
</div>
<div
class="dot"
(click)="toggleRead(notification, $event)"
[matTooltip]="(notification.read ? 'notifications.mark-unread' : 'notifications.mark-read') | translate"
class="dot"
matTooltipPosition="before"
></div>
</div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core';
@ -13,26 +13,54 @@ interface Notification {
templateUrl: './notifications.component.html',
styleUrls: ['./notifications.component.scss']
})
export class NotificationsComponent implements OnInit {
public notifications: Notification[] = [
{ message: 'This is a notification with longer text wrapping on multiple lines', eventTime: 1607340971000, read: false },
export class NotificationsComponent {
notifications: Notification[] = [
{
message: 'This is a notification with longer text wrapping on multiple lines',
eventTime: 1607340971000,
read: false
},
{ message: 'This is a <a>link</a>', eventTime: 1607254981000, read: true },
{ message: 'This is a <b>notification 1</b>', eventTime: 1607254571000, read: false },
{ message: 'Notification', eventTime: 1607385727000, read: true },
{ message: 'Another notification', eventTime: 1606829412000, read: false }
];
public groupedNotifications: { dateString: string; notifications: Notification[] }[] = [];
groupedNotifications: { dateString: string; notifications: Notification[] }[] = [];
constructor(private _translateService: TranslateService) {
this._groupNotifications();
}
ngOnInit(): void {}
public get hasUnread() {
get hasUnread() {
return this.notifications.filter((notification) => !notification.read).length > 0;
}
day(group: { dateString: string; notifications: Notification[] }): string {
moment.locale(this._translateService.currentLang);
return moment(group.notifications[0].eventTime).calendar({
sameDay: `[${this._translateService.instant('notifications.today')}]`,
lastDay: `[${this._translateService.instant('notifications.yesterday')}]`,
nextDay: `[${this._translateService.instant('notifications.tomorrow')}]`,
nextWeek: 'D MMMM',
lastWeek: 'D MMMM',
sameElse: 'D MMMM'
});
}
eventTime(eventTime: number): string {
moment.locale(this._translateService.currentLang);
if (moment().isSame(eventTime, 'day')) {
return moment(eventTime).fromNow();
} else {
return moment(eventTime).format('hh:mm A');
}
}
toggleRead(notification: Notification, $event) {
$event.stopPropagation();
notification.read = !notification.read;
}
private _groupNotifications() {
const res = {};
for (const notification of this.notifications) {
@ -44,30 +72,4 @@ export class NotificationsComponent implements OnInit {
this.groupedNotifications.push({ dateString: key, notifications: res[key] });
}
}
public day(group: { dateString: string; notifications: Notification[] }): string {
moment.locale(this._translateService.currentLang);
return moment(group.notifications[0].eventTime).calendar({
sameDay: `[${this._translateService.instant('notifications.today')}]`,
lastDay: `[${this._translateService.instant('notifications.yesterday')}]`,
nextDay: `[${this._translateService.instant('notifications.tomorrow')}]`,
nextWeek: 'D MMMM',
lastWeek: 'D MMMM',
sameElse: 'D MMMM'
});
}
public eventTime(eventTime: number): string {
moment.locale(this._translateService.currentLang);
if (moment().isSame(eventTime, 'day')) {
return moment(eventTime).fromNow();
} else {
return moment(eventTime).format('hh:mm A');
}
}
public toggleRead(notification: Notification, $event) {
$event.stopPropagation();
notification.read = !notification.read;
}
}

View File

@ -1,32 +1,20 @@
<div class="row" [style.display]="state.value === 'inactive' ? 'none' : ''">
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
<div [style.display]="state.value === 'inactive' ? 'none' : ''" class="row">
<div *ngIf="title" [attr.aria-label]="title" [class]="options.titleClass">
{{ title }}
</div>
<div
*ngIf="message && options.enableHtml"
role="alert"
aria-live="polite"
[class]="options.messageClass"
[innerHTML]="message"
></div>
<div
*ngIf="message && !options.enableHtml"
role="alert"
aria-live="polite"
[class]="options.messageClass"
[attr.aria-label]="message"
>
<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>
<div class="actions-wrapper" *ngIf="actions && actions.length">
<a *ngFor="let action of actions" (click)="callAction($event, action.action)">
<div *ngIf="actions && actions.length" class="actions-wrapper">
<a (click)="callAction($event, action.action)" *ngFor="let action of actions">
{{ action.title }}
</a>
</div>
</div>
<div class="text-right">
<a *ngIf="options.closeButton" (click)="remove()" class="toast-close-button">
<a (click)="remove()" *ngIf="options.closeButton" class="toast-close-button">
<mat-icon svgIcon="red:close"></mat-icon>
</a>
</div>

View File

@ -7,16 +7,17 @@ import { Toast, ToastPackage, ToastrService } from 'ngx-toastr';
styleUrls: ['./toast.component.scss']
})
export class ToastComponent extends Toast {
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
super(toastrService, toastPackage);
constructor(protected readonly _toastrService: ToastrService, readonly toastPackage: ToastPackage) {
super(_toastrService, toastPackage);
}
public get actions() {
get actions() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return this.options.actions;
}
public callAction($event: MouseEvent, action: Function) {
callAction($event: MouseEvent, action: Function) {
$event.stopPropagation();
if (action) {
action();

View File

@ -3,9 +3,9 @@
<div class="overlay-shadow"></div>
<div class="dialog">
<div class="dialog-header">
<div class="heading-l" [translate]="'user-profile.title'"></div>
<div [translate]="'user-profile.title'" class="heading-l"></div>
</div>
<form [formGroup]="formGroup" (submit)="save()">
<form (submit)="save()" [formGroup]="formGroup">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="red-input-group required">

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { UserService } from '../../services/user.service';
import { PermissionsService } from '../../services/permissions.service';
import { LanguageService } from '../../i18n/language.service';
import { UserService } from '@services/user.service';
import { PermissionsService } from '@services/permissions.service';
import { LanguageService } from '@i18n/language.service';
import { TranslateService } from '@ngx-translate/core';
import { UserControllerService } from '@redaction/red-ui-http';
@ -19,12 +19,12 @@ interface ProfileModel {
styleUrls: ['./user-profile-screen.component.scss']
})
export class UserProfileScreenComponent implements OnInit {
public viewReady = false;
public formGroup: FormGroup;
viewReady = false;
formGroup: FormGroup;
private _profileModel: ProfileModel;
constructor(
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder,
private readonly _userService: UserService,
private readonly _userControllerService: UserControllerService,
@ -39,10 +39,6 @@ export class UserProfileScreenComponent implements OnInit {
});
}
ngOnInit() {
this._initializeForm();
}
get languageChanged(): boolean {
return this._profileModel['language'] !== this.formGroup.get('language').value;
}
@ -64,6 +60,10 @@ export class UserProfileScreenComponent implements OnInit {
return this._translateService.langs;
}
ngOnInit() {
this._initializeForm();
}
async save(): Promise<void> {
this.viewReady = false;

View File

@ -24,7 +24,7 @@ export abstract class ComponentHasChanges implements ComponentCanDeactivate {
@Injectable({ providedIn: 'root' })
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
constructor(private readonly translateService: TranslateService) {}
constructor(private readonly _translateService: TranslateService) {}
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
// if there are no pending changes, just allow deactivation; else confirm first
@ -33,6 +33,6 @@ export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate
: // 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'));
confirm(this._translateService.instant('pending-changes-guard'));
}
}

View File

@ -1,7 +1,7 @@
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';
import { AppLoadStateService } from '@services/app-load-state.service';
@Injectable({
providedIn: 'root'

View File

@ -5,36 +5,34 @@ import { TranslateService } from '@ngx-translate/core';
providedIn: 'root'
})
export class LanguageService {
constructor(private translateService: TranslateService) {
translateService.addLangs(['en', 'de']);
constructor(private readonly _translateService: TranslateService) {
_translateService.addLangs(['en', 'de']);
}
get currentLanguage() {
return this.translateService.currentLang;
return this._translateService.currentLang;
}
chooseAndSetInitialLanguage() {
let defaultLang: string;
const localStorageLang = localStorage.getItem('redaction.language');
// const browserLang = this.translateService.getBrowserLang();
// const browserLang = this._translateService.getBrowserLang();
const browserLang = 'en'; // Force language to english until translations are ready
// @ts-ignore
if (this.translateService.getLangs().includes(localStorageLang)) {
if (this._translateService.getLangs().includes(localStorageLang)) {
defaultLang = localStorageLang;
// @ts-ignore
} else if (this.translateService.getLangs().includes(browserLang)) {
} else if (this._translateService.getLangs().includes(browserLang)) {
defaultLang = browserLang;
} else {
defaultLang = 'en';
}
document.documentElement.lang = defaultLang;
this.translateService.setDefaultLang(defaultLang);
this.translateService.use(defaultLang).subscribe(() => {});
this._translateService.setDefaultLang(defaultLang);
this._translateService.use(defaultLang).subscribe(() => {});
}
changeLanguage(language: string) {
localStorage.setItem('redaction.language', language);
document.documentElement.lang = language;
this.translateService.use(language).subscribe(() => {});
this._translateService.use(language).subscribe(() => {});
}
}

View File

@ -1,4 +1,4 @@
import { UserWrapper } from '../../services/user.service';
import { UserWrapper } from '@services/user.service';
import { AnnotationWrapper } from './annotation.wrapper';
export class AnnotationPermissions {
@ -16,7 +16,17 @@ export class AnnotationPermissions {
canForceRedaction: boolean;
public static forUser(isManagerAndOwner: boolean, user: UserWrapper, annotation: AnnotationWrapper) {
get canPerformMultipleRemoveActions() {
return (
<any>this.canMarkTextOnlyAsFalsePositive +
<any>this.canMarkAsFalsePositive +
<any>this.canRemoveOrSuggestToRemoveFromDictionary +
<any>this.canRemoveOrSuggestToRemoveOnlyHere >=
2
);
}
static forUser(isManagerAndOwner: boolean, user: UserWrapper, annotation: AnnotationWrapper) {
const permissions: AnnotationPermissions = new AnnotationPermissions();
permissions.canUndo =
@ -40,14 +50,4 @@ export class AnnotationPermissions {
return permissions;
}
public get canPerformMultipleRemoveActions() {
return (
<any>this.canMarkTextOnlyAsFalsePositive +
<any>this.canMarkAsFalsePositive +
<any>this.canRemoveOrSuggestToRemoveFromDictionary +
<any>this.canRemoveOrSuggestToRemoveOnlyHere >=
2
);
}
}

View File

@ -9,7 +9,7 @@ import {
ViewedPages
} from '@redaction/red-ui-http';
import { FileStatusWrapper } from './file-status.wrapper';
import { UserWrapper } from '../../services/user.service';
import { UserWrapper } from '@services/user.service';
import { AnnotationWrapper } from './annotation.wrapper';
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
import { ViewMode } from './view-mode';
@ -38,9 +38,7 @@ export class FileDataModel {
let allAnnotations = entries.map((entry) => AnnotationWrapper.fromData(entry));
if (!areDevFeaturesEnabled) {
allAnnotations = allAnnotations.filter((annotation) => {
return !annotation.isFalsePositive;
});
allAnnotations = allAnnotations.filter((annotation) => !annotation.isFalsePositive);
}
const visibleAnnotations = allAnnotations.filter((annotation) => {
@ -113,7 +111,7 @@ export class FileDataModel {
}
// an entry for this request already exists in the redactionLog
if (!!relevantRedactionLogEntry) {
if (relevantRedactionLogEntry) {
relevantRedactionLogEntry.userId = forceRedaction.user;
relevantRedactionLogEntry.dictionaryEntry = false;
relevantRedactionLogEntry.force = true;
@ -132,14 +130,14 @@ export class FileDataModel {
const relevantRedactionLogEntry = result.find((r) => r.id === manual.id);
// a redaction-log entry is marked as a reason for another entry - hide it
if (!!markedAsReasonRedactionLogEntry) {
if (markedAsReasonRedactionLogEntry) {
if (!(this._hasAlreadyBeenProcessed(manual) && manual.status === 'APPROVED')) {
markedAsReasonRedactionLogEntry.hidden = true;
}
}
// an entry for this request already exists in the redactionLog
if (!!relevantRedactionLogEntry) {
if (relevantRedactionLogEntry) {
if (relevantRedactionLogEntry.status === 'DECLINED') {
relevantRedactionLogEntry.hidden = true;
return;
@ -178,7 +176,7 @@ export class FileDataModel {
redactionLogEntryWrapper.manualRedactionType = 'ADD';
redactionLogEntryWrapper.manual = true;
redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id];
if (!!markedAsReasonRedactionLogEntry) {
if (markedAsReasonRedactionLogEntry) {
// cleanup reason if the reason is another annotationId - it is not needed for drawing
redactionLogEntryWrapper.reason = null;
}

View File

@ -1,5 +1,5 @@
import { FileAttributeConfig, FileAttributesConfig, FileStatus } from '@redaction/red-ui-http';
import { StatusSorter } from '../../utils/sorters/status-sorter';
import { FileAttributesConfig, FileStatus } from '@redaction/red-ui-http';
import { StatusSorter } from '@utils/sorters/status-sorter';
export class FileStatusWrapper {
primaryAttribute: string;

View File

@ -2,10 +2,10 @@ import { ManualRedactionEntry } from '@redaction/red-ui-http';
export class ManualRedactionEntryWrapper {
constructor(
public readonly quads: any,
public readonly manualRedactionEntry: ManualRedactionEntry,
public readonly type: 'DICTIONARY' | 'REDACTION' | 'FALSE_POSITIVE',
public readonly annotationType: 'TEXT' | 'RECTANGLE' = 'TEXT',
public readonly rectId?: string
readonly quads: any,
readonly manualRedactionEntry: ManualRedactionEntry,
readonly type: 'DICTIONARY' | 'REDACTION' | 'FALSE_POSITIVE',
readonly annotationType: 'TEXT' | 'RECTANGLE' = 'TEXT',
readonly rectId?: string
) {}
}

View File

@ -1,4 +1,4 @@
import { Rectangle, Comment } from '@redaction/red-ui-http';
import { Comment, Rectangle } from '@redaction/red-ui-http';
export interface RedactionLogEntryWrapper {
color?: Array<number>;

View File

@ -1,12 +1,12 @@
import { NgModule } from '@angular/core';
import { AuthGuard } from '../auth/auth.guard';
import { CompositeRouteGuard } from '../../guards/composite-route.guard';
import { CompositeRouteGuard } from '@guards/composite-route.guard';
import { RedRoleGuard } from '../auth/red-role.guard';
import { AppStateGuard } from '../../state/app-state.guard';
import { AppStateGuard } from '@state/app-state.guard';
import { RuleSetsListingScreenComponent } from './screens/rule-sets-listing/rule-sets-listing-screen.component';
import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component';
import { DictionaryOverviewScreenComponent } from './screens/dictionary-overview/dictionary-overview-screen.component';
import { PendingChangesGuard } from '../../guards/can-deactivate.guard';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { RulesScreenComponent } from './screens/rules/rules-screen.component';
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
import { WatermarkScreenComponent } from './screens/watermark/watermark-screen.component';

View File

@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import { RulesScreenComponent } from './screens/rules/rules-screen.component';
import { SharedModule } from '../shared/shared.module';
import { SharedModule } from '@shared/shared.module';
import { RuleSetsListingScreenComponent } from './screens/rule-sets-listing/rule-sets-listing-screen.component';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';

View File

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

View File

@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { UserPreferenceService } from '../../../../services/user-preference.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { AppStateService } from '@state/app-state.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { PermissionsService } from '@services/permissions.service';
@Component({
selector: 'redaction-admin-breadcrumbs',
@ -9,11 +9,11 @@ import { PermissionsService } from '../../../../services/permissions.service';
styleUrls: ['./admin-breadcrumbs.component.scss']
})
export class AdminBreadcrumbsComponent {
@Input() public root = false;
@Input() root = false;
constructor(
public readonly userPreferenceService: UserPreferenceService,
public readonly permissionService: PermissionsService,
public readonly appStateService: AppStateService
readonly userPreferenceService: UserPreferenceService,
readonly permissionService: PermissionsService,
readonly appStateService: AppStateService
) {}
}

View File

@ -1,109 +1,109 @@
<ngx-charts-chart
[view]="[width + legendSpacing, height]"
[showLegend]="legend"
[legendOptions]="legendOptions"
(legendLabelActivate)="onActivate($event)"
(legendLabelClick)="onClick($event)"
(legendLabelDeactivate)="onDeactivate($event)"
[activeEntries]="activeEntries"
[animations]="animations"
(legendLabelClick)="onClick($event)"
(legendLabelActivate)="onActivate($event)"
(legendLabelDeactivate)="onDeactivate($event)"
[legendOptions]="legendOptions"
[showLegend]="legend"
[view]="[width + legendSpacing, height]"
>
<svg:g [attr.transform]="transform" class="bar-chart chart">
<svg:g
ngx-charts-x-axis
*ngIf="xAxis"
[xScale]="xScale"
[dims]="dims"
[showLabel]="showXAxisLabel"
[labelText]="xAxisLabel"
[tickFormatting]="xAxisTickFormatting"
(dimensionsChanged)="updateXAxisHeight($event)"
*ngIf="xAxis"
[dims]="dims"
[labelText]="xAxisLabel"
[showLabel]="showXAxisLabel"
[tickFormatting]="xAxisTickFormatting"
[xScale]="xScale"
ngx-charts-x-axis
></svg:g>
<svg:g
ngx-charts-y-axis
(dimensionsChanged)="updateYAxisWidth($event)"
*ngIf="yAxis"
[yScale]="yScale"
[dims]="dims"
[yOrient]="yOrientLeft"
[labelText]="yAxisLabel"
[showGridLines]="showGridLines"
[showLabel]="showYAxisLabel"
[labelText]="yAxisLabel"
[tickFormatting]="yAxisTickFormatting"
(dimensionsChanged)="updateYAxisWidth($event)"
[yOrient]="yOrientLeft"
[yScale]="yScale"
ngx-charts-y-axis
></svg:g>
<svg:g
ngx-charts-y-axis
(dimensionsChanged)="updateYAxisWidth($event)"
*ngIf="yAxis"
[yScale]="yScaleLine"
[dims]="dims"
[yOrient]="yOrientRight"
[labelText]="yAxisLabelRight"
[showGridLines]="showGridLines"
[showLabel]="showRightYAxisLabel"
[labelText]="yAxisLabelRight"
[tickFormatting]="yRightAxisTickFormatting"
(dimensionsChanged)="updateYAxisWidth($event)"
[yOrient]="yOrientRight"
[yScale]="yScaleLine"
ngx-charts-y-axis
></svg:g>
<svg:g
ngx-combo-charts-series-vertical
[xScale]="xScale"
[yScale]="yScale"
[colors]="colors"
[series]="results"
[seriesLine]="lineChart"
[dims]="dims"
[gradient]="gradient"
[tooltipDisabled]="true"
(activate)="onActivate($event)"
(bandwidth)="updateLineWidth($event)"
(deactivate)="onDeactivate($event)"
[activeEntries]="activeEntries"
[animations]="animations"
[colors]="colors"
[dims]="dims"
[gradient]="gradient"
[noBarWhenZero]="noBarWhenZero"
(activate)="onActivate($event)"
(deactivate)="onDeactivate($event)"
(bandwidth)="updateLineWidth($event)"
[seriesLine]="lineChart"
[series]="results"
[tooltipDisabled]="true"
[xScale]="xScale"
[yScale]="yScale"
ngx-combo-charts-series-vertical
></svg:g>
</svg:g>
<svg:g [attr.transform]="transform" class="line-chart chart">
<svg:g>
<svg:g *ngFor="let series of lineChart; trackBy: trackBy">
<svg:g
ngx-charts-line-series
[activeEntries]="activeEntries"
[animations]="animations"
[colors]="colorsLine"
[curve]="curve"
[data]="series"
[rangeFillOpacity]="rangeFillOpacity"
[scaleType]="scaleType"
[xScale]="xScaleLine"
[yScale]="yScaleLine"
[colors]="colorsLine"
[data]="series"
[activeEntries]="activeEntries"
[scaleType]="scaleType"
[curve]="curve"
[rangeFillOpacity]="rangeFillOpacity"
[animations]="animations"
ngx-charts-line-series
/>
</svg:g>
<svg:g
ngx-charts-tooltip-area
*ngIf="!tooltipDisabled"
[dims]="dims"
[xSet]="xSet"
[xScale]="xScaleLine"
[yScale]="yScaleLine"
[results]="combinedSeries"
[colors]="colorsLine"
[tooltipDisabled]="tooltipDisabled"
(hover)="updateHoveredVertical($event)"
*ngIf="!tooltipDisabled"
[colors]="colorsLine"
[dims]="dims"
[results]="combinedSeries"
[tooltipDisabled]="tooltipDisabled"
[xScale]="xScaleLine"
[xSet]="xSet"
[yScale]="yScaleLine"
ngx-charts-tooltip-area
/>
<svg:g *ngFor="let series of lineChart">
<svg:g
ngx-charts-circle-series
[xScale]="xScaleLine"
[yScale]="yScaleLine"
(activate)="onActivate($event)"
(deactivate)="onDeactivate($event)"
[activeEntries]="activeEntries"
[colors]="colorsLine"
[data]="series"
[scaleType]="scaleType"
[visibleValue]="hoveredVertical"
[activeEntries]="activeEntries"
[tooltipDisabled]="tooltipDisabled"
(activate)="onActivate($event)"
(deactivate)="onDeactivate($event)"
[visibleValue]="hoveredVertical"
[xScale]="xScaleLine"
[yScale]="yScaleLine"
ngx-charts-circle-series
/>
</svg:g>
</svg:g>

View File

@ -21,6 +21,7 @@
&:focus {
outline: none;
}
&.hidden {
display: none;
}

View File

@ -1,11 +1,11 @@
import { Component, Input, ViewEncapsulation, Output, EventEmitter, ViewChild, HostListener, ContentChild, TemplateRef } 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, LineSeriesComponent, ViewDimensions, ColorHelper, calculateViewDimensions } from '@swimlane/ngx-charts';
import { BaseChartComponent, calculateViewDimensions, ColorHelper, LineSeriesComponent, ViewDimensions } from '@swimlane/ngx-charts';
@Component({
// tslint:disable-next-line: component-selector
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'combo-chart-component',
templateUrl: './combo-chart.component.html',
styleUrls: ['./combo-chart.component.scss'],
@ -364,9 +364,7 @@ export class ComboChartComponent extends BaseChartComponent {
}
onActivate(item) {
const idx = this.activeEntries.findIndex((d) => {
return 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;
}
@ -376,9 +374,7 @@ export class ComboChartComponent extends BaseChartComponent {
}
onDeactivate(item) {
const idx = this.activeEntries.findIndex((d) => {
return 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,9 +1,9 @@
import { Component, Input, Output, EventEmitter, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { trigger, style, animate, transition } from '@angular/animations';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations';
import { formatLabel } from '@swimlane/ngx-charts';
@Component({
// tslint:disable-next-line: component-selector
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'g[ngx-combo-charts-series-vertical]',
template: `
<svg:g
@ -68,7 +68,7 @@ export class ComboSeriesVerticalComponent implements OnChanges {
x: any;
y: any;
ngOnChanges(changes): void {
ngOnChanges(): void {
this.update();
}
@ -163,23 +163,21 @@ export class ComboSeriesVerticalComponent implements OnChanges {
this.getSeriesTooltips(this.seriesLine, index);
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-label'>${tooltipLabel}</span>
<span class='tooltip-val'> Y1 - ${value.toLocaleString()} Y2 - ${lineValue.toLocaleString()}%</span>
`;
return bar;
});
}
getSeriesTooltips(seriesLine, index) {
return seriesLine.map((d) => {
return d.series[index];
});
return seriesLine.map((d) => d.series[index]);
}
isActive(entry): boolean {
if (!this.activeEntries) return false;
const item = this.activeEntries.find((d) => {
return 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

@ -2,18 +2,18 @@
<redaction-circle-button
(action)="openDeleteRuleSetDialog($event)"
*ngIf="permissionsService.isAdmin()"
icon="red:trash"
tooltip="project-templates-listing.action.delete"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openEditRuleSetDialog($event)"
*ngIf="permissionsService.isAdmin()"
icon="red:edit"
tooltip="project-templates-listing.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
</div>

View File

@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { AppStateService } from '../../../../state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { AppStateService } from '@state/app-state.service';
import { Router } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
@ -17,14 +17,14 @@ export class RuleSetActionsComponent {
private readonly _dialogService: AdminDialogService,
private readonly _appStateService: AppStateService,
private readonly _router: Router,
public readonly permissionsService: PermissionsService
readonly permissionsService: PermissionsService
) {
if (!this.ruleSetId) {
this.ruleSetId = this._appStateService.activeRuleSetId;
}
}
public get ruleSet() {
get ruleSet() {
return this._appStateService.getRuleSetById(this.ruleSetId);
}

View File

@ -1,15 +1,15 @@
<div class="all-caps-label" [translate]="type"></div>
<div [translate]="type" class="all-caps-label"></div>
<ng-container *ngFor="let item of items[type]">
<div
class="item"
*ngIf="
(!item.onlyAdmin || permissionsService.isAdmin()) &&
(!item.onlyDevMode || userPreferenceService.areDevFeaturesEnabled) &&
(!item.userManagerOnly || permissionsService.canManageUsers())
"
[routerLink]="prefix + item.screen"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="prefix + item.screen"
class="item"
routerLinkActive="active"
>
{{ item.label || item.screen | translate }}

View File

@ -1,17 +1,17 @@
import { Component, Input, OnInit } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { UserPreferenceService } from '../../../../services/user-preference.service';
import { AppStateService } from '../../../../state/app-state.service';
import { Component, Input } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { AppStateService } from '@state/app-state.service';
@Component({
selector: 'redaction-side-nav',
templateUrl: './side-nav.component.html',
styleUrls: ['./side-nav.component.scss']
})
export class SideNavComponent implements OnInit {
export class SideNavComponent {
@Input() type: 'settings' | 'project-templates';
public 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 },
@ -31,14 +31,12 @@ export class SideNavComponent implements OnInit {
constructor(
private readonly _appStateService: AppStateService,
public readonly userPreferenceService: UserPreferenceService,
public readonly permissionsService: PermissionsService
readonly userPreferenceService: UserPreferenceService,
readonly permissionsService: PermissionsService
) {}
ngOnInit(): void {}
public get prefix() {
if (!!this._appStateService.activeDictionaryType) {
get prefix() {
if (this._appStateService.activeDictionaryType) {
return '../../';
}

View File

@ -1,5 +1,5 @@
<div class="collapsed-wrapper">
<redaction-circle-button (action)="toggleCollapse.emit()" icon="red:expand" tooltipPosition="before" tooltip="user-stats.expand"></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>
@ -16,10 +16,10 @@
<div class="mt-44">
<redaction-simple-doughnut-chart
[config]="chartData"
[strokeWidth]="15"
[radius]="63"
[strokeWidth]="15"
[subtitle]="'user-stats.chart.users'"
totalType="sum"
direction="row"
totalType="sum"
></redaction-simple-doughnut-chart>
</div>

View File

@ -1,16 +1,12 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
@Component({
selector: 'redaction-users-stats',
templateUrl: './users-stats.component.html',
styleUrls: ['./users-stats.component.scss']
})
export class UsersStatsComponent implements OnInit {
@Output() public toggleCollapse = new EventEmitter();
export class UsersStatsComponent {
@Output() toggleCollapse = new EventEmitter();
@Input() chartData: DoughnutChartConfig[];
constructor() {}
ngOnInit(): void {}
}

View File

@ -13,15 +13,15 @@
</ng-container>
<div class="first-row">
<div class="red-input-group required" *ngIf="!dictionary">
<div *ngIf="!dictionary" class="red-input-group required">
<label translate="add-edit-dictionary.form.name"></label>
<input formControlName="type" name="type" type="text" placeholder="{{ 'add-edit-dictionary.form.name-placeholder' | translate }}" />
<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" type="number" placeholder="{{ 'add-edit-dictionary.form.rank-placeholder' | translate }}" />
<input formControlName="rank" name="rank" placeholder="{{ 'add-edit-dictionary.form.rank-placeholder' | translate }}" type="number" />
</div>
<div class="red-input-group required">
@ -30,19 +30,19 @@
class="hex-color-input"
formControlName="hexColor"
name="hexColor"
type="text"
placeholder="{{ 'add-edit-dictionary.form.color-placeholder' | translate }}"
type="text"
/>
<div
class="input-icon"
[style.background]="dictionaryForm.get('hexColor').value"
(colorPickerChange)="dictionaryForm.get('hexColor').setValue($event)"
[colorPicker]="dictionaryForm.get('hexColor').value"
[cpOutputFormat]="'hex'"
(colorPickerChange)="dictionaryForm.get('hexColor').setValue($event)"
[style.background]="dictionaryForm.get('hexColor').value"
class="input-icon"
>
<mat-icon
svgIcon="red:color-picker"
*ngIf="!dictionaryForm.get('hexColor').value || dictionaryForm.get('hexColor').value?.length === 0"
svgIcon="red:color-picker"
></mat-icon>
</div>
</div>
@ -51,30 +51,30 @@
<div class="red-input-group w-450">
<label translate="add-edit-dictionary.form.description"></label>
<textarea
redactionHasScrollbar
rows="4"
formControlName="description"
name="description"
type="text"
placeholder="{{ 'add-edit-dictionary.form.description-placeholder' | translate }}"
redactionHasScrollbar
rows="4"
type="text"
></textarea>
</div>
<div class="red-input-group slider-row">
<mat-button-toggle-group name="hint" formControlName="hint" appearance="legacy">
<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-group>
</div>
<div class="red-input-group">
<mat-checkbox name="caseSensitive" formControlName="caseSensitive" color="primary">
<mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive">
{{ 'add-edit-dictionary.form.case-sensitive' | translate }}
</mat-checkbox>
</div>
<div class="red-input-group">
<mat-checkbox name="addToDictionaryAction" formControlName="addToDictionaryAction" color="primary">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }}
</mat-checkbox>
</div>
@ -87,5 +87,5 @@
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,10 +1,10 @@
import { Component, Inject } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { Observable } from 'rxjs';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
@Component({
@ -14,7 +14,7 @@ import { TranslateService } from '@ngx-translate/core';
})
export class AddEditDictionaryDialogComponent {
dictionaryForm: FormGroup;
public readonly dictionary: TypeValue;
readonly dictionary: TypeValue;
private readonly _ruleSetId: string;
constructor(
@ -39,11 +39,11 @@ export class AddEditDictionaryDialogComponent {
});
}
public get dictCaseSensitive() {
get dictCaseSensitive() {
return this.dictionary ? !this.dictionary.caseInsensitive : false;
}
public get changed(): boolean {
get changed(): boolean {
if (!this.dictionary) return true;
for (const key of Object.keys(this.dictionaryForm.getRawValue())) {

View File

@ -7,7 +7,7 @@
<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" type="text" placeholder="{{ 'add-edit-file-attribute.form.name-placeholder' | translate }}" />
<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,8 +15,8 @@
<input
formControlName="csvColumnHeader"
name="csvColumnHeader"
type="text"
placeholder="{{ 'add-edit-file-attribute.form.column-header-placeholder' | translate }}"
type="text"
/>
</div>
@ -31,11 +31,11 @@
<div class="options-wrapper">
<div class="red-input-group">
<mat-slide-toggle formControlName="readonly" color="primary">{{ '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 name="primaryAttribute" formControlName="primaryAttribute" color="primary">
<mat-checkbox color="primary" formControlName="primaryAttribute" name="primaryAttribute">
{{ 'add-edit-file-attribute.form.primary' | translate }}
</mat-checkbox>
</div>
@ -48,5 +48,5 @@
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@ -10,10 +10,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
styleUrls: ['./add-edit-file-attribute-dialog.component.scss']
})
export class AddEditFileAttributeDialogComponent {
public fileAttributeForm: FormGroup;
public fileAttribute: FileAttributeConfig;
public ruleSetId: string;
public readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
fileAttributeForm: FormGroup;
fileAttribute: FileAttributeConfig;
ruleSetId: string;
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
constructor(
private readonly _appStateService: AppStateService,
@ -33,7 +33,7 @@ export class AddEditFileAttributeDialogComponent {
});
}
public get changed(): boolean {
get changed(): boolean {
if (!this.fileAttribute) return true;
for (const key of Object.keys(this.fileAttributeForm.getRawValue())) {

View File

@ -7,7 +7,7 @@
<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" type="text" placeholder="{{ 'add-edit-project-template.form.name-placeholder' | translate }}" />
<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,19 +15,19 @@
<textarea
formControlName="description"
name="description"
type="text"
rows="4"
placeholder="{{ 'add-edit-project-template.form.description-placeholder' | translate }}"
rows="4"
type="text"
></textarea>
</div>
<div class="validity">
<div>
<mat-checkbox [checked]="hasValidFrom" (change)="hasValidFrom = !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 [checked]="hasValidTo" (change)="hasValidTo = !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,8 +35,8 @@
<div>
<div class="red-input-group datepicker-wrapper">
<ng-container *ngIf="hasValidFrom">
<input placeholder="dd/mm/yy" [matDatepicker]="fromPicker" formControlName="validFrom" />
<mat-datepicker-toggle matSuffix [for]="fromPicker">
<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>
<mat-datepicker #fromPicker></mat-datepicker>
@ -45,8 +45,8 @@
<div class="red-input-group datepicker-wrapper">
<ng-container *ngIf="hasValidTo">
<input placeholder="dd/mm/yy" [matDatepicker]="toPicker" formControlName="validTo" />
<mat-datepicker-toggle matSuffix [for]="toPicker">
<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>
<mat-datepicker #toPicker></mat-datepicker>
@ -58,17 +58,17 @@
<p class="download-includes">{{ 'download-includes' | translate }}</p>
<div class="space-between">
<redaction-select
class="w-410"
[label]="'report-type.label' | translate: { length: this.ruleSetForm.controls['reportTypes'].value.length }"
[options]="reportTypesEnum"
[translatePrefix]="'report-type.'"
[label]="'report-type.label' | translate: { length: this.ruleSetForm.controls['reportTypes'].value.length }"
class="w-410"
formControlName="reportTypes"
></redaction-select>
<redaction-select
class="w-410"
[label]="'download-type.label' | translate: { length: this.ruleSetForm.controls['downloadFileTypes'].value.length }"
[options]="downloadTypesEnum"
[translatePrefix]="'download-type.'"
[label]="'download-type.label' | translate: { length: this.ruleSetForm.controls['downloadFileTypes'].value.length }"
class="w-410"
formControlName="downloadFileTypes"
></redaction-select>
</div>
@ -81,5 +81,5 @@
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,11 +1,11 @@
import { Component, Inject } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as moment from 'moment';
import { Moment } from 'moment';
import { RuleSetControllerService, RuleSetModel } from '@redaction/red-ui-http';
import { applyIntervalConstraints } from '../../../../utils/date-inputs-utils';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
@Component({
selector: 'redaction-add-edit-rule-set-dialog',
@ -13,11 +13,11 @@ import { applyIntervalConstraints } from '../../../../utils/date-inputs-utils';
styleUrls: ['./add-edit-rule-set-dialog.component.scss']
})
export class AddEditRuleSetDialogComponent {
public ruleSetForm: FormGroup;
public hasValidFrom: boolean;
public hasValidTo: boolean;
public downloadTypesEnum = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
public reportTypesEnum = Object.values(RuleSetModel.ReportTypesEnum);
ruleSetForm: FormGroup;
hasValidFrom: boolean;
hasValidTo: boolean;
downloadTypesEnum = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
reportTypesEnum = Object.values(RuleSetModel.ReportTypesEnum);
private _previousValidFrom: Moment;
private _previousValidTo: Moment;
@ -53,29 +53,7 @@ export class AddEditRuleSetDialogComponent {
});
}
private _applyValidityIntervalConstraints(value): boolean {
if (applyIntervalConstraints(value, this._previousValidFrom, this._previousValidTo, this.ruleSetForm, 'validFrom', 'validTo')) {
return true;
}
this._previousValidFrom = this.ruleSetForm.get('validFrom').value;
this._previousValidTo = this.ruleSetForm.get('validTo').value;
return false;
}
private _requiredIfValidator(predicate) {
return (formControl) => {
if (!formControl.parent) {
return null;
}
if (predicate()) {
return Validators.required(formControl);
}
return null;
};
}
public get changed(): boolean {
get changed(): boolean {
if (!this.ruleSet) return true;
for (const key of Object.keys(this.ruleSetForm.getRawValue())) {
@ -113,4 +91,26 @@ export class AddEditRuleSetDialogComponent {
await this._appStateService.loadDictionaryData();
this.dialogRef.close({ ruleSet });
}
private _applyValidityIntervalConstraints(value): boolean {
if (applyIntervalConstraints(value, this._previousValidFrom, this._previousValidTo, this.ruleSetForm, 'validFrom', 'validTo')) {
return true;
}
this._previousValidFrom = this.ruleSetForm.get('validFrom').value;
this._previousValidTo = this.ruleSetForm.get('validTo').value;
return false;
}
private _requiredIfValidator(predicate) {
return (formControl) => {
if (!formControl.parent) {
return null;
}
if (predicate()) {
return Validators.required(formControl);
}
return null;
};
}
}

View File

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

View File

@ -2,7 +2,7 @@ import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { UserService } from '../../../../services/user.service';
import { UserService } from '@services/user.service';
@Component({
selector: 'redaction-add-edit-user-dialog',
@ -10,9 +10,9 @@ import { UserService } from '../../../../services/user.service';
styleUrls: ['./add-edit-user-dialog.component.scss']
})
export class AddEditUserDialogComponent {
public userForm: FormGroup;
public ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
private ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
userForm: FormGroup;
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor(
private readonly _formBuilder: FormBuilder,
@ -28,9 +28,10 @@ export class AddEditUserDialogComponent {
value: this.user && this.user.roles.indexOf(role) !== -1,
disabled:
this.user &&
Object.keys(this.ROLE_REQUIREMENTS).reduce((value, key) => {
return value || (role === this.ROLE_REQUIREMENTS[key] && this.user.roles.indexOf(key) !== -1);
}, false)
Object.keys(this._ROLE_REQUIREMENTS).reduce(
(value, key) => value || (role === this._ROLE_REQUIREMENTS[key] && this.user.roles.indexOf(key) !== -1),
false
)
}
]
}),
@ -46,20 +47,7 @@ export class AddEditUserDialogComponent {
this._setRolesRequirements();
}
private _setRolesRequirements() {
for (const key of Object.keys(this.ROLE_REQUIREMENTS)) {
this.userForm.controls[key].valueChanges.subscribe((checked) => {
if (checked) {
this.userForm.patchValue({ [this.ROLE_REQUIREMENTS[key]]: true });
this.userForm.controls[this.ROLE_REQUIREMENTS[key]].disable();
} else {
this.userForm.controls[this.ROLE_REQUIREMENTS[key]].enable();
}
});
}
}
public get changed(): boolean {
get changed(): boolean {
if (!this.user) return true;
for (const key of Object.keys(this.userForm.getRawValue())) {
@ -71,18 +59,7 @@ export class AddEditUserDialogComponent {
return false;
}
public async save() {
this.dialogRef.close({
action: this.user ? 'UPDATE' : 'CREATE',
user: { ...this.userForm.getRawValue(), roles: this.activeRoles }
});
}
public async delete() {
this.dialogRef.close('DELETE');
}
public get activeRoles(): string[] {
get activeRoles(): string[] {
return this.ROLES.reduce((acc, role) => {
if (this.userForm.get(role).value) {
acc.push(role);
@ -90,4 +67,28 @@ export class AddEditUserDialogComponent {
return acc;
}, []);
}
async save() {
this.dialogRef.close({
action: this.user ? 'UPDATE' : 'CREATE',
user: { ...this.userForm.getRawValue(), roles: this.activeRoles }
});
}
async delete() {
this.dialogRef.close('DELETE');
}
private _setRolesRequirements() {
for (const key of Object.keys(this._ROLE_REQUIREMENTS)) {
this.userForm.controls[key].valueChanges.subscribe((checked) => {
if (checked) {
this.userForm.patchValue({ [this._ROLE_REQUIREMENTS[key]]: true });
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].disable();
} else {
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].enable();
}
});
}
}
}

View File

@ -3,7 +3,7 @@
{{ 'confirm-delete-file-attribute.title.' + type | translate: { name: fileAttribute?.label } }}
</div>
<div class="inline-dialog-toast toast-error" *ngIf="showToast">
<div *ngIf="showToast" class="inline-dialog-toast toast-error">
<div translate="confirm-delete-file-attribute.toast-error"></div>
<a (click)="showToast = false" class="toast-close-button">
<mat-icon svgIcon="red:close"></mat-icon>
@ -16,18 +16,18 @@
<mat-checkbox
*ngFor="let checkbox of checkboxes; let idx = index"
[(ngModel)]="checkbox.value"
color="primary"
[class.error]="!checkbox.value && showToast"
color="primary"
>
{{ 'confirm-delete-file-attribute.' + checkbox.label | translate }}
</mat-checkbox>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button (click)="deleteFileAttribute()">
<button (click)="deleteFileAttribute()" color="primary" mat-flat-button>
{{ 'confirm-delete-file-attribute.delete.' + type | translate }}
</button>
<div class="all-caps-label cancel" (click)="cancel()" [translate]="'confirm-delete-file-attribute.cancel.' + type"></div>
<div (click)="cancel()" [translate]="'confirm-delete-file-attribute.cancel.' + type" class="all-caps-label cancel"></div>
</div>
<redaction-circle-button icon="red:close" mat-dialog-close class="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,5 @@
import { Component, Inject } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@ -9,12 +9,12 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
styleUrls: ['./confirm-delete-file-attribute-dialog.component.scss']
})
export class ConfirmDeleteFileAttributeDialogComponent {
public fileAttribute: FileAttributeConfig;
public checkboxes = [
fileAttribute: FileAttributeConfig;
checkboxes = [
{ value: false, label: 'impacted-documents.' + this.type },
{ value: false, label: 'lost-details' }
];
public showToast = false;
showToast = false;
constructor(
private readonly _appStateService: AppStateService,
@ -24,11 +24,15 @@ export class ConfirmDeleteFileAttributeDialogComponent {
this.fileAttribute = data;
}
public get valid() {
get valid() {
return this.checkboxes[0].value && this.checkboxes[1].value;
}
public deleteFileAttribute() {
get type(): 'bulk' | 'single' {
return !this.fileAttribute ? 'bulk' : 'single';
}
deleteFileAttribute() {
if (this.valid) {
this.dialogRef.close(true);
} else {
@ -36,11 +40,7 @@ export class ConfirmDeleteFileAttributeDialogComponent {
}
}
public get type(): 'bulk' | 'single' {
return !this.fileAttribute ? 'bulk' : 'single';
}
public cancel() {
cancel() {
this.dialogRef.close();
}
}

View File

@ -1,7 +1,7 @@
<section class="dialog">
<div class="dialog-header heading-l" [translate]="'confirm-delete-users.title.' + type"></div>
<div [translate]="'confirm-delete-users.title.' + type" class="dialog-header heading-l"></div>
<div class="inline-dialog-toast toast-error" *ngIf="showToast">
<div *ngIf="showToast" class="inline-dialog-toast toast-error">
<div translate="confirm-delete-users.toast-error"></div>
<a (click)="showToast = false" class="toast-close-button">
<mat-icon svgIcon="red:close"></mat-icon>
@ -14,18 +14,18 @@
<mat-checkbox
*ngFor="let checkbox of checkboxes; let idx = index"
[(ngModel)]="checkbox.value"
color="primary"
[class.error]="!checkbox.value && showToast"
color="primary"
>
{{ 'confirm-delete-users.' + checkbox.label | translate: { projectsCount: projectsCount } }}
</mat-checkbox>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button (click)="deleteUser()">
<button (click)="deleteUser()" color="primary" mat-flat-button>
{{ 'confirm-delete-users.delete.' + type | translate }}
</button>
<div class="all-caps-label cancel" (click)="cancel()" [translate]="'confirm-delete-users.cancel.' + type"></div>
<div (click)="cancel()" [translate]="'confirm-delete-users.cancel.' + type" class="all-caps-label cancel"></div>
</div>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,20 +1,20 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
@Component({
selector: 'redaction-confirm-delete-users-dialog',
templateUrl: './confirm-delete-users-dialog.component.html',
styleUrls: ['./confirm-delete-users-dialog.component.scss']
})
export class ConfirmDeleteUsersDialogComponent implements OnInit {
public checkboxes = [
export class ConfirmDeleteUsersDialogComponent {
checkboxes = [
{ value: false, label: 'impacted-projects' },
{ value: false, label: 'impacted-documents.' + this.type }
];
public showToast = false;
public projectsCount: number;
showToast = false;
projectsCount: number;
constructor(
@Inject(MAT_DIALOG_DATA) public users: User[],
@ -31,7 +31,13 @@ export class ConfirmDeleteUsersDialogComponent implements OnInit {
}).length;
}
ngOnInit(): void {}
get valid() {
return this.checkboxes[0].value && this.checkboxes[1].value;
}
get type(): 'bulk' | 'single' {
return this.users.length > 1 ? 'bulk' : 'single';
}
async deleteUser() {
if (this.valid) {
@ -41,15 +47,7 @@ export class ConfirmDeleteUsersDialogComponent implements OnInit {
}
}
public get valid() {
return this.checkboxes[0].value && this.checkboxes[1].value;
}
public cancel() {
cancel() {
this.dialogRef.close();
}
public get type(): 'bulk' | 'single' {
return this.users.length > 1 ? 'bulk' : 'single';
}
}

View File

@ -1,5 +1,5 @@
<section class="dialog">
<div class="dialog-header heading-l" [translate]="'default-colors-screen.types.' + this.colorKey"></div>
<div [translate]="'default-colors-screen.types.' + this.colorKey" class="dialog-header heading-l"></div>
<form (submit)="saveColors()" [formGroup]="colorForm">
<div class="dialog-content">
@ -9,17 +9,17 @@
class="hex-color-input"
formControlName="color"
name="color"
type="text"
placeholder="{{ 'edit-color-dialog.form.color-placeholder' | translate }}"
type="text"
/>
<div
class="input-icon"
[style.background]="colorForm.get('color').value"
(colorPickerChange)="colorForm.get('color').setValue($event)"
[colorPicker]="colorForm.get('color').value"
[cpOutputFormat]="'hex'"
(colorPickerChange)="colorForm.get('color').setValue($event)"
[style.background]="colorForm.get('color').value"
class="input-icon"
>
<mat-icon svgIcon="red:color-picker" *ngIf="!colorForm.get('color').value || colorForm.get('color').value?.length === 0"></mat-icon>
<mat-icon *ngIf="!colorForm.get('color').value || colorForm.get('color').value?.length === 0" svgIcon="red:color-picker"></mat-icon>
</div>
</div>
</div>
@ -31,5 +31,5 @@
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { NotificationService, NotificationType } from '@services/notification.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
@ -11,11 +11,11 @@ import { TranslateService } from '@ngx-translate/core';
styleUrls: ['./edit-color-dialog.component.scss']
})
export class EditColorDialogComponent {
public readonly colors: Colors;
public readonly colorKey: string;
readonly colors: Colors;
readonly colorKey: string;
colorForm: FormGroup;
private readonly _initialColor: string;
private readonly _ruleSetId: string;
public colorForm: FormGroup;
constructor(
private readonly _formBuilder: FormBuilder,
@ -35,7 +35,7 @@ export class EditColorDialogComponent {
});
}
public get changed(): boolean {
get changed(): boolean {
return this.colorForm.get('color').value !== this._initialColor;
}

View File

@ -13,39 +13,39 @@
<ng-container *ngIf="areSomeEntitiesSelected">
<redaction-circle-button
[matMenuTriggerFor]="readOnlyMenu"
icon="red:read-only"
tooltip="file-attributes-csv-import.table-header.actions.read-only"
type="dark-bg"
icon="red:read-only"
>
</redaction-circle-button>
<redaction-circle-button
(action)="deactivateSelection()"
icon="red:trash"
tooltip="file-attributes-csv-import.table-header.actions.remove-selected"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<div class="separator"></div>
<redaction-chevron-button text="file-attributes-csv-import.table-header.actions.type" [matMenuTriggerFor]="typeMenu"></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
mat-menu-item
(click)="setAttributeForSelection('readonly', true)"
mat-menu-item
translate="file-attributes-csv-import.table-header.actions.enable-read-only"
></button>
<button
mat-menu-item
(click)="setAttributeForSelection('readonly', false)"
mat-menu-item
translate="file-attributes-csv-import.table-header.actions.disable-read-only"
></button>
</mat-menu>
<mat-menu #typeMenu="matMenu" class="no-padding-bottom">
<button *ngFor="let type of typeOptions" mat-menu-item (click)="setAttributeForSelection('type', type)">
<button (click)="setAttributeForSelection('type', type)" *ngFor="let type of typeOptions" mat-menu-item>
{{ 'file-attribute-types.' + type | translate }}
</button>
</mat-menu>
@ -55,71 +55,71 @@
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name label="file-attributes-csv-import.table-col-names.name" class="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.read-only"
class="flex-center"
label="file-attributes-csv-import.table-col-names.read-only"
leftIcon="red:read-only"
></redaction-table-col-name>
<redaction-table-col-name
class="flex-center"
label="file-attributes-csv-import.table-col-names.primary"
rightIcon="red:status-info"
rightIconTooltip="file-attributes-csv-import.table-col-names.primary-info-tooltip"
class="flex-center"
></redaction-table-col-name>
<div></div>
<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 -->
<div
class="table-item"
*cdkVirtualFor="let field of displayedEntities"
(mouseenter)="setHoveredColumn.emit(field.csvColumn)"
(mouseleave)="setHoveredColumn.emit()"
*cdkVirtualFor="let field of displayedEntities"
class="table-item"
>
<div class="selection-column" (click)="toggleEntitySelected($event, field)">
<div (click)="toggleEntitySelected($event, field)" class="selection-column">
<redaction-round-checkbox [active]="isEntitySelected(field)"></redaction-round-checkbox>
</div>
<div class="name" [class.editing]="field.editingName">
<div [class.editing]="field.editingName" class="name">
<div *ngIf="!field.editingName">
{{ field.name }}
</div>
<form (submit)="field.editingName = false; field.name = field.temporaryName" *ngIf="field.editingName">
<div class="red-input-group w-200">
<input name="name" [(ngModel)]="field.temporaryName" />
<input [(ngModel)]="field.temporaryName" name="name" />
</div>
</form>
<redaction-circle-button
class="edit-name-button"
*ngIf="!field.editingName"
(action)="field.editingName = true"
*ngIf="!field.editingName"
class="edit-name-button"
icon="red:edit"
tooltip="file-attributes-csv-import.action.edit-name"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
<ng-container *ngIf="field.editingName">
<redaction-circle-button
(action)="field.editingName = false; field.name = field.temporaryName"
icon="red:check"
tooltip="file-attributes-csv-import.action.save-name"
type="dark-bg"
icon="red:check"
>
</redaction-circle-button>
<redaction-circle-button
(action)="field.editingName = false; field.temporaryName = field.name"
icon="red:close"
tooltip="file-attributes-csv-import.action.cancel-edit-name"
type="dark-bg"
icon="red:close"
>
</redaction-circle-button>
</ng-container>
@ -146,9 +146,9 @@
<redaction-circle-button
(action)="field.primaryAttribute = false; toggleFieldActive.emit(field)"
[removeTooltip]="true"
icon="red:trash"
tooltip="file-attributes-csv-import.action.remove"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
</div>

View File

@ -1,5 +1,5 @@
import { Component, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { BaseListingComponent } from '../../../../shared/base/base-listing.component';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { Field } from '../file-attributes-csv-import-dialog.component';
import { FileAttributeConfig } from '@redaction/red-ui-http';
@ -9,12 +9,12 @@ import { FileAttributeConfig } from '@redaction/red-ui-http';
styleUrls: ['./active-fields-listing.component.scss']
})
export class ActiveFieldsListingComponent extends BaseListingComponent<Field> implements OnChanges {
@Input() public allEntities: Field[];
@Output() public allEntitiesChange = new EventEmitter<Field[]>();
@Output() public setHoveredColumn = new EventEmitter<string>();
@Output() public toggleFieldActive = new EventEmitter<Field>();
@Input() allEntities: Field[];
@Output() allEntitiesChange = new EventEmitter<Field[]>();
@Output() setHoveredColumn = new EventEmitter<string>();
@Output() toggleFieldActive = new EventEmitter<Field>();
public 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';
@ -29,20 +29,20 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
}
}
public deactivateSelection() {
deactivateSelection() {
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 = [];
}
public setAttributeForSelection(attribute: string, value: any) {
setAttributeForSelection(attribute: string, value: any) {
for (const csvColumn of this.selectedEntitiesIds) {
this.allEntities.find((f) => f.csvColumn === csvColumn)[attribute] = value;
}
}
public togglePrimary(field: Field) {
togglePrimary(field: Field) {
if (field.primaryAttribute) {
field.primaryAttribute = false;
} else {

View File

@ -1,5 +1,5 @@
<section class="dialog">
<div translate="file-attributes-csv-import.title" class="dialog-header heading-l"></div>
<div class="dialog-header heading-l" translate="file-attributes-csv-import.title"></div>
<div class="dialog-content">
<div class="sub-header">
@ -10,18 +10,18 @@
</div>
</div>
<div class="right">
<form [formGroup]="baseConfigForm" (submit)="changedParseConfig && readFile()">
<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>
<input
type="text"
[placeholder]="'file-attributes-csv-import.key-column-placeholder' | translate"
matInput
formControlName="filenameMappingColumnHeaderName"
[matAutocomplete]="auto"
[placeholder]="'file-attributes-csv-import.key-column-placeholder' | translate"
formControlName="filenameMappingColumnHeaderName"
matInput
type="text"
/>
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption>
<mat-option *ngFor="let field of filteredKeyOptions | async" [value]="field">
{{ field }}
</mat-option>
@ -32,25 +32,25 @@
<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"
formControlName="delimiter"
name="delimiter"
type="text"
[placeholder]="'file-attributes-csv-import.delimiter-placeholder' | translate"
/>
</div>
<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"
formControlName="encoding"
name="encoding"
type="text"
[placeholder]="'file-attributes-csv-import.encoding-placeholder' | translate"
/>
</div>
<redaction-circle-button
*ngIf="changedParseConfig"
(action)="readFile()"
*ngIf="changedParseConfig"
icon="red:check"
tooltip="file-attributes-csv-import.parse-csv"
></redaction-circle-button>
@ -68,40 +68,40 @@
</div>
<div class="actions">
<redaction-circle-button
icon="red:search"
(click)="isSearchOpen = !isSearchOpen"
[attr.aria-expanded]="isSearchOpen"
icon="red:search"
></redaction-circle-button>
<div class="quick-activation">
<span
class="all-caps-label primary pointer"
(click)="activateAll()"
class="all-caps-label primary pointer"
translate="file-attributes-csv-import.quick-activation.all"
></span>
<span
class="all-caps-label primary pointer"
(click)="deactivateAll()"
class="all-caps-label primary pointer"
translate="file-attributes-csv-import.quick-activation.none"
></span>
</div>
</div>
</div>
<div class="search-input-container" *ngIf="isSearchOpen">
<div *ngIf="isSearchOpen" class="search-input-container">
<redaction-search-input
[form]="searchForm"
placeholder="file-attributes-csv-import.search.placeholder"
width="full"
></redaction-search-input>
</div>
<div class="csv-header-pill-content" [class.search-open]="isSearchOpen">
<div [class.search-open]="isSearchOpen" class="csv-header-pill-content">
<div
class="csv-header-pill-wrapper"
*ngFor="let field of displayedEntities"
(click)="toggleFieldActive(field)"
(mouseenter)="setHoveredColumn(field.csvColumn)"
(mouseleave)="setHoveredColumn()"
(click)="toggleFieldActive(field)"
*ngFor="let field of displayedEntities"
class="csv-header-pill-wrapper"
>
<div class="csv-header-pill" [class.selected]="isActive(field)">
<div [class.selected]="isActive(field)" class="csv-header-pill">
<div class="name">
{{ field.csvColumn }}
</div>
@ -113,19 +113,19 @@
</div>
</div>
</div>
<div class="center" [class.collapsed]="!previewExpanded" (mouseenter)="keepPreview = true" (mouseleave)="keepPreview = false; setHoveredColumn()">
<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 }}
</div>
<redaction-circle-button
[icon]="previewExpanded ? 'red:expand' : 'red:collapse'"
(click)="previewExpanded = !previewExpanded"
[icon]="previewExpanded ? 'red:expand' : 'red:collapse'"
></redaction-circle-button>
</div>
<div class="csv-part-content" [class.hidden]="!previewExpanded">
<div class="no-column-data" *ngIf="!hoveredColumn" translate="file-attributes-csv-import.no-hovered-column"></div>
<div class="no-column-data" *ngIf="hoveredColumn && !columnSample.length">
<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 && !columnSample.length" class="no-column-data">
{{ 'file-attributes-csv-import.no-sample-data-for' | translate: { column: hoveredColumn } }}
</div>
<div *ngFor="let row of columnSample">
@ -135,21 +135,21 @@
</div>
<div class="content-container">
<redaction-active-fields-listing
[(allEntities)]="activeFields"
(setHoveredColumn)="setHoveredColumn($event)"
(toggleFieldActive)="toggleFieldActive($event)"
[(allEntities)]="activeFields"
></redaction-active-fields-listing>
</div>
</div>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button (click)="save()" [disabled]="changedParseConfig || baseConfigForm.invalid">
<button (click)="save()" [disabled]="changedParseConfig || baseConfigForm.invalid" color="primary" mat-flat-button>
{{ 'file-attributes-csv-import.save.label' | translate }}
</button>
<div class="all-caps-label cancel" (click)="dialogRef.close()">{{ '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 icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,14 +1,14 @@
import { Component, Inject, Injector, ViewChild } from '@angular/core';
import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { AppStateService } from '../../../../state/app-state.service';
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 { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
export interface Field {
@ -28,22 +28,20 @@ export interface Field {
styleUrls: ['./file-attributes-csv-import-dialog.component.scss']
})
export class FileAttributesCsvImportDialogComponent extends BaseListingComponent<Field> {
protected readonly _searchKey = 'csvColumn';
public csvFile: File;
public ruleSetId: string;
public parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] };
public hoveredColumn: string;
public activeFields: Field[] = [];
public baseConfigForm: FormGroup;
public isSearchOpen = false;
public previewExpanded = true;
public filteredKeyOptions: Observable<string[]>;
public keepPreview = false;
public columnSample = [];
public initialParseConfig: { delimiter?: string; encoding?: string } = {};
csvFile: File;
ruleSetId: string;
parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] };
hoveredColumn: string;
activeFields: Field[] = [];
baseConfigForm: FormGroup;
isSearchOpen = false;
previewExpanded = true;
filteredKeyOptions: Observable<string[]>;
keepPreview = false;
columnSample = [];
initialParseConfig: { delimiter?: string; encoding?: string } = {};
@ViewChild(CdkVirtualScrollViewport, { static: false }) cdkVirtualScrollViewport: CdkVirtualScrollViewport;
protected readonly _searchKey = 'csvColumn';
constructor(
private readonly _appStateService: AppStateService,
@ -67,16 +65,14 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
this.readFile();
}
private _autocompleteStringValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if ((this.parseResult?.meta?.fields || []).indexOf(control.value) !== -1) {
return null; /* valid option selected */
}
return { invalidAutocompleteString: { value: control.value } };
};
get changedParseConfig(): boolean {
return (
this.initialParseConfig.delimiter !== this.baseConfigForm.get('delimiter').value ||
this.initialParseConfig.encoding !== this.baseConfigForm.get('encoding').value
);
}
public readFile() {
readFile() {
const reader = new FileReader();
reader.addEventListener('load', async (event) => {
const parsedCsv = <any>event.target.result;
@ -97,7 +93,7 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
for (const entity of this.allEntities) {
const existing = this.data.existingConfiguration.fileAttributeConfigs.find((a) => a.csvColumnHeader === entity.csvColumn);
if (!!existing) {
if (existing) {
entity.id = existing.id;
entity.name = existing.label;
entity.temporaryName = existing.label;
@ -130,11 +126,11 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
reader.readAsText(this.csvFile, this.baseConfigForm.get('encoding').value);
}
public getSample(csvColumn: string) {
getSample(csvColumn: string) {
return this.parseResult?.data?.length ? this.parseResult?.data[0][csvColumn] : '';
}
public getEntries(csvColumn: string) {
getEntries(csvColumn: string) {
if (this.parseResult?.data) {
let count = 0;
for (const entry of this.parseResult.data) {
@ -148,11 +144,11 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
}
}
public isActive(field: Field): boolean {
isActive(field: Field): boolean {
return this.activeFields.indexOf(field) !== -1;
}
public toggleFieldActive(field: Field) {
toggleFieldActive(field: Field) {
if (!this.isActive(field)) {
this.activeFields = [...this.activeFields, field];
} else {
@ -161,28 +157,15 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
}
}
private _buildAttribute(csvColumn: string): Field {
const sample = this.getSample(csvColumn);
const isNumber = sample && !isNaN(sample);
return {
csvColumn,
name: csvColumn,
temporaryName: csvColumn,
type: isNumber ? FileAttributeConfig.TypeEnum.NUMBER : FileAttributeConfig.TypeEnum.TEXT,
readonly: false,
primaryAttribute: false
};
}
public activateAll() {
activateAll() {
this.activeFields = [...this.allEntities];
}
public deactivateAll() {
deactivateAll() {
this.activeFields = [];
}
public async save() {
async save() {
const newPrimary = !!this.activeFields.find((attr) => attr.primaryAttribute);
if (newPrimary) {
@ -195,16 +178,14 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
...this.data.existingConfiguration.fileAttributeConfigs.filter(
(a) => !this.allEntities.find((entity) => entity.csvColumn === a.csvColumnHeader)
),
...this.activeFields.map((field) => {
return {
id: field.id,
csvColumnHeader: field.csvColumn,
editable: !field.readonly,
label: field.name,
type: field.type,
primaryAttribute: field.primaryAttribute
};
})
...this.activeFields.map((field) => ({
id: field.id,
csvColumnHeader: field.csvColumn,
editable: !field.readonly,
label: field.name,
type: field.type,
primaryAttribute: field.primaryAttribute
}))
]
};
@ -226,7 +207,7 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
this.dialogRef.close(true);
}
public setHoveredColumn(column?: string) {
setHoveredColumn(column?: string) {
setTimeout(() => {
if (this.keepPreview && !column) {
return;
@ -241,10 +222,25 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
}, 0);
}
public get changedParseConfig(): boolean {
return (
this.initialParseConfig.delimiter !== this.baseConfigForm.get('delimiter').value ||
this.initialParseConfig.encoding !== this.baseConfigForm.get('encoding').value
);
private _autocompleteStringValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if ((this.parseResult?.meta?.fields || []).indexOf(control.value) !== -1) {
return null; /* valid option selected */
}
return { invalidAutocompleteString: { value: control.value } };
};
}
private _buildAttribute(csvColumn: string): Field {
const sample = this.getSample(csvColumn);
const isNumber = sample && !isNaN(sample);
return {
csvColumn,
name: csvColumn,
temporaryName: csvColumn,
type: isNumber ? FileAttributeConfig.TypeEnum.NUMBER : FileAttributeConfig.TypeEnum.TEXT,
readonly: false,
primaryAttribute: false
};
}
}

View File

@ -5,7 +5,7 @@
<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" type="text" placeholder="{{ 'smtp-auth-config.form.username-placeholder' | translate }}" />
<input formControlName="user" name="user" placeholder="{{ 'smtp-auth-config.form.username-placeholder' | translate }}" type="text" />
</div>
<div class="red-input-group required w-300">
@ -22,5 +22,5 @@
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -1,7 +1,7 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from '../../../../services/user.service';
import { UserService } from '@services/user.service';
import { SMTPConfigurationModel } from '@redaction/red-ui-http';
@Component({
@ -9,8 +9,8 @@ import { SMTPConfigurationModel } from '@redaction/red-ui-http';
templateUrl: './smtp-auth-dialog.component.html',
styleUrls: ['./smtp-auth-dialog.component.scss']
})
export class SmtpAuthDialogComponent implements OnInit {
public authForm: FormGroup;
export class SmtpAuthDialogComponent {
authForm: FormGroup;
constructor(
private readonly _formBuilder: FormBuilder,
@ -24,9 +24,7 @@ export class SmtpAuthDialogComponent implements OnInit {
});
}
ngOnInit(): void {}
public save() {
save() {
this.dialogRef.close(this.authForm.getRawValue());
}
}

View File

@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { TranslateService } from '@ngx-translate/core';
import { Moment } from 'moment';
import { applyIntervalConstraints } from '../../../../utils/date-inputs-utils';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
const PAGE_SIZE = 50;
@ -14,21 +14,21 @@ const PAGE_SIZE = 50;
styleUrls: ['./audit-screen.component.scss']
})
export class AuditScreenComponent {
public filterForm: FormGroup;
public viewReady = false;
public categories: string[] = [];
public userIds: Set<string>;
public logs: AuditResponse;
public currentPage = 1;
readonly ALL_CATEGORIES = 'audit-screen.all-categories';
readonly ALL_USERS = 'audit-screen.all-users';
public ALL_CATEGORIES = 'audit-screen.all-categories';
public ALL_USERS = 'audit-screen.all-users';
filterForm: FormGroup;
viewReady = false;
categories: string[] = [];
userIds: Set<string>;
logs: AuditResponse;
currentPage = 1;
private _previousFrom: Moment;
private _previousTo: Moment;
constructor(
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder,
private readonly _auditControllerService: AuditControllerService,
private readonly _translateService: TranslateService
@ -49,6 +49,17 @@ export class AuditScreenComponent {
this._fetchData();
}
get totalPages(): number {
if (!this.logs) {
return 0;
}
return Math.ceil(this.logs.totalHits / PAGE_SIZE);
}
pageChanged(page: number) {
this._fetchData(page);
}
private _updateDateFilters(value): boolean {
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
return true;
@ -66,7 +77,7 @@ export class AuditScreenComponent {
const userId = this.filterForm.get('userId').value;
const from = this.filterForm.get('from').value;
let to = this.filterForm.get('to').value;
if (!!to) {
if (to) {
to = to.clone().add(1, 'd');
}
const logsRequestBody: AuditSearchRequest = {
@ -93,15 +104,4 @@ export class AuditScreenComponent {
this.viewReady = true;
});
}
public get totalPages(): number {
if (!this.logs) {
return 0;
}
return Math.ceil(this.logs.totalHits / PAGE_SIZE);
}
public pageChanged(page: number) {
this._fetchData(page);
}
}

View File

@ -5,7 +5,7 @@
<div class="flex-1 actions">
<redaction-rule-set-actions></redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="below" icon="red:close"></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
</div>
</div>
@ -23,14 +23,14 @@
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name
label="default-colors-screen.table-col-names.key"
column="key"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="key"
label="default-colors-screen.table-col-names.key"
></redaction-table-col-name>
<redaction-table-col-name label="default-colors-screen.table-col-names.color" class="flex-center"></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 +38,13 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div class="table-item" *cdkVirtualFor="let color of allEntities | sortBy: sortingOption.order:sortingOption.column">
<div *cdkVirtualFor="let color of allEntities | sortBy: sortingOption.order:sortingOption.column" class="table-item">
<div>
<div class="table-item-title heading" [translate]="'default-colors-screen.types.' + color.key"></div>
<div [translate]="'default-colors-screen.types.' + color.key" class="table-item-title heading"></div>
</div>
<div class="color-wrapper">
<div class="color-square" [ngStyle]="{ 'background-color': color.value }"></div>
<div [ngStyle]="{ 'background-color': color.value }" class="color-square"></div>
</div>
<div class="actions-container">
@ -52,9 +52,9 @@
<redaction-circle-button
(action)="openEditColorDialog($event, color)"
*ngIf="permissionsService.isAdmin()"
icon="red:edit"
tooltip="default-colors-screen.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
</div>

View File

@ -1,10 +1,10 @@
import { Component, Injector } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
import { ActivatedRoute } from '@angular/router';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { BaseListingComponent } from '@shared/base/base-listing.component';
@Component({
selector: 'redaction-default-colors-screen',
@ -12,9 +12,8 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen
styleUrls: ['./default-colors-screen.component.scss']
})
export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: string; value: string }> {
viewReady = false;
protected readonly _sortKey = 'default-colors';
public viewReady = false;
private _colorsObj: Colors;
constructor(
@ -22,7 +21,7 @@ export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: st
private readonly _activatedRoute: ActivatedRoute,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _dialogService: AdminDialogService,
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
protected readonly _injector: Injector
) {
super(_injector);
@ -30,10 +29,17 @@ export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: st
this._loadColors();
}
public async loadRuleSetsData(): Promise<void> {
async loadRuleSetsData(): Promise<void> {
await this._appStateService.loadAllRuleSets();
}
openEditColorDialog($event: any, color: { key: string; value: string }) {
$event.stopPropagation();
this._dialogService.openEditColorsDialog(this._colorsObj, color.key, this._appStateService.activeRuleSetId, async () => {
this._loadColors();
});
}
private _loadColors() {
this._dictionaryControllerService
.getColors(this._appStateService.activeRuleSetId)
@ -47,11 +53,4 @@ export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: st
this.viewReady = true;
});
}
openEditColorDialog($event: any, color: { key: string; value: string }) {
$event.stopPropagation();
this._dialogService.openEditColorsDialog(this._colorsObj, color.key, this._appStateService.activeRuleSetId, async () => {
this._loadColors();
});
}
}

View File

@ -3,9 +3,9 @@
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="flex-1 actions">
<redaction-rule-set-actions> </redaction-rule-set-actions>
<redaction-rule-set-actions></redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="below" icon="red:close"></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
</div>
</div>
@ -32,9 +32,9 @@
<redaction-search-input [form]="searchForm" [placeholder]="'dictionary-listing.search'"></redaction-search-input>
<div class="actions">
<redaction-icon-button
(action)="openAddEditDictionaryDialog()"
*ngIf="permissionsService.isAdmin()"
icon="red:plus"
(action)="openAddEditDictionaryDialog()"
text="dictionary-listing.add-new"
type="primary"
></redaction-icon-button>
@ -42,36 +42,36 @@
</div>
</div>
<div class="table-header" redactionSyncWidth="table-item" [class.no-data]="!allEntities.length">
<div [class.no-data]="!allEntities.length" class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
label="dictionary-listing.table-col-names.type"
column="label"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="label"
label="dictionary-listing.table-col-names.type"
></redaction-table-col-name>
<redaction-table-col-name
label="dictionary-listing.table-col-names.order-of-importance"
column="rank"
class="flex-center"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
class="flex-center"
column="rank"
label="dictionary-listing.table-col-names.order-of-importance"
></redaction-table-col-name>
<redaction-table-col-name label="dictionary-listing.table-col-names.hint-redaction" class="flex-center"></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>
<redaction-empty-state
*ngIf="!allEntities.length"
icon="red:dictionary"
(action)="openAddEditDictionaryDialog()"
*ngIf="!allEntities.length"
[showButton]="permissionsService.isAdmin()"
icon="red:dictionary"
screen="dictionary-listing"
></redaction-empty-state>
@ -79,16 +79,16 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div
class="table-item pointer"
*cdkVirtualFor="let dict of displayedEntities | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[dict.type]"
class="table-item pointer"
>
<div class="selection-column" (click)="toggleEntitySelected($event, dict)">
<div (click)="toggleEntitySelected($event, dict)" class="selection-column">
<redaction-round-checkbox [active]="isEntitySelected(dict)"></redaction-round-checkbox>
</div>
<div>
<div class="color-square" [ngStyle]="{ 'background-color': dict.hexColor }"></div>
<div [ngStyle]="{ 'background-color': dict.hexColor }" class="color-square"></div>
<div class="dict-name">
<div class="table-item-title heading">
{{ dict.label }}
@ -119,18 +119,18 @@
<redaction-circle-button
(action)="openDeleteDictionaryDialog($event, dict)"
*ngIf="permissionsService.isAdmin()"
icon="red:trash"
tooltip="dictionary-listing.action.delete"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openAddEditDictionaryDialog($event, dict)"
*ngIf="permissionsService.isAdmin()"
icon="red:edit"
tooltip="dictionary-listing.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
</div>
@ -144,11 +144,11 @@
<redaction-simple-doughnut-chart
*ngIf="allEntities.length"
[config]="chartData"
[strokeWidth]="15"
[counterText]="'dictionary-listing.stats.charts.entries' | translate"
[radius]="82"
[strokeWidth]="15"
[subtitle]="'dictionary-listing.stats.charts.types'"
totalType="count"
[counterText]="'dictionary-listing.stats.charts.entries' | translate"
></redaction-simple-doughnut-chart>
</div>
</div>

View File

@ -1,13 +1,13 @@
import { Component, Injector, OnInit } from '@angular/core';
import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { defaultIfEmpty, tap } from 'rxjs/operators';
import { forkJoin } from 'rxjs';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { BaseListingComponent } from '@shared/base/base-listing.component';
@Component({
selector: 'redaction-dictionary-listing-screen',
@ -15,19 +15,18 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen
styleUrls: ['./dictionary-listing-screen.component.scss']
})
export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValue> implements OnInit {
viewReady = false;
chartData: DoughnutChartConfig[] = [];
protected readonly _searchKey = 'label';
protected readonly _selectionKey = 'type';
protected readonly _sortKey = 'dictionary-listing';
public viewReady = false;
public chartData: DoughnutChartConfig[] = [];
constructor(
private readonly _dialogService: AdminDialogService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
protected readonly _injector: Injector
) {
super(_injector);
@ -38,6 +37,23 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
this._loadDictionaryData();
}
openAddEditDictionaryDialog($event?: MouseEvent, dict?: TypeValue) {
$event?.stopPropagation();
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();
});
}
private _loadDictionaryData() {
const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeRuleSetId];
this.allEntities = Object.keys(appStateDictionaryData)
@ -71,21 +87,4 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
this.chartData.sort((a, b) => (a.label < b.label ? -1 : 1));
this.viewReady = true;
}
openAddEditDictionaryDialog($event?: MouseEvent, dict?: TypeValue) {
$event?.stopPropagation();
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();
});
}
}

View File

@ -6,46 +6,46 @@
<redaction-circle-button
(action)="openDeleteDictionaryDialog($event)"
*ngIf="permissionsService.isAdmin()"
icon="red:trash"
tooltip="dictionary-overview.action.delete"
tooltipPosition="below"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openEditDictionaryDialog($event)"
*ngIf="permissionsService.isAdmin()"
icon="red:edit"
tooltip="dictionary-overview.action.edit"
tooltipPosition="below"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
<redaction-circle-button
(action)="download()"
icon="red:download"
tooltip="dictionary-overview.action.download"
tooltipPosition="below"
icon="red:download"
></redaction-circle-button>
<redaction-circle-button
*ngIf="permissionsService.isAdmin()"
(action)="fileInput.click()"
*ngIf="permissionsService.isAdmin()"
icon="red:upload"
tooltip="dictionary-overview.action.upload"
tooltipPosition="below"
icon="red:upload"
></redaction-circle-button>
<input #fileInput (change)="upload($event)" hidden class="file-upload-input" type="file" accept="text/plain" />
<input #fileInput (change)="upload($event)" accept="text/plain" class="file-upload-input" hidden type="file" />
<redaction-circle-button
class="ml-6"
[routerLink]="['..']"
class="ml-6"
icon="red:close"
tooltip="common.close"
tooltipPosition="below"
icon="red:close"
></redaction-circle-button>
</div>
</div>
@ -59,43 +59,43 @@
<div class="actions-bar">
<div class="red-input-group w-450 mr-32">
<input
[class.with-matches]="searchText.length > 0"
type="text"
[(ngModel)]="searchText"
(keyup)="searchChanged(searchText)"
#inputElement
(keyup)="searchChanged(searchText)"
[(ngModel)]="searchText"
[class.with-matches]="searchText.length > 0"
placeholder="{{ 'dictionary-overview.search' | translate }}"
type="text"
/>
<div class="input-icons">
<div class="no-input" *ngIf="searchText.length === 0">
<div *ngIf="searchText.length === 0" class="no-input">
<mat-icon svgIcon="red:search"></mat-icon>
</div>
<div class="with-input" *ngIf="searchText.length > 0">
<div *ngIf="searchText.length > 0" class="with-input">
<div class="search-match-text">
{{ currentMatch + '/' + searchPositions.length }}
</div>
<mat-icon svgIcon="red:arrow-up" class="pointer" (click)="previousSearchMatch()"></mat-icon>
<mat-icon svgIcon="red:arrow-down" class="pointer" (click)="nextSearchMatch()"></mat-icon>
<mat-icon svgIcon="red:close" (click)="searchChanged(''); inputElement.focus()" class="pointer"></mat-icon>
<mat-icon (click)="previousSearchMatch()" class="pointer" svgIcon="red:arrow-up"></mat-icon>
<mat-icon (click)="nextSearchMatch()" class="pointer" svgIcon="red:arrow-down"></mat-icon>
<mat-icon (click)="searchChanged(''); inputElement.focus()" class="pointer" svgIcon="red:close"></mat-icon>
</div>
</div>
</div>
<form class="compare-form" [formGroup]="compareForm">
<form [formGroup]="compareForm" class="compare-form">
<div class="red-input-group mr-16">
<mat-checkbox formControlName="active" color="primary"> {{ 'dictionary-overview.compare.compare' | translate }} </mat-checkbox>
<mat-checkbox color="primary" formControlName="active"> {{ 'dictionary-overview.compare.compare' | translate }} </mat-checkbox>
</div>
<div class="red-input-group w-200 mr-8">
<mat-select formControlName="ruleSet">
<mat-option *ngFor="let ruleSet of ruleSets" [value]="ruleSet">
{{ ruleSet === SELECT_RULESET ? (ruleSet.name | translate) : ruleSet.name }}
{{ ruleSet === selectRuleSet ? (ruleSet.name | translate) : ruleSet.name }}
</mat-option>
</mat-select>
</div>
<div class="red-input-group w-200">
<mat-select formControlName="dictionary">
<mat-option *ngFor="let dictionary of dictionaries" [value]="dictionary">
{{ dictionary === SELECT_DICTIONARY ? (dictionary.label | translate) : dictionary.label }}
{{ dictionary === selectDictionary ? (dictionary.label | translate) : dictionary.label }}
</mat-option>
</mat-select>
</div>
@ -105,40 +105,40 @@
<div class="editor-container">
<ace-editor
#editorComponent
[mode]="'text'"
[theme]="'eclipse'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
(textChanged)="textChanged($event)"
[autoUpdateContent]="true"
[mode]="'text'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
<div class="no-dictionary-selected" *ngIf="compareForm.get('active').value && compareForm.get('dictionary').value === SELECT_DICTIONARY">
<div *ngIf="compareForm.get('active').value && compareForm.get('dictionary').value === selectDictionary" class="no-dictionary-selected">
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span class="heading-l" translate="dictionary-overview.select-dictionary"></span>
</div>
<ace-editor
#compareEditorComponent
*ngIf="compareForm.get('active').value && compareForm.get('dictionary').value !== SELECT_DICTIONARY"
*ngIf="compareForm.get('active').value && compareForm.get('dictionary').value !== selectDictionary"
[mode]="'text'"
[theme]="'eclipse'"
[options]="aceOptions"
[readOnly]="true"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
</div>
<div class="changes-box" *ngIf="hasChanges && permissionsService.isAdmin()" [class.offset]="compareForm.get('active').value">
<redaction-icon-button icon="red:check" (action)="saveEntries()" text="dictionary-overview.save-changes" type="primary"></redaction-icon-button>
<div class="all-caps-label cancel" (click)="revert()" translate="dictionary-overview.revert-changes"></div>
<div *ngIf="hasChanges && permissionsService.isAdmin()" [class.offset]="compareForm.get('active').value" class="changes-box">
<redaction-icon-button (action)="saveEntries()" icon="red:check" text="dictionary-overview.save-changes" type="primary"></redaction-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="dictionary-overview.revert-changes"></div>
</div>
</div>
<div class="right-container">
<div class="dictionary-header">
<div class="color-box" [style.backgroundColor]="dictionary.hexColor"></div>
<div [style.backgroundColor]="dictionary.hexColor" class="color-box"></div>
<div class="heading-xl">
{{ dictionary.type | humanize }}
</div>
@ -166,7 +166,7 @@
</div>
</div>
<div class="pb-32 mt-20" *ngIf="!!dictionary.description">
<div *ngIf="!!dictionary.description" class="pb-32 mt-20">
<div class="heading" translate="dictionary-overview.dictionary-details.description"></div>
<div class="mt-8">{{ dictionary.description }}</div>
</div>

View File

@ -102,6 +102,7 @@
flex: 1;
justify-content: flex-end;
align-items: center;
.red-input-group {
margin-top: 0;
}

View File

@ -1,19 +1,20 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { DictionaryControllerService, RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { ActivatedRoute, Router } from '@angular/router';
import { AceEditorComponent } from 'ng2-ace-editor';
import { debounce } from '../../../../utils/debounce';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { debounce } from '@utils/debounce';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '../../../../guards/can-deactivate.guard';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog.service';
declare var ace;
declare let ace;
const MIN_WORD_LENGTH = 2;
@Component({
selector: 'redaction-dictionary-overview-screen',
@ -21,8 +22,6 @@ declare var ace;
styleUrls: ['./dictionary-overview-screen.component.scss']
})
export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit {
static readonly MIN_WORD_LENGTH: number = 2;
activeEditMarkers: any[] = [];
activeSearchMarkers: any[] = [];
searchPositions: any[] = [];
@ -35,18 +34,18 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
searchText = '';
processing = true;
public SELECT_RULESET = { name: 'dictionary-overview.compare.select-ruleset' };
public SELECT_DICTIONARY = { label: 'dictionary-overview.compare.select-dictionary' };
public ruleSets: RuleSetModel[];
public dictionaries: TypeValue[] = [this.SELECT_DICTIONARY];
public compareForm: FormGroup;
selectRuleSet = { name: 'dictionary-overview.compare.select-ruleset' };
selectDictionary = { label: 'dictionary-overview.compare.select-dictionary' };
ruleSets: RuleSetModel[];
dictionaries: TypeValue[] = [this.selectDictionary];
compareForm: FormGroup;
@ViewChild('editorComponent', { static: true }) private _editorComponent: AceEditorComponent;
@ViewChild('compareEditorComponent') private _compareEditorComponent: AceEditorComponent;
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor(
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
private readonly _notificationService: NotificationService,
protected readonly _translateService: TranslateService,
private readonly _dictionaryControllerService: DictionaryControllerService,
@ -61,17 +60,17 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
this.compareForm = this._formBuilder.group({
active: [false],
ruleSet: [{ value: this.SELECT_RULESET, disabled: true }],
dictionary: [{ value: this.SELECT_DICTIONARY, disabled: true }]
ruleSet: [{ value: this.selectRuleSet, disabled: true }],
dictionary: [{ value: this.selectDictionary, disabled: true }]
});
this.compareForm.valueChanges.subscribe((value) => {
this._setFieldStatus('ruleSet', value.active);
this._setFieldStatus('dictionary', value.active && this.compareForm.get('ruleSet').value !== this.SELECT_RULESET);
this._setFieldStatus('dictionary', value.active && this.compareForm.get('ruleSet').value !== this.selectRuleSet);
this._loadDictionaries();
});
this.ruleSets = [this.SELECT_RULESET, ...this._appStateService.ruleSets];
this.ruleSets = [this.selectRuleSet, ...this._appStateService.ruleSets];
this._initializeEditor();
@ -84,11 +83,11 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
});
}
public get dictionary(): TypeValue {
get dictionary(): TypeValue {
return this._appStateService.activeDictionary;
}
public get hasChanges() {
get hasChanges() {
return (
this.currentDictionaryEntries.length &&
(this.activeEditMarkers.length > 0 ||
@ -112,14 +111,14 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
});
}
public openEditDictionaryDialog($event: any) {
openEditDictionaryDialog($event: any) {
$event.stopPropagation();
this._dialogService.openAddEditDictionaryDialog(this.dictionary, this.dictionary.ruleSetId, async () => {
await this._appStateService.loadDictionaryData();
});
}
public openDeleteDictionaryDialog($event: any) {
openDeleteDictionaryDialog($event: any) {
this._dialogService.openDeleteDictionaryDialog($event, this.dictionary, this.dictionary.ruleSetId, async () => {
await this._appStateService.loadDictionaryData();
this._router.navigate(['..']);
@ -127,7 +126,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
@debounce()
public searchChanged(text: string) {
searchChanged(text: string) {
this.searchText = text.toLowerCase();
this._applySearchMarkers();
this.currentMatch = 0;
@ -135,7 +134,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
@debounce(500)
public textChanged($event: any) {
textChanged($event: any) {
this._applySearchMarkers();
this.currentDictionaryEntries = $event.split('\n');
this.changedLines = [];
@ -151,28 +150,28 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
}
const Range = ace.require('ace/range').Range;
const range = ace.require('ace/range').Range;
for (const i of this.changedLines) {
const entry = this.currentDictionaryEntries[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'));
}
if (entry?.trim().length > 0 && entry.trim().length < DictionaryOverviewScreenComponent.MIN_WORD_LENGTH) {
if (entry?.trim().length > 0 && entry.trim().length < MIN_WORD_LENGTH) {
// show lines that are too short
this.activeEditMarkers.push(this._editorComponent.getEditor().getSession().addMarker(new Range(i, 0, i, 1), 'too-short-marker', 'fullLine'));
this.activeEditMarkers.push(this._editorComponent.getEditor().getSession().addMarker(new range(i, 0, i, 1), 'too-short-marker', 'fullLine'));
}
}
}
public async saveEntries() {
async saveEntries() {
let entriesToAdd = [];
this.currentDictionaryEntries.forEach((currentEntry) => {
entriesToAdd.push(currentEntry);
});
// remove empty lines
entriesToAdd = entriesToAdd.filter((e) => e && e.trim().length > 0).map((e) => e.trim());
const invalidRowsExist = entriesToAdd.filter((e) => e.length < DictionaryOverviewScreenComponent.MIN_WORD_LENGTH);
const invalidRowsExist = entriesToAdd.filter((e) => e.length < MIN_WORD_LENGTH);
if (invalidRowsExist.length === 0) {
// can add at least 1 - block UI
this.processing = true;
@ -210,13 +209,13 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
}
public revert() {
revert() {
DictionaryOverviewScreenComponent._setEditorValue(this._editorComponent, this.initialDictionaryEntries);
this.searchChanged('');
this.processing = false;
}
public nextSearchMatch() {
nextSearchMatch() {
// length = 3
if (this.searchPositions.length > 0) {
this.currentMatch = this.currentMatch < this.searchPositions.length ? this.currentMatch + 1 : 1;
@ -224,14 +223,14 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
}
public previousSearchMatch() {
previousSearchMatch() {
if (this.searchPositions.length > 0) {
this.currentMatch = this.currentMatch > 1 ? this.currentMatch - 1 : this.searchPositions.length;
this._gotoLine();
}
}
public download(): void {
download(): void {
const content = this._editorComponent.getEditor().getValue();
const blob = new Blob([content], {
type: 'text/plain;charset=utf-8'
@ -239,7 +238,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
saveAs(blob, `${this.dictionary.label}.txt`);
}
public upload($event): void {
upload($event): void {
const file = $event.target.files[0];
const fileReader = new FileReader();
@ -253,18 +252,18 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
private _syncActiveLines() {
if (!!this._compareEditorComponent) {
if (this._compareEditorComponent) {
this._compareEditorComponent.getEditor().gotoLine(this._activeRow);
}
}
private _onRuleSetChanged() {
this._loadDictionaries();
this.compareForm.patchValue({ dictionary: this.SELECT_DICTIONARY });
this.compareForm.patchValue({ dictionary: this.selectDictionary });
}
private _onDictionaryChanged(dictionary: TypeValue) {
if (dictionary !== this.SELECT_DICTIONARY) {
if (dictionary !== this.selectDictionary) {
this._dictionaryControllerService.getDictionaryForType(dictionary.type, dictionary.ruleSetId).subscribe(
(data) => {
this.compareDictionaryEntries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
@ -285,12 +284,12 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
private _loadDictionaries() {
const ruleSetId = this.compareForm.get('ruleSet').value.ruleSetId;
if (!ruleSetId) {
this.dictionaries = [this.SELECT_DICTIONARY];
this.dictionaries = [this.selectDictionary];
return;
}
const appStateDictionaryData = this._appStateService.dictionaryData[ruleSetId];
this.dictionaries = [
this.SELECT_DICTIONARY,
this.selectDictionary,
...Object.keys(appStateDictionaryData)
.map((key) => appStateDictionaryData[key])
.filter((d) => !d.virtual || d.type === 'false_positive')
@ -317,13 +316,13 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
});
this.activeSearchMarkers = [];
const Range = ace.require('ace/range').Range;
const range = ace.require('ace/range').Range;
for (const position of this.searchPositions) {
this.activeSearchMarkers.push(
this._editorComponent
.getEditor()
.getSession()
.addMarker(new Range(position.row, position.column, position.row, position.column + position.length), 'search-marker', 'text')
.addMarker(new range(position.row, position.column, position.row, position.column + position.length), 'search-marker', 'text')
);
}
}

View File

@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { DigitalSignature, DigitalSignatureControllerService } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { lastIndexOfEnd } from '../../../../utils/functions';
import { PermissionsService } from '@services/permissions.service';
import { lastIndexOfEnd } from '@utils/functions';
@Component({
selector: 'redaction-digital-signature-screen',
@ -12,22 +12,26 @@ import { lastIndexOfEnd } from '../../../../utils/functions';
styleUrls: ['./digital-signature-screen.component.scss']
})
export class DigitalSignatureScreenComponent {
public digitalSignature: DigitalSignature;
public digitalSignatureForm: FormGroup;
digitalSignature: DigitalSignature;
digitalSignatureForm: FormGroup;
public viewReady = false;
public digitalSignatureExists = false;
viewReady = false;
digitalSignatureExists = false;
constructor(
private readonly _digitalSignatureControllerService: DigitalSignatureControllerService,
private readonly _notificationService: NotificationService,
private readonly _formBuilder: FormBuilder,
private readonly _translateService: TranslateService,
public readonly permissionsService: PermissionsService
readonly permissionsService: PermissionsService
) {
this.loadDigitalSignatureAndInitializeForm();
}
get hasDigitalSignatureSet() {
return this.digitalSignatureExists || !!this.digitalSignatureForm.get('base64EncodedPrivateKey').value;
}
saveDigitalSignature() {
const digitalSignature = {
...this.digitalSignatureForm.getRawValue()
@ -119,6 +123,8 @@ export class DigitalSignatureScreenComponent {
});
}
formChanged() {}
private _initForm() {
this.digitalSignatureForm = this._formBuilder.group({
certificateName: [this.digitalSignature.certificateName, Validators.required],
@ -129,10 +135,4 @@ export class DigitalSignatureScreenComponent {
base64EncodedPrivateKey: this.digitalSignatureExists ? null : [this.digitalSignature.base64EncodedPrivateKey, Validators.required]
});
}
get hasDigitalSignatureSet() {
return this.digitalSignatureExists || !!this.digitalSignatureForm.get('base64EncodedPrivateKey').value;
}
formChanged() {}
}

View File

@ -3,7 +3,7 @@
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions flex-1">
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="below" icon="red:close"></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
</div>
</div>
@ -27,11 +27,11 @@
</span>
<redaction-circle-button
(click)="openConfirmDeleteAttributeDialog($event)"
*ngIf="areSomeEntitiesSelected"
icon="red:trash"
tooltip="file-attributes-listing.bulk-actions.delete"
type="dark-bg"
icon="red:trash"
(click)="openConfirmDeleteAttributeDialog($event)"
>
</redaction-circle-button>
@ -39,19 +39,19 @@
<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'])" class="csv-input" type="file" accept=".csv" />
<input #fileInput (change)="importCSV($event.target['files'])" accept=".csv" class="csv-input" type="file" />
<redaction-circle-button
(action)="fileInput.click()"
icon="red:upload"
tooltip="file-attributes-listing.upload-csv"
tooltipPosition="above"
icon="red:upload"
type="dark-bg"
></redaction-circle-button>
<redaction-icon-button
icon="red:plus"
(action)="openAddEditAttributeDialog($event)"
icon="red:plus"
text="file-attributes-listing.add-new"
type="primary"
></redaction-icon-button>
@ -62,37 +62,37 @@
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
label="file-attributes-listing.table-col-names.name"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="label"
label="file-attributes-listing.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name
label="file-attributes-listing.table-col-names.type"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="type"
label="file-attributes-listing.table-col-names.type"
></redaction-table-col-name>
<redaction-table-col-name
label="file-attributes-listing.table-col-names.read-only"
class="flex-center"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
class="flex-center"
column="editable"
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
class="flex-center"
label="file-attributes-listing.table-col-names.primary"
rightIcon="red:status-info"
rightIconTooltip="file-attributes-listing.table-col-names.primary-info-tooltip"
class="flex-center"
></redaction-table-col-name>
<div></div>
@ -100,7 +100,7 @@
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!allEntities.length" screen="file-attributes-listing" icon="red:attribute"></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,8 +110,8 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div class="table-item" *cdkVirtualFor="let attribute of displayedEntities | sortBy: sortingOption.order:sortingOption.column">
<div class="selection-column" (click)="toggleEntitySelected($event, attribute)">
<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>
</div>
@ -119,36 +119,36 @@
<span>{{ attribute.label }}</span>
</div>
<div class="small-label" [translate]="'file-attribute-types.' + attribute.type"></div>
<div [translate]="'file-attribute-types.' + attribute.type" class="small-label"></div>
<div class="center read-only">
<mat-icon
svgIcon="red:read-only"
*ngIf="!attribute.editable"
[matTooltip]="'file-attributes-listing.read-only' | translate"
matTooltipPosition="above"
svgIcon="red:read-only"
></mat-icon>
</div>
<div class="small-label">
{{ attribute.csvColumnHeader }}
</div>
<div class="center">
<redaction-round-checkbox *ngIf="attribute.primaryAttribute" [size]="18" [active]="true"></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">
<redaction-circle-button
(action)="openAddEditAttributeDialog($event, attribute)"
icon="red:edit"
tooltip="file-attributes-listing.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
icon="red:trash"
tooltip="file-attributes-listing.action.delete"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
</div>

View File

@ -1,10 +1,10 @@
import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { BaseListingComponent } from '@shared/base/base-listing.component';
@Component({
selector: 'redaction-file-attributes-listing-screen',
@ -12,19 +12,17 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen
styleUrls: ['./file-attributes-listing-screen.component.scss']
})
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit {
viewReady = false;
loading = false;
protected readonly _searchKey = 'label';
protected readonly _selectionKey = 'id';
protected readonly _sortKey = 'file-attributes-listing';
public viewReady = false;
public loading = false;
private _existingConfiguration: FileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor(
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
private readonly _fileAttributesService: FileAttributesControllerService,
private readonly _appStateService: AppStateService,
private readonly _activatedRoute: ActivatedRoute,
@ -39,6 +37,37 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
await this._loadData();
}
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();
});
}
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();
}
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();
});
}
private async _loadData() {
try {
const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise();
@ -51,35 +80,4 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
this.loading = false;
}
}
public 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();
});
}
public 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();
}
await this._loadData();
});
}
public 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();
});
}
}

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui-http';
import { AppConfigService } from '../../../app-config/app-config.service';
import { AppConfigService } from '@app-config/app-config.service';
import * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core';
@ -11,9 +11,30 @@ import { TranslateService } from '@ngx-translate/core';
styleUrls: ['./license-information-screen.component.scss']
})
export class LicenseInformationScreenComponent implements OnInit {
currentInfo: LicenseReport = {};
totalInfo: LicenseReport = {};
unlicensedInfo: LicenseReport = {};
totalLicensedNumberOfPages = 0;
analysisPercentageOfLicense = 100;
viewReady = false;
barChart: any[] = [];
lineChartSeries: any[] = [];
yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month');
yAxisLabelRight = this._translateService.instant('license-info-screen.chart.total-pages');
lineChartScheme = {
selectable: true,
group: 'Ordinal',
domain: ['#dd4d50', '#5ce594', '#0389ec']
};
comboBarScheme = {
selectable: true,
group: 'Ordinal',
domain: ['#0389ec']
};
constructor(
public readonly permissionsService: PermissionsService,
public readonly appConfigService: AppConfigService,
readonly permissionsService: PermissionsService,
readonly appConfigService: AppConfigService,
private readonly _licenseReportController: LicenseReportControllerService,
private readonly _translateService: TranslateService
) {}
@ -21,32 +42,8 @@ export class LicenseInformationScreenComponent implements OnInit {
get currentYear(): number {
return new Date().getFullYear();
}
public currentInfo: LicenseReport = {};
public totalInfo: LicenseReport = {};
public unlicensedInfo: LicenseReport = {};
public totalLicensedNumberOfPages = 0;
public analysisPercentageOfLicense = 100;
public viewReady = false;
barChart: any[] = [];
lineChartSeries: any[] = [];
yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month');
yAxisLabelRight = this._translateService.instant('license-info-screen.chart.total-pages');
lineChartScheme = {
selectable: true,
group: 'Ordinal',
domain: ['#dd4d50', '#5ce594', '#0389ec']
};
comboBarScheme = {
selectable: true,
group: 'Ordinal',
domain: ['#0389ec']
};
public async ngOnInit() {
async ngOnInit() {
this.totalLicensedNumberOfPages = this.appConfigService.getConfig('LICENSE_PAGE_COUNT', 0);
const startDate = moment(this.appConfigService.getConfig('LICENSE_START'), 'DD-MM-YYYY');
const endDate = moment(this.appConfigService.getConfig('LICENSE_END'), 'DD-MM-YYYY');
@ -74,6 +71,16 @@ export class LicenseInformationScreenComponent implements OnInit {
});
}
sendMail(): void {
const licenseCustomer = this.appConfigService.getConfig('LICENSE_CUSTOMER');
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 })
].join('%0D%0A');
window.location.href = `mailto:${this.appConfigService.getConfig('LICENSE_EMAIL')}?subject=${subject}&body=${body}`;
}
private async _setMonthlyStats(startDate: moment.Moment, endDate: moment.Moment) {
const [startMonth, startYear] = [startDate.month(), startDate.year()];
const [endMonth, endYear] = [endDate.month(), endDate.year()];
@ -144,14 +151,4 @@ export class LicenseInformationScreenComponent implements OnInit {
}
];
}
sendMail(): void {
const licenseCustomer = this.appConfigService.getConfig('LICENSE_CUSTOMER');
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 })
].join('%0D%0A');
window.location.href = `mailto:${this.appConfigService.getConfig('LICENSE_EMAIL')}?subject=${subject}&body=${body}`;
}
}

View File

@ -1,9 +1,9 @@
import { Component, Injector, OnInit } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { UserPreferenceService } from '../../../../services/user-preference.service';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { RuleSetModel } from '@redaction/red-ui-http';
@Component({
@ -19,8 +19,8 @@ export class RuleSetsListingScreenComponent extends BaseListingComponent<RuleSet
constructor(
private readonly _dialogService: AdminDialogService,
private readonly _appStateService: AppStateService,
public readonly permissionsService: PermissionsService,
public readonly userPreferenceService: UserPreferenceService,
readonly permissionsService: PermissionsService,
readonly userPreferenceService: UserPreferenceService,
protected readonly _injector: Injector
) {
super(_injector);
@ -30,13 +30,21 @@ export class RuleSetsListingScreenComponent extends BaseListingComponent<RuleSet
this.loadRuleSetsData();
}
public loadRuleSetsData() {
loadRuleSetsData() {
this._appStateService.reset();
this.allEntities = this._appStateService.ruleSets;
this._executeSearchImmediately();
this._loadRuleSetStats();
}
openAddRuleSetDialog() {
this._dialogService.openAddEditRuleSetDialog(null, async (newRuleSet) => {
if (newRuleSet) {
this.loadRuleSetsData();
}
});
}
private _loadRuleSetStats() {
this.allEntities.forEach((rs) => {
const dictionaries = this._appStateService.dictionaryData[rs.ruleSetId];
@ -50,12 +58,4 @@ export class RuleSetsListingScreenComponent extends BaseListingComponent<RuleSet
}
});
}
public openAddRuleSetDialog() {
this._dialogService.openAddEditRuleSetDialog(null, async (newRuleSet) => {
if (newRuleSet) {
this.loadRuleSetsData();
}
});
}
}

View File

@ -5,7 +5,7 @@
<div class="flex-1 actions">
<redaction-rule-set-actions></redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="below" icon="red:close"></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
</div>
</div>
@ -17,20 +17,20 @@
<div class="editor-container">
<ace-editor
#editorComponent
[mode]="'java'"
[theme]="'eclipse'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
(textChanged)="textChanged($event)"
[autoUpdateContent]="true"
[mode]="'java'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
[text]="rules"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
</div>
<div class="changes-box" *ngIf="hasChanges && permissionsService.isAdmin()">
<redaction-icon-button icon="red:check" (action)="save()" text="rules-screen.save-changes" type="primary"></redaction-icon-button>
<div (click)="revert()" translate="rules-screen.revert-changes" class="all-caps-label cancel"></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>
</div>
</div>
</section>

View File

@ -1,15 +1,15 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { AceEditorComponent } from 'ng2-ace-editor';
import { RulesControllerService } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '../../../../guards/can-deactivate.guard';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { ActivatedRoute } from '@angular/router';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
declare var ace;
declare let ace;
@Component({
selector: 'redaction-rules-screen',
@ -17,14 +17,14 @@ declare var ace;
styleUrls: ['./rules-screen.component.scss']
})
export class RulesScreenComponent extends ComponentHasChanges {
public aceOptions = { showPrintMargin: false };
public rules: string;
public processing = true;
aceOptions = { showPrintMargin: false };
rules: string;
processing = true;
public initialLines: string[] = [];
public currentLines: string[] = [];
public changedLines: number[] = [];
public activeEditMarkers: any[] = [];
initialLines: string[] = [];
currentLines: string[] = [];
changedLines: number[] = [];
activeEditMarkers: any[] = [];
@ViewChild('editorComponent', { static: true })
editorComponent: AceEditorComponent;
@ -33,7 +33,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
private _fileInput: ElementRef;
constructor(
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
private readonly _rulesControllerService: RulesControllerService,
private readonly _appStateService: AppStateService,
private readonly _notificationService: NotificationService,
@ -45,19 +45,11 @@ export class RulesScreenComponent extends ComponentHasChanges {
this._initialize();
}
private _initialize() {
this._rulesControllerService.downloadRules(this._appStateService.activeRuleSetId).subscribe(
(rules) => {
this.rules = rules.rules;
this.revert();
},
() => {
this.processing = false;
}
);
get hasChanges(): boolean {
return this.activeEditMarkers.length > 0;
}
public textChanged($event: any) {
textChanged($event: any) {
this.currentLines = $event.split('\n');
this.changedLines = [];
this.activeEditMarkers.forEach((am) => {
@ -72,21 +64,17 @@ export class RulesScreenComponent extends ComponentHasChanges {
}
}
const Range = ace.require('ace/range').Range;
const range = ace.require('ace/range').Range;
for (const i of this.changedLines) {
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'));
}
}
}
public get hasChanges(): boolean {
return this.activeEditMarkers.length > 0;
}
public async save(): Promise<void> {
async save(): Promise<void> {
this.processing = true;
this._rulesControllerService
.uploadRules({
@ -109,14 +97,14 @@ export class RulesScreenComponent extends ComponentHasChanges {
);
}
public revert(): void {
revert(): void {
this.initialLines = this.rules.split('\n');
this.editorComponent.getEditor().setValue(this.rules);
this.editorComponent.getEditor().clearSelection();
this.processing = false;
}
public download(): void {
download(): void {
const content = this.editorComponent.getEditor().getValue();
const blob = new Blob([content], {
type: 'text/plain;charset=utf-8'
@ -124,7 +112,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
saveAs(blob, 'rules.txt');
}
public upload($event): void {
upload($event): void {
const file = $event.target.files[0];
const fileReader = new FileReader();
@ -136,4 +124,16 @@ export class RulesScreenComponent extends ComponentHasChanges {
fileReader.readAsText(file);
}
}
private _initialize() {
this._rulesControllerService.downloadRules(this._appStateService.activeRuleSetId).subscribe(
(rules) => {
this.rules = rules.rules;
this.revert();
},
() => {
this.processing = false;
}
);
}
}

View File

@ -1,9 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { SmtpConfigurationControllerService, SMTPConfigurationModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
@Component({
@ -12,13 +12,13 @@ import { TranslateService } from '@ngx-translate/core';
styleUrls: ['./smtp-config-screen.component.scss']
})
export class SmtpConfigScreenComponent implements OnInit {
public viewReady = false;
public configForm: FormGroup;
viewReady = false;
configForm: FormGroup;
private _initialValue: SMTPConfigurationModel;
constructor(
public readonly permissionsService: PermissionsService,
readonly permissionsService: PermissionsService,
private readonly _smtpConfigService: SmtpConfigurationControllerService,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
@ -46,21 +46,8 @@ export class SmtpConfigScreenComponent implements OnInit {
}
});
}
async ngOnInit() {
await this._loadData();
}
private async _loadData() {
try {
this._initialValue = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise();
this.configForm.patchValue(this._initialValue, { emitEvent: false });
} catch (e) {
} finally {
this.viewReady = true;
}
}
public get changed(): boolean {
get changed(): boolean {
if (!this._initialValue) return true;
for (const key of Object.keys(this.configForm.getRawValue())) {
@ -72,14 +59,18 @@ export class SmtpConfigScreenComponent implements OnInit {
return false;
}
public async save() {
async ngOnInit() {
await this._loadData();
}
async save() {
this.viewReady = false;
await this._smtpConfigService.updateSMTPConfiguration(this.configForm.getRawValue()).toPromise();
this._initialValue = this.configForm.getRawValue();
this.viewReady = true;
}
public openAuthConfigDialog(skipDisableOnCancel?: boolean) {
openAuthConfigDialog(skipDisableOnCancel?: boolean) {
this._dialogService.openSMTPAuthConfigDialog(this.configForm.getRawValue(), (authConfig) => {
if (authConfig) {
this.configForm.patchValue(authConfig);
@ -89,10 +80,10 @@ export class SmtpConfigScreenComponent implements OnInit {
});
}
public async testConnection() {
async testConnection() {
this.viewReady = false;
try {
const res = 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,
@ -104,4 +95,14 @@ export class SmtpConfigScreenComponent implements OnInit {
this.viewReady = true;
}
}
private async _loadData() {
try {
this._initialValue = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise();
this.configForm.patchValue(this._initialValue, { emitEvent: false });
} catch (e) {
} finally {
this.viewReady = true;
}
}
}

View File

@ -1,12 +1,12 @@
import { Component, Injector, OnInit } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { UserService } from '../../../../services/user.service';
import { PermissionsService } from '@services/permissions.service';
import { UserService } from '@services/user.service';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { TranslateChartService } from '../../../../services/translate-chart.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { TranslateChartService } from '@services/translate-chart.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
@Component({
selector: 'redaction-user-listing-screen',
@ -14,16 +14,15 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen
styleUrls: ['./user-listing-screen.component.scss']
})
export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit {
viewReady = false;
loading = false;
collapsedDetails = false;
chartData: DoughnutChartConfig[] = [];
protected readonly _selectionKey = 'userId';
public viewReady = false;
public loading = false;
public collapsedDetails = false;
public chartData: DoughnutChartConfig[] = [];
constructor(
public readonly permissionsService: PermissionsService,
public readonly userService: UserService,
readonly permissionsService: PermissionsService,
readonly userService: UserService,
private readonly _translateService: TranslateService,
private readonly _adminDialogService: AdminDialogService,
private readonly _userControllerService: UserControllerService,
@ -33,15 +32,15 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
super(_injector);
}
public async ngOnInit() {
get canDeleteSelected(): boolean {
return this.selectedEntitiesIds.indexOf(this.userService.userId) === -1;
}
async ngOnInit() {
await this._loadData();
}
protected _searchField(user: any): string {
return this.userService.getName(user);
}
public openAddEditUserDialog($event: MouseEvent, user?: User) {
openAddEditUserDialog($event: MouseEvent, user?: User) {
$event.stopPropagation();
this._adminDialogService.openAddEditUserDialog(user, async (result) => {
if (result === 'DELETE') {
@ -58,7 +57,7 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
});
}
public openDeleteUserDialog(users: User[], $event?: MouseEvent) {
openDeleteUserDialog(users: User[], $event?: MouseEvent) {
$event?.stopPropagation();
this._adminDialogService.openConfirmDeleteUsersDialog(users, async () => {
this.loading = true;
@ -67,6 +66,29 @@ 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');
}
async toggleActive(user: User) {
this.loading = true;
user.roles = this.userService.isActive(user) ? [] : ['RED_USER'];
await this._userControllerService.addRoleToUsers(user.roles, user.userId).toPromise();
await this._loadData();
}
toggleCollapsedDetails() {
this.collapsedDetails = !this.collapsedDetails;
}
async bulkDelete() {
this.openDeleteUserDialog(this.allEntities.filter((u) => this.isEntitySelected(u)));
}
protected _searchField(user: any): string {
return this.userService.getName(user);
}
private async _loadData() {
this.allEntities = await this._userControllerService.getAllUsers().toPromise();
this._executeSearchImmediately();
@ -111,27 +133,4 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
].filter((type) => type.value > 0)
);
}
public getDisplayRoles(user: User) {
return user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') || this._translateService.instant('roles.NO_ROLE');
}
public async toggleActive(user: User) {
this.loading = true;
user.roles = this.userService.isActive(user) ? [] : ['RED_USER'];
await this._userControllerService.addRoleToUsers(user.roles, user.userId).toPromise();
await this._loadData();
}
public toggleCollapsedDetails() {
this.collapsedDetails = !this.collapsedDetails;
}
public async bulkDelete() {
this.openDeleteUserDialog(this.allEntities.filter((u) => this.isEntitySelected(u)));
}
public get canDeleteSelected(): boolean {
return this.selectedEntitiesIds.indexOf(this.userService.userId) === -1;
}
}

View File

@ -3,7 +3,7 @@
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions flex-1">
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="below" icon="red:close"></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" icon="red:close" tooltip="common.close" tooltipPosition="below"></redaction-circle-button>
</div>
</div>
@ -14,31 +14,31 @@
<div class="content-container">
<div #viewer class="viewer"></div>
<div class="changes-box" *ngIf="changed && permissionsService.isAdmin()">
<div *ngIf="changed && permissionsService.isAdmin()" class="changes-box">
<redaction-icon-button
(action)="save()"
[disabled]="configForm.invalid"
icon="red:check"
(action)="save()"
text="watermark-screen.action.save"
type="primary"
></redaction-icon-button>
<div (click)="revert()" translate="watermark-screen.action.revert" class="all-caps-label cancel"></div>
<div (click)="revert()" class="all-caps-label cancel" translate="watermark-screen.action.revert"></div>
</div>
</div>
<div class="right-container" redactionHasScrollbar>
<div class="heading-xl" [translate]="'watermark-screen.title'"></div>
<form [formGroup]="configForm" (keyup)="configChanged()">
<div [translate]="'watermark-screen.title'" class="heading-xl"></div>
<form (keyup)="configChanged()" [formGroup]="configForm">
<div class="red-input-group w-300">
<textarea
redactionHasScrollbar
formControlName="text"
[placeholder]="'watermark-screen.form.text-placeholder' | translate"
(mousemove)="triggerChanges()"
[placeholder]="'watermark-screen.form.text-placeholder' | translate"
class="w-full"
formControlName="text"
name="text"
type="text"
redactionHasScrollbar
rows="4"
type="text"
></textarea>
</div>
@ -46,11 +46,11 @@
<label class="all-caps-label mb-8" translate="watermark-screen.form.orientation"></label>
<div class="square-options">
<div
(click)="setValue('orientation', option)"
*ngFor="let option of ['VERTICAL', 'HORIZONTAL', 'DIAGONAL']"
[class.active]="configForm.get('orientation').value === option"
[class.disabled]="configForm.get('orientation').disabled"
[ngClass]="option"
(click)="setValue('orientation', option)"
*ngFor="let option of ['VERTICAL', 'HORIZONTAL', 'DIAGONAL']"
>
<span>ABC</span>
</div>
@ -59,33 +59,33 @@
<div class="red-input-group">
<label class="all-caps-label" translate="watermark-screen.form.font-size"></label>
<mat-slider formControlName="fontSize" min="5" max="50" color="primary" (change)="configChanged()"></mat-slider>
<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 formControlName="opacity" min="1" color="primary" (change)="configChanged()"></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>
<input
formControlName="hexColor"
class="hex-color-input"
formControlName="hexColor"
name="hexColor"
type="text"
placeholder="{{ 'add-edit-dictionary.form.color-placeholder' | translate }}"
type="text"
/>
<div
class="input-icon"
(colorPickerChange)="setValue('hexColor', $event)"
[class.disabled]="configForm.get('hexColor').disabled"
[style.background]="configForm.get('hexColor').value"
[colorPicker]="configForm.get('hexColor').value"
[cpDisabled]="configForm.get('hexColor').disabled"
[cpOutputFormat]="'hex'"
[cpPosition]="'top-right'"
[cpUseRootViewContainer]="true"
(colorPickerChange)="setValue('hexColor', $event)"
[style.background]="configForm.get('hexColor').value"
class="input-icon"
>
<mat-icon
*ngIf="!configForm.get('hexColor')?.value || configForm.get('hexColor').value?.length === 0"
@ -98,9 +98,6 @@
<label class="all-caps-label mb-8" translate="watermark-screen.form.font-type"></label>
<div class="square-options">
<div
[class.active]="configForm.get('fontType').value === option.value"
[class.disabled]="configForm.get('fontType').disabled"
[ngClass]="option.value"
(click)="setValue('fontType', option.value)"
*ngFor="
let option of [
@ -109,6 +106,9 @@
{ value: 'courier', display: 'Courier' }
]
"
[class.active]="configForm.get('fontType').value === option.value"
[class.disabled]="configForm.get('fontType').disabled"
[ngClass]="option.value"
>
{{ option.display }}
</div>

View File

@ -1,16 +1,16 @@
import { ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
import { AppStateService } from '../../../../state/app-state.service';
import { environment } from '../../../../../environments/environment';
import { AppStateService } from '@state/app-state.service';
import { environment } from '@environments/environment';
import { HttpClient } from '@angular/common/http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { debounce } from '../../../../utils/debounce';
import { debounce } from '@utils/debounce';
import { WatermarkControllerService, WatermarkModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { ActivatedRoute } from '@angular/router';
import { hexToRgb } from '../../../../utils/functions';
import { hexToRgb } from '@utils/functions';
import { BASE_HREF } from '../../../../tokens';
export const DEFAULT_WATERMARK: WatermarkModel = {
@ -28,30 +28,16 @@ export const DEFAULT_WATERMARK: WatermarkModel = {
styleUrls: ['./watermark-screen.component.scss']
})
export class WatermarkScreenComponent implements OnInit {
viewReady = false;
configForm: FormGroup;
private _instance: WebViewerInstance;
private _watermark: WatermarkModel = {};
@ViewChild('viewer', { static: true })
private _viewer: ElementRef;
public viewReady = false;
public configForm: FormGroup;
get changed(): boolean {
if (this._watermark === DEFAULT_WATERMARK) {
return true;
}
for (const key of Object.keys(this._watermark)) {
if (this._watermark[key] !== this.configForm.get(key)?.value) {
return true;
}
}
return false;
}
constructor(
public readonly permissionsService: PermissionsService,
public readonly appStateService: AppStateService,
readonly permissionsService: PermissionsService,
readonly appStateService: AppStateService,
@Inject(BASE_HREF) private readonly _baseHref: string,
private readonly _translateService: TranslateService,
private readonly _watermarkControllerService: WatermarkControllerService,
@ -65,31 +51,28 @@ export class WatermarkScreenComponent implements OnInit {
this._initForm();
}
get changed(): boolean {
if (this._watermark === DEFAULT_WATERMARK) {
return true;
}
for (const key of Object.keys(this._watermark)) {
if (this._watermark[key] !== this.configForm.get(key)?.value) {
return true;
}
}
return false;
}
ngOnInit(): void {
this._loadWatermark();
}
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();
}
);
}
@debounce()
public configChanged() {
configChanged() {
this._drawWatermark();
}
public save() {
save() {
const watermark = {
...this.configForm.getRawValue()
};
@ -113,12 +96,34 @@ export class WatermarkScreenComponent implements OnInit {
);
}
public revert() {
revert() {
this.configForm.setValue({ ...this._watermark });
this.configChanged();
}
public triggerChanges() {}
triggerChanges() {}
setValue(type: 'fontType' | 'orientation' | 'hexColor', value: any) {
if (!this.configForm.get(type).disabled) {
this.configForm.get(type).setValue(value);
this.configChanged();
}
}
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();
}
);
}
private _loadViewer() {
if (!this._instance) {
@ -158,16 +163,16 @@ export class WatermarkScreenComponent implements OnInit {
}
private async _drawWatermark() {
const PDFNet = this._instance.PDFNet;
const pdfNet = this._instance.PDFNet;
const document = await this._instance.docViewer.getDocument().getPDFDoc();
await PDFNet.runWithCleanup(
await pdfNet.runWithCleanup(
async () => {
await document.lock();
const pageSet = await PDFNet.PageSet.createSinglePage(1);
const pageSet = await pdfNet.PageSet.createSinglePage(1);
await PDFNet.Stamper.deleteStamps(document, pageSet);
await pdfNet.Stamper.deleteStamps(document, pageSet);
const text = this.configForm.get('text').value || '';
const fontSize = this.configForm.get('fontSize').value;
@ -178,8 +183,8 @@ export class WatermarkScreenComponent implements OnInit {
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));
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.setOpacity(opacity / 100);
switch (orientation) {
@ -195,7 +200,7 @@ 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);
@ -219,13 +224,6 @@ export class WatermarkScreenComponent implements OnInit {
});
}
public setValue(type: 'fontType' | 'orientation' | 'hexColor', value: any) {
if (!this.configForm.get(type).disabled) {
this.configForm.get(type).setValue(value);
this.configChanged();
}
}
private _convertFont(fontType: any): number {
switch (fontType) {
case 'times-new-roman':

View File

@ -16,9 +16,9 @@ import {
import { AddEditFileAttributeDialogComponent } from '../dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
import { AddEditDictionaryDialogComponent } from '../dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
import { AddEditRuleSetDialogComponent } from '../dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component';
import { NotificationService } from '../../../services/notification.service';
import { ConfirmationDialogComponent } from '../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { AppStateService } from '../../../state/app-state.service';
import { NotificationService } from '@services/notification.service';
import { ConfirmationDialogComponent } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { AppStateService } from '@state/app-state.service';
import { ConfirmDeleteFileAttributeDialogComponent } from '../dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component';
import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-color-dialog.component';
import { TranslateService } from '@ngx-translate/core';
@ -53,7 +53,7 @@ export class AdminDialogService {
private readonly _manualRedactionControllerService: ManualRedactionControllerService
) {}
public 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) => {
@ -65,7 +65,7 @@ export class AdminDialogService {
return ref;
}
public 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 +77,7 @@ export class AdminDialogService {
return ref;
}
public 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 +93,7 @@ export class AdminDialogService {
return ref;
}
public 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 +109,7 @@ export class AdminDialogService {
return ref;
}
public openAddEditRuleSetDialog(ruleSet: RuleSetModel, cb?: Function): MatDialogRef<AddEditRuleSetDialogComponent> {
openAddEditRuleSetDialog(ruleSet: RuleSetModel, cb?: Function): MatDialogRef<AddEditRuleSetDialogComponent> {
const ref = this._dialog.open(AddEditRuleSetDialogComponent, {
...dialogConfig,
width: '900px',
@ -126,7 +126,7 @@ export class AdminDialogService {
return ref;
}
public openImportFileAttributeCSVDialog(
openImportFileAttributeCSVDialog(
csv: File,
ruleSetId: string,
existingConfiguration: FileAttributesConfig,
@ -146,11 +146,7 @@ export class AdminDialogService {
return ref;
}
public 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 },
@ -166,7 +162,7 @@ export class AdminDialogService {
return ref;
}
public openConfirmDeleteFileAttributeDialog(
openConfirmDeleteFileAttributeDialog(
fileAttribute: FileAttributeConfig,
ruleSetId: string,
cb?: Function
@ -186,7 +182,7 @@ export class AdminDialogService {
return ref;
}
public openSMTPAuthConfigDialog(smtpConfig: SMTPConfigurationModel, cb?: Function): MatDialogRef<SmtpAuthDialogComponent> {
openSMTPAuthConfigDialog(smtpConfig: SMTPConfigurationModel, cb?: Function): MatDialogRef<SmtpAuthDialogComponent> {
const ref = this._dialog.open(SmtpAuthDialogComponent, {
...dialogConfig,
data: smtpConfig,
@ -202,7 +198,7 @@ export class AdminDialogService {
return ref;
}
public openAddEditUserDialog(user?: User, cb?: Function): MatDialogRef<AddEditUserDialogComponent> {
openAddEditUserDialog(user?: User, cb?: Function): MatDialogRef<AddEditUserDialogComponent> {
const ref = this._dialog.open(AddEditUserDialogComponent, {
...dialogConfig,
data: user,
@ -218,7 +214,7 @@ export class AdminDialogService {
return ref;
}
public 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

@ -1,9 +1,9 @@
import { Inject, Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
import { UserService } from '../../services/user.service';
import { AppLoadStateService } from '../../services/app-load-state.service';
import { AppConfigKey, AppConfigService } from '../app-config/app-config.service';
import { UserService } from '@services/user.service';
import { AppLoadStateService } from '@services/app-load-state.service';
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
import { BASE_HREF } from '../../tokens';
@Injectable({
@ -21,7 +21,7 @@ export class AuthGuard extends KeycloakAuthGuard {
super(_router, _keycloak);
}
public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (!this.authenticated) {
await this._keycloak.login({
idpHint: this._appConfigService.getConfig(AppConfigKey.OAUTH_IDP_HINT, null),

View File

@ -1,15 +1,15 @@
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { AppConfigModule } from '../app-config/app-config.module';
import { AppConfigModule } from '@app-config/app-config.module';
import { KeycloakAngularModule, KeycloakOptions, KeycloakService } from 'keycloak-angular';
import { AppConfigKey, AppConfigService } from '../app-config/app-config.service';
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
import { BASE_HREF } from '../../tokens';
export function keycloakInitializer(keycloak: KeycloakService, appConfigService: AppConfigService, baseUrl) {
return () => {
return appConfigService
return () =>
appConfigService
.loadAppConfig()
.toPromise()
.then(() => {
@ -33,7 +33,6 @@ export function keycloakInitializer(keycloak: KeycloakService, appConfigService:
};
return keycloak.init(options).then(() => configureAutomaticRedirectToLoginScreen(keycloak));
});
};
}
function configureAutomaticRedirectToLoginScreen(keyCloakService: KeycloakService) {

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { UserService } from '../../services/user.service';
import { AppLoadStateService } from '../../services/app-load-state.service';
import { UserService } from '@services/user.service';
import { AppLoadStateService } from '@services/app-load-state.service';
import { Observable } from 'rxjs';
@Injectable({

View File

@ -9,7 +9,7 @@ import { DomSanitizer } from '@angular/platform-browser';
exports: [MatIconModule]
})
export class IconsModule {
constructor(private iconRegistry: MatIconRegistry, private sanitizer: DomSanitizer) {
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
const icons = [
'add',
'analyse',
@ -84,7 +84,7 @@ 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

@ -1,87 +1,87 @@
<div [class.visible]="menuOpen" *ngIf="canPerformAnnotationActions" class="annotation-actions">
<div *ngIf="canPerformAnnotationActions" [class.visible]="menuOpen" class="annotation-actions">
<redaction-circle-button
(action)="annotationActionsService.forceRedaction($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canForceRedaction"
tooltipPosition="before"
tooltip="annotation-actions.force-redaction.label"
icon="red:thumb-up"
tooltip="annotation-actions.force-redaction.label"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canAcceptRecommendation"
tooltipPosition="before"
tooltip="annotation-actions.accept-recommendation.label"
icon="red:check"
tooltip="annotation-actions.accept-recommendation.label"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.markAsFalsePositive($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canMarkTextOnlyAsFalsePositive && !annotationPermissions.canPerformMultipleRemoveActions"
tooltipPosition="before"
tooltip="annotation-actions.remove-annotation.false-positive"
icon="red:thumb-down"
tooltip="annotation-actions.remove-annotation.false-positive"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.acceptSuggestion($event, [annotation], annotationsChanged)"
type="dark-bg"
*ngIf="annotationPermissions.canAcceptSuggestion"
tooltipPosition="before"
tooltip="annotation-actions.accept-suggestion.label"
icon="red:check"
tooltip="annotation-actions.accept-suggestion.label"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.undoDirectAction($event, [annotation], annotationsChanged)"
*ngIf="annotationPermissions.canUndo"
type="dark-bg"
icon="red:undo"
tooltipPosition="before"
tooltip="annotation-actions.undo"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="hideAnnotation($event)"
*ngIf="annotation.isImage && viewerAnnotation?.isVisible()"
type="dark-bg"
icon="red:visibility-off"
tooltipPosition="before"
tooltip="annotation-actions.hide"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="showAnnotation($event)"
*ngIf="annotation.isImage && !viewerAnnotation?.isVisible()"
type="dark-bg"
icon="red:visibility"
tooltipPosition="before"
tooltip="annotation-actions.show"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button
(action)="annotationActionsService.rejectSuggestion($event, [annotation], annotationsChanged)"
type="dark-bg"
icon="red:close"
*ngIf="annotationPermissions.canRejectSuggestion"
tooltipPosition="before"
icon="red:close"
tooltip="annotation-actions.reject-suggestion"
tooltipPosition="before"
type="dark-bg"
>
</redaction-circle-button>
<redaction-annotation-remove-actions
[annotations]="[annotation]"
[(menuOpen)]="menuOpen"
[annotationsChanged]="annotationsChanged"
[annotations]="[annotation]"
></redaction-annotation-remove-actions>
</div>

View File

@ -1,8 +1,8 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper';
import { AppStateService } from '../../../../state/app-state.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { AnnotationPermissions } from '../../../../models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationActionsService } from '../../services/annotation-actions.service';
import { Annotations, WebViewerInstance } from '@pdftron/webviewer';
@ -27,6 +27,10 @@ export class AnnotationActionsComponent implements OnInit {
private _permissionsService: PermissionsService
) {}
get viewerAnnotation(): Annotations.Annotation {
return this.viewer.annotManager.getAnnotationById(this.annotation.id);
}
ngOnInit(): void {
this.annotationPermissions = AnnotationPermissions.forUser(
this._permissionsService.isManagerAndOwner(),
@ -35,17 +39,13 @@ export class AnnotationActionsComponent implements OnInit {
);
}
public get viewerAnnotation(): Annotations.Annotation {
return this.viewer.annotManager.getAnnotationById(this.annotation.id);
}
public hideAnnotation($event: MouseEvent) {
hideAnnotation($event: MouseEvent) {
$event.stopPropagation();
this.viewer.annotManager.hideAnnotations([this.viewerAnnotation]);
this.viewer.annotManager.deselectAllAnnotations();
}
public showAnnotation($event: MouseEvent) {
showAnnotation($event: MouseEvent) {
$event.stopPropagation();
this.viewer.annotManager.showAnnotations([this.viewerAnnotation]);
this.viewer.annotManager.deselectAllAnnotations();

View File

@ -1,37 +1,37 @@
<redaction-circle-button
(action)="suggestRemoveAnnotations($event, false)"
[type]="btnType"
icon="red:trash"
*ngIf="permissions.canRemoveOrSuggestToRemoveOnlyHere && permissions.canNotPerformMultipleRemoveActions"
[tooltipPosition]="tooltipPosition"
[type]="btnType"
icon="red:trash"
tooltip="annotation-actions.suggest-remove-annotation"
>
</redaction-circle-button>
<redaction-circle-button
*ngIf="permissions.canPerformMultipleRemoveActions"
(action)="openMenu($event)"
*ngIf="permissions.canPerformMultipleRemoveActions"
[class.active]="menuOpen"
[matMenuTriggerFor]="menu"
[tooltipPosition]="tooltipPosition"
tooltip="annotation-actions.suggest-remove-annotation"
[type]="btnType"
icon="red:trash"
tooltip="annotation-actions.suggest-remove-annotation"
>
</redaction-circle-button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="suggestRemoveAnnotations($event, true)" mat-menu-item *ngIf="permissions.canRemoveOrSuggestToRemoveFromDictionary">
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></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)" mat-menu-item *ngIf="permissions.canRemoveOrSuggestToRemoveOnlyHere">
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></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)" mat-menu-item *ngIf="permissions.canMarkAsFalsePositive">
<mat-icon svgIcon="red:thumb-down" class="false-positive-icon"></mat-icon>
<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>
</mat-menu>

View File

@ -1,9 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper';
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationActionsService } from '../../services/annotation-actions.service';
import { AnnotationPermissions } from '../../../../models/file/annotation.permissions';
import { PermissionsService } from '../../../../services/permissions.service';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { PermissionsService } from '@services/permissions.service';
import { MatMenuTrigger } from '@angular/material/menu';
@Component({
@ -11,7 +11,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
templateUrl: './annotation-remove-actions.component.html',
styleUrls: ['./annotation-remove-actions.component.scss']
})
export class AnnotationRemoveActionsComponent implements OnInit {
export class AnnotationRemoveActionsComponent {
@Output() menuOpenChange = new EventEmitter<boolean>();
@Input() annotationsChanged: EventEmitter<AnnotationWrapper>;
@Input() menuOpen: boolean;
@ -19,7 +19,7 @@ export class AnnotationRemoveActionsComponent implements OnInit {
@Input() tooltipPosition: 'before' | 'above' = 'before';
@ViewChild(MatMenuTrigger) matMenuTrigger: MatMenuTrigger;
public permissions: {
permissions: {
canRemoveOrSuggestToRemoveOnlyHere: boolean;
canPerformMultipleRemoveActions: boolean;
canNotPerformMultipleRemoveActions: boolean;
@ -28,49 +28,47 @@ export class AnnotationRemoveActionsComponent implements OnInit {
};
constructor(
public readonly appStateService: AppStateService,
readonly appStateService: AppStateService,
private readonly _annotationActionsService: AnnotationActionsService,
private readonly _permissionsService: PermissionsService
) {}
private _annotations: AnnotationWrapper[];
public get annotations(): AnnotationWrapper[] {
get annotations(): AnnotationWrapper[] {
return this._annotations;
}
@Input()
public set annotations(value: AnnotationWrapper[]) {
set annotations(value: AnnotationWrapper[]) {
this._annotations = value.filter((a) => a !== undefined);
this._setPermissions();
}
public get dictionaryColor() {
get dictionaryColor() {
return this.appStateService.getDictionaryColor('suggestion-add-dictionary');
}
public get suggestionColor() {
get suggestionColor() {
return this.appStateService.getDictionaryColor('suggestion');
}
ngOnInit(): void {}
public openMenu($event: MouseEvent) {
openMenu($event: MouseEvent) {
$event.stopPropagation();
this.matMenuTrigger.openMenu();
this.menuOpenChange.emit(true);
}
public onMenuClosed() {
onMenuClosed() {
this.menuOpenChange.emit(false);
}
public suggestRemoveAnnotations($event, removeFromDict: boolean) {
suggestRemoveAnnotations($event, removeFromDict: boolean) {
$event.stopPropagation();
this._annotationActionsService.suggestRemoveAnnotation($event, this.annotations, removeFromDict, this.annotationsChanged);
}
public markAsFalsePositive($event) {
markAsFalsePositive($event) {
this._annotationActionsService.markAsFalsePositive($event, this.annotations, this.annotationsChanged);
}

View File

@ -2,31 +2,31 @@
<redaction-circle-button
(action)="delete()"
*ngIf="canDelete"
icon="red:trash"
tooltip="project-overview.bulk.delete"
type="dark-bg"
icon="red:trash"
></redaction-circle-button>
<redaction-circle-button
(action)="assign()"
*ngIf="canAssign"
icon="red:assign"
tooltip="project-overview.bulk.assign"
type="dark-bg"
icon="red:assign"
></redaction-circle-button>
<redaction-file-download-btn [project]="project" [file]="selectedFiles"> </redaction-file-download-btn>
<redaction-file-download-btn [file]="selectedFiles" [project]="project"></redaction-file-download-btn>
<redaction-circle-button
(action)="setToUnderApproval()"
*ngIf="canSetToUnderApproval"
icon="red:ready-for-approval"
tooltip="project-overview.under-approval"
type="dark-bg"
icon="red:ready-for-approval"
>
</redaction-circle-button>
<redaction-circle-button (action)="setToUnderReview()" *ngIf="canSetToUnderReview" tooltip="project-overview.under-review" type="dark-bg" icon="red:undo">
<redaction-circle-button (action)="setToUnderReview()" *ngIf="canSetToUnderReview" icon="red:undo" tooltip="project-overview.under-review" type="dark-bg">
</redaction-circle-button>
<!-- Approved-->
@ -34,24 +34,24 @@
(action)="approveDocuments()"
*ngIf="isReadyForApproval"
[disabled]="!canApprove"
type="dark-bg"
icon="red:approved"
[tooltip]="canApprove ? 'project-overview.approve' : 'project-overview.approve-disabled'"
icon="red:approved"
type="dark-bg"
>
</redaction-circle-button>
<!-- Back to approval -->
<redaction-circle-button (action)="setToUnderApproval()" *ngIf="canUndoApproval" tooltip="project-overview.under-approval" type="dark-bg" icon="red:undo">
<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" tooltip="project-overview.ocr-file" type="dark-bg" icon="red:ocr"></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()"
*ngIf="canReanalyse"
icon="red:refresh"
tooltip="project-overview.bulk.reanalyse"
type="dark-bg"
icon="red:refresh"
></redaction-circle-button>
</ng-container>

View File

@ -1,12 +1,12 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { UserService } from '../../../../services/user.service';
import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
import { PermissionsService } from '../../../../services/permissions.service';
import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { FileActionService } from '../../services/file-action.service';
import { Observable } from 'rxjs';
import { StatusOverlayService } from '../../../upload-download/services/status-overlay.service';
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
import { ProjectsDialogService } from '../../services/projects-dialog.service';
@Component({
@ -16,8 +16,8 @@ import { ProjectsDialogService } from '../../services/projects-dialog.service';
})
export class ProjectOverviewBulkActionsComponent {
@Input() selectedFileIds: string[];
@Output() private reload = new EventEmitter();
public loading = false;
loading = false;
@Output() private _reload = new EventEmitter();
constructor(
private readonly _appStateService: AppStateService,
@ -39,52 +39,76 @@ export class ProjectOverviewBulkActionsComponent {
return this.selectedFileIds.map((fileId) => this._appStateService.getFileById(this._appStateService.activeProject.project.projectId, fileId));
}
public get areAllFilesSelected() {
get areAllFilesSelected() {
return this._appStateService.activeProject.files.length !== 0 && this.selectedFileIds.length === this._appStateService.activeProject.files.length;
}
public get areSomeFilesSelected() {
get areSomeFilesSelected() {
return this.selectedFileIds.length > 0;
}
public get canDelete() {
get canDelete() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canDeleteFile(file), true);
}
public get canAssign() {
get canAssign() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canAssignReviewer(file), true);
}
public get canReanalyse() {
get canReanalyse() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canReanalyseFile(file), true);
}
public get canOcr() {
get canOcr() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canOcrFile(file), true);
}
public get fileStatuses() {
get fileStatuses() {
return this.selectedFiles.map((file) => file.fileStatus.status);
}
public delete() {
// Under review
get canSetToUnderReview() {
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);
}
// Approve
get isReadyForApproval() {
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);
}
// Undo approval
get canUndoApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canUndoApproval(file), true);
}
delete() {
this.loading = true;
this._dialogService.openDeleteFilesDialog(null, this._appStateService.activeProject.project.projectId, this.selectedFileIds, () => {
this.reload.emit();
this._reload.emit();
this.loading = false;
this.selectedFileIds.splice(0, this.selectedFileIds.length);
});
}
public assign() {
assign() {
this.loading = true;
this._dialogService.openBulkAssignFileReviewerDialog(this.selectedFileIds, () => {
this.reload.emit();
this._reload.emit();
this.loading = false;
});
}
public async reanalyse() {
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));
}
@ -93,46 +117,22 @@ export class ProjectOverviewBulkActionsComponent {
this._performBulkAction(this._fileActionService.ocrFile(this.selectedFiles));
}
// Under review
public get canSetToUnderReview() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderReview(file), true);
}
public setToUnderReview() {
setToUnderReview() {
this._performBulkAction(this._fileActionService.setFileUnderReview(this.selectedFiles));
}
// Under approval
public get canSetToUnderApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderApproval(file), true);
}
public setToUnderApproval() {
setToUnderApproval() {
this._performBulkAction(this._fileActionService.setFileUnderApproval(this.selectedFiles));
}
// Approve
public get isReadyForApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.isReadyForApproval(file), true);
}
public get canApprove() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canApprove(file), true);
}
public approveDocuments() {
approveDocuments() {
this._performBulkAction(this._fileActionService.setFileApproved(this.selectedFiles));
}
// Undo approval
public get canUndoApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canUndoApproval(file), true);
}
private _performBulkAction(obs: Observable<any>) {
this.loading = true;
obs.subscribe().add(() => {
this.reload.emit();
this._reload.emit();
this.loading = false;
});
}

View File

@ -34,7 +34,7 @@
<form (submit)="addComment()" *ngIf="addingComment && permissionsService.canAddComment()" [formGroup]="commentForm">
<div class="red-input-group">
<input [placeholder]="translateService.instant('comments.add-comment')" formControlName="comment" name="comment" type="text" class="w-full" />
<input [placeholder]="translateService.instant('comments.add-comment')" class="w-full" formControlName="comment" name="comment" type="text" />
</div>
</form>

View File

@ -42,6 +42,7 @@
.comment-icon.comment-owner {
display: none;
}
.trash-icon.comment-owner {
display: initial;
}

View File

@ -2,11 +2,11 @@ import { ChangeDetectorRef, Component, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Comment } from '@redaction/red-ui-http';
import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper';
import { UserService } from '../../../../services/user.service';
import { AppStateService } from '../../../../state/app-state.service';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { UserService } from '@services/user.service';
import { AppStateService } from '@state/app-state.service';
import { TranslateService } from '@ngx-translate/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { PermissionsService } from '@services/permissions.service';
@Component({
selector: 'redaction-comments',
@ -14,14 +14,14 @@ import { PermissionsService } from '../../../../services/permissions.service';
styleUrls: ['./comments.component.scss']
})
export class CommentsComponent {
@Input() public annotation: AnnotationWrapper;
public expanded = false;
public commentForm: FormGroup;
public addingComment = false;
@Input() annotation: AnnotationWrapper;
expanded = false;
commentForm: FormGroup;
addingComment = false;
constructor(
public readonly translateService: TranslateService,
public readonly permissionsService: PermissionsService,
readonly translateService: TranslateService,
readonly permissionsService: PermissionsService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
@ -37,7 +37,7 @@ export class CommentsComponent {
return !this.annotation.isChangeLogRemoved;
}
public toggleExpandComments($event: MouseEvent): void {
toggleExpandComments($event: MouseEvent): void {
$event.stopPropagation();
if (!this.annotation.comments.length) {
return;
@ -46,7 +46,7 @@ export class CommentsComponent {
this._changeDetectorRef.detectChanges();
}
public toggleAddingComment($event?: MouseEvent): void {
toggleAddingComment($event?: MouseEvent): void {
$event?.stopPropagation();
this.addingComment = !this.addingComment;
if (this.addingComment) {
@ -55,7 +55,7 @@ export class CommentsComponent {
this._changeDetectorRef.detectChanges();
}
public addComment(): void {
addComment(): void {
const value = this.commentForm.value.comment;
if (value) {
this._manualAnnotationService.addComment(value, this.annotation.id).subscribe((commentResponse) => {
@ -70,7 +70,7 @@ export class CommentsComponent {
}
}
public deleteComment(comment: Comment): void {
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) {
@ -79,11 +79,11 @@ export class CommentsComponent {
});
}
public isCommentOwner(comment: Comment): boolean {
isCommentOwner(comment: Comment): boolean {
return comment.user === this._userService.userId;
}
public getOwnerName(comment: Comment): string {
getOwnerName(comment: Comment): string {
return this._userService.getNameForId(comment.user);
}
}

View File

@ -1,23 +1,23 @@
<div class="right-title heading" translate="file-preview.tabs.document-info.label">
<div>
<redaction-circle-button
icon="red:edit"
(action)="edit()"
tooltipPosition="before"
icon="red:edit"
tooltip="file-preview.tabs.document-info.edit"
tooltipPosition="before"
></redaction-circle-button>
<redaction-circle-button
icon="red:close"
(action)="closeDocumentInfoView.emit()"
tooltipPosition="before"
icon="red:close"
tooltip="file-preview.tabs.document-info.close"
tooltipPosition="before"
></redaction-circle-button>
</div>
</div>
<div class="right-content" redactionHasScrollbar>
<div class="section">
<div class="attribute" *ngFor="let attr of fileAttributesConfig?.fileAttributeConfigs">
<div *ngFor="let attr of fileAttributesConfig?.fileAttributeConfigs" class="attribute">
<div class="small-label">{{ attr.label }}:</div>
<div>{{ (file.fileAttributes?.attributeIdToValue)[attr.id] || '-' }}</div>
</div>

View File

@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FileAttributesConfig, FileStatus } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import { ProjectsDialogService } from '../../services/projects-dialog.service';
@Component({
@ -8,27 +8,25 @@ import { ProjectsDialogService } from '../../services/projects-dialog.service';
templateUrl: './document-info.component.html',
styleUrls: ['./document-info.component.scss']
})
export class DocumentInfoComponent implements OnInit {
export class DocumentInfoComponent {
@Input() file: FileStatus;
@Output() closeDocumentInfoView = new EventEmitter();
public fileAttributesConfig: FileAttributesConfig;
fileAttributesConfig: FileAttributesConfig;
constructor(private readonly _appStateService: AppStateService, private readonly _dialogService: ProjectsDialogService) {
this.fileAttributesConfig = this._appStateService.activeFileAttributesConfig;
}
ngOnInit(): void {}
public get project() {
get project() {
return this._appStateService.getProjectById(this.file.projectId);
}
public edit() {
this._dialogService.openDocumentInfoDialog(this.file);
}
public get ruleSetName(): string {
get ruleSetName(): string {
return this._appStateService.getRuleSetById(this.project.ruleSetId).name;
}
edit() {
this._dialogService.openDocumentInfoDialog(this.file);
}
}

View File

@ -1,4 +1,4 @@
<div class="action-buttons" [class.active]="actionMenuOpen" *ngIf="screen === 'project-overview'">
<div *ngIf="screen === 'project-overview'" [class.active]="actionMenuOpen" class="action-buttons">
<ng-container *ngTemplateOutlet="actions"></ng-container>
<redaction-status-bar
*ngIf="fileStatus.isWorkable"
@ -41,21 +41,21 @@
<!-- download redacted file-->
<redaction-file-download-btn
[file]="fileStatus"
[project]="appStateService.activeProject"
[tooltipClass]="'small'"
[tooltipPosition]="tooltipPosition"
[type]="buttonType"
[project]="appStateService.activeProject"
[file]="fileStatus"
>
</redaction-file-download-btn>
<redaction-circle-button
*ngIf="screen === 'file-preview'"
(action)="toggleViewDocumentInfo()"
*ngIf="screen === 'file-preview'"
[attr.aria-expanded]="activeDocumentInfo"
icon="red:status-info"
tooltip="file-preview.document-info"
tooltipPosition="below"
icon="red:status-info"
[attr.aria-expanded]="activeDocumentInfo"
></redaction-circle-button>
<!-- Ready for approval-->
@ -86,9 +86,9 @@
*ngIf="permissionsService.isReadyForApproval(fileStatus)"
[disabled]="!permissionsService.canApprove(fileStatus)"
[tooltipPosition]="tooltipPosition"
[tooltip]="permissionsService.canApprove(fileStatus) ? 'project-overview.approve' : 'project-overview.approve-disabled'"
[type]="buttonType"
icon="red:approved"
[tooltip]="permissionsService.canApprove(fileStatus) ? 'project-overview.approve' : 'project-overview.approve-disabled'"
>
</redaction-circle-button>
@ -117,10 +117,10 @@
<redaction-circle-button
(action)="reanalyseFile($event, fileStatus, 100)"
*ngIf="permissionsService.canReanalyseFile(fileStatus) && screen === 'file-preview'"
tooltipPosition="below"
icon="red:refresh"
tooltip="file-preview.reanalyse-notification"
tooltipClass="warn small"
tooltipPosition="below"
type="warn"
>
</redaction-circle-button>
@ -140,11 +140,11 @@
<div class="red-input-group">
<mat-slide-toggle
(click)="toggleAnalysis($event)"
[disabled]="!permissionsService.isManager()"
[checked]="!fileStatus?.isExcluded"
[matTooltip]="toggleTooltip | translate"
[matTooltipPosition]="tooltipPosition"
[class.mr-24]="screen === 'project-overview'"
[disabled]="!permissionsService.isManager()"
[matTooltipPosition]="tooltipPosition"
[matTooltip]="toggleTooltip | translate"
color="primary"
>
</mat-slide-toggle>

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { AppStateService } from '../../../../state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { AppStateService } from '@state/app-state.service';
import { FileActionService } from '../../services/file-action.service';
import { ProjectsDialogService } from '../../services/projects-dialog.service';
@ -19,12 +19,28 @@ export class FileActionsComponent implements OnInit {
screen: 'file-preview' | 'project-overview';
constructor(
public readonly permissionsService: PermissionsService,
public readonly appStateService: AppStateService,
readonly permissionsService: PermissionsService,
readonly appStateService: AppStateService,
private readonly _dialogService: ProjectsDialogService,
private readonly _fileActionService: FileActionService
) {}
get tooltipPosition() {
return this.screen === 'file-preview' ? 'below' : 'above';
}
get buttonType() {
return this.screen === 'file-preview' ? 'default' : 'dark-bg';
}
get toggleTooltip(): string {
if (!this.permissionsService.isManager()) {
return 'file-preview.toggle-analysis.only-managers';
}
return this.fileStatus?.isExcluded ? 'file-preview.toggle-analysis.enable' : 'file-preview.toggle-analysis.disable';
}
ngOnInit(): void {
if (!this.fileStatus) {
this.fileStatus = this.appStateService.activeFile;
@ -39,18 +55,10 @@ export class FileActionsComponent implements OnInit {
}
}
public toggleViewDocumentInfo() {
toggleViewDocumentInfo() {
this.actionPerformed.emit('view-document-info');
}
public get tooltipPosition() {
return this.screen === 'file-preview' ? 'below' : 'above';
}
public get buttonType() {
return this.screen === 'file-preview' ? 'default' : 'dark-bg';
}
openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
this._dialogService.openDeleteFilesDialog($event, fileStatusWrapper.projectId, [fileStatusWrapper.fileId], () => {
this.actionPerformed.emit('delete');
@ -110,12 +118,4 @@ export class FileActionsComponent implements OnInit {
await this.appStateService.getFiles();
this.actionPerformed.emit(this.fileStatus?.isExcluded ? 'enable-analysis' : 'disable-analysis');
}
get toggleTooltip(): string {
if (!this.permissionsService.isManager()) {
return 'file-preview.toggle-analysis.only-managers';
}
return this.fileStatus?.isExcluded ? 'file-preview.toggle-analysis.enable' : 'file-preview.toggle-analysis.disable';
}
}

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