198 lines
6.9 KiB
TypeScript
198 lines
6.9 KiB
TypeScript
import {
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
DestroyRef,
|
|
inject,
|
|
} from '@angular/core';
|
|
import { ToastrService } from 'ngx-toastr';
|
|
import {distinctUntilChanged, map, take, tap} from 'rxjs';
|
|
import { ThemeService } from 'src/app/_services/theme.service';
|
|
import {SiteTheme, ThemeProvider} from 'src/app/_models/preferences/site-theme';
|
|
import { User } from 'src/app/_models/user';
|
|
import { AccountService } from 'src/app/_services/account.service';
|
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
|
import { SiteThemeProviderPipe } from '../../_pipes/site-theme-provider.pipe';
|
|
import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe';
|
|
import { AsyncPipe, NgTemplateOutlet} from '@angular/common';
|
|
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
|
import {shareReplay} from "rxjs/operators";
|
|
import {CarouselReelComponent} from "../../carousel/_components/carousel-reel/carousel-reel.component";
|
|
import {SeriesCardComponent} from "../../cards/series-card/series-card.component";
|
|
import {ImageComponent} from "../../shared/image/image.component";
|
|
import {DownloadableSiteTheme} from "../../_models/theme/downloadable-site-theme";
|
|
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
|
import {SafeUrlPipe} from "../../_pipes/safe-url.pipe";
|
|
import {ScrobbleProvider} from "../../_services/scrobbling.service";
|
|
import {ConfirmService} from "../../shared/confirm.service";
|
|
import {FileSystemFileEntry, NgxFileDropEntry, NgxFileDropModule} from "ngx-file-drop";
|
|
import {ReactiveFormsModule} from "@angular/forms";
|
|
import {Select2Module} from "ng-select2-component";
|
|
import {LoadingComponent} from "../../shared/loading/loading.component";
|
|
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
|
import {PreviewImageModalComponent} from "../../shared/_components/carousel-modal/preview-image-modal.component";
|
|
|
|
interface ThemeContainer {
|
|
downloadable?: DownloadableSiteTheme;
|
|
site?: SiteTheme;
|
|
isSiteTheme: boolean;
|
|
name: string;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-theme-manager',
|
|
templateUrl: './theme-manager.component.html',
|
|
styleUrls: ['./theme-manager.component.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
standalone: true,
|
|
imports: [AsyncPipe, SentenceCasePipe, SiteThemeProviderPipe, TranslocoDirective, CarouselReelComponent,
|
|
SeriesCardComponent, ImageComponent, DefaultValuePipe, NgTemplateOutlet, SafeUrlPipe, NgxFileDropModule,
|
|
ReactiveFormsModule, Select2Module, LoadingComponent]
|
|
})
|
|
export class ThemeManagerComponent {
|
|
private readonly destroyRef = inject(DestroyRef);
|
|
protected readonly themeService = inject(ThemeService);
|
|
private readonly accountService = inject(AccountService);
|
|
private readonly toastr = inject(ToastrService);
|
|
private readonly cdRef = inject(ChangeDetectorRef);
|
|
private readonly confirmService = inject(ConfirmService);
|
|
private readonly modalService = inject(NgbModal);
|
|
|
|
protected readonly ThemeProvider = ThemeProvider;
|
|
protected readonly ScrobbleProvider = ScrobbleProvider;
|
|
|
|
currentTheme: SiteTheme | undefined;
|
|
user: User | undefined;
|
|
selectedTheme: ThemeContainer | undefined;
|
|
downloadableThemes: Array<DownloadableSiteTheme> = [];
|
|
downloadedThemes: Array<SiteTheme> = [];
|
|
hasAdmin$ = this.accountService.currentUser$.pipe(
|
|
takeUntilDestroyed(this.destroyRef),
|
|
map(c => c && this.accountService.hasAdminRole(c)),
|
|
shareReplay({refCount: true, bufferSize: 1}),
|
|
);
|
|
|
|
files: NgxFileDropEntry[] = [];
|
|
acceptableExtensions = ['.css'].join(',');
|
|
isUploadingTheme: boolean = false;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.themeService.themes$.pipe(tap(themes => {
|
|
this.downloadedThemes = themes;
|
|
this.cdRef.markForCheck();
|
|
})).subscribe();
|
|
|
|
this.loadDownloadableThemes();
|
|
|
|
this.themeService.currentTheme$.pipe(takeUntilDestroyed(this.destroyRef), distinctUntilChanged()).subscribe(theme => {
|
|
this.currentTheme = theme;
|
|
this.cdRef.markForCheck();
|
|
});
|
|
|
|
}
|
|
|
|
loadDownloadableThemes() {
|
|
this.themeService.getDownloadableThemes().subscribe(d => {
|
|
this.downloadableThemes = d;
|
|
this.cdRef.markForCheck();
|
|
});
|
|
}
|
|
|
|
async deleteTheme(theme: SiteTheme) {
|
|
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-theme'))) {
|
|
return;
|
|
}
|
|
|
|
this.themeService.deleteTheme(theme.id).subscribe(_ => {
|
|
this.removeDownloadedTheme(theme);
|
|
this.loadDownloadableThemes();
|
|
});
|
|
}
|
|
|
|
removeDownloadedTheme(theme: SiteTheme) {
|
|
this.selectedTheme = undefined;
|
|
this.downloadableThemes = this.downloadableThemes.filter(d => d.name !== theme.name);
|
|
this.cdRef.markForCheck();
|
|
}
|
|
|
|
applyTheme(theme: SiteTheme) {
|
|
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
|
if (!user) return;
|
|
const pref = Object.assign({}, user.preferences);
|
|
pref.theme = theme;
|
|
this.accountService.updatePreferences(pref).subscribe();
|
|
// Updating theme emits the new theme to load on the themes$
|
|
});
|
|
}
|
|
|
|
updateDefault(theme: SiteTheme) {
|
|
this.themeService.setDefault(theme.id).subscribe(() => {
|
|
this.toastr.success(translate('theme-manager.updated-toastr', {name: theme.name}));
|
|
});
|
|
}
|
|
|
|
selectTheme(theme: SiteTheme | DownloadableSiteTheme | undefined) {
|
|
if (theme === undefined) {
|
|
this.selectedTheme = undefined;
|
|
return;
|
|
}
|
|
|
|
if (theme.hasOwnProperty('provider')) {
|
|
this.selectedTheme = {
|
|
isSiteTheme: true,
|
|
site: theme as SiteTheme,
|
|
name: theme.name
|
|
};
|
|
} else {
|
|
this.selectedTheme = {
|
|
isSiteTheme: false,
|
|
downloadable: theme as DownloadableSiteTheme,
|
|
name: theme.name
|
|
};
|
|
}
|
|
|
|
this.cdRef.markForCheck();
|
|
}
|
|
|
|
downloadTheme(theme: DownloadableSiteTheme) {
|
|
this.themeService.downloadTheme(theme).subscribe(downloadedTheme => {
|
|
this.removeDownloadedTheme(downloadedTheme);
|
|
this.themeService.getThemes().subscribe(themes => {
|
|
this.downloadedThemes = themes;
|
|
const oldTheme = this.downloadedThemes.filter(d => d.name === theme.name)[0];
|
|
this.selectTheme(oldTheme);
|
|
this.cdRef.markForCheck();
|
|
});
|
|
|
|
});
|
|
}
|
|
|
|
public dropped(files: NgxFileDropEntry[]) {
|
|
this.files = files;
|
|
for (const droppedFile of files) {
|
|
// Is it a file?
|
|
if (droppedFile.fileEntry.isFile) {
|
|
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
|
|
fileEntry.file((file: File) => {
|
|
this.themeService.uploadTheme(file, droppedFile).subscribe(t => {
|
|
this.isUploadingTheme = false;
|
|
this.cdRef.markForCheck();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
this.isUploadingTheme = true;
|
|
this.cdRef.markForCheck();
|
|
}
|
|
|
|
previewImage(imgUrl: string) {
|
|
if (imgUrl === '') return;
|
|
|
|
const ref = this.modalService.open(PreviewImageModalComponent, {size: 'xl', fullscreen: 'lg'});
|
|
ref.componentInstance.title = this.selectedTheme!.name;
|
|
ref.componentInstance.image = imgUrl;
|
|
}
|
|
}
|