184 lines
7.6 KiB
TypeScript
184 lines
7.6 KiB
TypeScript
import { Component, computed, inject, OnInit, signal } from '@angular/core';
|
|
import { ReactiveFormsModule } from '@angular/forms';
|
|
import { AiOrManual } from './steps/ai-or-manual/ai-or-manual';
|
|
import { AIOrManualSubmitEvent } from './steps/ai-or-manual/AIOrManualSubmitEvent';
|
|
import { Infer } from './steps/infer/infer';
|
|
import { EnterRecipeData } from './steps/enter-recipe-data/enter-recipe-data';
|
|
import { RecipeUploadTrail } from './recipe-upload-trail/recipe-upload-trail';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import { StepClickEvent } from './recipe-upload-trail/StepClickEvent';
|
|
import { RecipeUploadClientModel } from '../../shared/client-models/RecipeUploadClientModel';
|
|
import { RecipeDraftService } from '../../shared/services/RecipeDraftService';
|
|
import { RecipeUploadStep } from '../../shared/client-models/RecipeUploadStep';
|
|
import { FileUploadEvent } from '../../shared/components/file-upload/FileUploadEvent';
|
|
import { tryMaybeInt } from '../../shared/util';
|
|
import { from, map, switchMap, tap } from 'rxjs';
|
|
import { Review } from './steps/review/review';
|
|
import { RecipeEditFormSubmitEvent } from '../../shared/components/recipe-edit-form/RecipeEditFormSubmitEvent';
|
|
import { QueryClient } from '@tanstack/angular-query-experimental';
|
|
import { ToastrService } from 'ngx-toastr';
|
|
|
|
@Component({
|
|
selector: 'app-recipe-upload-page',
|
|
imports: [ReactiveFormsModule, AiOrManual, Infer, EnterRecipeData, RecipeUploadTrail, Review],
|
|
templateUrl: './recipe-upload-page.html',
|
|
styleUrl: './recipe-upload-page.css',
|
|
})
|
|
export class RecipeUploadPage implements OnInit {
|
|
protected readonly model = signal<RecipeUploadClientModel>({
|
|
inProgressStep: RecipeUploadStep.START,
|
|
});
|
|
|
|
protected readonly displayStep = signal<number>(RecipeUploadStep.START);
|
|
protected readonly inProgressStep = computed(() => this.model().inProgressStep);
|
|
protected readonly includeInfer = signal(false);
|
|
protected readonly sourceFile = computed(() => this.model().inputSourceFile ?? null);
|
|
|
|
private readonly router = inject(Router);
|
|
private readonly activatedRoute = inject(ActivatedRoute);
|
|
private readonly recipeDraftService = inject(RecipeDraftService);
|
|
private readonly queryClient = inject(QueryClient);
|
|
private readonly toastrService = inject(ToastrService);
|
|
|
|
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 {
|
|
this.activatedRoute.queryParamMap
|
|
.pipe(
|
|
map((paramMap) => {
|
|
const stepParam: string | null = paramMap.get('step');
|
|
const step = tryMaybeInt(stepParam);
|
|
return [paramMap.get('draftId'), step] as const;
|
|
}),
|
|
switchMap(([draftId, step]) => {
|
|
if (draftId !== null) {
|
|
return this.recipeDraftService.getRecipeUploadClientModel(draftId).pipe(
|
|
tap(async (recipeUploadClientModel) => {
|
|
await this.switchModel(recipeUploadClientModel);
|
|
}),
|
|
switchMap((updatedModel) => {
|
|
if (step !== null && this.isValidStep(step)) {
|
|
return from(this.changeDisplayStep(step));
|
|
} else {
|
|
return from(this.changeDisplayStep(updatedModel.inProgressStep));
|
|
}
|
|
}),
|
|
);
|
|
} else if (step !== null && this.isValidStep(step)) {
|
|
return from(this.changeDisplayStep(step));
|
|
} else {
|
|
return from(this.changeDisplayStep(RecipeUploadStep.START));
|
|
}
|
|
}),
|
|
)
|
|
.subscribe();
|
|
}
|
|
|
|
private async switchModel(
|
|
model: RecipeUploadClientModel,
|
|
switchStep: boolean | RecipeUploadStep = false,
|
|
): Promise<void> {
|
|
this.model.set(model);
|
|
this.includeInfer.set(!!model.draft?.lastInference || model.inProgressStep === RecipeUploadStep.INFER);
|
|
if (switchStep === true) {
|
|
await this.changeDisplayStep(model.inProgressStep);
|
|
} else if (typeof switchStep === 'number') {
|
|
await this.changeDisplayStep(switchStep);
|
|
}
|
|
}
|
|
|
|
private async changeDisplayStep(targetStep: number): Promise<void> {
|
|
this.displayStep.set(targetStep);
|
|
await this.router.navigate([], {
|
|
relativeTo: this.activatedRoute,
|
|
queryParams: {
|
|
draftId: this.model().draft?.id,
|
|
step: targetStep,
|
|
},
|
|
queryParamsHandling: 'replace',
|
|
});
|
|
}
|
|
|
|
protected async onStepClick(event: StepClickEvent): Promise<void> {
|
|
await this.changeDisplayStep(event.step);
|
|
}
|
|
|
|
protected onSourceFileChange(event: FileUploadEvent) {
|
|
if (event._tag === 'file-add-event') {
|
|
this.model.update((model) => ({
|
|
...model,
|
|
inputSourceFile: event.file,
|
|
}));
|
|
} else {
|
|
this.model.update((model) => ({
|
|
...model,
|
|
inputSourceFile: null,
|
|
}));
|
|
}
|
|
}
|
|
|
|
protected async onAiOrManualSubmit(event: AIOrManualSubmitEvent): Promise<void> {
|
|
if (event.mode === 'manual') {
|
|
const model = await this.recipeDraftService.createManualDraft();
|
|
await this.switchModel(model, true);
|
|
} else {
|
|
await this.switchModel(
|
|
{
|
|
...this.model(),
|
|
inputSourceFile: this.sourceFile(),
|
|
inProgressStep: RecipeUploadStep.INFER,
|
|
},
|
|
true,
|
|
);
|
|
this.recipeDraftService
|
|
.startInference(this.model())
|
|
.pipe(
|
|
tap((updated) => {
|
|
this.switchModel(updated, true);
|
|
}),
|
|
switchMap((model) => this.recipeDraftService.pollUntilInferenceComplete(model)),
|
|
)
|
|
.subscribe({
|
|
next: (inferredModel) => {
|
|
this.switchModel(inferredModel, true);
|
|
},
|
|
error: (e) => {
|
|
console.error(e);
|
|
this.toastrService.error('Error while extracting recipe data');
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
protected async onEnterRecipeDataSubmit(event: RecipeEditFormSubmitEvent): Promise<void> {
|
|
const model = await this.recipeDraftService.updateDraft(this.model().draft!.id, event);
|
|
await this.switchModel(model, RecipeUploadStep.REVIEW);
|
|
}
|
|
|
|
protected async onDeleteDraft(): Promise<void> {
|
|
await this.recipeDraftService.deleteDraft(this.model().draft!.id);
|
|
await this.queryClient.invalidateQueries({
|
|
queryKey: ['recipe-upload', 'in-progress-drafts'],
|
|
});
|
|
await this.switchModel(
|
|
{
|
|
inProgressStep: RecipeUploadStep.START,
|
|
},
|
|
RecipeUploadStep.START,
|
|
);
|
|
}
|
|
|
|
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 readonly RecipeUploadStep = RecipeUploadStep;
|
|
}
|