diff --git a/src/Ombi/ClientApp/package.json b/src/Ombi/ClientApp/package.json index 8bbb2f141..c68ed3782 100644 --- a/src/Ombi/ClientApp/package.json +++ b/src/Ombi/ClientApp/package.json @@ -31,6 +31,8 @@ "@ngu/carousel": "^3.0.2", "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", + "@ngxs/devtools-plugin": "^3.7.2", + "@ngxs/store": "^3.7.2", "@types/jquery": "^3.3.29", "@yellowspot/ng-truncate": "^1.4.0", "angular-bootstrap-md": "^7.5.4", @@ -41,6 +43,7 @@ "core-js": "^2.5.4", "eventemitter2": "^5.0.1", "fullcalendar": "^4.0.0-alpha.4", + "immer": "^9.0.6", "jquery": "3.3.1", "lodash": "^4.17.21", "moment": "^2.29.1", diff --git a/src/Ombi/ClientApp/src/app/app.component.ts b/src/Ombi/ClientApp/src/app/app.component.ts index 64a59ea4a..cbfcf0af0 100644 --- a/src/Ombi/ClientApp/src/app/app.component.ts +++ b/src/Ombi/ClientApp/src/app/app.component.ts @@ -13,6 +13,7 @@ import { ICustomizationSettings, ICustomPage } from "./interfaces"; import { SignalRNotificationService } from './services/signlarnotification.service'; import { DOCUMENT } from '@angular/common'; +import { CustomizationFacade } from './state/customization'; @Component({ @@ -42,6 +43,7 @@ export class AppComponent implements OnInit { public authService: AuthService, private readonly router: Router, private readonly settingsService: SettingsService, + private customizationFacade: CustomizationFacade, public readonly translate: TranslateService, private readonly customPageService: CustomPageService, public overlayContainer: OverlayContainer, @@ -83,11 +85,8 @@ export class AppComponent implements OnInit { } public ngOnInit() { - // window["loading_screen"].finish(); - - this.settingsService.getCustomization().subscribe(x => { + this.customizationFacade.settings$().subscribe(x => { this.customizationSettings = x; - if (this.customizationSettings && this.customizationSettings.applicationName) { this.applicationName = this.customizationSettings.applicationName; this.document.getElementsByTagName('title')[0].innerText = this.applicationName; diff --git a/src/Ombi/ClientApp/src/app/app.module.ts b/src/Ombi/ClientApp/src/app/app.module.ts index ae4122eb7..8ec4a3b16 100644 --- a/src/Ombi/ClientApp/src/app/app.module.ts +++ b/src/Ombi/ClientApp/src/app/app.module.ts @@ -13,10 +13,12 @@ import { AuthService } from "./auth/auth.service"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { BrowserModule } from "@angular/platform-browser"; import { ButtonModule } from "primeng/button"; +import { CUSTOMIZATION_INITIALIZER } from "./state/customization/customization-initializer"; import { ConfirmDialogModule } from "primeng/confirmdialog"; import { CookieComponent } from "./auth/cookie.component"; import { CookieService } from "ng2-cookies"; import { CustomPageComponent } from "./custompage/custompage.component"; +import { CustomizationState } from "./state/customization/customization.state"; import { DataViewModule } from "primeng/dataview"; import { DialogModule } from "primeng/dialog"; import { JwtModule } from "@auth0/angular-jwt"; @@ -46,6 +48,8 @@ import { MatTooltipModule } from "@angular/material/tooltip"; import { MyNavComponent } from './my-nav/my-nav.component'; import { NavSearchComponent } from "./my-nav/nav-search.component"; import { NgModule } from "@angular/core"; +import { NgxsModule } from '@ngxs/store'; +import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; import { NotificationService } from "./services"; import { OverlayModule } from "@angular/cdk/overlay"; import { OverlayPanelModule } from "primeng/overlaypanel"; @@ -60,38 +64,7 @@ import { TokenResetPasswordComponent } from "./login/tokenresetpassword.componen import { TooltipModule } from "primeng/tooltip"; import { TranslateHttpLoader } from "@ngx-translate/http-loader"; import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor"; - -// Components - - - - - - - - - - - -// Services - - - - - - - - - - - - - - - - - - +import { environment } from "../environments/environment"; const routes: Routes = [ { path: "*", component: PageNotFoundComponent }, @@ -185,7 +158,14 @@ export function JwtTokenGetter() { }, }), SidebarModule, - MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule + MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule, + NgxsModule.forRoot([CustomizationState], { + developmentMode: !environment.production, + }), + ...environment.production ? [] : + [ + NgxsReduxDevtoolsPluginModule.forRoot(), + ] ], declarations: [ AppComponent, @@ -230,7 +210,8 @@ export function JwtTokenGetter() { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true - } + }, + CUSTOMIZATION_INITIALIZER ], bootstrap: [AppComponent], }) diff --git a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts index 4d1d4166c..33f8873f8 100644 --- a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts +++ b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts @@ -10,6 +10,8 @@ import { DomSanitizer } from "@angular/platform-browser"; import { ImageService } from "../services"; import { fadeInOutAnimation } from "../animations/fadeinout"; +import { CustomizationFacade } from "../state/customization"; +import { ThousandShortPipe } from "../pipes/ThousandShortPipe"; @Component({ templateUrl: "./landingpage.component.html", @@ -29,10 +31,11 @@ export class LandingPageComponent implements OnDestroy, OnInit { constructor(private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService, + private customizationFacade: CustomizationFacade, @Inject(APP_BASE_HREF) href :string) { this.href = href } public ngOnInit() { - this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); + this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x); this.settingsService.getLandingPage().subscribe(x => this.landingPageSettings = x); this.images.getRandomBackground().subscribe(x => { this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 19%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 79%, transparent 80%), url(" + x.url + ")"); diff --git a/src/Ombi/ClientApp/src/app/login/login.component.ts b/src/Ombi/ClientApp/src/app/login/login.component.ts index aeeb5c5e7..592758c9f 100644 --- a/src/Ombi/ClientApp/src/app/login/login.component.ts +++ b/src/Ombi/ClientApp/src/app/login/login.component.ts @@ -16,6 +16,7 @@ import { ImageService } from "../services"; import { fadeInOutAnimation } from "../animations/fadeinout"; import { StorageService } from "../shared/storage/storage-service"; import { MatSnackBar } from "@angular/material/snack-bar"; +import { CustomizationFacade } from "../state/customization"; @Component({ templateUrl: "./login.component.html", @@ -60,6 +61,7 @@ export class LoginComponent implements OnDestroy, OnInit { private status: StatusService, private fb: FormBuilder, private settingsService: SettingsService, + private customziationFacade: CustomizationFacade, private images: ImageService, private sanitizer: DomSanitizer, private route: ActivatedRoute, @@ -99,13 +101,13 @@ export class LoginComponent implements OnDestroy, OnInit { } public ngOnInit() { + + this.customziationFacade.settings$().subscribe(x => this.customizationSettings = x); + this.settingsService .getAuthentication() .subscribe((x) => (this.authenticationSettings = x)); this.settingsService.getClientId().subscribe((x) => (this.clientId = x)); - this.settingsService - .getCustomization() - .subscribe((x) => (this.customizationSettings = x)); this.images.getRandomBackground().subscribe((x) => { this.background = this.sanitizer.bypassSecurityTrustStyle( "url(" + x.url + ")" diff --git a/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts b/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts index 104d77c68..cb030b386 100644 --- a/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts +++ b/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts @@ -6,6 +6,7 @@ import { fadeInOutAnimation } from "../animations/fadeinout"; import { ICustomizationSettings } from "../interfaces"; import { IdentityService, ImageService, NotificationService, SettingsService } from "../services"; +import { CustomizationFacade } from "../state/customization"; @Component({ templateUrl: "./resetpassword.component.html", @@ -23,7 +24,7 @@ export class ResetPasswordComponent implements OnInit { constructor(private identityService: IdentityService, private notify: NotificationService, private fb: FormBuilder, private settingsService: SettingsService, @Inject(APP_BASE_HREF) href:string, - private images: ImageService, private sanitizer: DomSanitizer) { + private images: ImageService, private sanitizer: DomSanitizer, private customizationFacade: CustomizationFacade) { this.href = href; this.form = this.fb.group({ email: ["", [Validators.required]], @@ -38,7 +39,7 @@ export class ResetPasswordComponent implements OnInit { if (base.length > 1) { this.baseUrl = base; } - this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); + this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x); this.settingsService.getEmailSettingsEnabled().subscribe(x => this.emailSettingsEnabled = x); } diff --git a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts index c4778a4bd..695a79abb 100644 --- a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts +++ b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts @@ -1,15 +1,15 @@ -import { PlatformLocation } from "@angular/common"; +import { ActivatedRoute, Params } from "@angular/router"; import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { DomSanitizer } from "@angular/platform-browser"; -import { Router } from "@angular/router"; -import { ActivatedRoute, Params } from "@angular/router"; +import { IdentityService, ImageService } from "../services"; +import { CustomizationFacade } from "../state/customization"; +import { DomSanitizer } from "@angular/platform-browser"; import { ICustomizationSettings } from "../interfaces"; import { IResetPasswordToken } from "../interfaces"; -import { IdentityService, ImageService } from "../services"; import { NotificationService } from "../services"; -import { SettingsService } from "../services"; +import { PlatformLocation } from "@angular/common"; +import { Router } from "@angular/router"; @Component({ templateUrl: "./tokenresetpassword.component.html", @@ -23,8 +23,9 @@ export class TokenResetPasswordComponent implements OnInit { public baseUrl: string; constructor(private identityService: IdentityService, private router: Router, private route: ActivatedRoute, private notify: NotificationService, - private fb: FormBuilder, private settingsService: SettingsService, private location: PlatformLocation, - private images: ImageService, private sanitizer: DomSanitizer) { + private fb: FormBuilder, private location: PlatformLocation, private images: ImageService, + private sanitizer: DomSanitizer, private customizationFacade: CustomizationFacade, + ) { this.route.queryParams .subscribe((params: Params) => { @@ -45,7 +46,7 @@ export class TokenResetPasswordComponent implements OnInit { if (base.length > 1) { this.baseUrl = base; } - this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); + this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x); } public onSubmit(form: FormGroup) { diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts index 2f96e8760..e03e073eb 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts @@ -1,9 +1,10 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; -import { IUser, RequestType, UserType } from '../interfaces'; +import { ICustomizationSettings, IUser, RequestType, UserType } from '../interfaces'; import { SettingsService, SettingsStateService } from '../services'; import { AdvancedSearchDialogComponent } from '../shared/advanced-search-dialog/advanced-search-dialog.component'; +import { CustomizationFacade } from '../state/customization'; import { FilterService } from '../discover/services/filter-service'; import { ILocalUser } from '../auth/IUserLogin'; import { INavBar } from '../interfaces/ICommon'; @@ -53,8 +54,11 @@ export class MyNavComponent implements OnInit { public welcomeText: string; public RequestType = RequestType; + private customizationSettings: ICustomizationSettings; + constructor(private breakpointObserver: BreakpointObserver, private settingsService: SettingsService, + private customizationFacade: CustomizationFacade, private store: StorageService, private filterService: FilterService, private dialogService: MatDialog, @@ -74,8 +78,10 @@ export class MyNavComponent implements OnInit { this.issuesEnabled = await this.settingsService.issueEnabled().toPromise(); this.settingState.setIssue(this.issuesEnabled); - const customizationSettings = await this.settingsService.getCustomization().toPromise(); - console.log("issues enabled: " + this.issuesEnabled); + this.customizationFacade.settings$().subscribe(settings => { + this.customizationSettings = settings; + }); + this.theme = this.store.get("theme"); if (!this.theme) { this.store.save("theme", "dark"); @@ -92,7 +98,7 @@ export class MyNavComponent implements OnInit { { id: "nav-userManagement", name: "NavigationBar.UserManagement", icon: "fas fa-users", link: "/usermanagement", requiresAdmin: true, enabled: true }, //id: "", { name: "NavigationBar.Calendar", icon: "calendar_today", link: "/calendar", requiresAdmin: false, enabled: true }, { id: "nav-adminDonate", name: "NavigationBar.Donate", icon: "fas fa-dollar-sign", link: "https://www.paypal.me/PlexRequestsNet", externalLink: true, requiresAdmin: true, enabled: true, toolTip: true, style: "color:red;", toolTipMessage: 'NavigationBar.DonateTooltip' }, - { id: "nav-userDonate", name: "NavigationBar.Donate", icon: "fas fa-dollar-sign", link: customizationSettings.customDonationUrl, externalLink: true, requiresAdmin: false, enabled: customizationSettings.enableCustomDonations, toolTip: true, toolTipMessage: customizationSettings.customDonationMessage }, + { id: "nav-userDonate", name: "NavigationBar.Donate", icon: "fas fa-dollar-sign", link: this.customizationSettings.customDonationUrl, externalLink: true, requiresAdmin: false, enabled: this.customizationSettings.enableCustomDonations, toolTip: true, toolTipMessage: this.customizationSettings.customDonationMessage }, { id: "nav-featureSuggestion", name: "NavigationBar.FeatureSuggestion", icon: "far fa-lightbulb", link: "https://features.ombi.io/", externalLink: true, requiresAdmin: true, enabled: true, toolTip: true, toolTipMessage: 'NavigationBar.FeatureSuggestionTooltip'}, { id: "nav-settings", name: "NavigationBar.Settings", icon: "fas fa-cogs", link: "/Settings/About", requiresAdmin: true, enabled: true }, ]; @@ -145,7 +151,7 @@ export class MyNavComponent implements OnInit { const emailHash = md5.appendStr(email).end(); this.userProfileImageUrl = `https://www.gravatar.com/avatar/${emailHash}?d=404`;; } - else{ + else { this.userProfileImageUrl = this.getFallbackProfileImageUrl(); } } diff --git a/src/Ombi/ClientApp/src/app/services/settings.service.ts b/src/Ombi/ClientApp/src/app/services/settings.service.ts index 88a578be3..3f4409d0c 100644 --- a/src/Ombi/ClientApp/src/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/src/app/services/settings.service.ts @@ -1,4 +1,4 @@ -import { PlatformLocation, APP_BASE_HREF } from "@angular/common"; +import { APP_BASE_HREF } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable, Inject } from "@angular/core"; import { Observable } from "rxjs"; diff --git a/src/Ombi/ClientApp/src/app/settings/customization/customization.component.ts b/src/Ombi/ClientApp/src/app/settings/customization/customization.component.ts index 4e2b22129..8daba404f 100644 --- a/src/Ombi/ClientApp/src/app/settings/customization/customization.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/customization/customization.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from "@angular/core"; +import { CustomizationFacade } from "../../state/customization"; import { ICustomizationSettings } from "../../interfaces"; import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; @@ -13,13 +14,14 @@ export class CustomizationComponent implements OnInit { public settings: ICustomizationSettings; public advanced: boolean; - constructor(private settingsService: SettingsService, private notificationService: NotificationService) { } + constructor(private settingsService: SettingsService, + private notificationService: NotificationService, + private customizationFacade: CustomizationFacade) { } public ngOnInit() { - this.settingsService.getCustomization().subscribe(x => { - this.settings = x; + this.customizationFacade.settings$().subscribe(x => { + this.settings = { ...x }; }); - } public save() { @@ -32,7 +34,7 @@ export class CustomizationComponent implements OnInit { } } - this.settingsService.saveCustomization(this.settings).subscribe(x => { + this.customizationFacade.saveSettings(this.settings).subscribe(x => { if (x) { this.notificationService.success("Successfully saved Ombi settings"); } else { diff --git a/src/Ombi/ClientApp/src/app/state/customization/customization-initializer.ts b/src/Ombi/ClientApp/src/app/state/customization/customization-initializer.ts new file mode 100644 index 000000000..ad5404c0d --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/customization-initializer.ts @@ -0,0 +1,10 @@ +import { APP_INITIALIZER } from "@angular/core"; +import { CustomizationFacade } from "."; +import { Observable } from "rxjs"; + +export const CUSTOMIZATION_INITIALIZER = { + provide: APP_INITIALIZER, + useFactory: (customizationFacade: CustomizationFacade) => (): Observable => customizationFacade.loadCustomziationSettings(), + multi: true, + deps: [CustomizationFacade], +}; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/customization/customization.actions.ts b/src/Ombi/ClientApp/src/app/state/customization/customization.actions.ts new file mode 100644 index 000000000..bb9855084 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/customization.actions.ts @@ -0,0 +1,11 @@ +import { ICustomizationSettings } from "../../interfaces"; + +export class LoadSettings { + public static readonly type = '[Customization] LoadSettings'; + } + export class UpdateSettings { + public static readonly type = '[Customization] UpdateSettings'; + + constructor(public settings: ICustomizationSettings) { } + } + diff --git a/src/Ombi/ClientApp/src/app/state/customization/customization.facade.ts b/src/Ombi/ClientApp/src/app/state/customization/customization.facade.ts new file mode 100644 index 000000000..cd98d26fa --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/customization.facade.ts @@ -0,0 +1,27 @@ +import { LoadSettings, UpdateSettings } from "./customization.actions"; + +import { CustomizationSelectors } from "./customization.selectors"; +import { ICustomizationSettings } from "../../interfaces"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { Store } from "@ngxs/store"; + +@Injectable({ + providedIn: 'root', +}) +export class CustomizationFacade { + + public constructor(private store: Store) {} + + public settings$ = (): Observable => this.store.select(CustomizationSelectors.customizationSettings); + + public loadCustomziationSettings = (): Observable => this.store.dispatch(new LoadSettings()); + + public logo = (): string => this.store.selectSnapshot(CustomizationSelectors.logo); + + public appName = (): string => this.store.selectSnapshot(CustomizationSelectors.applicationName); + + public appUrl = (): string => this.store.selectSnapshot(CustomizationSelectors.applicationUrl); + + public saveSettings = (settings: ICustomizationSettings): Observable => this.store.dispatch(new UpdateSettings(settings)); +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/customization/customization.selectors.ts b/src/Ombi/ClientApp/src/app/state/customization/customization.selectors.ts new file mode 100644 index 000000000..ad70069f2 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/customization.selectors.ts @@ -0,0 +1,26 @@ +import { CUSTOMIZATION_STATE_TOKEN } from "./types"; +import { ICustomizationSettings } from "../../interfaces"; +import { Selector } from "@ngxs/store"; + +export class CustomizationSelectors { + + @Selector([CUSTOMIZATION_STATE_TOKEN]) + public static customizationSettings(settings: ICustomizationSettings): ICustomizationSettings { + return settings; + } + + @Selector([CustomizationSelectors.customizationSettings]) + public static logo({logo}: ICustomizationSettings): string { + return logo; + } + + @Selector([CustomizationSelectors.customizationSettings]) + public static applicationName({applicationName}: ICustomizationSettings): string { + return applicationName; + } + + @Selector([CustomizationSelectors.customizationSettings]) + public static applicationUrl({applicationUrl}: ICustomizationSettings): string { + return applicationUrl; + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts b/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts new file mode 100644 index 000000000..57b3f5908 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts @@ -0,0 +1,34 @@ +import { Action, State, StateContext } from "@ngxs/store"; +import { LoadSettings, UpdateSettings } from "./customization.actions"; + +import { CUSTOMIZATION_STATE_TOKEN } from "./types"; +import { ICustomizationSettings } from "../../interfaces"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { SettingsService } from "../../services"; +import { produce } from 'immer'; +import { tap } from "rxjs/operators"; + +@State({ + name: CUSTOMIZATION_STATE_TOKEN +}) +@Injectable() +export class CustomizationState { + constructor(private settingsService: SettingsService) { } + + @Action(LoadSettings) + public load({ setState }: StateContext): Observable { + return this.settingsService.getCustomization().pipe( + tap(settings => + setState(settings) + ) + ); + } + + @Action(UpdateSettings) + public update({ setState }: StateContext, { settings }: UpdateSettings): Observable { + return this.settingsService.saveCustomization(settings).pipe( + tap(() => setState(settings)) + ); + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/customization/index.ts b/src/Ombi/ClientApp/src/app/state/customization/index.ts new file mode 100644 index 000000000..e8fdeb050 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/index.ts @@ -0,0 +1,4 @@ +export * from './customization.state'; +export * from './customization.actions'; +export * from './customization.facade'; +export * from './customization.selectors'; diff --git a/src/Ombi/ClientApp/src/app/state/customization/types.ts b/src/Ombi/ClientApp/src/app/state/customization/types.ts new file mode 100644 index 000000000..98eada49a --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/customization/types.ts @@ -0,0 +1,4 @@ +import { ICustomizationSettings } from "../../interfaces"; +import { StateToken } from "@ngxs/store"; + +export const CUSTOMIZATION_STATE_TOKEN = new StateToken('customizationSettings'); \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts index c9d3d2eb1..afa2496cf 100644 --- a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts +++ b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts @@ -1,12 +1,13 @@ import { Component, Inject, OnInit } from "@angular/core"; import { AuthService } from "../../../auth/auth.service"; import { TranslateService } from "@ngx-translate/core"; -import { AvailableLanguages, ILanguage } from "./user-preference.constants"; -import { IdentityService, NotificationService, SettingsService, ValidationService } from "../../../services"; -import { ICustomizationSettings, IUser, UserType } from "../../../interfaces"; +import { AvailableLanguages } from "./user-preference.constants"; +import { IdentityService, NotificationService, ValidationService } from "../../../services"; +import { IUser, UserType } from "../../../interfaces"; import { Md5 } from "ts-md5"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { APP_BASE_HREF } from "@angular/common"; +import { CustomizationFacade } from "../../../state/customization"; @Component({ templateUrl: "./user-preference.component.html", @@ -22,21 +23,22 @@ export class UserPreferenceComponent implements OnInit { public qrCodeEnabled: boolean; public countries: string[]; public selectedCountry: string; - public customizationSettings: ICustomizationSettings; public UserType = UserType; public baseUrl: string; public passwordForm: FormGroup; private user: IUser; + private applicationUrl: string = this.customizationFacade.appUrl(); + private logo: string = this.customizationFacade.logo(); constructor(private authService: AuthService, private readonly translate: TranslateService, private readonly notification: NotificationService, private readonly identityService: IdentityService, - private readonly settingsService: SettingsService, private readonly fb: FormBuilder, private readonly validationService: ValidationService, + private readonly customizationFacade: CustomizationFacade, @Inject(APP_BASE_HREF) public internalBaseUrl: string) { } public async ngOnInit() { @@ -47,14 +49,13 @@ export class UserPreferenceComponent implements OnInit { if (user.name) { this.username = user.name; } - this.customizationSettings = await this.settingsService.getCustomization().toPromise(); this.selectedLang = this.translate.currentLang; const accessToken = await this.identityService.getAccessToken().toPromise(); - this.qrCode = `${this.customizationSettings.applicationUrl}|${accessToken}`; + this.qrCode = `${this.applicationUrl}|${accessToken}`; - if(!this.customizationSettings.applicationUrl) { + if(!this.applicationUrl) { this.qrCodeEnabled = false; } else { this.qrCodeEnabled = true; @@ -64,7 +65,6 @@ export class UserPreferenceComponent implements OnInit { this.selectedCountry = this.user.streamingCountry; this.setProfileImageUrl(this.user); this.identityService.getSupportedStreamingCountries().subscribe(x => this.countries = x); - this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.passwordForm = this.fb.group({ password: [null], @@ -112,8 +112,8 @@ export class UserPreferenceComponent implements OnInit { } private getFallbackProfileImageUrl() { - return this.customizationSettings?.logo - ? this.customizationSettings.logo + return this.logo + ? this.logo : "https://raw.githubusercontent.com/Ombi-app/Ombi/gh-pages/img/android-chrome-512x512.png"; } diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html index a4b79d21d..67152f7d9 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.html @@ -182,7 +182,7 @@ - + diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts index eac863282..9b9e8bdfd 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts @@ -4,6 +4,7 @@ import { ICheckbox, ICustomizationSettings, INotificationAgent, INotificationPre import { IdentityService, MessageService, RadarrService, SettingsService, SonarrService } from "../services"; import { Clipboard } from '@angular/cdk/clipboard'; +import { CustomizationFacade } from "../state/customization"; import { Location } from "@angular/common"; @Component({ @@ -30,7 +31,7 @@ export class UserManagementUserComponent implements OnInit { public requestLimitTypes: RequestLimitType[]; public RequestLimitType = RequestLimitType; - private customization: ICustomizationSettings; + private appUrl: string = this.customizationFacade.appUrl(); private accessToken: string; constructor(private identityService: IdentityService, @@ -41,7 +42,9 @@ export class UserManagementUserComponent implements OnInit { private sonarrService: SonarrService, private radarrService: RadarrService, private clipboard: Clipboard, - private location: Location) { + private location: Location, + private customizationFacade: CustomizationFacade, + ) { this.route.params.subscribe((params: any) => { if(params.id) { @@ -68,7 +71,6 @@ export class UserManagementUserComponent implements OnInit { this.radarrService.getQualityProfilesFromSettings().subscribe(x => this.radarrQualities = x); this.radarrService.getRootFoldersFromSettings().subscribe(x => this.radarrRootFolders = x); - this.settingsService.getCustomization().subscribe(x => this.customization = x); this.identityService.getUserAccessToken(this.userId).subscribe(x => this.accessToken = x); if(!this.edit) { @@ -191,7 +193,7 @@ export class UserManagementUserComponent implements OnInit { } public async appLink() { - this.clipboard.copy(`ombi://${this.customization.applicationUrl}|${this.accessToken}`); + this.clipboard.copy(`ombi://${this.appUrl}|${this.accessToken}`); this.notificationService.send("Copied!"); } diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html index 67d303d7d..02a790145 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html @@ -105,8 +105,8 @@ - - + + diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts index d119c8961..2bd2b3d89 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts @@ -2,6 +2,7 @@ import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; import { IdentityService, NotificationService, SettingsService } from "../services"; +import { CustomizationFacade } from "../state/customization"; import { MatSort } from "@angular/material/sort"; import { MatTableDataSource } from "@angular/material/table"; import { SelectionModel } from "@angular/cdk/collections"; @@ -21,7 +22,7 @@ export class UserManagementComponent implements OnInit { public users: IUser[]; public checkAll = false; public emailSettings: IEmailNotificationSettings; - public customizationSettings: ICustomizationSettings; + public applicationUrl: string; public showBulkEdit = false; public availableClaims: ICheckbox[]; public bulkMovieLimit?: number; @@ -35,7 +36,8 @@ export class UserManagementComponent implements OnInit { constructor(private identityService: IdentityService, private settingsService: SettingsService, private notificationService: NotificationService, - private plexSettings: SettingsService) { + private plexSettings: SettingsService, + private customizationFacade: CustomizationFacade) { this.dataSource = new MatTableDataSource(); } @@ -49,7 +51,7 @@ export class UserManagementComponent implements OnInit { this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable); this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); - this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); + this.applicationUrl = this.customizationFacade.appUrl(); this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x); } diff --git a/src/Ombi/ClientApp/yarn.lock b/src/Ombi/ClientApp/yarn.lock index 53fe6560b..6585ff2f9 100644 --- a/src/Ombi/ClientApp/yarn.lock +++ b/src/Ombi/ClientApp/yarn.lock @@ -1352,6 +1352,20 @@ dependencies: tslib "^2.0.0" +"@ngxs/devtools-plugin@^3.7.2": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@ngxs/devtools-plugin/-/devtools-plugin-3.7.2.tgz#995424e5faf48df55a1b54b9e1b36ce9c47c1d52" + integrity sha512-kRuOx1GPXHHZZAeQMm1J1msTZxjgiAUY4NR7bzaQPn+UwSY2OgGEsd8driMM5YSTF1hOjcFHinaLCM1vmu8FmQ== + dependencies: + tslib "^1.9.0" + +"@ngxs/store@^3.7.2": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@ngxs/store/-/store-3.7.2.tgz#1088b0669adc382d36ca7ae8438c603e55879b42" + integrity sha512-1cnAjHOGCovfvhjtcAWBajrMXos97Un3c8ekKoS8FIHnq3aQOzY/ePspDRNi9kTcuBJ/r/xl097JC1ssEuNbyg== + dependencies: + tslib "^1.9.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -4387,6 +4401,11 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= +immer@^9.0.6: + version "9.0.6" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73" + integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"