RED-6412: always unsubscribe

This commit is contained in:
Dan Percic 2023-03-26 15:10:33 +03:00
parent d6c2a89c33
commit 07abfefe56
26 changed files with 504 additions and 281 deletions

View File

@ -45,12 +45,14 @@
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/recommended--extra",
"plugin:@angular-eslint/template/process-inline-templates",
"plugin:prettier/recommended"
"plugin:prettier/recommended",
"plugin:rxjs/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"rxjs/no-ignored-subscription": "error",
"@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "error",
"@angular-eslint/no-input-rename": "error",

View File

@ -1,4 +1,4 @@
import { Component, Inject, Renderer2, ViewContainerRef } from '@angular/core';
import { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
import { RouterHistoryService } from '@services/router-history.service';
import { REDDocumentViewer } from './modules/pdf-viewer/services/document-viewer.service';
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
@ -7,6 +7,8 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { ROLES } from '@users/roles';
import { AppConfig } from '@red/domain';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Subscription } from 'rxjs';
function loadCustomTheme() {
const cssFileName = getConfig<AppConfig>().THEME;
@ -27,24 +29,49 @@ function loadCustomTheme() {
selector: 'redaction-root',
templateUrl: './app.component.html',
})
export class AppComponent {
export class AppComponent implements OnDestroy {
readonly #subscription = new Subscription();
constructor(
/** ViewContainerRef needs to be injected for the color picker to work */
readonly viewContainerRef: ViewContainerRef,
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
private readonly _routerHistoryService: RouterHistoryService,
private readonly _userPreferenceService: UserPreferenceService,
userPreferenceService: UserPreferenceService,
readonly documentViewer: REDDocumentViewer,
private readonly _dossierChangesService: DossiersChangesService,
@Inject(DOCUMENT) private readonly _document: Document,
private readonly _renderer: Renderer2,
private readonly _permissionsService: IqserPermissionsService,
dossierChangesService: DossiersChangesService,
@Inject(DOCUMENT) document: Document,
renderer: Renderer2,
permissionsService: IqserPermissionsService,
private readonly _router: Router,
route: ActivatedRoute,
) {
this._renderer.addClass(this._document.body, _userPreferenceService.getTheme());
renderer.addClass(document.body, userPreferenceService.getTheme());
loadCustomTheme();
// TODO: Find a better place to initialize dossiers refresh
if (_permissionsService.has(ROLES.dossiers.read)) {
_dossierChangesService.initializeRefresh();
if (permissionsService.has(ROLES.dossiers.read)) {
const refreshSub = dossierChangesService.initializeRefresh().subscribe();
this.#subscription.add(refreshSub);
}
const sub = route.queryParamMap.subscribe(queryParams => this.#navigate(queryParams));
this.#subscription.add(sub);
}
ngOnDestroy() {
this.#subscription.unsubscribe();
}
#navigate(queryParams: ParamMap) {
if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
return this._router.navigate([], {
queryParams: {
state: null,
session_state: null,
code: null,
},
queryParamsHandling: 'merge',
});
}
}
}

View File

@ -1,7 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ActivatedRoute, Router } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
@ -132,7 +131,7 @@ export const appModuleFactory = (config: AppConfig) => {
provide: TOKEN_LOGGER_CONFIG,
useValue: {
level: environment.production ? NgxLoggerLevel.ERROR : NgxLoggerLevel.DEBUG,
enableSourceMaps: true,
enableSourceMaps: false,
timestampFormat: 'mm:ss:SSS',
disableFileDetails: true,
features: {
@ -145,7 +144,7 @@ export const appModuleFactory = (config: AppConfig) => {
enabled: false,
},
PDF: {
enabled: true,
enabled: false,
},
FILE: {
enabled: false,
@ -241,26 +240,7 @@ export const appModuleFactory = (config: AppConfig) => {
],
bootstrap: [AppComponent],
})
class AppModule {
constructor(private readonly _router: Router, private readonly _route: ActivatedRoute) {
this._configureKeyCloakRouteHandling();
}
private _configureKeyCloakRouteHandling() {
this._route.queryParamMap.subscribe(queryParams => {
if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
this._router.navigate([], {
queryParams: {
state: null,
session_state: null,
code: null,
},
queryParamsHandling: 'merge',
});
}
});
}
}
class AppModule {}
return AppModule;
};

View File

@ -6,6 +6,7 @@ import { firstValueFrom } from 'rxjs';
import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
@Injectable({ providedIn: 'root' })
export class DossierFilesGuard implements CanActivate {
@ -14,6 +15,7 @@ export class DossierFilesGuard implements CanActivate {
private readonly _filesMapService: FilesMapService,
private readonly _filesService: FilesService,
private readonly _dictionaryService: DictionaryService,
private readonly _dictionaryMapService: DossierDictionariesMapService,
private readonly _router: Router,
) {}
@ -41,8 +43,10 @@ export class DossierFilesGuard implements CanActivate {
async loadDossierData(dossierId: string, dossierTemplateId: string) {
const promises = [];
const dictionary$ = this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
promises.push(firstValueFrom(dictionary$));
if (!this._dictionaryMapService.has(dossierId)) {
const dictionary$ = this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
promises.push(firstValueFrom(dictionary$));
}
if (!this._filesMapService.has(dossierId)) {
promises.push(firstValueFrom(this._filesService.loadAll(dossierId)));

View File

@ -43,7 +43,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<IFi
constructor(
private readonly _toaster: Toaster,
private readonly _formBuilder: UntypedFormBuilder,
readonly dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
readonly dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent, boolean>,
private readonly _fileAttributesService: FileAttributesService,
@Inject(MAT_DIALOG_DATA) readonly data: IFileAttributesCSVImportData,
) {

View File

@ -123,7 +123,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
const csv = files[0];
this._fileInput.nativeElement.value = null;
const ref = this._dialog.open<FileAttributesCsvImportDialogComponent, IFileAttributesCSVImportData>(
const ref = this._dialog.open<FileAttributesCsvImportDialogComponent, IFileAttributesCSVImportData, boolean>(
FileAttributesCsvImportDialogComponent,
{
...largeDialogConfig,
@ -136,8 +136,10 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
},
);
await firstValueFrom(ref.afterClosed());
await this.#loadData();
const result = await firstValueFrom(ref.afterClosed());
if (result) {
await this.#loadData();
}
}
async openConfigurationsDialog() {

View File

@ -1,6 +1,7 @@
import { Injectable, TemplateRef } from '@angular/core';
import {
ActionConfig,
getConfig,
getCurrentUser,
getParam,
IFilterGroup,
@ -19,6 +20,7 @@ import {
import {
annotationDefaultColorConfig,
AnnotationShapeMap,
AppConfig,
DOSSIER_ID,
File,
IFileAttributeConfig,
@ -36,7 +38,6 @@ import { UserService } from '@users/user.service';
import { DossiersDialogService } from '../shared-dossiers/services/dossiers-dialog.service';
import { annotationFilterChecker, RedactionFilterSorter } from '../../utils';
import { workloadTranslations } from '@translations/workload-translations';
import { ConfigService as AppConfigService } from '@services/config.service';
import { BehaviorSubject, Observable } from 'rxjs';
import { BulkActionsService } from './services/bulk-actions.service';
import dayjs from 'dayjs';
@ -50,16 +51,16 @@ import { ROLES } from '@users/roles';
@Injectable()
export class ConfigService {
readonly listingMode$: Observable<ListingMode>;
readonly dossierId = getParam(DOSSIER_ID);
readonly currentUser = getCurrentUser<User>();
private readonly _listingMode$ = new BehaviorSubject<ListingMode>(ListingModes.table);
readonly #dossierId = getParam(DOSSIER_ID);
readonly #currentUser = getCurrentUser<User>();
readonly #config = getConfig<AppConfig>();
readonly #listingMode$: BehaviorSubject<ListingMode>;
constructor(
private readonly _permissionsService: PermissionsService,
private readonly _translateService: TranslateService,
private readonly _userService: UserService,
private readonly _dialogService: DossiersDialogService,
private readonly _appConfigService: AppConfigService,
private readonly _bulkActionsService: BulkActionsService,
private readonly _defaultColorsService: DefaultColorsService,
private readonly _dictionariesMapService: DictionariesMapService,
@ -67,23 +68,23 @@ export class ConfigService {
private readonly _dossiersService: DossiersService,
private readonly _iqserPermissionsService: IqserPermissionsService,
) {
this.listingMode$ = this._listingMode$.asObservable();
const previousListingMode = this._userPreferenceService.getFilesListingMode();
this._listingMode$.next(previousListingMode ? previousListingMode : ListingModes.table);
const listingMode = previousListingMode ? previousListingMode : ListingModes.table;
this.#listingMode$ = new BehaviorSubject<ListingMode>(listingMode);
this.listingMode$ = this.#listingMode$.asObservable();
}
get listingMode(): ListingMode {
return this._listingMode$.value;
return this.#listingMode$.value;
}
set listingMode(listingMode: ListingMode) {
this._listingMode$.next(listingMode);
this.#listingMode$.next(listingMode);
this._userPreferenceService.saveFilesListingMode(listingMode).then();
}
get #dossier() {
return this._dossiersService.find(this.dossierId);
return this._dossiersService.find(this.#dossierId);
}
workflowConfig(): WorkflowConfig<File, WorkflowFileStatus> {
@ -159,7 +160,7 @@ export class ConfigService {
label: this._translateService.instant('dossier-overview.header-actions.edit'),
action: () => this._openEditDossierDialog(dossierId),
icon: 'iqser:edit',
hide: !this.currentUser.isManager && !this._iqserPermissionsService.has(ROLES.dossiers.edit),
hide: !this.#currentUser.isManager && !this._iqserPermissionsService.has(ROLES.dossiers.edit),
helpModeKey: 'edit_dossier_in_dossier',
overlappingElements: [OverlappingElements.USER_MENU],
disabled$,
@ -403,8 +404,7 @@ export class ConfigService {
return filterGroups;
}
_recentlyModifiedChecker = (file: File) =>
dayjs(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(dayjs());
_recentlyModifiedChecker = (file: File) => dayjs(file.lastUpdated).add(this.#config.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(dayjs());
_assignedToMeChecker = (file: File) => file.assignee === this._userService.currentUser.id;
@ -413,7 +413,7 @@ export class ConfigService {
_assignedToOthersChecker = (file: File) => file.assignee && file.assignee !== this._userService.currentUser.id;
private _quickFilters(entities: File[]): NestedFilter[] {
const recentPeriod = this._appConfigService.values.RECENT_PERIOD_IN_HOURS;
const recentPeriod = this.#config.RECENT_PERIOD_IN_HOURS;
return [
{
id: 'recent',

View File

@ -53,7 +53,7 @@
</iqser-workflow>
</div>
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar *ngIf="dossierAttributes$ | async">
<div *ngIf="dossierAttributes$ | async" [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
<redaction-dossier-details
(toggleCollapse)="collapsedDetails = !collapsedDetails"
[dossierAttributes]="dossierAttributes"
@ -79,11 +79,11 @@
[file]="file"
></redaction-table-item>
</ng-template>
</ng-container>
<ng-template #needsWorkFilterTemplate let-filter="filter">
<redaction-type-filter [dossierTemplateId]="dossierTemplateId" [filter]="filter"></redaction-type-filter>
</ng-template>
<ng-template #needsWorkFilterTemplate let-filter="filter">
<redaction-type-filter [dossierTemplateId]="dossier.dossierTemplateId" [filter]="filter"></redaction-type-filter>
</ng-template>
</ng-container>
<input
#fileInput

View File

@ -1,4 +1,4 @@
import { Component, ElementRef, HostListener, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { Component, ElementRef, HostListener, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import {
Dossier,
DOSSIER_ID,
@ -26,6 +26,7 @@ import {
LoadingService,
NestedFilter,
OnAttach,
OnDetach,
shareLast,
TableColumnConfig,
TableComponent,
@ -34,7 +35,6 @@ import {
import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { PermissionsService } from '@services/permissions.service';
import { NavigationEnd, Router } from '@angular/router';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { ConfigService } from '../config.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
@ -44,14 +44,13 @@ import { FilesService } from '@services/files/files.service';
import { BulkActionsService } from '../services/bulk-actions.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
import { NGXLogger } from 'ngx-logger';
@Component({
templateUrl: './dossier-overview-screen.component.html',
styleUrls: ['./dossier-overview-screen.component.scss'],
providers: [...listingProvidersFactory(DossierOverviewScreenComponent), ConfigService, BulkActionsService, dossiersServiceProvider],
})
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnAttach {
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnAttach, OnDetach, OnDestroy {
readonly listingModes = ListingModes;
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
@ -69,10 +68,9 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
readonly dossier$: Observable<Dossier>;
readonly files$: Observable<File[]>;
readonly dossierId = getParam(DOSSIER_ID);
readonly dossierTemplateId: string;
readonly workflowConfig: WorkflowConfig<File, WorkflowFileStatus>;
readonly dossierAttributes$: Observable<DossierAttributeConfig[]>;
#currentDossier: Dossier;
#dossier: Dossier;
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef;
@ -80,8 +78,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
private _fileAttributeConfigs: IFileAttributeConfig[];
constructor(
private readonly _router: Router,
private readonly _logger: NGXLogger,
readonly configService: ConfigService,
private readonly _errorService: ErrorService,
private readonly _filesService: FilesService,
@ -98,16 +94,12 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
private readonly _dossierAttributesService: DossierAttributesService,
) {
super();
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.#currentDossier = dossier)));
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.#dossier = dossier)));
this.dossierAttributes$ = this._dossierAttributesService.all$.pipe(tap(() => this.#updateDossierAttributes()));
this.#currentDossier = _dossiersService.find(this.dossierId);
this.#dossier = _dossiersService.find(this.dossierId);
this.workflowConfig = configService.workflowConfig();
this.dossierTemplateId = this.#currentDossier.dossierTemplateId;
this.files$ = merge(this.#files$, this.#dossierFilesChange$).pipe(shareLast());
this._updateFileAttributes();
_router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
this._fileDropOverlayService.cleanupFileDropHandling();
});
this.#updateFileAttributes();
}
get checkedRequiredFilters(): NestedFilter[] {
@ -141,30 +133,30 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
this._setRemovableSubscriptions();
this.#initFileDropHandling();
this.addSubscription = this._dossierTemplatesService
.getEntityChanged$(this.dossierTemplateId)
.pipe(
skip(1),
tap(() => {
this._updateFileAttributes();
}),
)
.subscribe();
this._loadingService.stop();
}
ngOnAttach() {
this.#initFileDropHandling();
this._setRemovableSubscriptions();
this._updateFileAttributes();
this.#updateFileAttributes();
this._tableComponent?.scrollToLastIndex();
}
override ngOnDetach() {
this.#cleanupFileDropHandling();
super.ngOnDetach();
}
override ngOnDestroy() {
this.#cleanupFileDropHandling();
super.ngOnDestroy();
}
@HostListener('drop', ['$event'])
onDrop(event: DragEvent): void {
if (this.permissionsService.canUploadFiles(this.#currentDossier)) {
handleFileDrop(event, this.#currentDossier, this._uploadFiles.bind(this));
if (this.permissionsService.canUploadFiles(this.#dossier)) {
handleFileDrop(event, this.#dossier, this._uploadFiles.bind(this));
}
}
@ -175,21 +167,27 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
}
async uploadFiles(files: Files): Promise<void> {
await this._uploadFiles(convertFiles(files, this.#currentDossier));
await this._uploadFiles(convertFiles(files, this.#dossier));
(this._fileInput as any).nativeElement.value = null;
}
#cleanupFileDropHandling() {
if (this.permissionsService.canUploadFiles(this.#dossier)) {
this._fileDropOverlayService.cleanupFileDropHandling();
}
}
#initFileDropHandling(): void {
if (this.permissionsService.canUploadFiles(this.#currentDossier)) {
if (this.permissionsService.canUploadFiles(this.#dossier)) {
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
}
}
async #updateDossierAttributes(): Promise<void> {
try {
this.dossierAttributes = await this._dossierAttributesService.getWithValues(this.#currentDossier);
this.dossierAttributes = await this._dossierAttributesService.getWithValues(this.#dossier);
} catch (e) {
this._logger.error('[DOSSIER ATTRIBUTES] Error: ', e);
console.error('[DOSSIER ATTRIBUTES] Error: ', e);
}
}
@ -198,6 +196,14 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
.getEntityDeleted$(this.dossierId)
.pipe(tap(() => this._handleDeletedDossier()))
.subscribe();
this.addActiveScreenSubscription = this._dossierTemplatesService
.getEntityChanged$(this.#dossier.dossierTemplateId)
.pipe(
skip(1),
tap(() => this.#updateFileAttributes()),
)
.subscribe();
}
private _handleDeletedDossier(): void {
@ -206,8 +212,9 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
);
}
private _updateFileAttributes(): void {
this._fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(this.dossierTemplateId)?.fileAttributeConfigs || [];
#updateFileAttributes() {
const attributes = this._fileAttributesService.getFileAttributeConfig(this.#dossier.dossierTemplateId);
this._fileAttributeConfigs = attributes?.fileAttributeConfigs || [];
this.displayedInFileListAttributes = this._fileAttributeConfigs.filter(config => config.displayedInFileList);
this.displayedAttributes = this.displayedInFileListAttributes.filter(c => c.displayedInFileList);
this.displayedWorkflowAttributes = this.#getDisplayedWorkflowAttributes(this.displayedAttributes);
@ -235,7 +242,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
const filterGroups = this.configService.filterGroups(
this.entitiesService.all,
this._fileAttributeConfigs,
this.dossierTemplateId,
this.#dossier.dossierTemplateId,
this._needsWorkFilterTemplate,
() => this.checkedRequiredFilters,
() => this.checkedNotRequiredFilters,

View File

@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnDestroy, ViewChild } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { MatDialogState } from '@angular/material/dialog';
import { MatDialog } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import {
AutoUnsubscribe,
@ -60,6 +60,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
constructor(
readonly filterService: FilterService,
readonly skippedService: SkippedService,
private readonly _dialog: MatDialog,
readonly state: FilePreviewStateService,
readonly pdf: PdfViewer,
readonly fileDataService: FileDataService,
@ -186,7 +187,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
handleKeyEvent($event: KeyboardEvent): void {
if (
!ALL_HOTKEY_ARRAY.includes($event.key) ||
this.state.dialogRef?.getState() === MatDialogState.OPEN ||
this._dialog.openDialogs.length ||
($event.target as IqserEventTarget).localName === 'input'
) {
return;

View File

@ -2,11 +2,10 @@ import { Component, Inject, OnInit } from '@angular/core';
import { BaseDialogComponent, CircleButtonTypes } from '@iqser/common-ui';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { RssService } from '@services/files/rss.service';
import { IFile, RssEntry, RssResult } from '@red/domain';
import { IFile, RssEntry } from '@red/domain';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { FilesMapService } from '@services/files/files-map.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { KeyValue } from '@angular/common';
interface RssData {
file: IFile;
@ -35,14 +34,14 @@ export class RssDialogComponent extends BaseDialogComponent implements OnInit {
await this.#loadData();
}
originalOrder = (a: KeyValue<string, RssResult>, b: KeyValue<string, RssResult>): number => 0;
originalOrder = (): number => 0;
exportJSON() {
this._rssService.exportJSON(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename).subscribe();
return firstValueFrom(this._rssService.exportJSON(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename));
}
exportXML() {
this._rssService.exportXML(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename).subscribe();
return firstValueFrom(this._rssService.exportXML(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename));
}
async exportAllInDossier() {
@ -53,8 +52,8 @@ export class RssDialogComponent extends BaseDialogComponent implements OnInit {
}
}
save(): void {
this.exportJSON();
save() {
return this.exportJSON();
}
async undo(originalKey: string) {

View File

@ -13,6 +13,7 @@ import {
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import {
AutoUnsubscribe,
Bind,
bool,
CircleButtonTypes,
ConfirmOption,
@ -29,9 +30,10 @@ import {
OnAttach,
OnDetach,
processFilters,
shareDistinctLast,
Toaster,
} from '@iqser/common-ui';
import { MatDialogState } from '@angular/material/dialog';
import { MatDialog } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
@ -88,7 +90,10 @@ export class FilePreviewScreenComponent
fullScreen = false;
readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId;
readonly file$ = this.state.file$.pipe(tap(file => this._fileDataService.loadAnnotations(file)));
readonly file$ = this.state.file$.pipe(
tap(file => this._fileDataService.loadAnnotations(file)),
shareDistinctLast(),
);
width: number;
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
@ -134,19 +139,9 @@ export class FilePreviewScreenComponent
private readonly _readableRedactionsService: ReadableRedactionsService,
private readonly _helpModeService: HelpModeService,
private readonly _suggestionsService: SuggestionsService,
private readonly _dialog: MatDialog,
) {
super();
document.documentElement.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
this.fullScreen = false;
}
});
this.pdf.instance.UI.hotkeys.on('command+f, ctrl+f', e => {
e.preventDefault();
this.pdf.focusSearch();
this.pdf.activateSearch();
});
}
get changed() {
@ -271,9 +266,22 @@ export class FilePreviewScreenComponent
this._viewerHeaderService.resetCompareButtons();
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
super.ngOnDetach();
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
this._changeRef.markForCheck();
}
ngOnDestroy() {
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
super.ngOnDestroy();
}
@Bind()
fullscreenListener() {
if (!document.fullscreenElement) {
this.fullScreen = false;
}
}
async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
if (!this.state.file.canBeOpened) {
return this._navigateToDossier();
@ -282,7 +290,6 @@ export class FilePreviewScreenComponent
this._viewModeService.switchToStandard();
await this.ngOnInit();
await this._fileDataService.loadRedactionLog();
this._viewerHeaderService.updateElements();
const page = previousRoute.queryParams.page ?? '1';
await this.#updateQueryParamsPage(Number(page));
@ -306,6 +313,7 @@ export class FilePreviewScreenComponent
}
this.pdfProxyService.configureElements();
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
}
ngAfterViewInit() {
@ -319,7 +327,7 @@ export class FilePreviewScreenComponent
return this._ngZone.run(() => {
const file = this.state.file;
this.state.dialogRef = this._dialogService.openDialog(
this._dialogService.openDialog(
'manualAnnotation',
{ manualRedactionEntryWrapper, dossierId: this.dossierId, file },
(result: { annotations: ManualRedactionEntryWrapper[]; dictionary?: Dictionary }) => {
@ -357,7 +365,7 @@ export class FilePreviewScreenComponent
return;
}
if (!ALL_HOTKEYS.includes($event.key) || this.state.dialogRef?.getState() === MatDialogState.OPEN) {
if (!ALL_HOTKEYS.includes($event.key) || this._dialog.openDialogs.length) {
return;
}
@ -550,24 +558,10 @@ export class FilePreviewScreenComponent
return selectToDrawIfDoesNotExist(newAnnotation);
}
if (this.userPreferenceService.areDevFeaturesEnabled) {
this.#logDiff(oldAnnotation, newAnnotation);
}
return true;
});
}
#logDiff(oldAnnotation: AnnotationWrapper, newAnnotation: AnnotationWrapper) {
import('@iqser/common-ui').then(commonUi => {
this._logger.info('[ANNOTATIONS] Changed annotation: ', {
value: oldAnnotation.value,
before: commonUi.deepDiffObj(newAnnotation, oldAnnotation),
after: commonUi.deepDiffObj(oldAnnotation, newAnnotation),
});
});
}
private _setExcludedPageStyles() {
const file = this._filesMapService.get(this.dossierId, this.fileId);
setTimeout(() => {
@ -619,6 +613,7 @@ export class FilePreviewScreenComponent
this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().subscribe();
this.addActiveScreenSubscription = this.state.dossierFileChange$.subscribe();
this.addActiveScreenSubscription = this.state.dossierDictionary$.subscribe();
this.addActiveScreenSubscription = this.state.blob$
.pipe(

View File

@ -1,13 +1,16 @@
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Injectable } from '@angular/core';
import { Injectable, OnDestroy } from '@angular/core';
import { EntitiesService, FilterService, ListingService, SearchService, SortingService } from '@iqser/common-ui';
import { filter, tap } from 'rxjs/operators';
import { MultiSelectService } from './multi-select.service';
import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { Subscription } from 'rxjs';
@Injectable()
export class AnnotationsListingService extends ListingService<AnnotationWrapper> {
export class AnnotationsListingService extends ListingService<AnnotationWrapper> implements OnDestroy {
readonly #subscriptions: Subscription;
constructor(
protected readonly _filterService: FilterService,
protected readonly _searchService: SearchService<AnnotationWrapper>,
@ -19,7 +22,7 @@ export class AnnotationsListingService extends ListingService<AnnotationWrapper>
) {
super(_filterService, _searchService, _entitiesService, _sortingService);
this.selectedLength$
this.#subscriptions = this.selectedLength$
.pipe(
filter(length => length > 1),
tap(() => this._multiSelectService.activate()),
@ -27,6 +30,10 @@ export class AnnotationsListingService extends ListingService<AnnotationWrapper>
.subscribe();
}
ngOnDestroy() {
this.#subscriptions.unsubscribe();
}
selectAnnotations(annotations: AnnotationWrapper[] | AnnotationWrapper) {
annotations = Array.isArray(annotations) ? annotations : [annotations];
const pageNumber = annotations[annotations.length - 1].pageNumber;

View File

@ -10,9 +10,9 @@ import {
ViewModes,
} from '@red/domain';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { BehaviorSubject, firstValueFrom, iif, Observable, Subject } from 'rxjs';
import { BehaviorSubject, firstValueFrom, iif, Observable, Subject, Subscription } from 'rxjs';
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
import { Injectable } from '@angular/core';
import { Injectable, OnDestroy } from '@angular/core';
import { FilePreviewStateService } from './file-preview-state.service';
import { ViewedPagesService } from '@services/files/viewed-pages.service';
import { UserPreferenceService } from '@users/user-preference.service';
@ -43,7 +43,7 @@ function chronologicallyBy<T>(property: (x: T) => string) {
}
@Injectable()
export class FileDataService extends EntitiesService<AnnotationWrapper, AnnotationWrapper> {
export class FileDataService extends EntitiesService<AnnotationWrapper, AnnotationWrapper> implements OnDestroy {
missingTypes = new Set<string>();
readonly annotations$: Observable<AnnotationWrapper[]>;
readonly earmarks$: Observable<Map<number, AnnotationWrapper[]>>;
@ -51,6 +51,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
readonly #redactionLog$ = new Subject<IRedactionLog>();
readonly #earmarks$ = new BehaviorSubject<Map<number, AnnotationWrapper[]>>(new Map());
#originalViewedPages: ViewedPage[] = [];
readonly #subscription: Subscription;
constructor(
private readonly _state: FilePreviewStateService,
@ -72,7 +73,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
super();
this.annotations$ = this.#annotations$;
this.earmarks$ = this.#earmarks$.asObservable();
this._viewModeService.viewMode$
this.#subscription = this._viewModeService.viewMode$
.pipe(
switchMap(viewMode =>
iif(
@ -103,6 +104,10 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
);
}
ngOnDestroy() {
this.#subscription.unsubscribe();
}
setEntities(entities: AnnotationWrapper[]): void {
// this is a light version of setEntities to skip looping too much
// used mostly for earmarks (which are usually a lot)

View File

@ -13,7 +13,6 @@ import { FilesService } from '@services/files/files.service';
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { MatDialogRef } from '@angular/material/dialog';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
const ONE_MEGABYTE = 1024 * 1024;
@ -42,14 +41,19 @@ export class FilePreviewStateService {
readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly fileId = getParam(FILE_ID);
dossier: Dossier;
dialogRef: MatDialogRef<unknown>;
file: File;
readonly dossierDictionary$: Observable<Dictionary>;
#dossierDictionary: Dictionary;
readonly #reloadBlob$ = new Subject();
// readonly #routeKey = getReusableRouteKey(inject(ActivatedRoute).snapshot);
// readonly isAttached = inject(CustomRouteReuseStrategy).attached$.pipe(
// map(route => getReusableRouteKey(route) === this.#routeKey),
// startWith(true),
// );
constructor(
router: Router,
filesMapService: FilesMapService,
private readonly _filesMapService: FilesMapService,
private readonly _injector: Injector,
private readonly _permissionsService: PermissionsService,
private readonly _filesService: FilesService,
@ -62,7 +66,13 @@ export class FilePreviewStateService {
) {
const dossiersService = dossiersServiceResolver(_injector, router);
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.dossier = dossier)));
this.file$ = filesMapService.watch$(this.dossierId, this.fileId).pipe(tap(file => (this.file = file)));
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId).pipe(tap(file => (this.file = file)));
// this.file$ = combineLatest([this.isAttached, file$]).pipe(
// filter(([isAttached]) => isAttached),
// map(([, file]) => file),
// log('file$'),
// shareDistinctLast(),
// );
[this.isReadonly$, this.isWritable$] = boolFactory(
combineLatest([this.file$, this.dossier$]),
([file, dossier]) => !_permissionsService.canPerformAnnotationActions(file, dossier),
@ -71,17 +81,13 @@ export class FilePreviewStateService {
this.blob$ = this.#blob$;
this.dossierFileChange$ = this.#dossierFilesChange$();
this._dossierDictionariesMapService
this.dossierDictionary$ = this._dossierDictionariesMapService
.watch$(this.dossierId, 'dossier_redaction')
.subscribe(dictionary => (this.#dossierDictionary = dictionary));
}
get dossierDictionary(): Dictionary {
return this.#dossierDictionary;
.pipe(tap(dictionary => (this.#dossierDictionary = dictionary)));
}
get dictionaries(): Dictionary[] {
return this._dictionariesMapService.get(this.dossierTemplateId).concat([this.dossierDictionary]);
return this._dictionariesMapService.get(this.dossierTemplateId).concat([this.#dossierDictionary]);
}
get blob(): Promise<Blob> {

View File

@ -32,6 +32,11 @@ export class REDAnnotationManager {
return this.#hidden$.value;
}
get #annotationSelected$() {
const onSelect$ = fromEvent<[Annotation[], string]>(this.#manager, 'annotationSelected');
return onSelect$.pipe(tap(value => console.log('Annotation selected: ', value)));
}
addToHidden(value: string) {
this.#hidden$.next(new Set([...this.hidden, value]));
}
@ -40,11 +45,6 @@ export class REDAnnotationManager {
this.#hidden$.next(new Set([...this.hidden].filter(v => v !== value)));
}
get #annotationSelected$() {
const onSelect$ = fromEvent<[Annotation[], string]>(this.#manager, 'annotationSelected');
return onSelect$.pipe(tap(value => console.log('Annotation selected: ', value)));
}
init(annotationManager: AnnotationManager) {
this.#manager = annotationManager;
this.annotationSelected$ = this.#annotationSelected$;

View File

@ -173,6 +173,7 @@ export class PdfViewer {
this.#configureElements();
this.#disableHotkeys();
this.#clearSearchResultsWhenVisibilityChanged();
this.#listenForCommandF();
return this.#instance;
}
@ -249,6 +250,14 @@ export class PdfViewer {
this.#instance.UI.textPopup.update([...popups, this.#searchButton, { dataElement: 'copyTextButton' }]);
}
#listenForCommandF() {
this.#instance.UI.hotkeys.on('command+f, ctrl+f', e => {
e.preventDefault();
this.focusSearch();
this.activateSearch();
});
}
#adjustPage(page: number) {
if (this.isCompare) {
if (page % 2 === 1) {

View File

@ -48,8 +48,8 @@ export class FileDropOverlayService {
}
openFileDropOverlay() {
const component = new ComponentPortal(FileDropComponent, null, this._createInjector());
if (!this._dropOverlayRef.hasAttached()) {
const component = new ComponentPortal(FileDropComponent, null, this._createInjector());
this._dropOverlayRef.attach(component);
}
}

View File

@ -13,7 +13,6 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
@Injectable({ providedIn: 'root' })
export class DossiersChangesService extends GenericService<Dossier> {
protected readonly _defaultModelPath = 'dossier';
#initializedRefresh = false;
readonly #activeDossiersService = inject(ActiveDossiersService);
readonly #archivedDossiersService = inject(ArchivedDossiersService);
@ -22,8 +21,8 @@ export class DossiersChangesService extends GenericService<Dossier> {
loadOnlyChanged(): Observable<IDossierChanges> {
const removeIfNotFound = (id: string) =>
catchError((error: HttpErrorResponse) => {
if (error.status === HttpStatusCode.NotFound) {
catchError((error: unknown) => {
if (error instanceof HttpErrorResponse && error.status === HttpStatusCode.NotFound) {
this.#activeDossiersService.remove(id);
this.#archivedDossiersService.remove(id);
return of([]);
@ -52,19 +51,13 @@ export class DossiersChangesService extends GenericService<Dossier> {
}
initializeRefresh() {
if (this.#initializedRefresh) {
return;
}
this.#initializedRefresh = true;
timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
.pipe(
switchMap(() => this.loadOnlyChanged()),
tap(changes => {
this.#activeDossiersService.emitFileChanges(changes);
this.#archivedDossiersService.emitFileChanges(changes);
}),
)
.subscribe();
return timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL).pipe(
switchMap(() => this.loadOnlyChanged()),
tap(changes => {
this.#activeDossiersService.emitFileChanges(changes);
this.#archivedDossiersService.emitFileChanges(changes);
}),
);
}
private _load(id: string): Observable<DossierStats[]> {

View File

@ -1,25 +1,23 @@
import { EventEmitter, Injectable } from '@angular/core';
import { EventEmitter, Injectable, OnDestroy } from '@angular/core';
import { ActiveDossiersService } from './active-dossiers.service';
import { ArchivedDossiersService } from './archived-dossiers.service';
import { firstValueFrom, forkJoin, merge } from 'rxjs';
import { map, skip, take } from 'rxjs/operators';
import { firstValueFrom, merge, Subscription } from 'rxjs';
import { skip, tap } from 'rxjs/operators';
import { Dossier } from '@red/domain';
@Injectable({
providedIn: 'root',
})
export class DossiersCacheService {
export class DossiersCacheService implements OnDestroy {
readonly changed$ = new EventEmitter<void>();
private _dossiers: Dossier[] = JSON.parse(localStorage.getItem('dossiers')) || [];
readonly #subscriptions = new Subscription();
constructor(
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _archivedDossiersService: ArchivedDossiersService,
) {
// Skip 1 to avoid clearing the cache when the dossier services are initialized
merge(_activeDossiersService.all$.pipe(skip(1)), _archivedDossiersService.all$.pipe(skip(1))).subscribe(() => {
this.set();
});
this.#subscriptions.add(this.#init().subscribe());
}
get empty(): boolean {
@ -30,23 +28,31 @@ export class DossiersCacheService {
return this._dossiers;
}
ngOnDestroy() {
this.#subscriptions.unsubscribe();
}
async load(): Promise<void> {
await firstValueFrom(
forkJoin([this._activeDossiersService.loadAll().pipe(take(1)), this._archivedDossiersService.loadAll().pipe(take(1))]).pipe(
map(list => list.flat()),
),
);
const activeDossiers = firstValueFrom(this._activeDossiersService.loadAll());
const archivedDossiers = firstValueFrom(this._archivedDossiersService.loadAll());
await Promise.all([activeDossiers, archivedDossiers]);
this.set();
}
set(): void {
const dossiers = [this._activeDossiersService.all, this._archivedDossiersService.all].flat();
this._dossiers = dossiers;
localStorage.setItem('dossiers', JSON.stringify(dossiers));
this._dossiers = [this._activeDossiersService.all, this._archivedDossiersService.all].flat();
localStorage.setItem('dossiers', JSON.stringify(this._dossiers));
this.changed$.emit();
}
get(dossierId: string) {
return this._dossiers.find(dossier => dossier.id === dossierId);
}
#init() {
// Skip 1 to avoid clearing the cache when the dossier services are initialized
const activeDossiers = this._activeDossiersService.all$.pipe(skip(1));
const archivedDossiers = this._archivedDossiersService.all$.pipe(skip(1));
return merge(activeDossiers, archivedDossiers).pipe(tap(() => this.set()));
}
}

View File

@ -6,7 +6,6 @@ import { inject } from '@angular/core';
import { DossierStatsService } from './dossier-stats.service';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { NGXLogger } from 'ngx-logger';
import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service';
const CONFLICT_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
@ -18,7 +17,6 @@ export abstract class DossiersService extends EntitiesService<IDossier, Dossier>
protected readonly _dossierStatsService = inject(DossierStatsService);
protected readonly _dashboardStatsService = inject(DashboardStatsService);
protected readonly _toaster = inject(Toaster);
protected readonly _logger = inject(NGXLogger);
protected abstract readonly _defaultModelPath: string;
protected readonly _entityClass = Dossier;

View File

@ -1,7 +1,7 @@
import { Inject, Injectable } from '@angular/core';
import { Inject, Injectable, OnDestroy } from '@angular/core';
import { BASE_HREF, EntitiesService, getConfig, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { EMPTY, firstValueFrom, iif, Observable, of, timer } from 'rxjs';
import { EMPTY, firstValueFrom, iif, merge, Observable, of, Subscription, timer } from 'rxjs';
import { AppConfig, Dossier, INotification, Notification, NotificationTypes } from '@red/domain';
import { map, switchMap, tap } from 'rxjs/operators';
import { notificationsTranslations } from '@translations/notifications-translations';
@ -20,11 +20,12 @@ const NOTIFICATIONS_THRESHOLD = 1000;
@Injectable({
providedIn: 'root',
})
export class NotificationsService extends EntitiesService<INotification, Notification> {
export class NotificationsService extends EntitiesService<INotification, Notification> implements OnDestroy {
protected readonly _defaultModelPath = 'notification';
protected readonly _entityClass = Notification;
readonly #config = getConfig<AppConfig>();
readonly #subscription = new Subscription();
constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
@ -34,20 +35,15 @@ export class NotificationsService extends EntitiesService<INotification, Notific
) {
super();
const prepareDossierCache = this._dossiersCacheService.empty ? of(this._dossiersCacheService.load()) : of(null);
prepareDossierCache.pipe(switchMap(() => this.loadAll())).subscribe();
if (this._dossiersCacheService.empty) {
this._dossiersCacheService.load().then(async () => await firstValueFrom(this.loadAll()));
}
timer(0, CHANGED_CHECK_INTERVAL)
.pipe(
switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
switchMap(() => this.#loadNotificationsIfChanged()),
)
.subscribe();
this.#subscription.add(this.#initTimerAndChanges().subscribe());
}
// Rebuild notifications when cached dossiers are updated
this._dossiersCacheService.changed$.subscribe(() => {
this.setEntities(this.all.map(e => this._new(e)));
});
ngOnDestroy() {
this.#subscription.unsubscribe();
}
@Validate()
@ -71,6 +67,17 @@ export class NotificationsService extends EntitiesService<INotification, Notific
);
}
#initTimerAndChanges() {
const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe(
switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
switchMap(() => this.#loadNotificationsIfChanged()),
);
// Rebuild notifications when cached dossiers are updated
const changed$ = this._dossiersCacheService.changed$.pipe(tap(() => this.setEntities(this.all.map(e => this._new(e)))));
return merge(timer$, changed$);
}
#filterNotifications(notifications: Notification[]): Notification[] {
const todayDate = dayjs(new Date());

@ -1 +1 @@
Subproject commit e1f818c1094fa4a8780e7907e04cc48176db3ac6
Subproject commit b4156074eb1951dfaa7e53aeec5611e466d817eb

View File

@ -14,7 +14,8 @@
},
"cli": {
"analytics": false,
"packageManager": "yarn"
"packageManager": "yarn",
"schematicCollections": ["@angular-eslint/schematics"]
},
"defaultProject": "red-ui",
"generators": {
@ -33,6 +34,12 @@
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json", "{workspaceRoot}/.eslintignore"]
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
}
},
"namedInputs": {

View File

@ -15,7 +15,8 @@
"migrate": "nx migrate --run-migrations",
"workspace-generator": "nx workspace-generator",
"analyze": "ng build --stats-json && webpack-bundle-analyzer dist/apps/red-ui/stats.json",
"prepare": "husky install"
"prepare": "husky install",
"lint": "ng lint"
},
"lint-staged": {
"*": "prettier --ignore-unknown --write",
@ -63,8 +64,10 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "15.1.4",
"@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "~15.1.0",
"@angular/compiler-cli": "15.1.2",
@ -78,13 +81,14 @@
"@types/jest": "^29.4.0",
"@types/lodash-es": "^4.17.6",
"@types/node": "18.15.1",
"@typescript-eslint/eslint-plugin": "5.54.1",
"@typescript-eslint/parser": "5.54.1",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"axios": "^1.3.4",
"dotenv": "16.0.3",
"eslint": "8.36.0",
"eslint": "^8.33.0",
"eslint-config-prettier": "8.7.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-rxjs": "^5.0.2",
"google-translate-api-browser": "^4.0.6",
"husky": "^8.0.3",
"jest": "^29.5.0",

300
yarn.lock
View File

@ -115,6 +115,11 @@
ora "5.4.1"
rxjs "6.6.7"
"@angular-eslint/builder@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-eslint/builder/-/builder-15.2.1.tgz#ce8c65e3b671897db75ad90b41ef4cd6efe626f0"
integrity sha512-7x2DANebLRl997Mj4DhZrnz5+vnSjavGGveJ0mBuU7CEsL0ZYLftdRqL0e0HtU3ksseS7xpchD6OM08nkNgySw==
"@angular-eslint/bundled-angular-compiler@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-15.2.1.tgz#7c77a4a19942868d844372b5b3b562c0d630de1e"
@ -140,6 +145,17 @@
"@angular-eslint/utils" "15.2.1"
"@typescript-eslint/utils" "5.48.2"
"@angular-eslint/schematics@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-eslint/schematics/-/schematics-15.2.1.tgz#f562e1b8b0824ade1cfdc5bbabab26c50510a7f1"
integrity sha512-0ZfBCejHWIcgy3J5kFs9sS/jqi8i5AptxggOwFySOlCLJ+CzNrktjD4jff1Zy8K/VLzY0Ci0BSZXvgWfP0k9Rg==
dependencies:
"@angular-eslint/eslint-plugin" "15.2.1"
"@angular-eslint/eslint-plugin-template" "15.2.1"
ignore "5.2.4"
strip-json-comments "3.1.1"
tmp "0.2.1"
"@angular-eslint/template-parser@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-eslint/template-parser/-/template-parser-15.2.1.tgz#dbe4978afdcea81b9d5cac3d672c20de5821dc54"
@ -446,6 +462,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/generator@^7.21.3":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce"
integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==
dependencies:
"@babel/types" "^7.21.3"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@7.18.6", "@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@ -549,6 +575,14 @@
"@babel/template" "^7.18.10"
"@babel/types" "^7.19.0"
"@babel/helper-function-name@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4"
integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==
dependencies:
"@babel/template" "^7.20.7"
"@babel/types" "^7.21.0"
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@ -728,6 +762,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
"@babel/parser@^7.10.3", "@babel/parser@^7.21.3":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
"@babel/parser@^7.19.3", "@babel/parser@^7.20.13", "@babel/parser@^7.20.7":
version "7.20.13"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.13.tgz#ddf1eb5a813588d2fb1692b70c6fce75b945c088"
@ -1459,6 +1498,22 @@
"@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/traverse@^7.10.3":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67"
integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.21.3"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.21.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.21.3"
"@babel/types" "^7.21.3"
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.16.0", "@babel/traverse@^7.19.3", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7":
version "7.20.13"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473"
@ -1500,6 +1555,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@babel/types@^7.10.3", "@babel/types@^7.21.0", "@babel/types@^7.21.3":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05"
integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==
dependencies:
"@babel/helper-string-parser" "^7.19.4"
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@babel/types@^7.19.3", "@babel/types@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
@ -2448,7 +2512,7 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.17"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
@ -4074,6 +4138,13 @@
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==
"@types/yargs@^17.0.0":
version "17.0.23"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.23.tgz#a7db3a2062c95ca1a5e0d5d5ddb6521cbc649e35"
integrity sha512-yuogunc04OnzGQCrfHx+Kk883Q4X0aSwmYZhKjI21m+SVYzjIbrWl8dOOwSv5hf2Um2pdCOXWo9isteZTNXUZQ==
dependencies:
"@types/yargs-parser" "*"
"@types/yargs@^17.0.8":
version "17.0.17"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.17.tgz#5672e5621f8e0fca13f433a8017aae4b7a2a03e7"
@ -4081,30 +4152,36 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz#0c5091289ce28372e38ab8d28e861d2dbe1ab29e"
integrity sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==
"@typescript-eslint/eslint-plugin@5.48.2":
version "5.48.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz#112e6ae1e23a1dc8333ce82bb9c65c2608b4d8a3"
integrity sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==
dependencies:
"@typescript-eslint/scope-manager" "5.54.1"
"@typescript-eslint/type-utils" "5.54.1"
"@typescript-eslint/utils" "5.54.1"
"@typescript-eslint/scope-manager" "5.48.2"
"@typescript-eslint/type-utils" "5.48.2"
"@typescript-eslint/utils" "5.48.2"
debug "^4.3.4"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
natural-compare-lite "^1.4.0"
regexpp "^3.2.0"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/parser@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.1.tgz#05761d7f777ef1c37c971d3af6631715099b084c"
integrity sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==
"@typescript-eslint/experimental-utils@^5.0.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.56.0.tgz#2699b5eed7651cc361ac1e6ea2d31a0e51e989a5"
integrity sha512-sxWuj0eO5nItmKgZmsBbChVt90EhfkuncDCPbLAVeEJ+SCjXMcZN3AhhNbxed7IeGJ4XwsdL3/FMvD4r+FLqqA==
dependencies:
"@typescript-eslint/scope-manager" "5.54.1"
"@typescript-eslint/types" "5.54.1"
"@typescript-eslint/typescript-estree" "5.54.1"
"@typescript-eslint/utils" "5.56.0"
"@typescript-eslint/parser@5.48.2":
version "5.48.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.2.tgz#c9edef2a0922d26a37dba03be20c5fff378313b3"
integrity sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==
dependencies:
"@typescript-eslint/scope-manager" "5.48.2"
"@typescript-eslint/types" "5.48.2"
"@typescript-eslint/typescript-estree" "5.48.2"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.46.1":
@ -4123,13 +4200,13 @@
"@typescript-eslint/types" "5.48.2"
"@typescript-eslint/visitor-keys" "5.48.2"
"@typescript-eslint/scope-manager@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz#6d864b4915741c608a58ce9912edf5a02bb58735"
integrity sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==
"@typescript-eslint/scope-manager@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz#62b4055088903b5254fa20403010e1c16d6ab725"
integrity sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==
dependencies:
"@typescript-eslint/types" "5.54.1"
"@typescript-eslint/visitor-keys" "5.54.1"
"@typescript-eslint/types" "5.56.0"
"@typescript-eslint/visitor-keys" "5.56.0"
"@typescript-eslint/type-utils@5.48.2":
version "5.48.2"
@ -4141,16 +4218,6 @@
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/type-utils@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz#4825918ec27e55da8bb99cd07ec2a8e5f50ab748"
integrity sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==
dependencies:
"@typescript-eslint/typescript-estree" "5.54.1"
"@typescript-eslint/utils" "5.54.1"
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types@5.46.1":
version "5.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.46.1.tgz#4e9db2107b9a88441c4d5ecacde3bb7a5ebbd47e"
@ -4161,10 +4228,10 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.2.tgz#635706abb1ec164137f92148f06f794438c97b8e"
integrity sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==
"@typescript-eslint/types@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.54.1.tgz#29fbac29a716d0f08c62fe5de70c9b6735de215c"
integrity sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==
"@typescript-eslint/types@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.56.0.tgz#b03f0bfd6fa2afff4e67c5795930aff398cbd834"
integrity sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==
"@typescript-eslint/typescript-estree@5.46.1":
version "5.46.1"
@ -4192,13 +4259,13 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz#df7b6ae05fd8fef724a87afa7e2f57fa4a599be1"
integrity sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==
"@typescript-eslint/typescript-estree@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz#48342aa2344649a03321e74cab9ccecb9af086c3"
integrity sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==
dependencies:
"@typescript-eslint/types" "5.54.1"
"@typescript-eslint/visitor-keys" "5.54.1"
"@typescript-eslint/types" "5.56.0"
"@typescript-eslint/visitor-keys" "5.56.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
@ -4219,18 +4286,18 @@
eslint-utils "^3.0.0"
semver "^7.3.7"
"@typescript-eslint/utils@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.54.1.tgz#7a3ee47409285387b9d4609ea7e1020d1797ec34"
integrity sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==
"@typescript-eslint/utils@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.56.0.tgz#db64705409b9a15546053fb4deb2888b37df1f41"
integrity sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.54.1"
"@typescript-eslint/types" "5.54.1"
"@typescript-eslint/typescript-estree" "5.54.1"
"@typescript-eslint/scope-manager" "5.56.0"
"@typescript-eslint/types" "5.56.0"
"@typescript-eslint/typescript-estree" "5.56.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
semver "^7.3.7"
"@typescript-eslint/utils@^5.36.1":
@ -4263,12 +4330,12 @@
"@typescript-eslint/types" "5.48.2"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.54.1":
version "5.54.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz#d7a8a0f7181d6ac748f4d47b2306e0513b98bf8b"
integrity sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==
"@typescript-eslint/visitor-keys@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz#f19eb297d972417eb13cb69b35b3213e13cc214f"
integrity sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==
dependencies:
"@typescript-eslint/types" "5.54.1"
"@typescript-eslint/types" "5.56.0"
eslint-visitor-keys "^3.3.0"
"@webassemblyjs/ast@1.11.1":
@ -4923,6 +4990,15 @@ batch@0.6.1:
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
bent@~7.3.6:
version "7.3.12"
resolved "https://registry.yarnpkg.com/bent/-/bent-7.3.12.tgz#e0a2775d4425e7674c64b78b242af4f49da6b035"
integrity sha512-T3yrKnVGB63zRuoco/7Ybl7BwwGZR0lceoVG5XmQyMIH9s19SV5m+a8qam4if0zQuAmOQTyPTPmsQBdAorGK3w==
dependencies:
bytesish "^0.4.1"
caseless "~0.12.0"
is-stream "^2.0.0"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -5096,6 +5172,11 @@ bytes@3.1.2:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
bytesish@^0.4.1:
version "0.4.4"
resolved "https://registry.yarnpkg.com/bytesish/-/bytesish-0.4.4.tgz#f3b535a0f1153747427aee27256748cff92347e6"
integrity sha512-i4uu6M4zuMUiyfZN4RU2+i9+peJh//pXhd9x1oSe1LBkZ3LEbCoygu8W0bXTukU1Jme2txKuotpCZRaC3FLxcQ==
cacache@17.0.4, cacache@^17.0.0:
version "17.0.4"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.4.tgz#5023ed892ba8843e3b7361c26d0ada37e146290c"
@ -5177,6 +5258,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb"
integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
chalk@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3"
@ -5191,7 +5277,7 @@ chalk@^2.0.0:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2, chalk@~4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -5395,6 +5481,11 @@ commander@^7.2.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
common-tags@^1.8.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -5919,6 +6010,11 @@ debug@~3.1.0:
dependencies:
ms "2.0.0"
decamelize@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9"
integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==
decimal.js@^10.4.2:
version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
@ -6433,6 +6529,15 @@ eslint-config-prettier@8.7.0:
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz#f1cc58a8afebc50980bd53475451df146c13182d"
integrity sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==
eslint-etc@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/eslint-etc/-/eslint-etc-5.2.0.tgz#c51c19a2ffb6447af76a1c5ffd13b9a212db859b"
integrity sha512-Gcm/NMa349FOXb1PEEfNMMyIANuorIc2/mI5Vfu1zENNsz+FBVhF62uY6gPUCigm/xDOc8JOnl+71WGnlzlDag==
dependencies:
"@typescript-eslint/experimental-utils" "^5.0.0"
tsutils "^3.17.1"
tsutils-etc "^1.4.1"
eslint-plugin-prettier@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b"
@ -6440,6 +6545,21 @@ eslint-plugin-prettier@^4.0.0:
dependencies:
prettier-linter-helpers "^1.0.0"
eslint-plugin-rxjs@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-rxjs/-/eslint-plugin-rxjs-5.0.2.tgz#0f6f31d227f7d11f4596c3bbbded16e278629684"
integrity sha512-Q2wsEHWInhZ3uz5df+YbD4g/NPQqAeYHjJuEsxqgVS+XAsYCuVE2pj9kADdMFy4GsQy2jt7KP+TOrnq1i6bI5Q==
dependencies:
"@typescript-eslint/experimental-utils" "^5.0.0"
common-tags "^1.8.0"
decamelize "^5.0.0"
eslint-etc "^5.1.0"
requireindex "~1.2.0"
rxjs-report-usage "^1.0.4"
tslib "^2.0.0"
tsutils "^3.0.0"
tsutils-etc "^1.4.1"
eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
@ -6473,7 +6593,7 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint@8.36.0:
eslint@^8.33.0:
version "8.36.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf"
integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==
@ -7126,7 +7246,7 @@ glob@8.0.3, glob@^8.0.1:
minimatch "^5.0.1"
once "^1.3.0"
glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0, glob@~7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -7480,6 +7600,11 @@ ignore-walk@^6.0.0:
dependencies:
minimatch "^5.0.1"
ignore@5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
ignore@^5.0.4, ignore@^5.1.9, ignore@^5.2.0:
version "5.2.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.2.tgz#7e5f30224584b67aeeefe383a24a61dce4cb370d"
@ -10830,7 +10955,7 @@ promise-retry@^2.0.1:
err-code "^2.0.2"
retry "^0.12.0"
prompts@^2.0.1:
prompts@^2.0.1, prompts@~2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
@ -11051,6 +11176,11 @@ require-from-string@^2.0.2:
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
requireindex@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@ -11150,6 +11280,19 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rxjs-report-usage@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/rxjs-report-usage/-/rxjs-report-usage-1.0.6.tgz#6e06034d9e1592e8a45bee877631638e4bac2576"
integrity sha512-omv1DIv5z1kV+zDAEjaDjWSkx8w5TbFp5NZoPwUipwzYVcor/4So9ZU3bUyQ1c8lxY5Q0Es/ztWW7PGjY7to0Q==
dependencies:
"@babel/parser" "^7.10.3"
"@babel/traverse" "^7.10.3"
"@babel/types" "^7.10.3"
bent "~7.3.6"
chalk "~4.1.0"
glob "~7.2.0"
prompts "~2.4.2"
rxjs@6.6.7, rxjs@^6.5.4:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@ -11765,7 +11908,7 @@ strip-final-newline@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@ -11995,6 +12138,13 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
tmp@0.2.1, tmp@~0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
dependencies:
rimraf "^3.0.0"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -12002,13 +12152,6 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
tmp@~0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
dependencies:
rimraf "^3.0.0"
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@ -12139,7 +12282,15 @@ tslib@^2.5.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
tsutils@^3.21.0:
tsutils-etc@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tsutils-etc/-/tsutils-etc-1.4.1.tgz#bd42a0079d534765ab314d087f8a89c77a68723f"
integrity sha512-6UPYgc7OXcIW5tFxlsZF3OVSBvDInl/BkS3Xsu64YITXk7WrnWTVByKWPCThFDBp5gl5IGHOzGMdQuDCE7OL4g==
dependencies:
"@types/yargs" "^17.0.0"
yargs "^17.0.0"
tsutils@^3.0.0, tsutils@^3.17.1, tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
@ -12819,6 +12970,19 @@ yargs@17.6.2, yargs@^17.2.1, yargs@^17.3.1, yargs@^17.6.2:
y18n "^5.0.5"
yargs-parser "^21.1.1"
yargs@^17.0.0:
version "17.7.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967"
integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.1.1"
yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"