All steps basically working, much more todo.
This commit is contained in:
parent
ff9ffd9e13
commit
b42971703f
@ -31,13 +31,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (mainImageUrl.isSuccess()) {
|
@if (mainImageUrl.isSuccess()) {
|
||||||
<img
|
@let maybeMainImageUrl = mainImageUrl.data();
|
||||||
id="main-image"
|
@if (!!maybeMainImageUrl) {
|
||||||
[src]="mainImageUrl.data()"
|
<img
|
||||||
[alt]="recipe.mainImage.alt"
|
id="main-image"
|
||||||
[height]="recipe.mainImage.height"
|
[src]="maybeMainImageUrl"
|
||||||
[width]="recipe.mainImage.width"
|
[alt]="recipe.mainImage!.alt"
|
||||||
/>
|
[height]="recipe.mainImage!.height"
|
||||||
|
[width]="recipe.mainImage!.width"
|
||||||
|
/>
|
||||||
|
} @else {
|
||||||
|
<p>!! Placeholder todo !!</p>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<div [innerHTML]="recipe.text"></div>
|
<div [innerHTML]="recipe.text"></div>
|
||||||
<app-recipe-comments-list [recipeUsername]="recipe.owner.username" [recipeSlug]="recipe.slug" />
|
<app-recipe-comments-list [recipeUsername]="recipe.owner.username" [recipeSlug]="recipe.slug" />
|
||||||
|
|||||||
@ -27,8 +27,8 @@ export class RecipePageContent {
|
|||||||
protected readonly mainImageUrl = injectQuery(() => {
|
protected readonly mainImageUrl = injectQuery(() => {
|
||||||
const recipe = this.recipeView().recipe;
|
const recipe = this.recipeView().recipe;
|
||||||
return {
|
return {
|
||||||
queryKey: ['images', recipe.mainImage.owner.username, recipe.mainImage.filename],
|
queryKey: ['recipe-main-images', recipe.owner.username, recipe.slug],
|
||||||
queryFn: () => this.imageService.getImage(recipe.mainImage.url),
|
queryFn: () => this.imageService.getImage(recipe.mainImage?.url)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,9 @@
|
|||||||
} @else if (displayStep() === RecipeUploadStep.INFER) {
|
} @else if (displayStep() === RecipeUploadStep.INFER) {
|
||||||
<app-infer></app-infer>
|
<app-infer></app-infer>
|
||||||
} @else if (displayStep() === RecipeUploadStep.ENTER_DATA) {
|
} @else if (displayStep() === RecipeUploadStep.ENTER_DATA) {
|
||||||
<app-enter-recipe-data [model]="model()"></app-enter-recipe-data>
|
<app-enter-recipe-data [model]="model()" (submit)="onEnterRecipeDataEvent($event)"></app-enter-recipe-data>
|
||||||
|
} @else if (displayStep() === RecipeUploadStep.REVIEW) {
|
||||||
|
<app-review [draft]="model().draft!" (publish)="onPublish()"></app-review>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@ -13,10 +13,12 @@ import { RecipeUploadStep } from '../../shared/client-models/RecipeUploadStep';
|
|||||||
import { FileUploadEvent } from '../../shared/components/file-upload/FileUploadEvent';
|
import { FileUploadEvent } from '../../shared/components/file-upload/FileUploadEvent';
|
||||||
import { tryMaybeInt } from '../../shared/util';
|
import { tryMaybeInt } from '../../shared/util';
|
||||||
import { from, map, switchMap, tap } from 'rxjs';
|
import { from, map, switchMap, tap } from 'rxjs';
|
||||||
|
import { EnterRecipeDataEvent } from './steps/enter-recipe-data/EnterRecipeDataEvent';
|
||||||
|
import { Review } from './steps/review/review';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-recipe-upload-page',
|
selector: 'app-recipe-upload-page',
|
||||||
imports: [ReactiveFormsModule, AiOrManual, Infer, EnterRecipeData, RecipeUploadTrail],
|
imports: [ReactiveFormsModule, AiOrManual, Infer, EnterRecipeData, RecipeUploadTrail, Review],
|
||||||
templateUrl: './recipe-upload-page.html',
|
templateUrl: './recipe-upload-page.html',
|
||||||
styleUrl: './recipe-upload-page.css',
|
styleUrl: './recipe-upload-page.css',
|
||||||
})
|
})
|
||||||
@ -32,7 +34,15 @@ export class RecipeUploadPage implements OnInit {
|
|||||||
|
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly activatedRoute = inject(ActivatedRoute);
|
private readonly activatedRoute = inject(ActivatedRoute);
|
||||||
private readonly recipeUploadService = inject(RecipeDraftService);
|
private readonly recipeDraftService = inject(RecipeDraftService);
|
||||||
|
|
||||||
|
private isValidStep(step: number): boolean {
|
||||||
|
if (this.model().draft?.lastInference || this.model().draft?.state === 'INFER') {
|
||||||
|
return step <= RecipeUploadStep.REVIEW;
|
||||||
|
} else {
|
||||||
|
return [0, 2, 3].includes(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
this.activatedRoute.queryParamMap
|
this.activatedRoute.queryParamMap
|
||||||
@ -43,21 +53,20 @@ export class RecipeUploadPage implements OnInit {
|
|||||||
return [paramMap.get('draftId'), step] as const;
|
return [paramMap.get('draftId'), step] as const;
|
||||||
}),
|
}),
|
||||||
switchMap(([draftId, step]) => {
|
switchMap(([draftId, step]) => {
|
||||||
const currentModel = this.model();
|
|
||||||
if (draftId !== null) {
|
if (draftId !== null) {
|
||||||
return this.recipeUploadService.getRecipeUploadClientModel(draftId).pipe(
|
return this.recipeDraftService.getRecipeUploadClientModel(draftId).pipe(
|
||||||
tap((recipeUploadClientModel) => {
|
tap(async (recipeUploadClientModel) => {
|
||||||
this.switchModel(recipeUploadClientModel);
|
await this.switchModel(recipeUploadClientModel);
|
||||||
}),
|
}),
|
||||||
switchMap((updatedModel) => {
|
switchMap((updatedModel) => {
|
||||||
if (step !== null && step <= updatedModel.inProgressStep) {
|
if (step !== null && this.isValidStep(step)) {
|
||||||
return from(this.changeDisplayStep(step));
|
return from(this.changeDisplayStep(step));
|
||||||
} else {
|
} else {
|
||||||
return from(this.changeDisplayStep(updatedModel.inProgressStep));
|
return from(this.changeDisplayStep(updatedModel.inProgressStep));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else if (step !== null && step <= currentModel.inProgressStep) {
|
} else if (step !== null && this.isValidStep(step)) {
|
||||||
return from(this.changeDisplayStep(step));
|
return from(this.changeDisplayStep(step));
|
||||||
} else {
|
} else {
|
||||||
return from(this.changeDisplayStep(RecipeUploadStep.START));
|
return from(this.changeDisplayStep(RecipeUploadStep.START));
|
||||||
@ -67,9 +76,14 @@ export class RecipeUploadPage implements OnInit {
|
|||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private switchModel(model: RecipeUploadClientModel): void {
|
private async switchModel(model: RecipeUploadClientModel, switchStep: boolean | RecipeUploadStep = false): Promise<void> {
|
||||||
this.model.set(model);
|
this.model.set(model);
|
||||||
this.includeInfer.set(!!model.draft?.lastInference);
|
this.includeInfer.set(!!model.draft?.lastInference);
|
||||||
|
if (switchStep === true) {
|
||||||
|
await this.changeDisplayStep(model.inProgressStep);
|
||||||
|
} else if (typeof switchStep === 'number') {
|
||||||
|
await this.changeDisplayStep(switchStep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async changeDisplayStep(targetStep: number): Promise<void> {
|
private async changeDisplayStep(targetStep: number): Promise<void> {
|
||||||
@ -103,13 +117,8 @@ export class RecipeUploadPage implements OnInit {
|
|||||||
|
|
||||||
protected async onAiOrManualSubmit(event: AIOrManualSubmitEvent): Promise<void> {
|
protected async onAiOrManualSubmit(event: AIOrManualSubmitEvent): Promise<void> {
|
||||||
if (event.mode === 'manual') {
|
if (event.mode === 'manual') {
|
||||||
this.model.update((model) => ({
|
const model = await this.recipeDraftService.createManualDraft();
|
||||||
...model,
|
await this.switchModel(model, true);
|
||||||
inputSourceFile: null,
|
|
||||||
inProgressStep: RecipeUploadStep.ENTER_DATA,
|
|
||||||
}));
|
|
||||||
await this.changeDisplayStep(RecipeUploadStep.ENTER_DATA);
|
|
||||||
this.includeInfer.set(false);
|
|
||||||
} else {
|
} else {
|
||||||
this.model.update((model) => ({
|
this.model.update((model) => ({
|
||||||
...model,
|
...model,
|
||||||
@ -118,97 +127,29 @@ export class RecipeUploadPage implements OnInit {
|
|||||||
}));
|
}));
|
||||||
await this.changeDisplayStep(RecipeUploadStep.INFER);
|
await this.changeDisplayStep(RecipeUploadStep.INFER);
|
||||||
this.includeInfer.set(true);
|
this.includeInfer.set(true);
|
||||||
this.recipeUploadService.doInference(this.model()).subscribe((updatedModel) => {
|
this.recipeDraftService.doInference(this.model()).subscribe((updatedModel) => {
|
||||||
this.model.set(updatedModel);
|
this.model.set(updatedModel);
|
||||||
this.changeDisplayStep(RecipeUploadStep.ENTER_DATA);
|
this.changeDisplayStep(RecipeUploadStep.ENTER_DATA);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private readonly sseClient = inject(SseClient);
|
protected async onEnterRecipeDataEvent(event: EnterRecipeDataEvent): Promise<void> {
|
||||||
// private readonly formBuilder = inject(FormBuilder);
|
if (event._type === 'submit') {
|
||||||
//
|
const { title, slug, rawText } = event.data;
|
||||||
// protected readonly sourceRecipeImage = signal<string | null>(null);
|
const model = await this.recipeDraftService.updateDraft(this.model().draft!.id, {
|
||||||
// protected readonly inferenceInProgress = signal(false);
|
title,
|
||||||
//
|
slug,
|
||||||
// protected readonly recipeUploadForm = this.formBuilder.group({
|
rawText
|
||||||
// file: this.formBuilder.control<File | null>(null, [Validators.required]),
|
});
|
||||||
// });
|
await this.switchModel(model, RecipeUploadStep.REVIEW);
|
||||||
//
|
}
|
||||||
// protected readonly recipeForm = new FormGroup({
|
}
|
||||||
// title: new FormControl('', [Validators.required]),
|
|
||||||
// recipeText: new FormControl('', Validators.required),
|
protected async onPublish(): Promise<void> {
|
||||||
// });
|
const recipe = await this.recipeDraftService.publish(this.model().draft!.id);
|
||||||
//
|
await this.router.navigate(['recipes', recipe.owner.username, recipe.slug]);
|
||||||
// protected onClear() {
|
}
|
||||||
// this.recipeUploadForm.reset();
|
|
||||||
// this.sourceRecipeImage.set(null);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// protected onFileChange(event: Event) {
|
|
||||||
// const fileInput = event.target as HTMLInputElement;
|
|
||||||
// if (fileInput.files && fileInput.files.length) {
|
|
||||||
// const file = fileInput.files[0];
|
|
||||||
// this.recipeUploadForm.controls.file.setValue(file);
|
|
||||||
// this.recipeUploadForm.controls.file.markAsTouched();
|
|
||||||
// this.recipeUploadForm.controls.file.updateValueAndValidity();
|
|
||||||
//
|
|
||||||
// // set source image
|
|
||||||
// this.sourceRecipeImage.set(URL.createObjectURL(file));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// protected onFileSubmit() {
|
|
||||||
// const rawValue = this.recipeUploadForm.getRawValue();
|
|
||||||
//
|
|
||||||
// this.inferenceInProgress.set(true);
|
|
||||||
//
|
|
||||||
// // upload form data
|
|
||||||
// const formData = new FormData();
|
|
||||||
// formData.append('recipeImageFile', rawValue.file!, rawValue.file!.name);
|
|
||||||
// this.sseClient
|
|
||||||
// .stream(
|
|
||||||
// `http://localhost:8080/inferences/recipe-extract-stream`,
|
|
||||||
// {
|
|
||||||
// keepAlive: false,
|
|
||||||
// reconnectionDelay: 1000,
|
|
||||||
// responseType: 'event',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// body: formData,
|
|
||||||
// },
|
|
||||||
// 'PUT',
|
|
||||||
// )
|
|
||||||
// .subscribe({
|
|
||||||
// next: (event) => {
|
|
||||||
// if (event.type === 'error') {
|
|
||||||
// const errorEvent = event as ErrorEvent;
|
|
||||||
// console.error(errorEvent.error, errorEvent.message);
|
|
||||||
// } else {
|
|
||||||
// const messageEvent = event as MessageEvent;
|
|
||||||
// const data: { delta: string } = JSON.parse(messageEvent.data);
|
|
||||||
// this.recipeForm.patchValue({
|
|
||||||
// recipeText: this.recipeForm.value.recipeText + data.delta,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // must do this so we auto-resize the textarea
|
|
||||||
// document.getElementById('recipe-text')?.dispatchEvent(new Event('input', { bubbles: true }));
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// complete: () => {
|
|
||||||
// this.inferenceInProgress.set(false);
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// protected onRecipeSubmit() {
|
|
||||||
// console.log(this.recipeForm.value);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// protected onRecipeTextChange(event: Event) {
|
|
||||||
// const textarea = event.target as HTMLTextAreaElement;
|
|
||||||
// textarea.style.height = 'auto';
|
|
||||||
// textarea.style.height = textarea.scrollHeight + 'px';
|
|
||||||
// }
|
|
||||||
protected readonly RecipeUploadStep = RecipeUploadStep;
|
protected readonly RecipeUploadStep = RecipeUploadStep;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
export type EnterRecipeDataEvent = EnterRecipeDataSubmitEvent
|
||||||
|
|
||||||
|
export interface EnterRecipeDataSubmitEvent {
|
||||||
|
_type: 'submit';
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
rawText: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<h2>Enter Recipe</h2>
|
<h2>Enter Recipe</h2>
|
||||||
<form [formGroup]="recipeFormGroup">
|
<form [formGroup]="recipeFormGroup" (submit)="onSubmit($event)">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Title</mat-label>
|
<mat-label>Title</mat-label>
|
||||||
<input matInput [formControl]="recipeFormGroup.controls.title" />
|
<input matInput [formControl]="recipeFormGroup.controls.title" />
|
||||||
@ -17,4 +17,5 @@
|
|||||||
(input)="onRecipeTextChange($event)"
|
(input)="onRecipeTextChange($event)"
|
||||||
></textarea>
|
></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<button matButton="filled" type="submit">Review</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -5,22 +5,25 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
Injector,
|
Injector,
|
||||||
input,
|
input,
|
||||||
OnInit,
|
OnInit, output,
|
||||||
runInInjectionContext,
|
runInInjectionContext,
|
||||||
viewChild,
|
viewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { RecipeUploadClientModel } from '../../../../shared/client-models/RecipeUploadClientModel';
|
import { RecipeUploadClientModel } from '../../../../shared/client-models/RecipeUploadClientModel';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
import { EnterRecipeDataEvent } from './EnterRecipeDataEvent';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-enter-recipe-data',
|
selector: 'app-enter-recipe-data',
|
||||||
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput],
|
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatButton],
|
||||||
templateUrl: './enter-recipe-data.html',
|
templateUrl: './enter-recipe-data.html',
|
||||||
styleUrl: './enter-recipe-data.css',
|
styleUrl: './enter-recipe-data.css',
|
||||||
})
|
})
|
||||||
export class EnterRecipeData implements OnInit {
|
export class EnterRecipeData implements OnInit {
|
||||||
public readonly model = input.required<RecipeUploadClientModel>();
|
public readonly model = input.required<RecipeUploadClientModel>();
|
||||||
|
public readonly submit = output<EnterRecipeDataEvent>();
|
||||||
|
|
||||||
protected recipeTextTextarea = viewChild.required<ElementRef<HTMLTextAreaElement>>('recipeTextTextarea');
|
protected recipeTextTextarea = viewChild.required<ElementRef<HTMLTextAreaElement>>('recipeTextTextarea');
|
||||||
|
|
||||||
@ -61,4 +64,17 @@ export class EnterRecipeData implements OnInit {
|
|||||||
protected onRecipeTextChange(event: Event): void {
|
protected onRecipeTextChange(event: Event): void {
|
||||||
this.updateTextareaHeight(event.target as HTMLTextAreaElement);
|
this.updateTextareaHeight(event.target as HTMLTextAreaElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onSubmit(event: SubmitEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
const { title, slug, text } = this.recipeFormGroup.value;
|
||||||
|
this.submit.emit({
|
||||||
|
_type: 'submit',
|
||||||
|
data: {
|
||||||
|
title: title!,
|
||||||
|
slug: slug!,
|
||||||
|
rawText: text!
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
<section>
|
||||||
|
<h2>Review and Publish</h2>
|
||||||
|
<p>Title: {{ draft().title }}</p>
|
||||||
|
<p>Slug: {{ draft().slug }}</p>
|
||||||
|
<div>
|
||||||
|
<p>Text: todo</p>
|
||||||
|
</div>
|
||||||
|
<button matButton="filled" (click)="onPublish()">Publish</button>
|
||||||
|
</section>
|
||||||
23
src/app/pages/recipe-upload-page/steps/review/review.spec.ts
Normal file
23
src/app/pages/recipe-upload-page/steps/review/review.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Review } from './review';
|
||||||
|
|
||||||
|
describe('Review', () => {
|
||||||
|
let component: Review;
|
||||||
|
let fixture: ComponentFixture<Review>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Review]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Review);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
22
src/app/pages/recipe-upload-page/steps/review/review.ts
Normal file
22
src/app/pages/recipe-upload-page/steps/review/review.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Component, input, output } from '@angular/core';
|
||||||
|
import { RecipeDraftViewModel } from '../../../../shared/models/RecipeDraftView.model';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-review',
|
||||||
|
imports: [
|
||||||
|
MatButton
|
||||||
|
],
|
||||||
|
templateUrl: './review.html',
|
||||||
|
styleUrl: './review.css',
|
||||||
|
})
|
||||||
|
export class Review {
|
||||||
|
|
||||||
|
public readonly draft = input.required<RecipeDraftViewModel>();
|
||||||
|
public readonly publish = output<void>();
|
||||||
|
|
||||||
|
protected onPublish(): void {
|
||||||
|
this.publish.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,4 +2,5 @@ export enum RecipeUploadStep {
|
|||||||
START,
|
START,
|
||||||
INFER,
|
INFER,
|
||||||
ENTER_DATA,
|
ENTER_DATA,
|
||||||
|
REVIEW
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,12 @@
|
|||||||
<article>
|
<article>
|
||||||
<a [routerLink]="recipePageLink()">
|
<a [routerLink]="recipePageLink()">
|
||||||
@if (mainImage.isSuccess()) {
|
@if (mainImage.isSuccess()) {
|
||||||
<img [src]="mainImage.data()" id="recipe-card-image" [alt]="recipe.mainImage.alt" />
|
@let maybeMainImageUrl = mainImage.data();
|
||||||
|
@if (!!maybeMainImageUrl) {
|
||||||
|
<img [src]="mainImage.data()" id="recipe-card-image" [alt]="recipe.mainImage?.alt" />
|
||||||
|
} @else {
|
||||||
|
<p>!! Placeholder todo !!</p>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</a>
|
</a>
|
||||||
<div id="title-and-visibility">
|
<div id="title-and-visibility">
|
||||||
|
|||||||
@ -30,8 +30,8 @@ export class RecipeCard {
|
|||||||
protected readonly mainImage = injectQuery(() => {
|
protected readonly mainImage = injectQuery(() => {
|
||||||
const recipe = this.recipe();
|
const recipe = this.recipe();
|
||||||
return {
|
return {
|
||||||
queryKey: ['images', recipe.mainImage.owner.username, recipe.mainImage.filename],
|
queryKey: ['recipe-main-images', recipe.owner.username, recipe.slug],
|
||||||
queryFn: () => this.imageService.getImage(recipe.mainImage.url),
|
queryFn: () => this.imageService.getImage(recipe.mainImage?.url)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export interface RecipeView {
|
|||||||
export interface Recipe {
|
export interface Recipe {
|
||||||
id: number;
|
id: number;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
mainImage: ImageView;
|
mainImage?: ImageView | null;
|
||||||
owner: ResourceOwner;
|
owner: ResourceOwner;
|
||||||
slug: string;
|
slug: string;
|
||||||
starCount: number;
|
starCount: number;
|
||||||
|
|||||||
@ -8,13 +8,17 @@ import { firstValueFrom, map } from 'rxjs';
|
|||||||
export class ImageService {
|
export class ImageService {
|
||||||
private readonly httpClient = inject(HttpClient);
|
private readonly httpClient = inject(HttpClient);
|
||||||
|
|
||||||
public getImage(backendUrl: string): Promise<string> {
|
public getImage(backendUrl?: string | null): Promise<string | null> {
|
||||||
return firstValueFrom(
|
if (!!backendUrl) {
|
||||||
this.httpClient
|
return firstValueFrom(
|
||||||
.get(backendUrl, {
|
this.httpClient
|
||||||
responseType: 'blob',
|
.get(backendUrl, {
|
||||||
})
|
responseType: 'blob',
|
||||||
.pipe(map((blob) => URL.createObjectURL(blob))),
|
})
|
||||||
);
|
.pipe(map((blob) => URL.createObjectURL(blob))),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { RecipeUploadStep } from '../client-models/RecipeUploadStep';
|
|||||||
import { RecipeDraftViewModel } from '../models/RecipeDraftView.model';
|
import { RecipeDraftViewModel } from '../models/RecipeDraftView.model';
|
||||||
import { EndpointService } from './EndpointService';
|
import { EndpointService } from './EndpointService';
|
||||||
import { WithStringDates } from '../util';
|
import { WithStringDates } from '../util';
|
||||||
|
import { Recipe } from '../models/Recipe.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -55,6 +56,51 @@ export class RecipeDraftService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createManualDraft(): Promise<RecipeUploadClientModel> {
|
||||||
|
return firstValueFrom(
|
||||||
|
this.http
|
||||||
|
.post<
|
||||||
|
WithStringDates<RecipeDraftViewModel>
|
||||||
|
>(this.endpointService.getUrl('recipeDrafts', ['manual']), null)
|
||||||
|
.pipe(
|
||||||
|
map((rawDraft) => this.hydrateView(rawDraft)),
|
||||||
|
map((draft) => ({
|
||||||
|
draft,
|
||||||
|
inProgressStep: RecipeUploadStep.ENTER_DATA,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateDraft(id: string, data: {
|
||||||
|
title?: string | null;
|
||||||
|
slug?: string | null;
|
||||||
|
rawText?: string | null;
|
||||||
|
}): Promise<RecipeUploadClientModel> {
|
||||||
|
return firstValueFrom(
|
||||||
|
this.http.put<WithStringDates<RecipeDraftViewModel>>(
|
||||||
|
this.endpointService.getUrl('recipeDrafts', [id]),
|
||||||
|
data
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map(rawView => this.hydrateView(rawView)),
|
||||||
|
map(draft => ({
|
||||||
|
draft,
|
||||||
|
inProgressStep: RecipeUploadStep.ENTER_DATA,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public publish(id: string): Promise<Recipe> {
|
||||||
|
return firstValueFrom(
|
||||||
|
this.http.post<Recipe>(
|
||||||
|
this.endpointService.getUrl('recipeDrafts', [id, 'publish']),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public doInference(model: RecipeUploadClientModel): Observable<RecipeUploadClientModel> {
|
public doInference(model: RecipeUploadClientModel): Observable<RecipeUploadClientModel> {
|
||||||
return of({
|
return of({
|
||||||
inProgressStep: RecipeUploadStep.ENTER_DATA,
|
inProgressStep: RecipeUploadStep.ENTER_DATA,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user