Compare commits

..

No commits in common. "cd532ef09244edfcfd9d9c55877aa05d560972cc" and "e1479f6078c971861407dc03f654cec1a5e794a4" have entirely different histories.

6 changed files with 53 additions and 75 deletions

View File

@ -31,7 +31,7 @@ export class RecipeEditPage implements OnInit {
const username = paramMap.get('username')!; const username = paramMap.get('username')!;
const slug = paramMap.get('slug')!; const slug = paramMap.get('slug')!;
this.loadingRecipe.set(true); this.loadingRecipe.set(true);
this.recipeService.getRecipeView(username, slug, true).subscribe({ this.recipeService.getRecipeView2(username, slug, true).subscribe({
next: (recipeView) => { next: (recipeView) => {
this.loadingRecipe.set(false); this.loadingRecipe.set(false);
this.recipeView.set(recipeView); this.recipeView.set(recipeView);

View File

@ -1,7 +1,9 @@
@if (loadingRecipe()) { @if (recipeView.isLoading()) {
<app-spinner></app-spinner> <p>Loading...</p>
} @else if (loadRecipeError()) { } @else if (recipeView.isSuccess()) {
<app-recipe-page-content [recipeView]="recipeView.data()"></app-recipe-page-content>
} @else if (recipeView.error(); as error) {
<p>{{ error.message }}</p>
} @else {
<p>There was an error loading the recipe.</p> <p>There was an error loading the recipe.</p>
} @else if (recipe(); as recipe) {
<app-recipe-page-content [recipeView]="recipe"></app-recipe-page-content>
} }

View File

@ -1,38 +1,23 @@
import { Component, inject, OnInit, signal } from '@angular/core'; import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { RecipeService } from '../../shared/services/RecipeService'; import { RecipeService } from '../../shared/services/RecipeService';
import { RecipePageContent } from './recipe-page-content/recipe-page-content'; import { RecipePageContent } from './recipe-page-content/recipe-page-content';
import { FullRecipeViewWrapper } from '../../shared/models/Recipe.model'; import { injectQuery } from '@tanstack/angular-query-experimental';
import { Spinner } from '../../shared/components/spinner/spinner';
@Component({ @Component({
selector: 'app-recipe-page', selector: 'app-recipe-page',
imports: [RecipePageContent, Spinner], imports: [RecipePageContent],
templateUrl: './recipe-page.html', templateUrl: './recipe-page.html',
styleUrl: './recipe-page.css', styleUrl: './recipe-page.css',
}) })
export class RecipePage implements OnInit { export class RecipePage {
private recipeService = inject(RecipeService); private recipeService = inject(RecipeService);
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
private username = this.route.snapshot.paramMap.get('username') as string; private username = this.route.snapshot.paramMap.get('username') as string;
private slug = this.route.snapshot.paramMap.get('slug') as string; private slug = this.route.snapshot.paramMap.get('slug') as string;
protected readonly loadingRecipe = signal(false); protected recipeView = injectQuery(() => ({
protected readonly loadRecipeError = signal<Error | null>(null); queryKey: ['recipe', this.username, this.slug],
protected readonly recipe = signal<FullRecipeViewWrapper | null>(null); queryFn: () => this.recipeService.getRecipeView(this.username, this.slug),
}));
public ngOnInit(): void {
this.loadingRecipe.set(true);
this.recipeService.getRecipeView(this.username, this.slug).subscribe({
next: (recipe) => {
this.loadingRecipe.set(false);
this.recipe.set(recipe);
},
error: (e) => {
this.loadingRecipe.set(false);
this.loadRecipeError.set(e);
console.error(e);
},
});
}
} }

View File

@ -6,13 +6,13 @@
</mat-form-field> </mat-form-field>
<button matButton="filled" type="submit" [disabled]="!searchRecipesForm.valid">Search</button> <button matButton="filled" type="submit" [disabled]="!searchRecipesForm.valid">Search</button>
</form> </form>
@if (loadingResults()) { @if (givenPrompt() !== null) {
<app-spinner></app-spinner> @if (resultsQuery.isLoading()) {
} @else if (loadResultsError()) { <p>Loading search results...</p>
<p>There was an error during search. Try again.</p> } @else if (resultsQuery.isSuccess()) {
} @else if (results()?.length) { <p>Showing results for {{ givenPrompt() }}</p>
<p>Showing results for '{{ submittedPrompt() }}'</p> <app-recipe-card-grid [recipes]="resultsQuery.data()" />
<app-recipe-card-grid [recipes]="results()!" /> } @else if (resultsQuery.isError()) {
} @else if (results()?.length === 0) { <p>There was an error during search.</p>
<p>There were no results for '{{ submittedPrompt() }}'</p> }
} }

View File

@ -1,30 +1,28 @@
import { Component, inject, OnInit, signal } from '@angular/core'; import { Component, inject, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { injectQuery } from '@tanstack/angular-query-experimental';
import { RecipeService } from '../../shared/services/RecipeService'; import { RecipeService } from '../../shared/services/RecipeService';
import { RecipeCardGrid } from '../../shared/components/recipe-card-grid/recipe-card-grid'; import { RecipeCardGrid } from '../../shared/components/recipe-card-grid/recipe-card-grid';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { MatFormField, MatInput, MatLabel } from '@angular/material/input'; import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
import { RecipeInfoView } from '../../shared/models/Recipe.model';
import { Spinner } from '../../shared/components/spinner/spinner';
@Component({ @Component({
selector: 'app-recipes-search-page', selector: 'app-recipes-search-page',
imports: [RecipeCardGrid, MatButton, MatFormField, MatInput, MatLabel, ReactiveFormsModule, Spinner], imports: [ReactiveFormsModule, RecipeCardGrid, MatButton, MatFormField, MatInput, MatLabel],
templateUrl: './recipes-search-page.html', templateUrl: './recipes-search-page.html',
styleUrl: './recipes-search-page.css', styleUrl: './recipes-search-page.css',
}) })
export class RecipesSearchPage implements OnInit { export class RecipesSearchPage {
private readonly recipeService = inject(RecipeService); private readonly recipeService = inject(RecipeService);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly activatedRoute = inject(ActivatedRoute); private readonly activatedRoute = inject(ActivatedRoute);
public ngOnInit(): void { public constructor() {
this.activatedRoute.queryParams.subscribe((queryParams) => { this.activatedRoute.queryParams.subscribe((queryParams) => {
if (queryParams['prompt']) { if (queryParams['prompt']) {
const prompt = queryParams['prompt'] as string; this.givenPrompt.set(queryParams['prompt']);
this.searchRecipesForm.controls.prompt.setValue(prompt); this.searchRecipesForm.controls.prompt.setValue(queryParams['prompt']);
this.loadResults(prompt);
} }
}); });
} }
@ -33,34 +31,20 @@ export class RecipesSearchPage implements OnInit {
prompt: new FormControl('', [Validators.required]), prompt: new FormControl('', [Validators.required]),
}); });
protected readonly submittedPrompt = signal<string | null>(null); protected readonly givenPrompt = signal<null | string>(null);
protected readonly loadingResults = signal(false);
protected readonly loadResultsError = signal<Error | null>(null);
protected readonly results = signal<RecipeInfoView[] | null>(null);
private loadResults(prompt: string): void { protected readonly resultsQuery = injectQuery(() => ({
this.submittedPrompt.set(prompt); queryFn: () => this.recipeService.aiSearch(this.givenPrompt()!),
this.loadingResults.set(true); queryKey: ['recipes-search', this.givenPrompt()],
this.recipeService.aiSearch(prompt).subscribe({ enabled: () => !!this.givenPrompt(),
next: (results) => { }));
this.loadingResults.set(false);
this.results.set(results);
},
error: (e) => {
this.loadingResults.set(false);
this.loadResultsError.set(e);
console.error(e);
},
});
}
protected async onPromptSubmit() { protected async onPromptSubmit() {
if (this.searchRecipesForm.value.prompt) { if (this.searchRecipesForm.value.prompt) {
const prompt = this.searchRecipesForm.value.prompt;
await this.router.navigate(['/recipes-search'], { await this.router.navigate(['/recipes-search'], {
queryParams: { prompt }, queryParams: { prompt: this.searchRecipesForm.value.prompt },
}); });
this.loadResults(prompt); this.givenPrompt.set(this.searchRecipesForm.value.prompt);
} }
} }
} }

View File

@ -76,7 +76,13 @@ export class RecipeService {
.pipe(map((res) => res.count)); .pipe(map((res) => res.count));
} }
public getRecipeView( public getRecipeView(username: string, slug: string): Promise<FullRecipeViewWrapper> {
return firstValueFrom(
this.http.get<FullRecipeViewWrapper>(this.endpointService.getUrl('recipes', [username, slug])),
);
}
public getRecipeView2(
username: string, username: string,
slug: string, slug: string,
includeRawText: boolean = false, includeRawText: boolean = false,
@ -143,15 +149,16 @@ export class RecipeService {
return comment; return comment;
} }
public aiSearch(prompt: string): Observable<FullRecipeView[]> { public async aiSearch(prompt: string): Promise<FullRecipeView[]> {
return this.http const recipeInfoViews = await firstValueFrom(
.post<{ results: FullRecipeView[] }>(this.endpointService.getUrl('recipes'), { this.http.post<{ results: FullRecipeView[] }>(this.endpointService.getUrl('recipes'), {
type: 'AI_PROMPT', type: 'AI_PROMPT',
data: { data: {
prompt, prompt,
}, },
}) }),
.pipe(map((res) => res.results)); );
return recipeInfoViews.results;
} }
public deleteRecipe(username: string, slug: string): Observable<void> { public deleteRecipe(username: string, slug: string): Observable<void> {