From a32e1631de25af0362a0f28528fa59389fe9c7bb Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Thu, 13 Oct 2022 23:37:50 -0500 Subject: [PATCH] Start moving saucenao into a dialog --- src/app/app.module.ts | 4 +- .../file-info-sheet.component.html | 4 ++ .../file-info-sheet.component.ts | 24 +++++++ .../saucenao-dialog.component.html | 35 ++++++++++ .../saucenao-dialog.component.scss | 18 +++++ .../saucenao-dialog.component.spec.ts | 23 +++++++ .../saucenao-dialog.component.ts | 68 +++++++++++++++++++ src/app/saucenao.service.ts | 27 ++++++-- src/app/send/send.component.ts | 17 ++++- 9 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.html create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.scss create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.spec.ts create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index cb4285b..bf00d94 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -68,6 +68,7 @@ import { TagNamespaceClassPipe } from './utils/tag-utils'; import { SystemPredicateDialogComponent } from './system-predicate-dialog/system-predicate-dialog.component'; import { MatNativeDateModule } from '@angular/material/core'; import { SortInputComponent } from './sort-input/sort-input.component'; +import { SaucenaoDialogComponent } from './saucenao-dialog/saucenao-dialog.component'; const MAT_MODULES = [ @@ -119,7 +120,8 @@ const MAT_MODULES = [ TagInputDialogComponent, TagNamespaceClassPipe, SystemPredicateDialogComponent, - SortInputComponent + SortInputComponent, + SaucenaoDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/file-info-sheet/file-info-sheet.component.html b/src/app/file-info-sheet/file-info-sheet.component.html index b46cee3..efd3dae 100644 --- a/src/app/file-info-sheet/file-info-sheet.component.html +++ b/src/app/file-info-sheet/file-info-sheet.component.html @@ -26,6 +26,10 @@

File Info

link Copy hyshare URL + + + + + + + + error + No results found + + + + + + + diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.scss b/src/app/saucenao-dialog/saucenao-dialog.component.scss new file mode 100644 index 0000000..6a6bb52 --- /dev/null +++ b/src/app/saucenao-dialog/saucenao-dialog.component.scss @@ -0,0 +1,18 @@ +.saucenao-thumbnail { + border-radius: 4px !important; +} + +.metadata-line span:not(:last-of-type) { + margin-right: 20px; +} + +.saucenao-card, .url-info-card { + margin-top: 16px; +} + +.saucenao-results { + margin-top: 16px; +} +.saucenao-spinner { + margin: 32px auto 0; +} diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.spec.ts b/src/app/saucenao-dialog/saucenao-dialog.component.spec.ts new file mode 100644 index 0000000..a6de48a --- /dev/null +++ b/src/app/saucenao-dialog/saucenao-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SaucenaoDialogComponent } from './saucenao-dialog.component'; + +describe('SaucenaoDialogComponent', () => { + let component: SaucenaoDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SaucenaoDialogComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SaucenaoDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.ts b/src/app/saucenao-dialog/saucenao-dialog.component.ts new file mode 100644 index 0000000..ec432ec --- /dev/null +++ b/src/app/saucenao-dialog/saucenao-dialog.component.ts @@ -0,0 +1,68 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MatDialog, MatDialogConfig, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { catchError, Observable } from 'rxjs'; +import { AddUrlOptions, HydrusAddService } from '../hydrus-add.service'; +import { SaucenaoService, SaucenaoResults, SaucenaoUrlorFile } from '../saucenao.service'; + +interface SaucenaoDialogData { + urlOrFile: SaucenaoUrlorFile; + addUrlOptions?: AddUrlOptions; +}; + +@Component({ + selector: 'app-saucenao-dialog', + templateUrl: './saucenao-dialog.component.html', + styleUrls: ['./saucenao-dialog.component.scss'] +}) +export class SaucenaoDialogComponent implements OnInit { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: SaucenaoDialogData, + public saucenaoService: SaucenaoService, + private addService: HydrusAddService, + private snackbar: MatSnackBar + ) { } + + onNoClick(): void { + this.dialogRef.close(); + } + + ngOnInit(): void { + } + + saucenaoResults$: Observable = this.saucenaoService.search(this.data.urlOrFile).pipe( + catchError((err, caught) => { + this.snackbar.open('Error: ' + err.message, undefined, { + duration: 5000 + }); + throw err; + }) + ) + + send(url: string) { + this.addService.addUrl(url, this.data.addUrlOptions).subscribe(res => { + this.snackbar.open(res.human_result_text, undefined, { + duration: 5000 + }); + }, error => { + console.log(error); + this.snackbar.open(`Error: ${error.message}`, undefined, { + duration: 10000 + }); + }); + } + + static open(dialog: MatDialog, data?: SaucenaoDialogData, config?: MatDialogConfig) { + return dialog.open( + SaucenaoDialogComponent, + { + width: '80vw', + data, + ...config + } + ); + } + +} diff --git a/src/app/saucenao.service.ts b/src/app/saucenao.service.ts index 363b01b..a071d2c 100644 --- a/src/app/saucenao.service.ts +++ b/src/app/saucenao.service.ts @@ -31,6 +31,8 @@ interface SacuenaoOptions { db?: number; } +export type SaucenaoUrlorFile = {url: string} | {file: Blob}; + export interface SaucenaoResults extends SagiriResult { urls: { site: string; @@ -51,7 +53,7 @@ export class SaucenaoService { constructor(private http: HttpClient, private settings: SettingsService) { } - buildForm(data: SacuenaoOptions & ({url: string} | {file: Blob})): FormData { + buildForm(data: SacuenaoOptions & SaucenaoUrlorFile): FormData { const form = new FormData(); for(const [key, value] of Object.entries(data)) { form.set(key, value); @@ -63,14 +65,25 @@ export class SaucenaoService { return !!this.settings.appSettings.saucenaoApiKey && !!this.settings.appSettings.saucenaoSearchProxy; } - public searchResponse(url: string, options?: SacuenaoOptions): Observable { + public validSaucenaoMime(mime: string) { + return ([ + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/gif', + 'image/bmp', + 'image/webp' + ].includes(mime)); + } + + public searchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable { return this.http.post(this.settings.appSettings.saucenaoSearchProxy, this.buildForm( { ...defaultSaucenaoOptions, ...options, api_key: this.settings.appSettings.saucenaoApiKey, - url + ...urlOrFile })).pipe( catchError(err => { if(!err.error.header) { @@ -106,16 +119,16 @@ export class SaucenaoService { ); } - public filteredSearchResponse(url: string, options?: SacuenaoOptions, minSimilarity: number = 70): Observable { - return this.searchResponse(url, options).pipe( + public filteredSearchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions, minSimilarity: number = 70): Observable { + return this.searchResponse(urlOrFile, options).pipe( map(resp => resp.results.filter(({ header: { index_id: id, similarity}}) => !!sites[id] && similarity >= minSimilarity) .sort((a, b) => b.header.similarity - a.header.similarity)) ); } // Adapted from https://github.com/ClarityCafe/Sagiri - public search(url: string, options?: SacuenaoOptions): Observable { - return this.filteredSearchResponse(url, options).pipe( + public search(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable { + return this.filteredSearchResponse(urlOrFile, options).pipe( map(results => results.map(result => { const { url, name, id, authorName, authorUrl }: { url: string, name: string, id: Indices, authorName: string | null, authorUrl: string | null } = resolveResult(result); const { diff --git a/src/app/send/send.component.ts b/src/app/send/send.component.ts index 743b8be..e4623f9 100644 --- a/src/app/send/send.component.ts +++ b/src/app/send/send.component.ts @@ -7,6 +7,8 @@ import { HydrusURLInfo, HydrusURLFiles } from '../hydrus-url'; import { ActivatedRoute, Router } from '@angular/router'; import { MatSnackBar } from '@angular/material/snack-bar'; import { SaucenaoService, SaucenaoResults } from '../saucenao.service'; +import { MatDialog } from '@angular/material/dialog'; +import { SaucenaoDialogComponent } from '../saucenao-dialog/saucenao-dialog.component'; @Component({ selector: 'app-send', @@ -25,6 +27,7 @@ export class SendComponent implements OnInit { private route: ActivatedRoute, private snackbar: MatSnackBar, private router: Router, + public dialog: MatDialog, public saucenaoService: SaucenaoService ) { } @@ -106,7 +109,7 @@ export class SendComponent implements OnInit { } saucenaoLookup() { - this.saucenaoLoading = true; + /* this.saucenaoLoading = true; this.saucenaoResults = null; const lookupUrl = this.sendForm.value.sendUrl; this.saucenaoService.search(lookupUrl).subscribe( @@ -120,7 +123,17 @@ export class SendComponent implements OnInit { duration: 5000 }); console.log(err); - }); + }); */ + const addUrlOptions: AddUrlOptions = {}; + if (this.sendForm.value.destPageName !== '') { + addUrlOptions.destination_page_name = this.sendForm.value.destPageName; + } + SaucenaoDialogComponent.open(this.dialog, { + urlOrFile: { + url: this.sendForm.value.sendUrl + }, + addUrlOptions + }) } }