Skip to content

Commit

Permalink
Start moving saucenao into a dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
floogulinc committed Oct 14, 2022
1 parent 9c95ec6 commit a32e163
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 10 deletions.
4 changes: 3 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -119,7 +120,8 @@ const MAT_MODULES = [
TagInputDialogComponent,
TagNamespaceClassPipe,
SystemPredicateDialogComponent,
SortInputComponent
SortInputComponent,
SaucenaoDialogComponent
],
imports: [
BrowserModule,
Expand Down
4 changes: 4 additions & 0 deletions src/app/file-info-sheet/file-info-sheet.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ <h2 mat-dialog-title class="sheet-title">File Info</h2>
<mat-icon>link</mat-icon>
<span>Copy hyshare URL</span>
</button>
<button mat-menu-item *ngIf="canSaucenao" (click)="saucenaoLookup()">
<mat-icon>search</mat-icon>
<span>SauceNAO lookup</span>
</button>
<button mat-menu-item (click)="reload()">
<mat-icon>refresh</mat-icon>
<span>Refresh</span>
Expand Down
24 changes: 24 additions & 0 deletions src/app/file-info-sheet/file-info-sheet.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { tagsObjectFromFile } from '../utils/tag-utils';
import { SettingsService } from '../settings.service';
import { BehaviorSubject, filter, map, shareReplay, switchMap } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { SaucenaoDialogComponent } from '../saucenao-dialog/saucenao-dialog.component';
import { SaucenaoService } from '../saucenao.service';

interface ShareData {
title?: string;
Expand Down Expand Up @@ -50,6 +53,8 @@ export class FileInfoSheetComponent {
private filesService: HydrusFilesService,
private snackbar: MatSnackBar,
public settings: SettingsService,
private dialog: MatDialog,
private saucenaoService: SaucenaoService
) { }

reload$ = new BehaviorSubject(null);
Expand Down Expand Up @@ -139,6 +144,25 @@ export class FileInfoSheetComponent {
});
}

canSaucenao = this.saucenaoService.canSaucenao && this.saucenaoService.validSaucenaoMime(this.data.file.mime);

saucenaoLookup() {
const snackBarRef = this.snackbar.open('Preparing search...');
this.filesService.getFileAsFile(this.data.file).subscribe(file => {
SaucenaoDialogComponent.open(this.dialog, {
urlOrFile: {
file
}
})
snackBarRef.dismiss();
}, error => {
snackBarRef.dismiss();
this.snackbar.open(`Error downloading file: ${error.message}`, undefined, {
duration: 10000
});
});
}


async deleteFile(){
try {
Expand Down
35 changes: 35 additions & 0 deletions src/app/saucenao-dialog/saucenao-dialog.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<h1 mat-dialog-title>SauceNAO Lookup</h1>
<div mat-dialog-content>
<div *ngIf="saucenaoResults$ | async as saucenaoResults; else loading">
<mat-card *ngFor="let result of saucenaoResults" class="saucenao-card">
<mat-card-header>
<img mat-card-avatar class="saucenao-thumbnail" [src]="result.thumbnail">
<mat-card-title>{{result.site}}{{result.authorName ? ' - ' + result.authorName : ''}}</mat-card-title>
<mat-card-subtitle>{{result.similarity}}%</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-list class="saucenao-url-list">
<mat-list-item *ngFor="let url of result.urls">
<div mat-line>{{url.site}}</div>
<div mat-line>{{url.url}}</div>
<a mat-icon-button rel="noopener noreferrer" target="_blank" href="{{url.url}}" title="Open URL in new tab">
<mat-icon>open_in_new</mat-icon>
</a>
<button mat-icon-button (click)="send(url.url)" title="Send to Hydrus">
<mat-icon>send</mat-icon>
</button>
</mat-list-item>
</mat-list>
</mat-card-content>
</mat-card>
<mat-card *ngIf="saucenaoResults.length === 0" class="saucenao-card">
<mat-card-header>
<mat-icon mat-card-avatar>error</mat-icon>
<mat-card-title>No results found</mat-card-title>
</mat-card-header>
</mat-card>
</div>
<ng-template #loading>
<mat-spinner class="saucenao-spinner"></mat-spinner>
</ng-template>
</div>
18 changes: 18 additions & 0 deletions src/app/saucenao-dialog/saucenao-dialog.component.scss
Original file line number Diff line number Diff line change
@@ -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;
}
23 changes: 23 additions & 0 deletions src/app/saucenao-dialog/saucenao-dialog.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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<SaucenaoDialogComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SaucenaoDialogComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(SaucenaoDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
68 changes: 68 additions & 0 deletions src/app/saucenao-dialog/saucenao-dialog.component.ts
Original file line number Diff line number Diff line change
@@ -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<SaucenaoDialogComponent>,
@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<SaucenaoResults[]> = 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<SaucenaoDialogData>) {
return dialog.open<SaucenaoDialogComponent, SaucenaoDialogData, undefined>(
SaucenaoDialogComponent,
{
width: '80vw',
data,
...config
}
);
}

}
27 changes: 20 additions & 7 deletions src/app/saucenao.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ interface SacuenaoOptions {
db?: number;
}

export type SaucenaoUrlorFile = {url: string} | {file: Blob};

export interface SaucenaoResults extends SagiriResult {
urls: {
site: string;
Expand All @@ -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);
Expand All @@ -63,14 +65,25 @@ export class SaucenaoService {
return !!this.settings.appSettings.saucenaoApiKey && !!this.settings.appSettings.saucenaoSearchProxy;
}

public searchResponse(url: string, options?: SacuenaoOptions): Observable<Response> {
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<Response> {
return this.http.post<Response>(this.settings.appSettings.saucenaoSearchProxy,
this.buildForm(
{
...defaultSaucenaoOptions,
...options,
api_key: this.settings.appSettings.saucenaoApiKey,
url
...urlOrFile
})).pipe(
catchError(err => {
if(!err.error.header) {
Expand Down Expand Up @@ -106,16 +119,16 @@ export class SaucenaoService {
);
}

public filteredSearchResponse(url: string, options?: SacuenaoOptions, minSimilarity: number = 70): Observable<Result[]> {
return this.searchResponse(url, options).pipe(
public filteredSearchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions, minSimilarity: number = 70): Observable<Result[]> {
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<SaucenaoResults[]> {
return this.filteredSearchResponse(url, options).pipe(
public search(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable<SaucenaoResults[]> {
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 {
Expand Down
17 changes: 15 additions & 2 deletions src/app/send/send.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -25,6 +27,7 @@ export class SendComponent implements OnInit {
private route: ActivatedRoute,
private snackbar: MatSnackBar,
private router: Router,
public dialog: MatDialog,
public saucenaoService: SaucenaoService
) { }

Expand Down Expand Up @@ -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(
Expand All @@ -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
})
}

}

0 comments on commit a32e163

Please sign in to comment.