diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 06a0821..2fdb313 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -10,7 +10,7 @@ import { import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { RouteTransitionAnimations } from './app.animation'; import { PRouteData } from './models/dto/picsur-routes.dto'; -import { BootstrapService } from './util/util-module/bootstrap.service'; +import { BootstrapService } from './util/bootstrap.service'; @Component({ selector: 'app-root', diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index fd65481..7debf95 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -9,7 +9,9 @@ import { AppRoutingModule } from './app.routing.module'; import { FooterModule } from './components/footer/footer.module'; import { HeaderModule } from './components/header/header.module'; import { GuardsModule } from './guards/guards.module'; -import { UtilModule } from './util/util-module/util.module'; +import { ApiErrorManagerModule } from './util/api-error-manager/api-error-manager.module'; +import { CompatibilityManagerModule } from './util/compatibilitiy-manager/compatibility-manager.module'; +import { SnackBarManagerModule } from './util/snackbar-manager/snackbar-manager.module'; @NgModule({ declarations: [AppComponent], @@ -19,7 +21,10 @@ import { UtilModule } from './util/util-module/util.module'; PortalModule, MatSidenavModule, - UtilModule.forRoot(), + SnackBarManagerModule.forRoot(), + CompatibilityManagerModule, + ApiErrorManagerModule, + GuardsModule, AppRoutingModule, diff --git a/frontend/src/app/components/copy-field/copy-field.component.ts b/frontend/src/app/components/copy-field/copy-field.component.ts index 094780d..e3cfdc3 100644 --- a/frontend/src/app/components/copy-field/copy-field.component.ts +++ b/frontend/src/app/components/copy-field/copy-field.component.ts @@ -1,7 +1,8 @@ import { Clipboard } from '@angular/cdk/clipboard'; import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { Fail, FT } from 'picsur-shared/dist/types'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; @Component({ selector: 'copy-field', @@ -9,6 +10,8 @@ import { UtilService } from 'src/app/util/util-module/util.service'; styleUrls: ['./copy-field.component.scss'], }) export class CopyFieldComponent { + private readonly logger = new Logger(CopyFieldComponent.name); + // Two parameters: name, value @Input() label: string = 'Loading...'; @Input() value: string = 'Loading...'; @@ -20,20 +23,20 @@ export class CopyFieldComponent { @Output('hide') onHide = new EventEmitter(); constructor( - private readonly utilService: UtilService, private readonly clipboard: Clipboard, + private readonly errorService: ErrorService, ) {} public copy() { if (this.clipboard.copy(this.value)) { - this.utilService.showSnackBar(`Copied ${this.label}!`, SnackBarType.Info); + this.errorService.info(`Copied ${this.label}!`); this.onCopy.emit(this.value); return; } - return this.utilService.showSnackBar( - 'Copying to clipboard failed', - SnackBarType.Error, + return this.errorService.showFailure( + Fail(FT.Internal, 'Copying to clipboard failed'), + this.logger, ); } diff --git a/frontend/src/app/components/copy-field/copy-field.module.ts b/frontend/src/app/components/copy-field/copy-field.module.ts index 8810dcc..c8e441b 100644 --- a/frontend/src/app/components/copy-field/copy-field.module.ts +++ b/frontend/src/app/components/copy-field/copy-field.module.ts @@ -4,11 +4,14 @@ import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { CopyFieldComponent } from './copy-field.component'; @NgModule({ declarations: [CopyFieldComponent], imports: [ CommonModule, + ErrorManagerModule, + MatInputModule, MatIconModule, MatButtonModule, diff --git a/frontend/src/app/components/header/header.component.ts b/frontend/src/app/components/header/header.component.ts index fc066ab..2f7eabd 100644 --- a/frontend/src/app/components/header/header.component.ts +++ b/frontend/src/app/components/header/header.component.ts @@ -12,10 +12,10 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { HasFailed } from 'picsur-shared/dist/types'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { PermissionService } from 'src/app/services/api/permission.service'; import { UserService } from 'src/app/services/api/user.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; @Component({ selector: 'app-header', @@ -24,12 +24,14 @@ import { UtilService } from 'src/app/util/util-module/util.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class HeaderComponent implements OnInit { + private readonly logger = new Logger(HeaderComponent.name); + constructor( private readonly router: Router, private readonly userService: UserService, private readonly permissionService: PermissionService, - private readonly utilService: UtilService, private readonly changeDetector: ChangeDetectorRef, + private readonly errorService: ErrorService ) {} @Input('enableHamburger') public set enableHamburger(value: boolean) { @@ -90,12 +92,10 @@ export class HeaderComponent implements OnInit { async doLogout() { const user = await this.userService.logout(); - if (HasFailed(user)) { - this.utilService.showSnackBar(user.getReason(), SnackBarType.Error); - return; - } + if (HasFailed(user)) + return this.errorService.showFailure(user, this.logger); - this.utilService.showSnackBar('Logout successful', SnackBarType.Success); + this.errorService.success('Logout successful'); } doSettings() { diff --git a/frontend/src/app/components/header/header.module.ts b/frontend/src/app/components/header/header.module.ts index ef0eef7..c935f1b 100644 --- a/frontend/src/app/components/header/header.module.ts +++ b/frontend/src/app/components/header/header.module.ts @@ -6,11 +6,14 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { HeaderComponent } from './header.component'; @NgModule({ imports: [ CommonModule, + ErrorManagerModule, + MatToolbarModule, MatButtonModule, RouterModule, diff --git a/frontend/src/app/components/pref-option/pref-option.component.ts b/frontend/src/app/components/pref-option/pref-option.component.ts index d2e9b9f..1be4723 100644 --- a/frontend/src/app/components/pref-option/pref-option.component.ts +++ b/frontend/src/app/components/pref-option/pref-option.component.ts @@ -7,9 +7,9 @@ import { import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types'; import { Subject } from 'rxjs'; import { Required } from 'src/app/models/decorators/required.decorator'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { Throttle } from 'src/app/util/throttle'; -import { UtilService } from 'src/app/util/util-module/util.service'; @Component({ selector: 'pref-option', @@ -17,6 +17,8 @@ import { UtilService } from 'src/app/util/util-module/util.service'; styleUrls: ['./pref-option.component.scss'], }) export class PrefOptionComponent implements OnInit { + private readonly logger = new Logger(PrefOptionComponent.name); + @Input() @Required pref: DecodedPref; @Input('update') @Required updateFunction: ( key: string, @@ -28,7 +30,7 @@ export class PrefOptionComponent implements OnInit { private updateSubject = new Subject(); - constructor(private readonly utilService: UtilService) {} + constructor(private readonly errorService: ErrorService) {} ngOnInit(): void { this.subscribeUpdate(); @@ -87,12 +89,9 @@ export class PrefOptionComponent implements OnInit { ? `Enabled ${this.name}` : `Disabled ${this.name}` : ''; - this.utilService.showSnackBar(message, SnackBarType.Success); + this.errorService.success(message); } else { - this.utilService.showSnackBar( - `Failed to update ${this.name}`, - SnackBarType.Error, - ); + this.errorService.showFailure(result, this.logger); } } diff --git a/frontend/src/app/components/pref-option/pref-option.module.ts b/frontend/src/app/components/pref-option/pref-option.module.ts index a3972e3..27535c6 100644 --- a/frontend/src/app/components/pref-option/pref-option.module.ts +++ b/frontend/src/app/components/pref-option/pref-option.module.ts @@ -3,11 +3,14 @@ import { NgModule } from '@angular/core'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { PrefOptionComponent } from './pref-option.component'; @NgModule({ imports: [ CommonModule, + ErrorManagerModule, + MatFormFieldModule, MatInputModule, MatSlideToggleModule, diff --git a/frontend/src/app/routes/images/images.component.ts b/frontend/src/app/routes/images/images.component.ts index 4b42b2a..348e13e 100644 --- a/frontend/src/app/routes/images/images.component.ts +++ b/frontend/src/app/routes/images/images.component.ts @@ -4,14 +4,11 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { ImageFileType } from 'picsur-shared/dist/dto/mimes.dto'; import { EImage } from 'picsur-shared/dist/entities/image.entity'; import { HasFailed } from 'picsur-shared/dist/types/failable'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { ImageService } from 'src/app/services/api/image.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { - BootstrapService, - BSScreenSize -} from 'src/app/util/util-module/bootstrap.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { BootstrapService, BSScreenSize } from 'src/app/util/bootstrap.service'; +import { DialogService } from 'src/app/util/dialog-manager/dialog.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; @Component({ templateUrl: './images.component.html', @@ -30,8 +27,9 @@ export class ImagesComponent implements OnInit { private readonly route: ActivatedRoute, private readonly router: Router, private readonly bootstrapService: BootstrapService, - private readonly utilService: UtilService, private readonly imageService: ImageService, + private readonly errorService: ErrorService, + private readonly dialogService: DialogService, ) {} ngOnInit() { @@ -71,7 +69,8 @@ export class ImagesComponent implements OnInit { getThumbnailUrl(image: EImage) { return ( - this.imageService.GetImageURL(image.id, ImageFileType.QOI) + '?height=480&shrinkonly=yes' + this.imageService.GetImageURL(image.id, ImageFileType.QOI) + + '?height=480&shrinkonly=yes' ); } @@ -80,7 +79,7 @@ export class ImagesComponent implements OnInit { } async deleteImage(image: EImage) { - const pressedButton = await this.utilService.showDialog({ + const pressedButton = await this.dialogService.showDialog({ title: `Are you sure you want to delete the image?`, description: 'This action cannot be undone.', buttons: [ @@ -98,15 +97,11 @@ export class ImagesComponent implements OnInit { if (pressedButton === 'delete') { const result = await this.imageService.DeleteImage(image.id ?? ''); - if (HasFailed(result)) { - this.utilService.showSnackBar( - 'Failed to delete image', - SnackBarType.Error, - ); - } else { - this.utilService.showSnackBar('Image deleted', SnackBarType.Success); - this.images = this.images?.filter((i) => i.id !== image.id) ?? null; - } + if (HasFailed(result)) + return this.errorService.showFailure(result, this.logger); + + this.errorService.success('Image deleted'); + this.images = this.images?.filter((i) => i.id !== image.id) ?? null; } } diff --git a/frontend/src/app/routes/images/images.module.ts b/frontend/src/app/routes/images/images.module.ts index 8079407..a4c313c 100644 --- a/frontend/src/app/routes/images/images.module.ts +++ b/frontend/src/app/routes/images/images.module.ts @@ -8,6 +8,8 @@ import { MasonryModule } from 'src/app/components/masonry/masonry.module'; import { PaginatorModule } from 'src/app/components/paginator/paginator.module'; import { PicsurImgModule } from 'src/app/components/picsur-img/picsur-img.module'; import { PipesModule } from 'src/app/pipes/pipes.module'; +import { DialogManagerModule } from 'src/app/util/dialog-manager/dialog-manager.module'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { ImagesComponent } from './images.component'; import { ImagesRoutingModule } from './images.routing.module'; @@ -15,6 +17,9 @@ import { ImagesRoutingModule } from './images.routing.module'; declarations: [ImagesComponent], imports: [ CommonModule, + ErrorManagerModule, + DialogManagerModule, + ImagesRoutingModule, MatCardModule, MatButtonModule, @@ -23,7 +28,7 @@ import { ImagesRoutingModule } from './images.routing.module'; PaginatorModule, PicsurImgModule, MomentModule, - PipesModule + PipesModule, ], }) export class ImagesRouteModule {} diff --git a/frontend/src/app/routes/processing/processing.component.ts b/frontend/src/app/routes/processing/processing.component.ts index d151d59..849dfe8 100644 --- a/frontend/src/app/routes/processing/processing.component.ts +++ b/frontend/src/app/routes/processing/processing.component.ts @@ -1,32 +1,37 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { HasFailed } from 'picsur-shared/dist/types'; +import { Fail, FT, HasFailed } from 'picsur-shared/dist/types'; import { ProcessingViewMeta } from 'src/app/models/dto/processing-view-meta.dto'; import { ImageService } from 'src/app/services/api/image.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; @Component({ templateUrl: './processing.component.html', }) export class ProcessingComponent implements OnInit { + private readonly logger = new Logger(ProcessingComponent.name); + constructor( private readonly router: Router, private readonly imageService: ImageService, - private readonly utilService: UtilService, + private readonly errorService: ErrorService, ) {} async ngOnInit() { const state = history.state as ProcessingViewMeta; if (!state) { - return this.utilService.quitError('Error'); + return this.errorService.quitFailure( + Fail(FT.UsrValidation, 'No state provided'), + this.logger, + ); } history.replaceState(null, ''); const id = await this.imageService.UploadImage(state.imageFile); - if (HasFailed(id)) { - return this.utilService.quitError(id.getReason()); - } + if (HasFailed(id)) + return this.errorService.quitFailure(id, this.logger); this.router.navigate([`/view/`, id], { replaceUrl: true }); } diff --git a/frontend/src/app/routes/processing/processing.module.ts b/frontend/src/app/routes/processing/processing.module.ts index 1dca312..116f462 100644 --- a/frontend/src/app/routes/processing/processing.module.ts +++ b/frontend/src/app/routes/processing/processing.module.ts @@ -1,11 +1,17 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { ProcessingComponent } from './processing.component'; import { ProcessingRoutingModule } from './processing.routing.module'; @NgModule({ declarations: [ProcessingComponent], - imports: [CommonModule, ProcessingRoutingModule, MatProgressSpinnerModule], + imports: [ + CommonModule, + ErrorManagerModule, + ProcessingRoutingModule, + MatProgressSpinnerModule, + ], }) export class ProcessingRouteModule {} diff --git a/frontend/src/app/routes/settings/apikeys/settings-apikeys.component.ts b/frontend/src/app/routes/settings/apikeys/settings-apikeys.component.ts index a22aca3..7588b68 100644 --- a/frontend/src/app/routes/settings/apikeys/settings-apikeys.component.ts +++ b/frontend/src/app/routes/settings/apikeys/settings-apikeys.component.ts @@ -3,15 +3,15 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { EApiKey } from 'picsur-shared/dist/entities/apikey.entity'; -import { HasFailed } from 'picsur-shared/dist/types'; +import { Fail, FT, HasFailed } from 'picsur-shared/dist/types'; import { BehaviorSubject, Subject } from 'rxjs'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { ApiKeysService } from 'src/app/services/api/apikeys.service'; import { UserService } from 'src/app/services/api/user.service'; import { Logger } from 'src/app/services/logger/logger.service'; +import { BootstrapService } from 'src/app/util/bootstrap.service'; +import { DialogService } from 'src/app/util/dialog-manager/dialog.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { Throttle } from 'src/app/util/throttle'; -import { BootstrapService } from 'src/app/util/util-module/bootstrap.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; @Component({ templateUrl: './settings-apikeys.component.html', @@ -36,10 +36,11 @@ export class SettingsApiKeysComponent implements OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; constructor( - private readonly utilService: UtilService, private readonly apikeysService: ApiKeysService, private readonly userService: UserService, private readonly clipboard: Clipboard, + private readonly errorService: ErrorService, + private readonly dialogService: DialogService, // Public because used in template public readonly bootstrapService: BootstrapService, ) {} @@ -52,13 +53,8 @@ export class SettingsApiKeysComponent implements OnInit { public async addApiKey() { const result = await this.apikeysService.createApiKey(); - if (HasFailed(result)) { - this.utilService.showSnackBar( - 'Failed to create api key', - SnackBarType.Error, - ); - return; - } + if (HasFailed(result)) + return this.errorService.showFailure(result, this.logger); const success = await this.fetchApiKeys( this.paginator.pageSize, @@ -70,35 +66,29 @@ export class SettingsApiKeysComponent implements OnInit { const clipboardResult = this.clipboard.copy(result.key); if (!clipboardResult) { - this.utilService.showSnackBar( - 'Failed to copy api key to clipboard', - SnackBarType.Error, + return this.errorService.showFailure( + Fail(FT.Internal, 'Failed to copy api key to clipboard'), + this.logger, ); } - this.utilService.showSnackBar( - 'Api key created and copied to clipboard', - SnackBarType.Success, - ); + this.errorService.success('Api key created and copied to clipboard'); } public copyKey(apikey: string) { const result = this.clipboard.copy(apikey); if (!result) { - this.utilService.showSnackBar( - 'Failed to copy api key to clipboard', - SnackBarType.Error, - ); - } else { - this.utilService.showSnackBar( - 'Api key copied to clipboard', - SnackBarType.Success, + return this.errorService.showFailure( + Fail(FT.Internal, 'Failed to copy api key to clipboard'), + this.logger, ); } + + this.errorService.success('Api key copied to clipboard'); } public async deleteApiKey(apikeyId: string) { - const pressedButton = await this.utilService.showDialog({ + const pressedButton = await this.dialogService.showDialog({ title: `Are you sure you want to delete this api key?`, description: 'This action cannot be undone.', buttons: [ @@ -117,12 +107,9 @@ export class SettingsApiKeysComponent implements OnInit { if (pressedButton === 'delete') { const result = await this.apikeysService.deleteApiKey(apikeyId); if (HasFailed(result)) { - this.utilService.showSnackBar( - 'Failed to delete api key', - SnackBarType.Error, - ); + this.errorService.showFailure(result, this.logger); } else { - this.utilService.showSnackBar('Api key deleted', SnackBarType.Success); + this.errorService.success('Api key deleted'); } } @@ -138,18 +125,10 @@ export class SettingsApiKeysComponent implements OnInit { async updateKeyName(name: string, apikeyID: string) { const result = await this.apikeysService.updateApiKey(apikeyID, name); - if (HasFailed(result)) { - this.logger.warn(result.print()); - this.utilService.showSnackBar( - 'Failed to update api key name', - SnackBarType.Error, - ); - } else { - this.utilService.showSnackBar( - 'Api key name updated', - SnackBarType.Success, - ); - } + if (HasFailed(result)) + return this.errorService.showFailure(result, this.logger); + + this.errorService.success('Api key name updated'); } @AutoUnsubscribe() @@ -180,13 +159,9 @@ export class SettingsApiKeysComponent implements OnInit { pageIndex, this.userService.snapshot?.id, ); - if (HasFailed(response)) { - this.utilService.showSnackBar( - 'Failed to fetch api keys', - SnackBarType.Error, - ); - this.logger.warn(response.print()); - return false; + if (HasFailed(response)){ + this.errorService.showFailure(response, this.logger); + return false; } this.dataSubject.next(response.results); diff --git a/frontend/src/app/routes/settings/apikeys/settings-apikeys.module.ts b/frontend/src/app/routes/settings/apikeys/settings-apikeys.module.ts index 09d70d8..17fc70a 100644 --- a/frontend/src/app/routes/settings/apikeys/settings-apikeys.module.ts +++ b/frontend/src/app/routes/settings/apikeys/settings-apikeys.module.ts @@ -9,6 +9,8 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; import { MomentModule } from 'ngx-moment'; import { FabModule } from 'src/app/components/fab/fab.module'; +import { DialogManagerModule } from 'src/app/util/dialog-manager/dialog-manager.module'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { SettingsApiKeyEditorComponent } from './apikey-editor/apikey-editor.component'; import { SettingsApiKeysComponent } from './settings-apikeys.component'; import { SettingsApiKeysRoutingModule } from './settings-apikeys.routing.module'; @@ -17,6 +19,9 @@ import { SettingsApiKeysRoutingModule } from './settings-apikeys.routing.module' declarations: [SettingsApiKeysComponent, SettingsApiKeyEditorComponent], imports: [ CommonModule, + ErrorManagerModule, + DialogManagerModule, + SettingsApiKeysRoutingModule, MatButtonModule, MatIconModule, diff --git a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts index dd928bb..0ca795e 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts @@ -3,12 +3,11 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { HasFailed } from 'picsur-shared/dist/types'; import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { UpdateRoleControl } from 'src/app/models/forms/update-role.control'; import { RolesService } from 'src/app/services/api/roles.service'; import { StaticInfoService } from 'src/app/services/api/static-info.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; enum EditMode { edit = 'edit', @@ -37,9 +36,9 @@ export class SettingsRolesEditComponent implements OnInit { constructor( private readonly route: ActivatedRoute, private readonly router: Router, - private readonly utilService: UtilService, private readonly rolesService: RolesService, private readonly staticInfo: StaticInfoService, + private readonly errorService: ErrorService, ) {} ngOnInit() { @@ -60,10 +59,8 @@ export class SettingsRolesEditComponent implements OnInit { // Fetch data and populate form const role = await this.rolesService.getRole(rolename); - if (HasFailed(role)) { - this.utilService.showSnackBar('Failed to get role', SnackBarType.Error); - return; - } + if (HasFailed(role)) + return this.errorService.showFailure(role, this.logger); this.model.putRoleName(role.name); this.model.putPermissions(role.permissions); } @@ -78,26 +75,16 @@ export class SettingsRolesEditComponent implements OnInit { if (this.adding) { const resultRole = await this.rolesService.createRole(data); - if (HasFailed(resultRole)) { - this.utilService.showSnackBar( - 'Failed to create role', - SnackBarType.Error, - ); - return; - } + if (HasFailed(resultRole)) + return this.errorService.showFailure(resultRole, this.logger); - this.utilService.showSnackBar('Role created', SnackBarType.Success); + this.errorService.success('Role created'); } else { const resultRole = await this.rolesService.updateRole(data); - if (HasFailed(resultRole)) { - this.utilService.showSnackBar( - 'Failed to update role', - SnackBarType.Error, - ); - return; - } + if (HasFailed(resultRole)) + return this.errorService.showFailure(resultRole, this.logger); - this.utilService.showSnackBar('Role updated', SnackBarType.Success); + this.errorService.success('Role updated'); } this.router.navigate(['/settings/roles']); diff --git a/frontend/src/app/routes/settings/roles/settings-roles.component.ts b/frontend/src/app/routes/settings/roles/settings-roles.component.ts index 070aac4..a14767d 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles.component.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles.component.ts @@ -6,12 +6,12 @@ import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { ERole } from 'picsur-shared/dist/entities/role.entity'; import { HasFailed } from 'picsur-shared/dist/types'; import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { RolesService } from 'src/app/services/api/roles.service'; import { StaticInfoService } from 'src/app/services/api/static-info.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { BootstrapService } from 'src/app/util/util-module/bootstrap.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { BootstrapService } from 'src/app/util/bootstrap.service'; +import { DialogService } from 'src/app/util/dialog-manager/dialog.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; @Component({ templateUrl: './settings-roles.component.html', @@ -37,10 +37,11 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit { @ViewChild(MatPaginator) paginator: MatPaginator; constructor( - private readonly utilService: UtilService, private readonly rolesService: RolesService, private readonly staticInfo: StaticInfoService, private readonly router: Router, + private readonly errorService: ErrorService, + private readonly dialogService: DialogService, // Public because used in template public readonly bootstrapService: BootstrapService, ) {} @@ -62,7 +63,7 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit { } async deleteRole(role: ERole) { - const pressedButton = await this.utilService.showDialog({ + const pressedButton = await this.dialogService.showDialog({ title: `Are you sure you want to delete ${role.name}?`, description: 'This action cannot be undone.', buttons: [ @@ -81,12 +82,9 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit { if (pressedButton === 'delete') { const result = await this.rolesService.deleteRole(role.name); if (HasFailed(result)) { - this.utilService.showSnackBar( - 'Failed to delete role', - SnackBarType.Error, - ); + this.errorService.showFailure(result, this.logger); } else { - this.utilService.showSnackBar('Role deleted', SnackBarType.Success); + this.errorService.success('Role deleted'); } } @@ -113,10 +111,8 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit { this.UndeletableRolesList = specialRoles.UndeletableRoles; this.ImmutableRolesList = specialRoles.ImmutableRoles; - if (HasFailed(roles)) { - this.utilService.showSnackBar('Failed to load roles', SnackBarType.Error); - return; - } + if (HasFailed(roles)) + return this.errorService.showFailure(roles, this.logger); this.dataSource.data = roles; } } diff --git a/frontend/src/app/routes/settings/roles/settings-roles.module.ts b/frontend/src/app/routes/settings/roles/settings-roles.module.ts index 8ed6ae2..1c1cdc0 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles.module.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles.module.ts @@ -10,6 +10,8 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; import { FabModule } from 'src/app/components/fab/fab.module'; import { ValuesPickerModule } from 'src/app/components/values-picker/values-picker.module'; +import { DialogManagerModule } from 'src/app/util/dialog-manager/dialog-manager.module'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { SettingsRolesEditComponent } from './settings-roles-edit/settings-roles-edit.component'; import { SettingsRolesComponent } from './settings-roles.component'; import { SettingsRolesRoutingModule } from './settings-roles.routing.module'; @@ -18,6 +20,9 @@ import { SettingsRolesRoutingModule } from './settings-roles.routing.module'; declarations: [SettingsRolesComponent, SettingsRolesEditComponent], imports: [ CommonModule, + ErrorManagerModule, + DialogManagerModule, + SettingsRolesRoutingModule, MatIconModule, MatButtonModule, diff --git a/frontend/src/app/routes/settings/sharex/settings-sharex.component.ts b/frontend/src/app/routes/settings/sharex/settings-sharex.component.ts index b202f30..897a058 100644 --- a/frontend/src/app/routes/settings/sharex/settings-sharex.component.ts +++ b/frontend/src/app/routes/settings/sharex/settings-sharex.component.ts @@ -6,12 +6,11 @@ import { EApiKey } from 'picsur-shared/dist/entities/apikey.entity'; import { HasFailed } from 'picsur-shared/dist/types'; import { BehaviorSubject } from 'rxjs'; import { scan } from 'rxjs/operators'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { ApiKeysService } from 'src/app/services/api/apikeys.service'; import { PermissionService } from 'src/app/services/api/permission.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { SimpleUtilService } from 'src/app/util/util-module/simple-util.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; +import { UtilService } from 'src/app/util/util.service'; import { BuildShareX } from './sharex-builder'; @Component({ @@ -41,13 +40,13 @@ export class SettingsShareXComponent implements OnInit { constructor( private readonly apikeysService: ApiKeysService, - private readonly utilService: UtilService, - private readonly simpleUtil: SimpleUtilService, private readonly permissionService: PermissionService, + private readonly utilService: UtilService, + private readonly errorService: ErrorService, ) {} ngOnInit(): void { - this.formatOptions = this.simpleUtil.getBaseFormatOptions(); + this.formatOptions = this.utilService.getBaseFormatOptions(); this.getNextBatch(); } @@ -67,22 +66,19 @@ export class SettingsShareXComponent implements OnInit { } const sharexConfig = BuildShareX( - this.simpleUtil.getHost(), + this.utilService.getHost(), this.key, '.' + ext, canUseDelete, ); - this.simpleUtil.downloadBuffer( + this.utilService.downloadBuffer( JSON.stringify(sharexConfig), 'Pisur-ShareX-target.sxcu', 'application/json', ); - this.utilService.showSnackBar( - 'Exported ShareX config', - SnackBarType.Success, - ); + this.errorService.success('Exported ShareX config'); } async getNextBatch() { @@ -90,10 +86,7 @@ export class SettingsShareXComponent implements OnInit { 50, Math.floor(this.loaded / 50), ); - if (HasFailed(newApiKeys)) { - this.utilService.showSnackBar(newApiKeys.getReason(), SnackBarType.Error); - return; - } + if (HasFailed(newApiKeys)) return this.errorService.showFailure(newApiKeys, this.logger); this.loaded += newApiKeys.results.length; this.available = newApiKeys.total; diff --git a/frontend/src/app/routes/settings/sharex/settings-sharex.module.ts b/frontend/src/app/routes/settings/sharex/settings-sharex.module.ts index c8a48a6..aa3c7fc 100644 --- a/frontend/src/app/routes/settings/sharex/settings-sharex.module.ts +++ b/frontend/src/app/routes/settings/sharex/settings-sharex.module.ts @@ -5,7 +5,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; import { MatSelectInfiniteScrollModule } from 'ng-mat-select-infinite-scroll'; -import { UtilModule } from 'src/app/util/util-module/util.module'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { SettingsShareXComponent } from './settings-sharex.component'; import { SettingsShareXRoutingModule } from './settings-sharex.routing.module'; @@ -13,13 +13,14 @@ import { SettingsShareXRoutingModule } from './settings-sharex.routing.module'; declarations: [SettingsShareXComponent], imports: [ CommonModule, + ErrorManagerModule, + SettingsShareXRoutingModule, MatSelectModule, MatSelectInfiniteScrollModule, MatInputModule, MatButtonModule, MatProgressSpinnerModule, - UtilModule, ], }) export class SettingsShareXRouteModule {} diff --git a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts index e3555fb..cd885b0 100644 --- a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts @@ -4,13 +4,12 @@ import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { ERole } from 'picsur-shared/dist/entities/role.entity'; import { HasFailed } from 'picsur-shared/dist/types'; import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { UpdateUserControl } from 'src/app/models/forms/update-user.control'; import { RolesService } from 'src/app/services/api/roles.service'; import { StaticInfoService } from 'src/app/services/api/static-info.service'; import { UserAdminService } from 'src/app/services/api/user-manage.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; enum EditMode { edit = 'edit', @@ -46,9 +45,9 @@ export class SettingsUsersEditComponent implements OnInit { private readonly route: ActivatedRoute, private readonly router: Router, private readonly userManageService: UserAdminService, - private readonly utilService: UtilService, private readonly rolesService: RolesService, private readonly staticInfo: StaticInfoService, + private readonly errorService: ErrorService, ) {} ngOnInit() { @@ -80,10 +79,8 @@ export class SettingsUsersEditComponent implements OnInit { // Fetch more data const user = await this.userManageService.getUser(uuid); - if (HasFailed(user)) { - this.utilService.showSnackBar('Failed to get user', SnackBarType.Error); - return; - } + if (HasFailed(user)) + return this.errorService.showFailure(user, this.logger); // Set that data instead this.model.putUsername(user.username); @@ -97,10 +94,8 @@ export class SettingsUsersEditComponent implements OnInit { private async initRoles() { const roles = await this.rolesService.getRoles(); - if (HasFailed(roles)) { - this.utilService.showSnackBar('Failed to get roles', SnackBarType.Error); - return; - } + if (HasFailed(roles)) + return this.errorService.showFailure(roles, this.logger); this.allFullRoles = roles; } @@ -131,29 +126,19 @@ export class SettingsUsersEditComponent implements OnInit { if (this.adding) { const data = this.model.getDataCreate(); const resultUser = await this.userManageService.createUser(data); - if (HasFailed(resultUser)) { - this.utilService.showSnackBar( - 'Failed to create user', - SnackBarType.Error, - ); - return; - } + if (HasFailed(resultUser)) + return this.errorService.showFailure(resultUser, this.logger); - this.utilService.showSnackBar('User created', SnackBarType.Success); + this.errorService.success('User created'); } else { const data = this.model.getDataUpdate(); if (!data.password) delete data.password; const resultUser = await this.userManageService.updateUser(data); - if (HasFailed(resultUser)) { - this.utilService.showSnackBar( - 'Failed to update user', - SnackBarType.Error, - ); - return; - } + if (HasFailed(resultUser)) + return this.errorService.showFailure(resultUser, this.logger); - this.utilService.showSnackBar('User updated', SnackBarType.Success); + this.errorService.success('User updated'); } this.router.navigate(['/settings/users']); diff --git a/frontend/src/app/routes/settings/users/settings-users.component.ts b/frontend/src/app/routes/settings/users/settings-users.component.ts index 40ce29c..91f1396 100644 --- a/frontend/src/app/routes/settings/users/settings-users.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users.component.ts @@ -5,13 +5,13 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { HasFailed } from 'picsur-shared/dist/types'; import { BehaviorSubject, Subject } from 'rxjs'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { StaticInfoService } from 'src/app/services/api/static-info.service'; import { UserAdminService } from 'src/app/services/api/user-manage.service'; import { Logger } from 'src/app/services/logger/logger.service'; +import { BootstrapService } from 'src/app/util/bootstrap.service'; +import { DialogService } from 'src/app/util/dialog-manager/dialog.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { Throttle } from 'src/app/util/throttle'; -import { BootstrapService } from 'src/app/util/util-module/bootstrap.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; @Component({ templateUrl: './settings-users.component.html', @@ -34,10 +34,11 @@ export class SettingsUsersComponent implements OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; constructor( - private readonly utilService: UtilService, private readonly userManageService: UserAdminService, private readonly staticInfo: StaticInfoService, private readonly router: Router, + private readonly errorService: ErrorService, + private readonly dialogService: DialogService, // Public because used in template public readonly bootstrapService: BootstrapService, ) {} @@ -60,7 +61,7 @@ export class SettingsUsersComponent implements OnInit { } public async deleteUser(user: EUser) { - const pressedButton = await this.utilService.showDialog({ + const pressedButton = await this.dialogService.showDialog({ title: `Are you sure you want to delete ${user.username}?`, description: 'This action cannot be undone.', buttons: [ @@ -79,12 +80,9 @@ export class SettingsUsersComponent implements OnInit { if (pressedButton === 'delete') { const result = await this.userManageService.deleteUser(user.id ?? ''); if (HasFailed(result)) { - this.utilService.showSnackBar( - 'Failed to delete user', - SnackBarType.Error, - ); + this.errorService.showFailure(result, this.logger); } else { - this.utilService.showSnackBar('User deleted', SnackBarType.Success); + this.errorService.success('User deleted'); } } @@ -122,10 +120,7 @@ export class SettingsUsersComponent implements OnInit { ): Promise { const response = await this.userManageService.getUsers(pageSize, pageIndex); if (HasFailed(response)) { - this.utilService.showSnackBar( - 'Failed to fetch users', - SnackBarType.Error, - ); + this.errorService.showFailure(response, this.logger); return false; } diff --git a/frontend/src/app/routes/settings/users/settings-users.module.ts b/frontend/src/app/routes/settings/users/settings-users.module.ts index bde417b..48e1fa8 100644 --- a/frontend/src/app/routes/settings/users/settings-users.module.ts +++ b/frontend/src/app/routes/settings/users/settings-users.module.ts @@ -10,6 +10,8 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; import { FabModule } from 'src/app/components/fab/fab.module'; import { ValuesPickerModule } from 'src/app/components/values-picker/values-picker.module'; +import { DialogManagerModule } from 'src/app/util/dialog-manager/dialog-manager.module'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { SettingsUsersEditComponent } from './settings-users-edit/settings-users-edit.component'; import { SettingsUsersComponent } from './settings-users.component'; import { SettingsUsersRoutingModule } from './settings-users.routing.module'; @@ -18,6 +20,9 @@ import { SettingsUsersRoutingModule } from './settings-users.routing.module'; declarations: [SettingsUsersComponent, SettingsUsersEditComponent], imports: [ CommonModule, + ErrorManagerModule, + DialogManagerModule, + SettingsUsersRoutingModule, MatButtonModule, MatIconModule, diff --git a/frontend/src/app/routes/upload/upload.component.ts b/frontend/src/app/routes/upload/upload.component.ts index 87f1281..3b1e393 100644 --- a/frontend/src/app/routes/upload/upload.component.ts +++ b/frontend/src/app/routes/upload/upload.component.ts @@ -3,10 +3,11 @@ import { Router } from '@angular/router'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { NgxDropzoneChangeEvent } from 'ngx-dropzone'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; +import { Fail, FT } from 'picsur-shared/dist/types'; import { debounceTime } from 'rxjs'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { PermissionService } from 'src/app/services/api/permission.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { ProcessingViewMeta } from '../../models/dto/processing-view-meta.dto'; @Component({ @@ -14,12 +15,14 @@ import { ProcessingViewMeta } from '../../models/dto/processing-view-meta.dto'; styleUrls: ['./upload.component.scss'], }) export class UploadComponent implements OnInit { + private readonly logger = new Logger(UploadComponent.name); + canUpload = true; constructor( - private readonly utilService: UtilService, - private readonly permissionService: PermissionService, private readonly router: Router, + private readonly permissionService: PermissionService, + private readonly errorService: ErrorService, ) {} ngOnInit(): void { @@ -36,11 +39,10 @@ export class UploadComponent implements OnInit { } onSelect(event: NgxDropzoneChangeEvent) { - if (event.addedFiles.length > 1) { - this.utilService.showSnackBar( + if (event.addedFiles.length > 1) + this.errorService.log( 'You uploaded multiple images, only one has been uploaded', ); - } const metadata: ProcessingViewMeta = { imageFile: event.addedFiles[0], @@ -51,36 +53,28 @@ export class UploadComponent implements OnInit { @HostListener('document:paste', ['$event']) onPaste(event: ClipboardEvent) { const items = event.clipboardData?.items; - if (!items) { - this.utilService.showSnackBar('Your clipboard is empty'); - return; - } + if (!items) return this.errorService.info('Your clipboard is empty'); const filteredItems = Array.from(items).filter( (item) => item.kind === 'file', ); - if (filteredItems.length === 0) { - this.utilService.showSnackBar( + if (filteredItems.length === 0) + return this.errorService.info( 'Your clipboard does not contain any images', ); - return; - } const blob = filteredItems[0].getAsFile(); - if (!blob) { - this.utilService.showSnackBar( - 'Error getting image from clipboard', - SnackBarType.Error, + if (!blob) + return this.errorService.showFailure( + Fail(FT.Internal, 'Error getting image from clipboard'), + this.logger, ); - return; - } - if (filteredItems.length > 1) { - this.utilService.showSnackBar( + if (filteredItems.length > 1) + this.errorService.log( 'You pasted multiple images, only one has been uploaded', ); - } const metadata: ProcessingViewMeta = { imageFile: blob, diff --git a/frontend/src/app/routes/upload/upload.module.ts b/frontend/src/app/routes/upload/upload.module.ts index ecb25f4..6af1225 100644 --- a/frontend/src/app/routes/upload/upload.module.ts +++ b/frontend/src/app/routes/upload/upload.module.ts @@ -1,11 +1,17 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NgxDropzoneModule } from 'ngx-dropzone'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { UploadComponent } from './upload.component'; import { UploadRoutingModule } from './upload.routing.module'; @NgModule({ declarations: [UploadComponent], - imports: [CommonModule, UploadRoutingModule, NgxDropzoneModule], + imports: [ + CommonModule, + ErrorManagerModule, + UploadRoutingModule, + NgxDropzoneModule, + ], }) export class UploadRouteModule {} diff --git a/frontend/src/app/routes/user/login/login.component.ts b/frontend/src/app/routes/user/login/login.component.ts index cd34a2c..8ceb3a6 100644 --- a/frontend/src/app/routes/user/login/login.component.ts +++ b/frontend/src/app/routes/user/login/login.component.ts @@ -3,12 +3,11 @@ import { Router } from '@angular/router'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { HasFailed } from 'picsur-shared/dist/types'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { UserPassModel } from 'src/app/models/forms-dto/userpass.dto'; import { PermissionService } from 'src/app/services/api/permission.service'; import { UserService } from 'src/app/services/api/user.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { LoginControl } from '../../../models/forms/login.control'; @Component({ @@ -27,7 +26,7 @@ export class LoginComponent implements OnInit { private readonly userService: UserService, private readonly permissionService: PermissionService, private readonly router: Router, - private readonly utilService: UtilService, + private readonly errorService: ErrorService, ) {} ngOnInit(): void { @@ -57,16 +56,10 @@ export class LoginComponent implements OnInit { const user = await this.userService.login(data.username, data.password); this.loading = false; - if (HasFailed(user)) { - this.logger.error(user.getReason()); - this.utilService.showSnackBar( - 'Login failed, please try again', - SnackBarType.Error, - ); - return; - } + if (HasFailed(user)) + return this.errorService.showFailure(user, this.logger); - this.utilService.showSnackBar('Login successful', SnackBarType.Success); + this.errorService.success('Login successful'); this.router.navigate(['/']); } diff --git a/frontend/src/app/routes/user/register/register.component.ts b/frontend/src/app/routes/user/register/register.component.ts index f32371e..91c4713 100644 --- a/frontend/src/app/routes/user/register/register.component.ts +++ b/frontend/src/app/routes/user/register/register.component.ts @@ -3,12 +3,11 @@ import { Router } from '@angular/router'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { HasFailed } from 'picsur-shared/dist/types'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { UserPassModel } from 'src/app/models/forms-dto/userpass.dto'; import { PermissionService } from 'src/app/services/api/permission.service'; import { UserService } from 'src/app/services/api/user.service'; import { Logger } from 'src/app/services/logger/logger.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { RegisterControl } from '../../../models/forms/register.control'; @Component({ @@ -27,7 +26,7 @@ export class RegisterComponent implements OnInit { private readonly userService: UserService, private readonly permissionService: PermissionService, private readonly router: Router, - private readonly utilService: UtilService, + private readonly errorService: ErrorService, ) {} ngOnInit(): void { @@ -58,12 +57,7 @@ export class RegisterComponent implements OnInit { if (HasFailed(user)) { this.loading = false; - this.logger.error(user.getReason()); - this.utilService.showSnackBar( - 'Register failed, please try again', - SnackBarType.Error, - ); - return; + return this.errorService.showFailure(user, this.logger); } if (!this.userService.isLoggedIn) { @@ -72,22 +66,14 @@ export class RegisterComponent implements OnInit { data.password, ); if (HasFailed(loginResult)) { - this.logger.error(loginResult.getReason()); - this.utilService.showSnackBar( - 'Failed to login after register', - SnackBarType.Error, - ); + this.loading = false; + + return this.errorService.showFailure(loginResult, this.logger); } - this.utilService.showSnackBar( - 'Register successful', - SnackBarType.Success, - ); + this.errorService.success('Register successful'); } else { - this.utilService.showSnackBar( - 'Register successful, did not log in', - SnackBarType.Success, - ); + this.errorService.success('Register successful, did not log in'); } this.loading = false; diff --git a/frontend/src/app/routes/user/user.module.ts b/frontend/src/app/routes/user/user.module.ts index 45211a1..ea1f3c5 100644 --- a/frontend/src/app/routes/user/user.module.ts +++ b/frontend/src/app/routes/user/user.module.ts @@ -4,6 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { LoginComponent } from './login/login.component'; import { RegisterComponent } from './register/register.component'; import { UserRoutingModule } from './user.routing.module'; @@ -12,6 +13,8 @@ import { UserRoutingModule } from './user.routing.module'; declarations: [LoginComponent, RegisterComponent], imports: [ CommonModule, + ErrorManagerModule, + UserRoutingModule, FormsModule, MatInputModule, diff --git a/frontend/src/app/routes/view/view.component.ts b/frontend/src/app/routes/view/view.component.ts index cb2302c..9e896fe 100644 --- a/frontend/src/app/routes/view/view.component.ts +++ b/frontend/src/app/routes/view/view.component.ts @@ -4,7 +4,8 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class'; import { AnimFileType, - FileType, ImageFileType, + FileType, + ImageFileType, SupportedFileTypeCategory } from 'picsur-shared/dist/dto/mimes.dto'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; @@ -14,12 +15,15 @@ import { EUser } from 'picsur-shared/dist/entities/user.entity'; import { HasFailed, HasSuccess } from 'picsur-shared/dist/types'; import { UUIDRegex } from 'picsur-shared/dist/util/common-regex'; import { ParseFileType } from 'picsur-shared/dist/util/parse-mime'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { ImageService } from 'src/app/services/api/image.service'; import { PermissionService } from 'src/app/services/api/permission.service'; import { UserService } from 'src/app/services/api/user.service'; -import { SimpleUtilService } from 'src/app/util/util-module/simple-util.service'; -import { UtilService } from 'src/app/util/util-module/util.service'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { DialogService } from 'src/app/util/dialog-manager/dialog.service'; +import { DownloadService } from 'src/app/util/download-manager/download.service'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; +import { UtilService } from 'src/app/util/util.service'; + import { CustomizeDialogComponent, CustomizeDialogData @@ -30,14 +34,19 @@ import { styleUrls: ['./view.component.scss'], }) export class ViewComponent implements OnInit { + private readonly logger = new Logger(ViewComponent.name); + constructor( private readonly route: ActivatedRoute, private readonly router: Router, private readonly imageService: ImageService, - private readonly utilService: UtilService, - private readonly simpleUtil: SimpleUtilService, private readonly permissionService: PermissionService, private readonly userService: UserService, + + private readonly errorService: ErrorService, + private readonly downloadService: DownloadService, + private readonly dialogService: DialogService, + private readonly utilService: UtilService, ) {} private id: string; @@ -71,13 +80,13 @@ export class ViewComponent implements OnInit { this.id = params.get('id') ?? ''; if (!UUIDRegex.test(this.id)) { - return this.utilService.quitError('Invalid image link'); + return this.errorService.quitError('Invalid image link', this.logger); } // Get metadata const metadata = await this.imageService.GetImageMeta(this.id); if (HasFailed(metadata)) - return this.utilService.quitError(metadata.getReason()); + return this.errorService.quitFailure(metadata, this.logger); // Get width of screen in pixels const width = window.innerWidth * window.devicePixelRatio; @@ -126,11 +135,11 @@ export class ViewComponent implements OnInit { } download() { - this.utilService.downloadFile(this.imageLinks.source); + this.downloadService.downloadFile(this.imageLinks.source); } share() { - this.utilService.shareFile(this.imageLinks.source); + this.downloadService.shareFile(this.imageLinks.source); } goBackHome() { @@ -138,7 +147,7 @@ export class ViewComponent implements OnInit { } async deleteImage() { - const pressedButton = await this.utilService.showDialog({ + const pressedButton = await this.dialogService.showDialog({ title: `Are you sure you want to delete the image?`, description: 'This action cannot be undone.', buttons: [ @@ -156,14 +165,10 @@ export class ViewComponent implements OnInit { if (pressedButton === 'delete') { const result = await this.imageService.DeleteImage(this.id); - if (HasFailed(result)) { - return this.utilService.showSnackBar( - 'Failed to delete image', - SnackBarType.Error, - ); - } + if (HasFailed(result)) + return this.errorService.showFailure(result, this.logger); - this.utilService.showSnackBar('Image deleted', SnackBarType.Success); + this.errorService.success('Image deleted'); this.router.navigate(['/']); } @@ -173,16 +178,20 @@ export class ViewComponent implements OnInit { const options: CustomizeDialogData = { imageID: this.id, selectedFormat: this.currentSelectedFormat, - formatOptions: this.simpleUtil.getBaseFormatOptions(), + formatOptions: this.utilService.getBaseFormatOptions(), }; if (options.selectedFormat === 'original') { options.selectedFormat = this.masterFileType.identifier; } - await this.utilService.showCustomDialog(CustomizeDialogComponent, options, { - dismissable: false, - }); + await this.dialogService.showCustomDialog( + CustomizeDialogComponent, + options, + { + dismissable: false, + }, + ); } @AutoUnsubscribe() @@ -224,7 +233,7 @@ export class ViewComponent implements OnInit { }); } - newOptions = newOptions.concat(this.simpleUtil.getBaseFormatOptions()); + newOptions = newOptions.concat(this.utilService.getBaseFormatOptions()); this.formatOptions = newOptions; } diff --git a/frontend/src/app/routes/view/view.module.ts b/frontend/src/app/routes/view/view.module.ts index a720c28..b656799 100644 --- a/frontend/src/app/routes/view/view.module.ts +++ b/frontend/src/app/routes/view/view.module.ts @@ -1,3 +1,4 @@ +import { DialogModule } from '@angular/cdk/dialog'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -13,6 +14,8 @@ import { CopyFieldModule } from 'src/app/components/copy-field/copy-field.module import { FabModule } from 'src/app/components/fab/fab.module'; import { PicsurImgModule } from 'src/app/components/picsur-img/picsur-img.module'; import { PipesModule } from 'src/app/pipes/pipes.module'; +import { DownloadManagerModule } from 'src/app/util/download-manager/dialog-manager.module'; +import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module'; import { CustomizeDialogComponent } from './customize-dialog/customize-dialog.component'; import { ViewComponent } from './view.component'; import { ViewRoutingModule } from './view.routing.module'; @@ -20,6 +23,10 @@ import { ViewRoutingModule } from './view.routing.module'; declarations: [ViewComponent, CustomizeDialogComponent], imports: [ CommonModule, + ErrorManagerModule, + DownloadManagerModule, + DialogModule, + CopyFieldModule, ViewRoutingModule, MatButtonModule, diff --git a/frontend/src/app/services/api/image.service.ts b/frontend/src/app/services/api/image.service.ts index 3a2fddd..ae4941a 100644 --- a/frontend/src/app/services/api/image.service.ts +++ b/frontend/src/app/services/api/image.service.ts @@ -21,7 +21,7 @@ import { HasSuccess, Open } from 'picsur-shared/dist/types/failable'; -import { SimpleUtilService } from 'src/app/util/util-module/simple-util.service'; +import { UtilService } from 'src/app/util/util.service'; import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto'; import { ApiService } from './api.service'; import { UserService } from './user.service'; @@ -32,8 +32,7 @@ import { UserService } from './user.service'; export class ImageService { constructor( private readonly api: ApiService, - private readonly simpleUtil: SimpleUtilService, - + private readonly util: UtilService, private readonly userService: UserService, ) {} @@ -110,7 +109,7 @@ export class ImageService { // Non api calls public GetImageURL(image: string, filetype: string | null): string { - const baseURL = this.simpleUtil.getHost(); + const baseURL = this.util.getHost(); const extension = FileType2Ext(filetype ?? ''); return `${baseURL}/i/${image}${ diff --git a/frontend/src/app/services/api/info.service.ts b/frontend/src/app/services/api/info.service.ts index 92e50bb..3cc0ef5 100644 --- a/frontend/src/app/services/api/info.service.ts +++ b/frontend/src/app/services/api/info.service.ts @@ -1,11 +1,8 @@ -import { Inject, Injectable } from '@angular/core'; -import { HISTORY } from '@ng-web-apis/common'; +import { Injectable } from '@angular/core'; import { InfoResponse } from 'picsur-shared/dist/dto/api/info.dto'; import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types'; import { SemVerRegex } from 'picsur-shared/dist/util/common-regex'; import { BehaviorSubject } from 'rxjs'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; -import { UtilService } from 'src/app/util/util-module/util.service'; import pkg from '../../../../package.json'; import { ServerInfo } from '../../models/dto/server-info.dto'; import { Logger } from '../logger/logger.service'; @@ -23,13 +20,7 @@ export class InfoService { private infoSubject = new BehaviorSubject(new ServerInfo()); - constructor( - private readonly api: ApiService, - private readonly utilService: UtilService, - @Inject(HISTORY) private readonly history: History, - ) { - this.checkCompatibility().catch(this.logger.error); - } + constructor(private readonly api: ApiService) {} public async pollInfo(): AsyncFailable { const response = await this.api.get(InfoResponse, '/api/info'); @@ -72,46 +63,4 @@ export class InfoService { return serverDecoded[0] === clientDecoded[0]; } } - - private async checkCompatibility() { - const isCompatible = await this.isCompatibleWithServer(); - - if (HasFailed(isCompatible)) { - this.utilService.showSnackBar( - 'There was an error checking compatibility', - SnackBarType.Warning, - ); - return; - } - - if (!isCompatible) { - this.utilService - .showDialog({ - title: 'Server is not compatible', - description: - 'The server is not compatible with this version of the client. You can ignore this, but expect things to not work.', - buttons: [ - { - text: 'Back', - name: 'back', - color: 'accent', - }, - { - text: 'Ignore', - name: 'ignore', - color: 'warn', - }, - ], - }) - .then((button) => { - if (button === 'ignore') { - this.logger.warn('Ignoring server compatibility'); - } else { - this.checkCompatibility(); - // Go to previous page - this.history.back(); - } - }); - } - } } diff --git a/frontend/src/app/services/api/sys-pref.service.ts b/frontend/src/app/services/api/sys-pref.service.ts index 74ef9cf..0c3ad2a 100644 --- a/frontend/src/app/services/api/sys-pref.service.ts +++ b/frontend/src/app/services/api/sys-pref.service.ts @@ -11,11 +11,16 @@ import { DecodedPref, PrefValueType } from 'picsur-shared/dist/dto/preferences.dto'; -import { AsyncFailable, Fail, FT, HasFailed, Map } from 'picsur-shared/dist/types'; +import { + AsyncFailable, + Fail, + FT, + HasFailed, + Map +} from 'picsur-shared/dist/types'; import { BehaviorSubject } from 'rxjs'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { Throttle } from 'src/app/util/throttle'; -import { UtilService } from 'src/app/util/util-module/util.service'; import { Logger } from '../logger/logger.service'; import { ApiService } from './api.service'; import { PermissionService } from './permission.service'; @@ -41,7 +46,7 @@ export class SysPrefService { constructor( private readonly api: ApiService, private readonly permissionsService: PermissionService, - private readonly utilService: UtilService, + private readonly errorService: ErrorService, ) { this.subscribePermissions(); } @@ -49,10 +54,7 @@ export class SysPrefService { private async refresh() { const result = await this.getPreferences(); if (HasFailed(result)) { - this.utilService.showSnackBar( - "Couldn't load system preferences", - SnackBarType.Error, - ); + this.errorService.showFailure(result, this.logger); this.flush(); } } diff --git a/frontend/src/app/services/api/usr-pref.service.ts b/frontend/src/app/services/api/usr-pref.service.ts index 833524c..f09cf2d 100644 --- a/frontend/src/app/services/api/usr-pref.service.ts +++ b/frontend/src/app/services/api/usr-pref.service.ts @@ -19,9 +19,8 @@ import { Map } from 'picsur-shared/dist/types'; import { BehaviorSubject } from 'rxjs'; -import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; +import { ErrorService } from 'src/app/util/error-manager/error.service'; import { Throttle } from 'src/app/util/throttle'; -import { UtilService } from 'src/app/util/util-module/util.service'; import { Logger } from '../logger/logger.service'; import { ApiService } from './api.service'; import { PermissionService } from './permission.service'; @@ -47,7 +46,7 @@ export class UsrPrefService { constructor( private readonly api: ApiService, private readonly permissionsService: PermissionService, - private readonly utilService: UtilService, + private readonly errorService: ErrorService, ) { this.subscribePermissions(); } @@ -55,10 +54,7 @@ export class UsrPrefService { private async refresh() { const result = await this.getPreferences(); if (HasFailed(result)) { - this.utilService.showSnackBar( - "Couldn't load user preferences", - SnackBarType.Error, - ); + this.errorService.showFailure(result, this.logger); this.flush(); } } diff --git a/frontend/src/app/util/api-error-manager/api-error-manager.module.ts b/frontend/src/app/util/api-error-manager/api-error-manager.module.ts new file mode 100644 index 0000000..4f828ed --- /dev/null +++ b/frontend/src/app/util/api-error-manager/api-error-manager.module.ts @@ -0,0 +1,15 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { SnackBarManagerModule } from '../snackbar-manager/snackbar-manager.module'; +import { ApiErrorService } from './api-error.service'; + +@NgModule({ + imports: [CommonModule, SnackBarManagerModule.forRoot()], + providers: [ApiErrorService], +}) +export class ApiErrorManagerModule { + // Start apiErrorService, the nothing function does nothing, but it silents the error. + constructor(apiErrorService: ApiErrorService) { + apiErrorService.nothing(); + } +} diff --git a/frontend/src/app/util/util-module/api-error.service.ts b/frontend/src/app/util/api-error-manager/api-error.service.ts similarity index 80% rename from frontend/src/app/util/util-module/api-error.service.ts rename to frontend/src/app/util/api-error-manager/api-error.service.ts index a39d213..6a68f9d 100644 --- a/frontend/src/app/util/util-module/api-error.service.ts +++ b/frontend/src/app/util/api-error-manager/api-error.service.ts @@ -3,7 +3,7 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { SnackBarType } from '../../models/dto/snack-bar-type.dto'; import { ApiService } from '../../services/api/api.service'; import { Logger } from '../../services/logger/logger.service'; -import { UtilService } from './util.service'; +import { SnackBarService } from '../snackbar-manager/snackbar.service'; @Injectable({ providedIn: 'root', @@ -13,7 +13,7 @@ export class ApiErrorService { constructor( private readonly apiSerivce: ApiService, - private readonly utilService: UtilService, + private readonly snackbarService: SnackBarService, ) { this.subscribeErrors(); } @@ -28,7 +28,7 @@ export class ApiErrorService { else url = error.url.url; if (url.startsWith('/api')) { - this.utilService.showSnackBar('Network Error', SnackBarType.Error); + this.snackbarService.showSnackBar('Network Error', SnackBarType.Error); } this.logger.error(error.error); diff --git a/frontend/src/app/util/util-module/bootstrap.service.ts b/frontend/src/app/util/bootstrap.service.ts similarity index 89% rename from frontend/src/app/util/util-module/bootstrap.service.ts rename to frontend/src/app/util/bootstrap.service.ts index 331d7f1..ed2a326 100644 --- a/frontend/src/app/util/util-module/bootstrap.service.ts +++ b/frontend/src/app/util/bootstrap.service.ts @@ -11,6 +11,7 @@ export enum BSScreenSize { lg = 3, xl = 4, xxl = 5, + xxxl = 6, } @Injectable({ @@ -24,6 +25,7 @@ export class BootstrapService { private lgObservable: Observable; private xlObservable: Observable; private xxlObservable: Observable; + private xxxlObservable: Observable; private screenSizeSubject: BehaviorSubject = new BehaviorSubject(BSScreenSize.xs); @@ -34,6 +36,7 @@ export class BootstrapService { this.lgObservable = this.createObserver('(min-width: 992px)'); this.xlObservable = this.createObserver('(min-width: 1200px)'); this.xxlObservable = this.createObserver('(min-width: 1400px)'); + this.xxxlObservable = this.createObserver('(min-width: 1600px)'); this.subscribeObservables(); } @@ -52,8 +55,11 @@ export class BootstrapService { this.lgObservable, this.xlObservable, this.xxlObservable, - ]).subscribe(([sm, md, lg, xl, xxl]) => { - const size = xxl + this.xxxlObservable, + ]).subscribe(([sm, md, lg, xl, xxl, xxxl]) => { + const size = xxxl + ? BSScreenSize.xxxl + : xxl ? BSScreenSize.xxl : xl ? BSScreenSize.xl diff --git a/frontend/src/app/util/compatibilitiy-manager/compatibility-manager.module.ts b/frontend/src/app/util/compatibilitiy-manager/compatibility-manager.module.ts new file mode 100644 index 0000000..c55dc50 --- /dev/null +++ b/frontend/src/app/util/compatibilitiy-manager/compatibility-manager.module.ts @@ -0,0 +1,15 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { DialogManagerModule } from '../dialog-manager/dialog-manager.module'; +import { ErrorManagerModule } from '../error-manager/error-manager.module'; +import { CompatibilityService } from './compatibility.service'; + +@NgModule({ + imports: [CommonModule, DialogManagerModule, ErrorManagerModule], + providers: [CompatibilityService], +}) +export class CompatibilityManagerModule { + constructor(compatibilityService: CompatibilityService) { + compatibilityService.nothing(); + } +} diff --git a/frontend/src/app/util/compatibilitiy-manager/compatibility.service.ts b/frontend/src/app/util/compatibilitiy-manager/compatibility.service.ts new file mode 100644 index 0000000..4b95ea4 --- /dev/null +++ b/frontend/src/app/util/compatibilitiy-manager/compatibility.service.ts @@ -0,0 +1,63 @@ +import { Inject, Injectable } from '@angular/core'; +import { HISTORY } from '@ng-web-apis/common'; +import { HasFailed } from 'picsur-shared/dist/types'; +import { InfoService } from 'src/app/services/api/info.service'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { DialogService } from '../dialog-manager/dialog.service'; +import { ErrorService } from '../error-manager/error.service'; + +@Injectable({ + providedIn: 'root', +}) +export class CompatibilityService { + private readonly logger = new Logger(CompatibilityService.name); + + constructor( + private readonly infoService: InfoService, + private readonly errorService: ErrorService, + private readonly dialogService: DialogService, + @Inject(HISTORY) private readonly history: History, + ) { + this.checkCompatibility().catch(this.logger.error); + } + + nothing() {} + + private async checkCompatibility() { + const isCompatible = await this.infoService.isCompatibleWithServer(); + + if (HasFailed(isCompatible)) { + return this.errorService.showFailure(isCompatible, this.logger); + } + + if (!isCompatible) { + this.dialogService + .showDialog({ + title: 'Server is not compatible', + description: + 'The server is not compatible with this version of the client. You can ignore this, but expect things to not work.', + buttons: [ + { + text: 'Back', + name: 'back', + color: 'accent', + }, + { + text: 'Ignore', + name: 'ignore', + color: 'warn', + }, + ], + }) + .then((button) => { + if (button === 'ignore') { + this.logger.warn('Ignoring server compatibility'); + } else { + this.checkCompatibility(); + // Go to previous page + this.history.back(); + } + }); + } + } +} diff --git a/frontend/src/app/util/util-module/confirm-dialog/confirm-dialog.component.html b/frontend/src/app/util/dialog-manager/confirm-dialog/confirm-dialog.component.html similarity index 100% rename from frontend/src/app/util/util-module/confirm-dialog/confirm-dialog.component.html rename to frontend/src/app/util/dialog-manager/confirm-dialog/confirm-dialog.component.html diff --git a/frontend/src/app/util/util-module/confirm-dialog/confirm-dialog.component.scss b/frontend/src/app/util/dialog-manager/confirm-dialog/confirm-dialog.component.scss similarity index 100% rename from frontend/src/app/util/util-module/confirm-dialog/confirm-dialog.component.scss rename to frontend/src/app/util/dialog-manager/confirm-dialog/confirm-dialog.component.scss diff --git a/frontend/src/app/util/util-module/confirm-dialog/confirm-dialog.component.ts b/frontend/src/app/util/dialog-manager/confirm-dialog/confirm-dialog.component.ts similarity index 100% rename from frontend/src/app/util/util-module/confirm-dialog/confirm-dialog.component.ts rename to frontend/src/app/util/dialog-manager/confirm-dialog/confirm-dialog.component.ts diff --git a/frontend/src/app/util/dialog-manager/dialog-manager.module.ts b/frontend/src/app/util/dialog-manager/dialog-manager.module.ts new file mode 100644 index 0000000..cad4f36 --- /dev/null +++ b/frontend/src/app/util/dialog-manager/dialog-manager.module.ts @@ -0,0 +1,20 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component'; +import { DialogService } from './dialog.service'; +import { DownloadDialogComponent } from './download-dialog/download-dialog.component'; + +@NgModule({ + imports: [ + CommonModule, + MatDialogModule, + MatButtonModule, + MatProgressBarModule, + ], + declarations: [ConfirmDialogComponent, DownloadDialogComponent], + providers: [DialogService], +}) +export class DialogManagerModule {} diff --git a/frontend/src/app/util/dialog-manager/dialog.service.ts b/frontend/src/app/util/dialog-manager/dialog.service.ts new file mode 100644 index 0000000..673847f --- /dev/null +++ b/frontend/src/app/util/dialog-manager/dialog.service.ts @@ -0,0 +1,45 @@ +import { ComponentType } from '@angular/cdk/portal'; +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { ConfirmDialogComponent, ConfirmDialogData } from './confirm-dialog/confirm-dialog.component'; + +@Injectable({ + providedIn: 'any', +}) +export class DialogService { + private readonly logger = new Logger(DialogService.name); + + constructor( + private readonly dialog: MatDialog, + ) {} + + public async showCustomDialog( + component: ComponentType, + data: any, + options?: { + dismissable?: boolean; + }, + ): Promise { + return new Promise((resolve, reject) => { + const ref = this.dialog.open(component, { + data, + panelClass: 'small-dialog-padding', + ...(options?.dismissable === false + ? {} + : { disableClose: true, closeOnNavigation: false }), + maxHeight: '90vh', + }); + const subscription = ref.beforeClosed().subscribe((result) => { + subscription.unsubscribe(); + resolve(result); + }); + }); + } + + public async showDialog( + options: ConfirmDialogData, + ): Promise { + return this.showCustomDialog(ConfirmDialogComponent, options); + } +} diff --git a/frontend/src/app/util/util-module/download-dialog/download-dialog.component.html b/frontend/src/app/util/dialog-manager/download-dialog/download-dialog.component.html similarity index 100% rename from frontend/src/app/util/util-module/download-dialog/download-dialog.component.html rename to frontend/src/app/util/dialog-manager/download-dialog/download-dialog.component.html diff --git a/frontend/src/app/util/util-module/download-dialog/download-dialog.component.ts b/frontend/src/app/util/dialog-manager/download-dialog/download-dialog.component.ts similarity index 100% rename from frontend/src/app/util/util-module/download-dialog/download-dialog.component.ts rename to frontend/src/app/util/dialog-manager/download-dialog/download-dialog.component.ts diff --git a/frontend/src/app/util/download-manager/dialog-manager.module.ts b/frontend/src/app/util/download-manager/dialog-manager.module.ts new file mode 100644 index 0000000..c5d38a6 --- /dev/null +++ b/frontend/src/app/util/download-manager/dialog-manager.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { MatDialogModule } from '@angular/material/dialog'; +import { ErrorManagerModule } from '../error-manager/error-manager.module'; +import { DownloadService } from './download.service'; + +@NgModule({ + imports: [CommonModule, MatDialogModule, ErrorManagerModule], + providers: [DownloadService], +}) +export class DownloadManagerModule {} diff --git a/frontend/src/app/util/download-manager/download.service.ts b/frontend/src/app/util/download-manager/download.service.ts new file mode 100644 index 0000000..8e88218 --- /dev/null +++ b/frontend/src/app/util/download-manager/download.service.ts @@ -0,0 +1,117 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Fail, FT, HasFailed } from 'picsur-shared/dist/types'; +import { ApiService } from 'src/app/services/api/api.service'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { DownloadDialogComponent } from '../dialog-manager/download-dialog/download-dialog.component'; +import { ErrorService } from '../error-manager/error.service'; +import { UtilService } from '../util.service'; + +@Injectable({ + providedIn: 'any', +}) +export class DownloadService { + private readonly logger = new Logger(DownloadService.name); + + constructor( + private readonly dialog: MatDialog, + private readonly api: ApiService, + private readonly util: UtilService, + private readonly errorService: ErrorService, + ) {} + + public showDownloadDialog(filename: string): () => void { + const ref = this.dialog.open(DownloadDialogComponent, { + data: { name: filename }, + disableClose: true, + closeOnNavigation: false, + }); + + return () => ref.close(); + } + + public async downloadFile(url: string) { + const closeDialog = this.showDownloadDialog('image'); + + const file = await this.api.getBuffer(url); + if (HasFailed(file)) + return this.errorService.showFailure(file, this.logger); + + this.util.downloadBuffer(file.buffer, file.name, file.mimeType); + + closeDialog(); + + this.errorService.info('Image downloaded'); + } + + public canShare(): boolean { + if (navigator.canShare === undefined || navigator.share === undefined) + return false; + + const testShare = navigator.canShare({ + url: 'https://www.example.com', + }); + + return testShare; + } + + public canShareFiles(): boolean { + if (!this.canShare()) return false; + + const testFile = new File([], 'test.txt'); + const testShare = navigator.canShare({ + files: [testFile], + }); + + return testShare; + } + + public async shareFile(url: string) { + if (!this.canShare()) + return this.errorService.warn( + 'Sharing is not supported on your device', + this.logger, + ); + + let shareObject: ShareData; + + if (!this.canShareFiles()) { + shareObject = { + url, + }; + } else { + const image = await this.api.getBuffer(url); + if (HasFailed(image)) + return this.errorService.showFailure(image, this.logger); + + this.logger.log(image.name, image.mimeType); + + const imageFile = new File([image.buffer], image.name, { + type: image.mimeType, + }); + + shareObject = { + files: [imageFile], + }; + } + + const canShare = navigator.canShare(shareObject); + if (!canShare) + return this.errorService.warn( + 'Sharing is not supported on your device', + this.logger, + ); + + try { + await navigator.share(shareObject); + } catch (e) { + if (e instanceof DOMException && e.message === 'Share canceled') { + } else { + this.errorService.showFailure( + Fail(FT.Internal, 'Sharing failed!', e), + this.logger, + ); + } + } + } +} diff --git a/frontend/src/app/util/error-manager/error-manager.module.ts b/frontend/src/app/util/error-manager/error-manager.module.ts new file mode 100644 index 0000000..dc5f684 --- /dev/null +++ b/frontend/src/app/util/error-manager/error-manager.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { SnackBarManagerModule } from '../snackbar-manager/snackbar-manager.module'; +import { ErrorService } from './error.service'; + +@NgModule({ + imports: [CommonModule, SnackBarManagerModule.forRoot(), RouterModule], + providers: [ErrorService], +}) +export class ErrorManagerModule {} diff --git a/frontend/src/app/util/error-manager/error.service.ts b/frontend/src/app/util/error-manager/error.service.ts new file mode 100644 index 0000000..b6bbcb9 --- /dev/null +++ b/frontend/src/app/util/error-manager/error.service.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Failure } from 'picsur-shared/dist/types'; +import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { SnackBarService } from '../snackbar-manager/snackbar.service'; + +@Injectable({ + providedIn: 'any', +}) +export class ErrorService { + constructor( + private readonly snackbar: SnackBarService, + private readonly router: Router, + ) {} + + public showFailure(error: Failure, logger: Logger): void { + if (error.isImportant()) { + logger.error(error.print()); + } else { + logger.warn(error.print()); + } + + this.snackbar.showSnackBar( + error.getReason(), + error.isImportant() ? SnackBarType.Error : SnackBarType.Warning, + ); + } + + public quitFailure(error: Failure, logger: Logger): void { + this.showFailure(error, logger); + this.router.navigate(['/']); + } + + public warn(warning: string, logger: Logger): void { + logger.warn(warning); + this.snackbar.showSnackBar(warning, SnackBarType.Warning); + } + + public error(error: string, logger: Logger): void { + logger.error(error); + this.snackbar.showSnackBar(error, SnackBarType.Error); + } + + public info(info: string) { + this.snackbar.showSnackBar(info, SnackBarType.Info); + } + + public success(success: string) { + this.snackbar.showSnackBar(success, SnackBarType.Success); + } + + public log(log: string) { + this.snackbar.showSnackBar(log, SnackBarType.Default); + } + + public quitWarn(warning: string, logger: Logger): void { + this.warn(warning, logger); + this.router.navigate(['/']); + } + + public quitError(error: string, logger: Logger): void { + this.error(error, logger); + this.router.navigate(['/']); + } +} diff --git a/frontend/src/app/util/snackbar-manager/snackbar-manager.module.ts b/frontend/src/app/util/snackbar-manager/snackbar-manager.module.ts new file mode 100644 index 0000000..d971200 --- /dev/null +++ b/frontend/src/app/util/snackbar-manager/snackbar-manager.module.ts @@ -0,0 +1,28 @@ +import { CommonModule } from '@angular/common'; +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { + MatSnackBarModule, + MAT_SNACK_BAR_DEFAULT_OPTIONS +} from '@angular/material/snack-bar'; +import { SnackBarService } from './snackbar.service'; + +@NgModule({ + imports: [CommonModule, MatSnackBarModule], + providers: [SnackBarService], +}) +export class SnackBarManagerModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: SnackBarManagerModule, + providers: [ + { + provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, + useValue: { + duration: 4000, + horizontalPosition: 'left', + }, + }, + ], + }; + } +} diff --git a/frontend/src/app/util/snackbar-manager/snackbar.service.ts b/frontend/src/app/util/snackbar-manager/snackbar.service.ts new file mode 100644 index 0000000..de07438 --- /dev/null +++ b/frontend/src/app/util/snackbar-manager/snackbar.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; +import { Logger } from 'src/app/services/logger/logger.service'; +import { BootstrapService, BSScreenSize } from '../bootstrap.service'; + +@Injectable({ + providedIn: 'any', +}) +export class SnackBarService { + private readonly logger = new Logger(SnackBarService.name); + + constructor( + private readonly snackBar: MatSnackBar, + private readonly bootstrap: BootstrapService, + ) {} + + public showSnackBar( + message: string, + type: SnackBarType = SnackBarType.Default, + duration: number | undefined | null = null, + ) { + let ref = this.snackBar.open(message, '', { + panelClass: ['mat-toolbar', 'snackbar', type], + verticalPosition: + this.bootstrap.screenSizeSnapshot() > BSScreenSize.xs + ? 'bottom' + : 'top', + ...(duration !== null ? { duration } : {}), + }); + } +} diff --git a/frontend/src/app/util/util-module/util.module.ts b/frontend/src/app/util/util-module/util.module.ts deleted file mode 100644 index 1d9205f..0000000 --- a/frontend/src/app/util/util-module/util.module.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { ModuleWithProviders, NgModule } from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { - MatSnackBarModule, - MAT_SNACK_BAR_DEFAULT_OPTIONS -} from '@angular/material/snack-bar'; -import { RouterModule } from '@angular/router'; -import { ApiErrorService } from './api-error.service'; -import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component'; -import { DownloadDialogComponent } from './download-dialog/download-dialog.component'; - -@NgModule({ - imports: [ - CommonModule, - MatSnackBarModule, - MatDialogModule, - MatButtonModule, - MatProgressBarModule, - RouterModule, - ], - declarations: [ConfirmDialogComponent, DownloadDialogComponent], -}) -export class UtilModule { - static forRoot(): ModuleWithProviders { - return { - ngModule: UtilModule, - providers: [ - { - provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, - useValue: { - duration: 4000, - horizontalPosition: 'left', - }, - }, - ], - }; - } - - // Start apiErrorService, the nothing function does nothing, but it silents the error. - constructor(apiErrorService: ApiErrorService) { - apiErrorService.nothing(); - } -} diff --git a/frontend/src/app/util/util-module/util.service.ts b/frontend/src/app/util/util-module/util.service.ts deleted file mode 100644 index f11fcca..0000000 --- a/frontend/src/app/util/util-module/util.service.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { ComponentType } from '@angular/cdk/portal'; -import { Injectable } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { Router } from '@angular/router'; -import { HasFailed } from 'picsur-shared/dist/types'; -import { ApiService } from 'src/app/services/api/api.service'; -import { Logger } from 'src/app/services/logger/logger.service'; -import { SnackBarType } from '../../models/dto/snack-bar-type.dto'; -import { BootstrapService, BSScreenSize } from './bootstrap.service'; -import { - ConfirmDialogComponent, - ConfirmDialogData -} from './confirm-dialog/confirm-dialog.component'; -import { DownloadDialogComponent } from './download-dialog/download-dialog.component'; -import { SimpleUtilService } from './simple-util.service'; - -@Injectable({ - providedIn: 'any', -}) -export class UtilService { - private readonly logger = new Logger(UtilService.name); - - constructor( - private readonly simpleUtil: SimpleUtilService, - private readonly snackBar: MatSnackBar, - private readonly dialog: MatDialog, - private readonly router: Router, - private readonly api: ApiService, - private readonly bootstrap: BootstrapService, - ) {} - - public quitError(message: string) { - this.showSnackBar(message, SnackBarType.Error); - this.router.navigate(['/']); - } - - public showSnackBar( - message: string, - type: SnackBarType = SnackBarType.Default, - duration: number | undefined | null = null, - ) { - let ref = this.snackBar.open(message, '', { - panelClass: ['mat-toolbar', 'snackbar', type], - verticalPosition: - this.bootstrap.screenSizeSnapshot() > BSScreenSize.xs - ? 'bottom' - : 'top', - ...(duration !== null ? { duration } : {}), - }); - } - - public async showCustomDialog( - component: ComponentType, - data: any, - options?: { - dismissable?: boolean; - }, - ): Promise { - return new Promise((resolve, reject) => { - const ref = this.dialog.open(component, { - data, - panelClass: 'small-dialog-padding', - ...(options?.dismissable === false - ? {} - : { disableClose: true, closeOnNavigation: false }), - maxHeight: '90vh', - }); - const subscription = ref.beforeClosed().subscribe((result) => { - subscription.unsubscribe(); - resolve(result); - }); - }); - } - - public async showDialog( - options: ConfirmDialogData, - ): Promise { - return this.showCustomDialog(ConfirmDialogComponent, options); - } - - public showDownloadDialog(filename: string): () => void { - const ref = this.dialog.open(DownloadDialogComponent, { - data: { name: filename }, - disableClose: true, - closeOnNavigation: false, - }); - - return () => ref.close(); - } - - public async downloadFile(url: string) { - const closeDialog = this.showDownloadDialog('image'); - - const file = await this.api.getBuffer(url); - if (HasFailed(file)) { - closeDialog(); - this.logger.error(file.getReason()); - this.showSnackBar('Error while downloading image', SnackBarType.Error); - return; - } - - this.simpleUtil.downloadBuffer(file.buffer, file.name, file.mimeType); - - closeDialog(); - this.showSnackBar('Image downloaded', SnackBarType.Info); - } - - public canShare(): boolean { - if (navigator.canShare === undefined || navigator.share === undefined) - return false; - - const testShare = navigator.canShare({ - url: 'https://www.example.com', - }); - - return testShare; - } - - public canShareFiles(): boolean { - if (!this.canShare()) return false; - - const testFile = new File([], 'test.txt'); - const testShare = navigator.canShare({ - files: [testFile], - }); - - return testShare; - } - - public async shareFile(url: string) { - if (!this.canShare()) { - this.showSnackBar( - 'Sharing is not supported on your device', - SnackBarType.Warning, - ); - return; - } - - let shareObject: ShareData; - - if (!this.canShareFiles()) { - shareObject = { - url, - }; - } else { - const image = await this.api.getBuffer(url); - if (HasFailed(image)) { - this.logger.error(image.getReason()); - this.showSnackBar('Error while sharing image', SnackBarType.Error); - return; - } - - this.logger.log(image.name, image.mimeType); - - const imageFile = new File([image.buffer], image.name, { - type: image.mimeType, - }); - - shareObject = { - files: [imageFile], - }; - } - - const canShare = navigator.canShare(shareObject); - if (!canShare) { - this.showSnackBar( - 'Sharing is not supported on your device', - SnackBarType.Warning, - ); - return; - } - - try { - await navigator.share(shareObject); - } catch (e) { - if (e instanceof DOMException && e.message === 'Share canceled') { - } else { - this.logger.error(e); - this.showSnackBar('Could not share', SnackBarType.Error); - } - } - } - - public async sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } -} diff --git a/frontend/src/app/util/util-module/simple-util.service.ts b/frontend/src/app/util/util.service.ts similarity index 82% rename from frontend/src/app/util/util-module/simple-util.service.ts rename to frontend/src/app/util/util.service.ts index 30cd194..fa6568a 100644 --- a/frontend/src/app/util/util-module/simple-util.service.ts +++ b/frontend/src/app/util/util.service.ts @@ -2,13 +2,13 @@ import { Inject, Injectable } from '@angular/core'; import { LOCATION } from '@ng-web-apis/common'; import { FileType2Ext, SupportedFileTypes } from 'picsur-shared/dist/dto/mimes.dto'; import { HasFailed } from 'picsur-shared/dist/types'; -import { Logger } from '../../services/logger/logger.service'; +import { Logger } from '../services/logger/logger.service'; @Injectable({ providedIn: 'any', }) -export class SimpleUtilService { - private readonly logger = new Logger(SimpleUtilService.name); +export class UtilService { + private readonly logger = new Logger(UtilService.name); constructor(@Inject(LOCATION) private readonly location: Location) {} @@ -49,4 +49,8 @@ export class SimpleUtilService { return newOptions; } + + public async sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } }