Pull request #252: Some branch

Merge in RED/ui from some-branch to master

* commit '26f5de1d30ef28a49d31a8635660884dfd47a467':
  chore(release)
  fixed legal basis issues with backend not exactly matching LB reason
  chore(release)
  duplicated keys in search quick fix
  chore(release)
  chore(release)
  manual redaction fixes
  working entire platform search
  add library for common ui
This commit is contained in:
Dan Percic 2021-07-26 14:05:38 +02:00 committed by Timo Bejan
commit 10976ab876
55 changed files with 871 additions and 331 deletions

View File

@ -1,5 +1,4 @@
{
"$schema": "node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"defaultCollection": "@nrwl/angular",
@ -9,12 +8,15 @@
"defaultProject": "red-ui",
"schematics": {
"@nrwl/angular:application": {
"linter": "eslint",
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"@nrwl/angular:library": {
"linter": "eslint",
"unitTestRunner": "jest"
}
},
"@nrwl/angular:component": {}
},
"projects": {
"red-ui": {
@ -186,6 +188,28 @@
"style": "scss"
}
}
},
"common-ui": {
"projectType": "library",
"root": "libs/common-ui",
"sourceRoot": "libs/common-ui/src",
"prefix": "redaction",
"architect": {
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/libs/common-ui"],
"options": {
"jestConfig": "libs/common-ui/jest.config.js",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["libs/common-ui/src/**/*.ts", "libs/common-ui/src/**/*.html"]
}
}
}
}
}
}

View File

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

View File

@ -32,6 +32,7 @@ import { GlobalErrorHandler } from '@utils/global-error-handler.service';
import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
import { configurationInitializer } from '@app-config/configuration.initializer';
import { AppConfigService } from '@app-config/app-config.service';
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
export function httpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -49,7 +50,15 @@ function cleanupBaseUrl(baseUrl: string) {
const screens = [BaseScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastComponent, NotificationsComponent, ...screens];
const components = [
AppComponent,
LogoComponent,
AuthErrorComponent,
ToastComponent,
NotificationsComponent,
SpotlightSearchComponent,
...screens
];
@NgModule({
declarations: [...components],

View File

@ -6,11 +6,7 @@
<mat-icon svgIcon="red:menu"></mat-icon>
</button>
<mat-menu #menuNav="matMenu">
<button
mat-menu-item
routerLink="/main/dossiers"
translate="top-bar.navigation-items.dossiers"
></button>
<button mat-menu-item routerLink="/main/dossiers" translate="top-bar.navigation-items.dossiers"></button>
<button
*ngIf="appStateService.activeDossier"
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId"
@ -20,22 +16,14 @@
</button>
<button
*ngIf="appStateService.activeFile"
[routerLink]="
'/main/dossiers/' +
appStateService.activeDossierId +
'/file/' +
appStateService.activeFile.fileId
"
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId + '/file/' + appStateService.activeFile.fileId"
mat-menu-item
>
{{ appStateService.activeFile.filename }}
</button>
</mat-menu>
</div>
<div
*ngIf="permissionsService.isUser()"
class="menu flex-2 visible-lg breadcrumbs-container"
>
<div *ngIf="permissionsService.isUser()" class="menu flex-2 visible-lg breadcrumbs-container">
<a
*ngIf="dossiersView"
[routerLinkActiveOptions]="{ exact: true }"
@ -49,15 +37,8 @@
{{ 'top-bar.navigation-items.back' | translate }}
</a>
<ng-container *ngIf="dossiersView">
<mat-icon
*ngIf="!appStateService.activeDossier"
class="primary"
svgIcon="red:arrow-down"
></mat-icon>
<mat-icon
*ngIf="appStateService.activeDossier"
svgIcon="red:arrow-right"
></mat-icon>
<mat-icon *ngIf="!appStateService.activeDossier" class="primary" svgIcon="red:arrow-down"></mat-icon>
<mat-icon *ngIf="appStateService.activeDossier" svgIcon="red:arrow-right"></mat-icon>
<a
*ngIf="appStateService.activeDossier"
[routerLinkActiveOptions]="{ exact: true }"
@ -70,12 +51,7 @@
<mat-icon *ngIf="appStateService.activeFile" svgIcon="red:arrow-right"></mat-icon>
<a
*ngIf="appStateService.activeFile"
[routerLink]="
'/main/dossiers/' +
appStateService.activeDossierId +
'/file/' +
appStateService.activeFile.fileId
"
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId + '/file/' + appStateService.activeFile.fileId"
class="breadcrumb"
routerLinkActive="active"
>
@ -90,26 +66,21 @@
<div class="app-name">{{ titleService.getTitle() }}</div>
</div>
<div class="menu right flex-2">
<redaction-notifications
*ngIf="userPreferenceService.areDevFeaturesEnabled"
class="mr-8"
></redaction-notifications>
<redaction-circle-button
*ngIf="!isSearchScreen"
[icon]="'red:search'"
(action)="openSpotlightSearch()"
[tooltip]="'search.header-label' | translate"
tooltipPosition="below"
></redaction-circle-button>
<redaction-user-button
[matMenuTriggerFor]="userMenu"
[showDot]="showPendingDownloadsDot"
[user]="user"
></redaction-user-button>
<redaction-notifications *ngIf="userPreferenceService.areDevFeaturesEnabled" class="mr-8"></redaction-notifications>
<redaction-user-button [matMenuTriggerFor]="userMenu" [showDot]="showPendingDownloadsDot" [user]="user"></redaction-user-button>
<mat-menu #userMenu="matMenu" xPosition="before">
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
<button
(click)="(item.action)"
*ngIf="item.show"
[routerLink]="item.routerLink"
mat-menu-item
translate
>
<button (click)="(item.action)" *ngIf="item.show" [routerLink]="item.routerLink" mat-menu-item translate>
{{ item.name }}
</button>
</ng-container>
@ -124,10 +95,5 @@
<div class="divider"></div>
</div>
<div
*ngIf="userPreferenceService.areDevFeaturesEnabled"
class="dev-mode"
translate="dev-mode"
></div>
<div *ngIf="userPreferenceService.areDevFeaturesEnabled" class="dev-mode" translate="dev-mode"></div>
<router-outlet></router-outlet>

View File

@ -7,6 +7,10 @@ import { Router } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
interface MenuItem {
name: string;
@ -44,6 +48,8 @@ export class BaseScreenComponent {
}
];
showSearch = false;
constructor(
readonly appStateService: AppStateService,
readonly permissionsService: PermissionsService,
@ -52,10 +58,11 @@ export class BaseScreenComponent {
readonly fileDownloadService: FileDownloadService,
private readonly _router: Router,
private readonly _userService: UserService,
private readonly _translateService: TranslateService
private readonly _translateService: TranslateService,
private readonly _dialog: MatDialog
) {
_router.events.subscribe(() => {
this._dossiersView = _router.url.indexOf('/main/dossiers') === 0;
this._dossiersView = _router.url.includes('/main/dossiers') && !this.isSearchScreen;
});
}
@ -65,6 +72,10 @@ export class BaseScreenComponent {
return this._dossiersView;
}
get isSearchScreen() {
return this._router.url.includes('/search');
}
get user() {
return this._userService.user;
}
@ -77,6 +88,44 @@ export class BaseScreenComponent {
return this._translateService.langs;
}
openSpotlightSearch() {
const spotlightSearchActions: SpotlightSearchAction[] = [
{
text: this._translateService.instant('search.this-dossier'),
icon: 'red:enter',
hide: !this.appStateService.activeDossier,
action: query => this._searchThisDossier(query)
},
{
text: this._translateService.instant('search.entire-platform'),
icon: 'red:enter',
action: query => this._searchEntirePlatform(query)
}
];
this._dialog.open(SpotlightSearchComponent, {
data: {
actionsConfig: spotlightSearchActions,
placeholder: this._translateService.instant('search.placeholder')
} as SpotlightSearchDialogData
});
}
private _searchThisDossier(query: string) {
this._router
.navigate(['main/dossiers/search'], {
queryParams: {
query: query,
dossierId: this.appStateService.activeDossier.dossierId
}
})
.then();
}
private _searchEntirePlatform(query: string) {
this._router.navigate(['main/dossiers/search'], { queryParams: { query: query } }).then();
}
logout() {
this._userService.logout();
}

View File

@ -0,0 +1,6 @@
export interface SpotlightSearchAction {
text: string;
action: (query: string) => void;
icon?: string;
hide?: boolean;
}

View File

@ -0,0 +1,6 @@
import { SpotlightSearchAction } from './spotlight-search-action';
export interface SpotlightSearchDialogData {
actionsConfig: SpotlightSearchAction[];
placeholder: string;
}

View File

@ -0,0 +1,43 @@
<div class="spotlight-wrapper">
<form [formGroup]="formGroup">
<div class="search d-flex">
<input
tabindex="-1"
id="query"
type="text"
formControlName="query"
autocomplete="off"
class="spotlight-row"
[placeholder]="data.placeholder"
/>
<mat-icon class="mr-34" *ngIf="(showActions$ | async) === false" [svgIcon]="'red:search'"></mat-icon>
<redaction-circle-button
*ngIf="showActions$ | async"
class="mr-24"
(action)="close()"
icon="red:close"
></redaction-circle-button>
</div>
<div class="divider"></div>
<ng-container *ngIf="showActions$ | async">
<ng-container *ngFor="let item of data.actionsConfig">
<button
*ngIf="!item.hide"
#actions
(keydown.space)="$event.preventDefault()"
(keyup.space)="$event.preventDefault()"
tabindex="0"
class="spotlight-row focus pointer"
(click)="item.action(formGroup.get('query').value); close()"
>
<mat-icon class="mr-16" [svgIcon]="item.icon"></mat-icon>
<span>{{ item.text }}</span>
</button>
</ng-container>
</ng-container>
</form>
</div>

View File

@ -0,0 +1,51 @@
@import 'apps/red-ui/src/assets/styles/red-variables';
.spotlight-wrapper {
overflow: hidden;
width: 750px;
margin: auto;
position: absolute;
top: 15%;
left: 0;
right: 0;
border-radius: 10px;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.3);
}
.spotlight-row {
display: block;
width: 750px;
height: 60px;
margin: auto;
text-align: left;
font-size: 16px;
font-weight: 500;
border: none;
outline: none;
color: $grey-1;
padding: 0 24px;
background-color: $white;
}
.focus:focus {
background-color: $grey-2;
}
.search {
background-color: $white;
align-items: center;
}
.divider {
height: 1px;
background-color: rgba(226, 228, 233, 0.9);
}
input {
width: 668px !important;
}
mat-icon {
width: 14px;
height: 14px;
}

View File

@ -0,0 +1,69 @@
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Inject, QueryList, ViewChildren } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { debounceTime, map, startWith, tap } from 'rxjs/operators';
import { debounce } from '@utils/debounce';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
@Component({
selector: 'redaction-spotlight-search',
templateUrl: './spotlight-search.component.html',
styleUrls: ['./spotlight-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpotlightSearchComponent {
@ViewChildren('actions')
private readonly _actions: QueryList<ElementRef>;
private _currentActionIdx = 0;
formGroup = this._formBuilder.group({ query: [''] });
query$ = this.formGroup.get('query').valueChanges.pipe(startWith(''));
showActions$ = this.query$.pipe(
debounceTime(300),
tap(value => this._restoreFocusOnAction(value === '')),
map(value => value !== '')
);
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _dialogRef: MatDialogRef<SpotlightSearchComponent>,
@Inject(MAT_DIALOG_DATA)
readonly data: SpotlightSearchDialogData
) {}
close() {
this._dialogRef.close();
}
@HostListener('document:keyup', ['$event'])
handleKeyDown(event: KeyboardEvent) {
if (event.code === 'ArrowDown' && this._actions) {
this._currentActionIdx++;
return this._restoreFocusOnAction(this._currentActionIdx === this._actions.length);
}
if (event.code === 'ArrowUp' && this._actions) {
if (this._currentActionIdx === 0) this._currentActionIdx = this._actions.length - 1;
else this._currentActionIdx--;
return this._restoreFocusOnAction();
}
if (['Tab'].includes(event.code)) {
return;
}
if (this._actions.find((_, index) => index === this._currentActionIdx)?.nativeElement === document.activeElement) {
let query = this.formGroup.get('query').value as string;
if (event.code === 'Backspace') query = query.substring(0, query.length - 1);
else if (event.key.length === 1) query = query + event.key;
this.formGroup.patchValue({ query: query });
}
document.getElementById('query').focus();
}
@debounce(50)
private _restoreFocusOnAction(resetToFirst = false) {
if (resetToFirst) this._currentActionIdx = 0;
this._actions.find((_, index) => index === this._currentActionIdx)?.nativeElement.focus();
}
}

View File

@ -359,7 +359,7 @@ export class AnnotationWrapper {
private static _getShortContent(entry: RedactionLogEntryWrapper) {
if (entry.legalBasis) {
const lb = entry.legalBasisMapping?.find(lbm => lbm.reason === entry.legalBasis);
const lb = entry.legalBasisMapping?.find(lbm => lbm.reason.toLowerCase().includes(entry.legalBasis.toLowerCase()));
if (lb) return lb.name;
}

View File

@ -21,42 +21,39 @@ export class RedRoleGuard implements CanActivate {
this._loadingService.stop();
obs.next(false);
obs.complete();
} else {
// we have at least 1 RED Role -> if it's not user he must be admin
return;
}
// we have at least 1 RED Role -> if it's not user he must be admin
if (
this._userService.user.isUserAdmin &&
!this._userService.user.isAdmin &&
!(
state.url.startsWith('/main/admin/users') ||
state.url.startsWith('/main/my-profile')
)
) {
this._router.navigate(['/main/admin/users']);
obs.next(false);
obs.complete();
return;
}
if (
this._userService.user.isUserAdmin &&
!this._userService.user.isAdmin &&
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
) {
this._router.navigate(['/main/admin/users']);
obs.next(false);
obs.complete();
return;
}
if (!this._userService.isUser() && state.url.startsWith('/main/dossiers')) {
this._router.navigate(['/main/admin']);
obs.next(false);
obs.complete();
return;
}
if (route.data.requiredRoles) {
if (this._userService.hasAnyRole(route.data.requiredRoles)) {
obs.next(true);
obs.complete();
} else {
this._router.navigate(['/main/dossiers']);
obs.next(false);
obs.complete();
}
} else {
if (!this._userService.isUser() && state.url.startsWith('/main/dossiers')) {
this._router.navigate(['/main/admin']);
obs.next(false);
obs.complete();
return;
}
if (route.data.requiredRoles) {
if (this._userService.hasAnyRole(route.data.requiredRoles)) {
obs.next(true);
obs.complete();
} else {
this._router.navigate(['/main/dossiers']);
obs.next(false);
obs.complete();
}
} else {
obs.next(true);
obs.complete();
}
});
}

View File

@ -27,10 +27,6 @@ mat-slide-toggle {
line-height: 33px;
}
.mr-24 {
margin-right: 24px;
}
mat-slide-toggle {
margin-left: 8px;
margin-right: 5px;

View File

@ -12,10 +12,7 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
export class NeedsWorkBadgeComponent {
@Input() needsWorkInput: FileStatusWrapper | DossierWrapper;
constructor(
private readonly _appStateService: AppStateService,
private readonly _permissionsService: PermissionsService
) {}
constructor(private readonly _appStateService: AppStateService, private readonly _permissionsService: PermissionsService) {}
get suggestionColor() {
return this._getDictionaryColor('suggestion');
@ -50,10 +47,7 @@ export class NeedsWorkBadgeComponent {
}
get hasAnnotationComments(): boolean {
return (
this.needsWorkInput instanceof FileStatusWrapper &&
(<any>this.needsWorkInput).hasAnnotationComments
);
return this.needsWorkInput instanceof FileStatusWrapper && (<any>this.needsWorkInput).hasAnnotationComments;
}
reanalysisRequired() {
@ -65,11 +59,6 @@ export class NeedsWorkBadgeComponent {
}
private _getDictionaryColor(type: string) {
let dossierTemplateId = null;
if (this.needsWorkInput instanceof DossierWrapper) {
dossierTemplateId = this.needsWorkInput.dossierTemplateId;
}
return this._appStateService.getDictionaryColor(type, dossierTemplateId);
return this._appStateService.getDictionaryColor(type);
}
}

View File

@ -1,15 +1,7 @@
<button
(click)="scroll(buttonType.TOP)"
[hidden]="!showScroll(buttonType.TOP)"
class="scroll-button top pointer"
>
<button (click)="scroll(buttonType.top)" [hidden]="!showScroll(buttonType.top)" class="scroll-button top pointer">
<mat-icon svgIcon="red:arrow-down-o"></mat-icon>
</button>
<button
(click)="scroll(buttonType.BOTTOM)"
[hidden]="!showScroll(buttonType.BOTTOM)"
class="scroll-button bottom pointer"
>
<button (click)="scroll(buttonType.bottom)" [hidden]="!showScroll(buttonType.bottom)" class="scroll-button bottom pointer">
<mat-icon svgIcon="red:arrow-down-o"></mat-icon>
</button>

View File

@ -1,10 +1,12 @@
import { Component, HostListener, Input } from '@angular/core';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
enum ButtonType {
TOP = 'top',
BOTTOM = 'bottom'
}
const ButtonTypes = {
top: 'top',
bottom: 'bottom'
} as const;
type ButtonType = keyof typeof ButtonTypes;
@Component({
selector: 'redaction-scroll-button',
@ -12,7 +14,7 @@ enum ButtonType {
styleUrls: ['./scroll-button.component.scss']
})
export class ScrollButtonComponent {
buttonType = ButtonType;
buttonType = ButtonTypes;
@Input()
scrollViewport: CdkVirtualScrollViewport;
@ -20,9 +22,7 @@ export class ScrollButtonComponent {
itemSize: number;
scroll(type: ButtonType): void {
const viewportSize =
(this.scrollViewport?.getViewportSize() - this.itemSize) *
(type === ButtonType.TOP ? -1 : 1);
const viewportSize = (this.scrollViewport?.getViewportSize() - this.itemSize) * (type === ButtonTypes.top ? -1 : 1);
const scrollOffset = this.scrollViewport?.measureScrollOffset('top');
this.scrollViewport?.scrollToOffset(scrollOffset + viewportSize, 'smooth');
}
@ -37,10 +37,10 @@ export class ScrollButtonComponent {
@HostListener('document:keyup', ['$event'])
spaceAndPageDownScroll(event: KeyboardEvent): void {
if (['Space', 'PageDown'].includes(event.code)) {
this.scroll(ButtonType.BOTTOM);
} else if (['PageUp'].includes(event.code)) {
this.scroll(ButtonType.TOP);
if (['Space', 'PageDown'].includes(event.code) && (event.target as any).tagName === 'BODY') {
this.scroll(ButtonTypes.bottom);
} else if (['PageUp'].includes(event.code) && (event.target as any).tagName === 'BODY') {
this.scroll(ButtonTypes.top);
}
}
}

View File

@ -1,21 +1,21 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { RouterModule, Routes } from '@angular/router';
import { DossierListingScreenComponent } from './screens/dossier-listing-screen/dossier-listing-screen.component';
import { CompositeRouteGuard } from '@guards/composite-route.guard';
import { AuthGuard } from '../auth/auth.guard';
import { RedRoleGuard } from '../auth/red-role.guard';
import { AppStateGuard } from '@state/app-state.guard';
import { DossierOverviewScreenComponent } from './screens/dossier-overview-screen/dossier-overview-screen.component';
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
import { FilePreviewScreenComponent } from './screens/file-preview-screen/file-preview-screen.component';
import { DossierOverviewScreenComponent } from './screens/dossier-overview-screen/dossier-overview-screen.component';
const routes = [
const routes: Routes = [
{
path: '',
component: DossierListingScreenComponent,
path: 'search',
component: SearchScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
@ -35,6 +35,16 @@ const routes = [
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
}
},
{
path: '',
pathMatch: 'full',
component: DossierListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
}
}
];

View File

@ -49,8 +49,9 @@ import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-d
import { EditDossierAttributesComponent } from './dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component';
import { DossiersService } from './services/dossiers.service';
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent];
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent, SearchScreenComponent];
const dialogs = [
AddDossierDialogComponent,

View File

@ -583,7 +583,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribeComponent impleme
/* Get the documentElement (<html>) to display the page in fullscreen */
private _cleanupAndRedrawManualAnnotations() {
this._fileDownloadService.loadActiveFileRedactionLogPreview().subscribe(redactionLogPreview => {
this._fileDownloadService.loadActiveFileRedactionLog().subscribe(redactionLogPreview => {
this.fileData.redactionLog = redactionLogPreview;
this._annotationDrawService.drawAnnotations(
this._instance,
@ -599,7 +599,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribeComponent impleme
const currentPageAnnotationIds = currentPageAnnotations.map(a => a.id);
this.fileData.fileStatus = await this.appStateService.reloadActiveFile();
this._fileDownloadService.loadActiveFileRedactionLogPreview().subscribe(redactionLogPreview => {
this._fileDownloadService.loadActiveFileRedactionLog().subscribe(redactionLogPreview => {
this.fileData.redactionLog = redactionLogPreview;
this.rebuildFilters();
if (this.viewMode === 'STANDARD') {

View File

@ -0,0 +1,98 @@
<section *ngIf="searchResults$ | async as searchResult">
<redaction-page-header
[showCloseButton]="true"
[searchPlaceholder]="'search.placeholder' | translate"
[searchWidth]="600"
></redaction-page-header>
<div class="overlay-shadow"></div>
<div class="red-content-inner">
<div class="content-container">
<redaction-table-header
[tableHeaderLabel]="'search-screen.table-header'"
[tableColConfigs]="tableColConfigs"
></redaction-table-header>
<redaction-empty-state
*ngIf="searchResult.length === 0"
[icon]="'red:search'"
[text]="'search-screen.no-data' | translate"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
<div
*cdkVirtualFor="let item of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.pointer]="true"
[routerLink]="item.routerLink"
class="table-item"
>
<div class="filename">
<div [matTooltip]="item.fileName" class="table-item-title heading" matTooltipPosition="above">
<span
*ngIf="item.highlights.filename; else defaultFilename"
class="highlights"
[innerHTML]="item.highlights.filename[0]"
></span>
<ng-template #defaultFilename>{{ item.fileName }}</ng-template>
</div>
<ng-container *ngIf="item.highlights['sections.text'] as highlights">
<div class="small-label" *ngIf="highlights.length > 0">
<span class="highlights" [innerHTML]="highlights[0]"></span>
</div>
<div class="small-label" *ngIf="highlights.length > 1">
<span class="highlights" [innerHTML]="highlights[1]"></span>
</div>
</ng-container>
<div class="small-label" *ngIf="item.unmatched as unmatched">
<span>
{{ 'search-screen.missing' | translate }}:<span *ngFor="let term of unmatched"
>&nbsp;<s>{{ term }}</s></span
>.&nbsp;{{ 'search-screen.must-contain' | translate }}:
<span
*ngFor="let term of unmatched"
(click)="$event.stopPropagation(); updateNavigation({ query: search$.getValue(), mustContain: term })"
>&nbsp;<u>{{ term }}</u></span
>
</span>
</div>
</div>
<div>
<redaction-status-bar
[small]="true"
[config]="[
{
color: item.status,
label: item.status | translate,
length: 1
}
]"
></redaction-status-bar>
</div>
<div class="small-label">
{{ item.dossierName }}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ item.pages }}
</div>
</div>
<div class="scrollbar-placeholder"></div>
</div>
</cdk-virtual-scroll-viewport>
<redaction-scroll-button
*ngIf="(screenStateService.noData$ | async) === false"
[itemSize]="itemSize"
[scrollViewport]="scrollViewport"
></redaction-scroll-button>
</div>
</div>
</section>

View File

@ -0,0 +1,38 @@
@import 'apps/red-ui/src/assets/styles/red-mixins';
@import 'apps/red-ui/src/assets/styles/red-variables';
.content-container {
position: relative;
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr auto 11px;
.table-item {
> div {
height: 85px;
padding: 0 24px;
}
.status-container {
width: 160px;
padding-right: 13px;
}
.highlights em {
background-color: #fffcc4;
}
.highlights {
@include line-clamp(1);
}
}
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 2fr 1fr 1fr auto;
}
}
}
}

View File

@ -0,0 +1,155 @@
import { Component, Injector, OnDestroy } from '@angular/core';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { SearchControllerService, SearchResult } from '@redaction/red-ui-http';
import { BehaviorSubject, Observable } from 'rxjs';
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { MatchedDocument } from '@redaction/red-ui-http';
import { TableColConfig } from '../../../shared/components/table-col-name/table-col-name.component';
import { FilterService } from '../../../shared/services/filter.service';
import { SearchService } from '../../../shared/services/search.service';
import { ScreenStateService } from '../../../shared/services/screen-state.service';
import { SortingService } from '../../../../services/sorting.service';
import { AppStateService } from '../../../../state/app-state.service';
import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '../../../../services/loading.service';
interface ListItem {
fileName: string;
matchedDocument: MatchedDocument;
unmatched: string[] | null;
highlights: { [key: string]: string[] };
routerLink: string;
status: string;
dossierName: string;
pages: number;
}
@Component({
templateUrl: './search-screen.component.html',
styleUrls: ['./search-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
})
export class SearchScreenComponent extends BaseListingComponent<ListItem> implements OnDestroy {
protected readonly _primaryKey = 'fileName';
readonly itemSize = 85;
readonly search$ = new BehaviorSubject<string>(null);
readonly searchResults$: Observable<ListItem[]> = this.search$.asObservable().pipe(
switchMap(query => this._search(query)),
map(searchResult => this._toMatchedDocuments(searchResult)),
map(documents => this._toListItems(documents)),
tap(result => this.screenStateService.setEntities(result)),
tap(() => this._loadingService.stop())
);
private _dossierId: string;
readonly tableColConfigs: TableColConfig[] = [
{
label: this._translateService.instant('search-screen.cols.document')
},
{
label: this._translateService.instant('search-screen.cols.status')
},
{
label: this._translateService.instant('search-screen.cols.dossier')
},
{
label: this._translateService.instant('search-screen.cols.pages')
}
];
constructor(
protected readonly _injector: Injector,
private readonly _searchControllerService: SearchControllerService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
private readonly _router: Router
) {
super(_injector);
this.addSubscription = _activatedRoute.queryParamMap
.pipe(
tap(() => this._loadingService.start()),
map(value => ({ query: value.get('query'), dossierId: value.get('dossierId') })),
tap(mappedValue => this._updateValues(mappedValue))
)
.subscribe();
this.addSubscription = this.searchService.searchForm
.get('query')
.valueChanges.pipe(debounceTime(300))
.subscribe(value => this.updateNavigation({ query: value }));
}
setInitialConfig() {
return;
}
updateNavigation({ query, mustContain }: { readonly query: string; readonly mustContain?: string }) {
const newQuery = query?.replace(mustContain, `"${mustContain}"`);
const queryParams = newQuery && newQuery !== '' ? { query: newQuery } : {};
const queryParamsHandling = this._dossierId ? 'merge' : '';
this._router
.navigate([], {
queryParams,
queryParamsHandling
})
.then();
}
private _search(query: string): Observable<SearchResult> {
return this._searchControllerService.search({
dossierId: this._dossierId,
queryString: query ?? '',
from: 0,
returnSections: true,
size: 100
});
}
private _updateValues({ query, dossierId }: { readonly query: string; readonly dossierId: string }) {
this._dossierId = dossierId;
this.searchService.searchValue = query;
this.search$.next(query);
}
private _getFileWrapper(dossierId: string, fileId: string): FileStatusWrapper {
return this._appStateService.getFileById(dossierId, fileId);
}
private _getDossierWrapper(dossierId: string) {
return this._appStateService.getDossierById(dossierId);
}
private _toMatchedDocuments({ matchedDocuments }: SearchResult) {
return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0);
}
private _toListItems(matchedDocuments: MatchedDocument[]) {
return matchedDocuments
.map<ListItem>(document => {
const fileStatus = this._getFileWrapper(document.dossierId, document.fileId);
if (!fileStatus) {
return undefined;
}
const { dossierId, dossierName } = this._getDossierWrapper(document.dossierId);
return {
matchedDocument: document,
unmatched: document.unmatchedTerms.length ? document.unmatchedTerms : null,
highlights: document.highlights,
status: fileStatus.status,
pages: fileStatus.numberOfPages,
dossierName: dossierName,
fileName: fileStatus.filename,
routerLink: `/main/dossiers/${dossierId}/file/${fileStatus.fileId}`
} as ListItem;
})
.filter(value => value);
}
}

View File

@ -23,8 +23,8 @@ export class PdfViewerDataService {
private readonly _viewedPagesControllerService: ViewedPagesControllerService
) {}
loadActiveFileRedactionLogPreview() {
return this._redactionLogControllerService.getRedactionLogPreview(
loadActiveFileRedactionLog() {
return this._redactionLogControllerService.getRedactionLog(
this._appStateService.activeDossierId,
this._appStateService.activeFileId
);
@ -37,9 +37,7 @@ export class PdfViewerDataService {
this._man.getManualRedaction(dossierId, fileId).subscribe();
const file$ = this.downloadOriginalFile(this._appStateService.activeFile);
const reactionLog$ = this._redactionLogControllerService
.getRedactionLogPreview(dossierId, fileId)
.pipe(catchError(() => of({})));
const reactionLog$ = this._redactionLogControllerService.getRedactionLog(dossierId, fileId).pipe(catchError(() => of({})));
const redactionChangeLog$ = this._redactionLogControllerService
.getRedactionChangeLog(dossierId, fileId)
.pipe(catchError(() => of({})));
@ -53,10 +51,7 @@ export class PdfViewerDataService {
getViewedPagesForActiveFile() {
if (this._permissionsService.canMarkPagesAsViewed()) {
return this._viewedPagesControllerService
.getViewedPages(
this._appStateService.activeDossierId,
this._appStateService.activeFileId
)
.getViewedPages(this._appStateService.activeDossierId, this._appStateService.activeFileId)
.pipe(catchError(() => of({ pages: [] })));
}
return of({ pages: [] });

View File

@ -9,10 +9,7 @@ import { DomSanitizer } from '@angular/platform-browser';
exports: [MatIconModule]
})
export class IconsModule {
constructor(
private readonly _iconRegistry: MatIconRegistry,
private readonly _sanitizer: DomSanitizer
) {
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
const icons = [
'add',
'analyse',
@ -38,6 +35,7 @@ export class IconsModule {
'download',
'edit',
'entries',
'enter',
'error',
'exclude-pages',
'exit-fullscreen',

View File

@ -17,7 +17,7 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
readonly permissionsService: PermissionsService;
readonly filterService: FilterService;
readonly sortingService: SortingService;
readonly searchService: SearchService<T>;
readonly searchService: SearchService;
readonly screenStateService: ScreenStateService<T>;
readonly sortedDisplayedEntities$: Observable<T[]>;

View File

@ -11,11 +11,7 @@
<span *ngIf="hint" [translate]="hint" class="hint"></span>
<!-- Search-->
<mat-icon
*ngIf="type === 'search' && !hasContent"
class="icon-right"
svgIcon="red:search"
></mat-icon>
<mat-icon *ngIf="type === 'search' && !hasContent" class="icon-right" svgIcon="red:search"></mat-icon>
<redaction-circle-button
(action)="clearContent()"
@ -23,8 +19,7 @@
[disabled]="form.invalid"
[size]="25"
icon="red:close"
>
</redaction-circle-button>
></redaction-circle-button>
<!-- Submit-->
<redaction-circle-button
@ -34,7 +29,6 @@
[icon]="icon"
[isSubmit]="true"
[size]="25"
>
</redaction-circle-button>
></redaction-circle-button>
</div>
</form>

View File

@ -29,7 +29,7 @@ export class InputWithActionComponent {
}
clearContent() {
this.form.patchValue({ query: '' });
this.form.patchValue({ query: '' }, { emitEvent: true });
}
executeAction($event?: MouseEvent) {

View File

@ -1,7 +1,7 @@
<div class="page-header">
<div *ngIf="pageLabel" class="breadcrumb">{{ pageLabel }}</div>
<div class="filters" *ngIf="filters$ | async as filters">
<div class="filters" [style.max-width]="computedWidth" [style.width]="computedWidth" *ngIf="filters$ | async as filters">
<div translate="filters.filter-by" *ngIf="filters.length"></div>
<ng-container *ngFor="let config of filters; trackBy: trackByLabel">
@ -16,6 +16,7 @@
</ng-container>
<redaction-input-with-action
[width]="searchWidth"
*ngIf="searchPlaceholder"
[form]="searchService.searchForm"
[placeholder]="searchPlaceholder"

View File

@ -11,17 +11,18 @@ import { combineLatest, Observable, of } from 'rxjs';
templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.scss']
})
export class PageHeaderComponent<T> {
export class PageHeaderComponent {
@Input() pageLabel: string;
@Input() showCloseButton: boolean;
@Input() actionConfigs: ActionConfig[];
@Input() buttonConfigs: ButtonConfig[];
@Input() searchPlaceholder: string;
@Input() searchWidth: number | 'full';
readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon)));
readonly showResetFilters$ = this._showResetFilters$;
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService<T>) {}
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {}
get _showResetFilters$(): Observable<boolean> {
if (!this.filterService) return of(false);
@ -36,6 +37,10 @@ export class PageHeaderComponent<T> {
);
}
get computedWidth() {
return this.searchWidth === 'full' ? '100%' : `${this.searchWidth}px`;
}
resetFilters(): void {
this.filterService.reset();
this.searchService.reset();

View File

@ -25,8 +25,8 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy {
@debounce(10)
matchWidth() {
const headerItems = this._elementRef.nativeElement.children;
// const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth);
const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
// const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth);
if (!tableRows || !tableRows.length) {
return;

View File

@ -26,7 +26,7 @@ export class ScreenStateService<T> {
readonly areSomeEntitiesSelected$ = this._areSomeEntitiesSelected$;
readonly notAllEntitiesSelected$ = this._notAllEntitiesSelected$;
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService<T>) {
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService) {
// setInterval(() => {
// console.log('All entities subs: ', this._allEntities$.observers);
// console.log('Displayed entities subs: ', this._displayedEntities$.observers);

View File

@ -3,7 +3,7 @@ import { FormBuilder } from '@angular/forms';
import { startWith } from 'rxjs/operators';
@Injectable()
export class SearchService<T> {
export class SearchService {
private _searchKey: string;
readonly searchForm = this._formBuilder.group({
@ -14,7 +14,7 @@ export class SearchService<T> {
constructor(private readonly _formBuilder: FormBuilder) {}
searchIn(entities: T[]) {
searchIn<T>(entities: T[]) {
if (!this._searchKey) return entities;
const searchValue = this.searchValue.toLowerCase();
@ -29,11 +29,15 @@ export class SearchService<T> {
return this.searchForm.get('query').value;
}
reset(): void {
this.searchForm.reset({ query: '' });
set searchValue(value: string) {
this.searchForm.patchValue({ query: value });
}
private _searchField(entity: T): string {
return entity[this._searchKey].toLowerCase();
reset(): void {
this.searchForm.reset({ query: '' }, { emitEvent: true });
}
private _searchField<T>(entity: T): string {
return entity[this._searchKey].toString().toLowerCase();
}
}

View File

@ -10,7 +10,7 @@ export class LoadingService {
private readonly _loadingEvent = new BehaviorSubject(false);
private _loadingStarted: number;
get isLoading(): Observable<boolean> {
get isLoading$(): Observable<boolean> {
return this._loadingEvent.asObservable();
}

View File

@ -10,7 +10,7 @@ export class RouterHistoryService {
constructor(private readonly _router: Router) {
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
if (event.url.startsWith('/main/dossiers')) {
if (event.url.startsWith('/main/dossiers') && !event.url.includes('/search')) {
this._lastDossiersScreen = event.url;
}
});

View File

@ -9,7 +9,7 @@ import {
ReanalysisControllerService,
StatusControllerService
} from '@redaction/red-ui-http';
import { Toaster } from '../services/toaster.service';
import { Toaster } from '@services/toaster.service';
import { TranslateService } from '@ngx-translate/core';
import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router';
import { UserService } from '@services/user.service';
@ -156,7 +156,12 @@ export class AppStateService {
}
private static _isDossierOverviewRoute(event: Event) {
return event instanceof ResolveStart && event.url.includes('/main/dossiers/') && !event.url.includes('/file/');
return (
event instanceof ResolveStart &&
event.url.includes('/main/dossiers/') &&
!event.url.includes('/file/') &&
!event.url.includes('/search')
);
}
private static _isRandomRoute(event: Event) {
@ -174,7 +179,7 @@ export class AppStateService {
dossierTemplateId = this.dossierTemplates[0]?.dossierTemplateId;
}
if (!dossierTemplateId) {
return undefined;
return '#cccccc';
}
const color = this._dictionaryData[dossierTemplateId][type]?.hexColor;
return color ?? this._dictionaryData[dossierTemplateId]['default'].hexColor;
@ -205,7 +210,7 @@ export class AppStateService {
}
getFileById(dossierId: string, fileId: string) {
return this.getDossierById(dossierId).files.find(file => file.fileId === fileId);
return this.getDossierById(dossierId)?.files.find(file => file.fileId === fileId);
}
async loadAllDossiers(emitEvents: boolean = true) {

View File

@ -1,6 +1,6 @@
{
"OAUTH_URL": "https://red-staging.iqser.cloud/auth/realms/redaction",
"API_URL": "https://red-staging.iqser.cloud/redaction-gateway-v1",
"OAUTH_URL": "https://dev-06.iqser.cloud/auth/realms/redaction",
"API_URL": "https://dev-06.iqser.cloud/redaction-gateway-v1",
"OAUTH_CLIENT_ID": "redaction",
"BACKEND_APP_VERSION": "4.4.40",
"FRONTEND_APP_VERSION": "1.1",

View File

@ -1435,5 +1435,24 @@
"hours": "hours",
"day": "day",
"days": "days"
},
"search": {
"header-label": "Search entire platform",
"placeholder": "Search for documents or document content",
"this-dossier": "in this dossier",
"entire-platform": "entire platform",
"close": "Close"
},
"search-screen": {
"table-header": "{{length}} search results",
"cols": {
"document": "Document",
"status": "Status",
"dossier": "Dossier",
"pages": "Pages"
},
"no-data": "Please enter a keyword in the search input to look for documents or document content.",
"missing": "Missing",
"must-contain": "Must contain"
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<title>B99F3D5E-879A-47E3-A8C7-84877EE418F3</title>
<g id="04.Search-all-in-Document" transform="translate(-319.000000, -144.000000)">
<g id="Group-30" transform="translate(295.000000, 121.000000)">
<g id="collapse" transform="translate(24.000000, 23.000000)" fill="currentColor"
fill-rule="nonzero">
<path
d="M2.38,0 L2.38,7.98 L10.5,7.98 L8.75,6.16 L9.73,5.18 L13.16,8.68 L9.73,12.18 L8.75,11.2 L10.5,9.38 L1.96,9.38 L1.959,9.379 L0.98,9.38 L0.98,0 L2.38,0 Z"
id="Combined-Shape"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -256,6 +256,10 @@ section.settings {
margin-left: 14px;
}
.mr-24 {
margin-right: 24px;
}
.pb-24 {
padding-bottom: 24px;
}
@ -358,6 +362,10 @@ section.settings {
margin-right: 16px;
}
.mr-34 {
margin-right: 34px;
}
.fit-content {
width: fit-content;
}

View File

@ -1,3 +1,3 @@
module.exports = {
projects: ['<rootDir>/apps/red-ui', '<rootDir>/libs/red-ui-http', '<rootDir>/libs/red-cache']
projects: ['<rootDir>/apps/red-ui', '<rootDir>/libs/red-ui-http', '<rootDir>/libs/red-cache', '<rootDir>/libs/common-ui']
};

View File

@ -0,0 +1,33 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "redaction",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "redaction",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
}

7
libs/common-ui/README.md Normal file
View File

@ -0,0 +1,7 @@
# common-ui
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test common-ui` to execute the unit tests.

View File

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

View File

@ -0,0 +1 @@
export * from './lib/common-ui.module';

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule]
})
export class CommonUiModule {}

View File

@ -0,0 +1 @@
import 'jest-preset-angular/setup-jest';

View File

@ -0,0 +1,24 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"]
},
"exclude": ["src/test-setup.ts", "**/*.spec.ts"],
"include": ["**/*.ts"]
}

View File

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

View File

@ -78,15 +78,11 @@ export class RedactionLogControllerService {
reportProgress: boolean = false
): Observable<any> {
if (dossierId === null || dossierId === undefined) {
throw new Error(
'Required parameter dossierId was null or undefined when calling getRedactionLog.'
);
throw new Error('Required parameter dossierId was null or undefined when calling getRedactionLog.');
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling getRedactionLog.'
);
throw new Error('Required parameter fileId was null or undefined when calling getRedactionLog.');
}
let headers = this.defaultHeaders;
@ -94,25 +90,20 @@ export class RedactionLogControllerService {
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken =
typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = ['application/json'];
const httpHeaderAcceptSelected: string | undefined =
this.configuration.selectHeaderAccept(httpHeaderAccepts);
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
return this.httpClient.request<RedactionChangeLog>(
'get',
`${this.basePath}/redactionChnageLog/${encodeURIComponent(
String(dossierId)
)}/${encodeURIComponent(String(fileId))}`,
`${this.basePath}/redactionChnageLog/${encodeURIComponent(String(dossierId))}/${encodeURIComponent(String(fileId))}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
@ -130,12 +121,7 @@ export class RedactionLogControllerService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getRedactionLog(
dossierId: string,
fileId: string,
observe?: 'body',
reportProgress?: boolean
): Observable<RedactionLog>;
public getRedactionLog(dossierId: string, fileId: string, observe?: 'body', reportProgress?: boolean): Observable<RedactionLog>;
public getRedactionLog(
dossierId: string,
@ -151,22 +137,13 @@ export class RedactionLogControllerService {
reportProgress?: boolean
): Observable<HttpEvent<RedactionLog>>;
public getRedactionLog(
dossierId: string,
fileId: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
public getRedactionLog(dossierId: string, fileId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (dossierId === null || dossierId === undefined) {
throw new Error(
'Required parameter dossierId was null or undefined when calling getRedactionLog1.'
);
throw new Error('Required parameter dossierId was null or undefined when calling getRedactionLog1.');
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling getRedactionLog1.'
);
throw new Error('Required parameter fileId was null or undefined when calling getRedactionLog1.');
}
let headers = this.defaultHeaders;
@ -174,101 +151,20 @@ export class RedactionLogControllerService {
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken =
typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = ['application/json'];
const httpHeaderAcceptSelected: string | undefined =
this.configuration.selectHeaderAccept(httpHeaderAccepts);
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
return this.httpClient.request<RedactionLog>(
'get',
`${this.basePath}/redactionLog/${encodeURIComponent(
String(dossierId)
)}/${encodeURIComponent(String(fileId))}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* Gets the redaction log preview
* None
* @param dossierId dossierId
* @param fileId fileId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getRedactionLogPreview(
dossierId: string,
fileId: string,
observe?: 'body',
reportProgress?: boolean
): Observable<RedactionLog>;
public getRedactionLogPreview(
dossierId: string,
fileId: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<RedactionLog>>;
public getRedactionLogPreview(
dossierId: string,
fileId: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<RedactionLog>>;
public getRedactionLogPreview(
dossierId: string,
fileId: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (dossierId === null || dossierId === undefined) {
throw new Error(
'Required parameter dossierId was null or undefined when calling getRedactionLogPreview.'
);
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling getRedactionLogPreview.'
);
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken =
typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = ['application/json'];
const httpHeaderAcceptSelected: string | undefined =
this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
return this.httpClient.request<RedactionLog>(
'get',
`${this.basePath}/sectionGrid/${encodeURIComponent(
String(dossierId)
)}/${encodeURIComponent(String(fileId))}/preview`,
`${this.basePath}/redactionLog/${encodeURIComponent(String(dossierId))}/${encodeURIComponent(String(fileId))}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
@ -286,12 +182,7 @@ export class RedactionLogControllerService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getSectionGrid(
dossierId: string,
fileId: string,
observe?: 'body',
reportProgress?: boolean
): Observable<SectionGrid>;
public getSectionGrid(dossierId: string, fileId: string, observe?: 'body', reportProgress?: boolean): Observable<SectionGrid>;
public getSectionGrid(
dossierId: string,
@ -307,22 +198,13 @@ export class RedactionLogControllerService {
reportProgress?: boolean
): Observable<HttpEvent<SectionGrid>>;
public getSectionGrid(
dossierId: string,
fileId: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
public getSectionGrid(dossierId: string, fileId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (dossierId === null || dossierId === undefined) {
throw new Error(
'Required parameter dossierId was null or undefined when calling getSectionGrid.'
);
throw new Error('Required parameter dossierId was null or undefined when calling getSectionGrid.');
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling getSectionGrid.'
);
throw new Error('Required parameter fileId was null or undefined when calling getSectionGrid.');
}
let headers = this.defaultHeaders;
@ -330,25 +212,20 @@ export class RedactionLogControllerService {
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken =
typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = ['application/json'];
const httpHeaderAcceptSelected: string | undefined =
this.configuration.selectHeaderAccept(httpHeaderAccepts);
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
return this.httpClient.request<SectionGrid>(
'get',
`${this.basePath}/sectionGrid/${encodeURIComponent(
String(dossierId)
)}/${encodeURIComponent(String(fileId))}`,
`${this.basePath}/sectionGrid/${encodeURIComponent(String(dossierId))}/${encodeURIComponent(String(fileId))}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,

View File

@ -78,3 +78,4 @@ export * from './legalBasisChangeRequest';
export * from './manualLegalBasisChange';
export * from './searchRequest';
export * from './searchResult';
export * from './matchedDocument';

View File

@ -30,6 +30,9 @@
},
"red-cache": {
"tags": []
},
"common-ui": {
"tags": []
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "2.134.0",
"version": "2.138.0",
"private": true,
"license": "MIT",
"scripts": {

Binary file not shown.

View File

@ -29,7 +29,8 @@
"@environments/*": ["apps/red-ui/src/environments/*"],
"@shared/*": ["apps/red-ui/src/app/modules/shared/*"],
"@app-config/*": ["apps/red-ui/src/app/modules/app-config/*"],
"@upload-download/*": ["apps/red-ui/src/app/modules/upload-download/*"]
"@upload-download/*": ["apps/red-ui/src/app/modules/upload-download/*"],
"@devplant/common-ui": ["libs/common-ui/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"],