diff --git a/.eslintrc.json b/.eslintrc.json index a305aede8..670fd062e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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", diff --git a/apps/red-ui/src/app/app.component.ts b/apps/red-ui/src/app/app.component.ts index edb0cd879..9a11ac19b 100644 --- a/apps/red-ui/src/app/app.component.ts +++ b/apps/red-ui/src/app/app.component.ts @@ -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().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', + }); } } } diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index dd6efcb22..4da89584c 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -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; }; diff --git a/apps/red-ui/src/app/guards/dossier-files-guard.ts b/apps/red-ui/src/app/guards/dossier-files-guard.ts index 4dddfa180..fce03e70e 100644 --- a/apps/red-ui/src/app/guards/dossier-files-guard.ts +++ b/apps/red-ui/src/app/guards/dossier-files-guard.ts @@ -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))); diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts index bac130bfe..010bdaa8a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts @@ -43,7 +43,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent, + readonly dialogRef: MatDialogRef, private readonly _fileAttributesService: FileAttributesService, @Inject(MAT_DIALOG_DATA) readonly data: IFileAttributesCSVImportData, ) { diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts index 86084ba5b..6ff7e657b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts @@ -123,7 +123,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent( + const ref = this._dialog.open( FileAttributesCsvImportDialogComponent, { ...largeDialogConfig, @@ -136,8 +136,10 @@ export class FileAttributesListingScreenComponent extends ListingComponent; - readonly dossierId = getParam(DOSSIER_ID); - readonly currentUser = getCurrentUser(); - private readonly _listingMode$ = new BehaviorSubject(ListingModes.table); + readonly #dossierId = getParam(DOSSIER_ID); + readonly #currentUser = getCurrentUser(); + readonly #config = getConfig(); + readonly #listingMode$: BehaviorSubject; 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); + 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 { @@ -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', diff --git a/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.html b/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.html index e951e7522..7c87c717c 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.html @@ -53,7 +53,7 @@ -
+
- - - - + + + + implements OnInit, OnAttach { +export class DossierOverviewScreenComponent extends ListingComponent 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 imple readonly dossier$: Observable; readonly files$: Observable; readonly dossierId = getParam(DOSSIER_ID); - readonly dossierTemplateId: string; readonly workflowConfig: WorkflowConfig; readonly dossierAttributes$: Observable; - #currentDossier: Dossier; + #dossier: Dossier; @ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true }) private readonly _needsWorkFilterTemplate: TemplateRef; @ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef; @@ -80,8 +78,6 @@ export class DossierOverviewScreenComponent extends ListingComponent 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 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 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 imple } async uploadFiles(files: Files): Promise { - 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 { 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 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 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 imple const filterGroups = this.configService.filterGroups( this.entitiesService.all, this._fileAttributeConfigs, - this.dossierTemplateId, + this.#dossier.dossierTemplateId, this._needsWorkFilterTemplate, () => this.checkedRequiredFilters, () => this.checkedNotRequiredFilters, diff --git a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts index 6e8fba035..595a6cbc2 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts @@ -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; diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts index d5ffaae8b..1f9ab5cc7 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts @@ -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, b: KeyValue): 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) { diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 432ecd4e1..74f342a1c 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -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( diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotations-listing.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotations-listing.service.ts index c44540b9a..60118d95c 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotations-listing.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotations-listing.service.ts @@ -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 { +export class AnnotationsListingService extends ListingService implements OnDestroy { + readonly #subscriptions: Subscription; + constructor( protected readonly _filterService: FilterService, protected readonly _searchService: SearchService, @@ -19,7 +22,7 @@ export class AnnotationsListingService extends ListingService ) { 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 .subscribe(); } + ngOnDestroy() { + this.#subscriptions.unsubscribe(); + } + selectAnnotations(annotations: AnnotationWrapper[] | AnnotationWrapper) { annotations = Array.isArray(annotations) ? annotations : [annotations]; const pageNumber = annotations[annotations.length - 1].pageNumber; diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts index 0a7402161..0374026eb 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts @@ -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(property: (x: T) => string) { } @Injectable() -export class FileDataService extends EntitiesService { +export class FileDataService extends EntitiesService implements OnDestroy { missingTypes = new Set(); readonly annotations$: Observable; readonly earmarks$: Observable>; @@ -51,6 +51,7 @@ export class FileDataService extends EntitiesService(); readonly #earmarks$ = new BehaviorSubject>(new Map()); #originalViewedPages: ViewedPage[] = []; + readonly #subscription: Subscription; constructor( private readonly _state: FilePreviewStateService, @@ -72,7 +73,7 @@ export class FileDataService extends EntitiesService iif( @@ -103,6 +104,10 @@ export class FileDataService extends EntitiesService; file: File; + readonly dossierDictionary$: Observable; #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 { diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts index da2f81069..88aca43ff 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts @@ -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$; diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts index cf68ffbf3..2ac4a9e15 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts @@ -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) { diff --git a/apps/red-ui/src/app/modules/upload-download/services/file-drop-overlay.service.ts b/apps/red-ui/src/app/modules/upload-download/services/file-drop-overlay.service.ts index cdf669173..ded6fe5be 100644 --- a/apps/red-ui/src/app/modules/upload-download/services/file-drop-overlay.service.ts +++ b/apps/red-ui/src/app/modules/upload-download/services/file-drop-overlay.service.ts @@ -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); } } diff --git a/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts b/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts index efdf7c7fd..ae62b488b 100644 --- a/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts @@ -13,7 +13,6 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; @Injectable({ providedIn: 'root' }) export class DossiersChangesService extends GenericService { protected readonly _defaultModelPath = 'dossier'; - #initializedRefresh = false; readonly #activeDossiersService = inject(ActiveDossiersService); readonly #archivedDossiersService = inject(ArchivedDossiersService); @@ -22,8 +21,8 @@ export class DossiersChangesService extends GenericService { loadOnlyChanged(): Observable { 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 { } 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 { diff --git a/apps/red-ui/src/app/services/dossiers/dossiers-cache.service.ts b/apps/red-ui/src/app/services/dossiers/dossiers-cache.service.ts index 76570444b..bf2f75ea0 100644 --- a/apps/red-ui/src/app/services/dossiers/dossiers-cache.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossiers-cache.service.ts @@ -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(); 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 { - 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())); + } } diff --git a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts index 9b27970db..11762ee9a 100644 --- a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts @@ -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 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; diff --git a/apps/red-ui/src/app/services/notifications.service.ts b/apps/red-ui/src/app/services/notifications.service.ts index 399cc5734..5e4a5b2a4 100644 --- a/apps/red-ui/src/app/services/notifications.service.ts +++ b/apps/red-ui/src/app/services/notifications.service.ts @@ -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 { +export class NotificationsService extends EntitiesService implements OnDestroy { protected readonly _defaultModelPath = 'notification'; protected readonly _entityClass = Notification; readonly #config = getConfig(); + readonly #subscription = new Subscription(); constructor( @Inject(BASE_HREF) private readonly _baseHref: string, @@ -34,20 +35,15 @@ export class NotificationsService extends EntitiesService 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 (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()); diff --git a/libs/common-ui b/libs/common-ui index e1f818c10..b4156074e 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit e1f818c1094fa4a8780e7907e04cc48176db3ac6 +Subproject commit b4156074eb1951dfaa7e53aeec5611e466d817eb diff --git a/nx.json b/nx.json index 1cc7bfd50..d70eb639c 100644 --- a/nx.json +++ b/nx.json @@ -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": { diff --git a/package.json b/package.json index 6d3681198..245b969b6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index ec676e067..50076bc01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"