Skip to content

Commit

Permalink
Create timers from instructions text
Browse files Browse the repository at this point in the history
  • Loading branch information
NoelDeMartin committed Jan 4, 2025
1 parent ab1522d commit 3cea831
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/components/AppKitchen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<div class="max-w-content px-edge w-full m-auto flex justify-center md:justify-end">
<Transition
enter-active-class="transition-transform duration-500 delay-1000"
enter-from-class="translate-y-[200%]"
enter-from-class="translate-y-[250%]"
enter-to-class="translate-y-0"
leave-active-class="transition-transform duration-500"
leave-from-class="translate-y-0"
leave-to-class="translate-y-[200%]"
leave-to-class="translate-y-[250%]"
>
<div v-if="$kitchen.show" class="flex flex-col space-y-1 items-center">
<CoreButton v-if="recipe && !recipeIsCooking" class="pointer-events-auto h-12" @click="recipe && $kitchen.cook(recipe)">
Expand Down
3 changes: 3 additions & 0 deletions src/lang/ca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,13 @@ timers:
resume: Continuar
stop: Aturar
delete: Eliminar
delete_confirm: Estàs segur que vols eliminar **{timer}**?
delete_confirm_accept: Sí, eliminar temporitzador
overtime: Alguns temporitzadors han acabat!
new:
title: Nou temporizador
name: Nom
name_dish: "{recipe} pas {step}"
name_default: Temporitzador {count}
duration: Duració
hours: hores
Expand Down
3 changes: 3 additions & 0 deletions src/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,13 @@ kitchen:
resume: Resume
stop: Stop
delete: Delete
delete_confirm: Are you sure that you want to delete **{timer}**?
delete_confirm_accept: Yes, delete timer
overtime: Some timers are running overtime!
new:
title: New timer
name: Name
name_dish: "{recipe} step {step}"
name_default: Timer {count}
duration: Duration
hours: hours
Expand Down
3 changes: 3 additions & 0 deletions src/lang/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,13 @@ kitchen:
resume: Continuar
stop: Parar
delete: Eliminar
delete_confirm: ¿Estás seguro de que quieres eliminar **{timer}**?
delete_confirm_accept: Sí, eliminar temporizador
overtime: ¡Algúnos temporizadores han acabado!
new:
title: Nuevo temporizador
name: Nombre
name_dish: "{recipe} paso {step}"
name_default: Temporizador {count}
duration: Duración
hours: horas
Expand Down
32 changes: 30 additions & 2 deletions src/models/Timer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ListenersManager from '@/utils/ListenersManager';
import type Dish from '@/models/Dish';
import type { Listeners } from '@/utils/ListenersManager';

export interface TimerListener {
Expand All @@ -10,28 +11,40 @@ export interface TimerListener {
export interface TimerJson {
name: string;
duration: number;
recipeId?: string;
recipeStep?: number;
startedAt?: number;
pausedAt?: number;
}

export default class Timer {

public static fromJson(json: TimerJson): Timer {
public static fromJson(json: TimerJson, dishes: Dish[]): Timer {
return new Timer(json.name, json.duration, {
dish: json.recipeId ? dishes.find(dish => dish.recipe.id === json.recipeId) : undefined,
step: json.recipeStep,
startedAt: typeof json.startedAt === 'number' ? new Date(json.startedAt) : undefined,
pausedAt: typeof json.pausedAt === 'number' ? new Date(json.pausedAt) : undefined,
});
}

public readonly name: string;
public readonly duration: number;
private dish?: Dish;
private step?: number;
private startedAt?: Date;
private pausedAt?: Date;
private _listeners = new ListenersManager<TimerListener>();

constructor(name: string, duration: number, state: { startedAt?: Date; pausedAt?: Date } = {}) {
constructor(
name: string,
duration: number,
state: { dish?: Dish; step?: number; startedAt?: Date; pausedAt?: Date } = {},
) {
this.name = name;
this.duration = duration;
this.dish = state.dish;
this.step = state.step;
this.startedAt = state.startedAt;
this.pausedAt = state.pausedAt;
}
Expand Down Expand Up @@ -76,6 +89,19 @@ export default class Timer {
return this.duration - Math.round(time - this.startedAt.getTime());
}

public setDish(dish: Dish, step: number): void {
this.dish = dish;
this.step = step;
}

public hasDish(dish: Dish, step?: number): boolean {
if (this.dish !== dish) {
return false;
}

return typeof step === undefined || step === this.step;
}

public play(): void {
if (this.isPaused()) {
this.resume();
Expand Down Expand Up @@ -118,6 +144,8 @@ export default class Timer {
return {
name: this.name,
duration: this.duration,
recipeId: this.dish?.recipe.id,
recipeStep: this.step,
startedAt: this.startedAt ? this.startedAt.getTime() : undefined,
pausedAt: this.pausedAt ? this.pausedAt.getTime() : undefined,
};
Expand Down
23 changes: 19 additions & 4 deletions src/routing/pages/kitchen/KitchenInstructions.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
<template>
<KitchenPage :recipe="recipe" :title="title">
<CoreMarkdown v-if="text" :text="text" class="text-gray-700 mt-2 flex-grow" />
<CoreMarkdown v-else :text="$t('kitchen.instructions.empty')" class="text-gray-700 mt-2 flex-grow" />
<div class="flex-grow">
<CoreMarkdown v-if="text" :text="text" class="text-gray-700 mt-2" />
<CoreMarkdown v-else :text="$t('kitchen.instructions.empty')" class="text-gray-700 mt-2" />

<template v-if="timers.length > 0">
<h3 class="mt-4 font-medium text-lg">
{{ $t('kitchen.timers.title') }}
</h3>

<ul class="mt-2">
<li v-for="(timer, index) of timers" :key="index" class="bg-gray-100 rounded-lg px-3 py-4 max-w-lg">
<KitchenTimer :timer="timer" />
</li>
</ul>
</template>
</div>

<div class="flex w-full justify-end space-x-2 mt-3">
<CoreButton v-if="position === 1" route="kitchen.ingredients" :route-params="{ recipe: recipe.slug }">
Expand Down Expand Up @@ -39,17 +53,18 @@ const props = defineProps({
});
const position = computed(() => parseInt(props.step));
const dish = computed(() => Kitchen.findDish(props.recipe));
const text = computed(() => {
return props.recipe.instructions?.find(instructionStep => instructionStep.position === position.value)?.text;
});
const title = computed(() => {
if (props.recipe.instructions?.length === 1 || !text.value) {
return translate('kitchen.instructions.emptyTitle');
}
return translate('kitchen.instructions.title', { step: props.step });
});
const timers = computed(() => Kitchen.timers.filter(timer => dish.value && timer.hasDish(dish.value, position.value)));
onMounted(() => Kitchen.findDish(props.recipe)?.updateStage(position.value));
onMounted(() => dish.value?.updateStage(position.value));
</script>
22 changes: 21 additions & 1 deletion src/routing/pages/kitchen/components/KitchenTimer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
rounded
class="self-end"
:title="$t('kitchen.timers.delete')"
@click="$kitchen.removeTimer(timer)"
@click="deleteTimer()"
>
<i-pepicons-trash class="w-4 h-4" aria-hidden="true" />
<span class="sr-only">
Expand All @@ -85,8 +85,12 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue';
import UI from '@/framework/core/facades/UI';
import { requiredObjectProp } from '@/framework/utils/vue';
import { translate } from '@/framework/utils/translate';
import Kitchen from '@/services/facades/Kitchen';
import { CoreColor } from '@/components/core';
import { setFrameInterval } from '@/utils/intervals';
import type Timer from '@/models/Timer';
Expand Down Expand Up @@ -137,6 +141,22 @@ function stopWatching(): void {
now.value = null;
}
async function deleteTimer(): Promise<void> {
const confirmed = await UI.confirm({
message: translate('kitchen.timers.delete_confirm', {
timer: props.timer.name,
}),
acceptText: translate('kitchen.timers.delete_confirm_accept'),
acceptColor: CoreColor.Danger,
});
if (!confirmed) {
return;
}
Kitchen.removeTimer(props.timer);
}
onMounted(() => {
clearListener = props.timer.listeners.add({
onStartedRunning: () => startWatching(),
Expand Down
32 changes: 28 additions & 4 deletions src/services/KitchenService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { arrayWithout, fail, required, value } from '@noeldemartin/utils';
import { arrayWithout, fail, required, stringMatchAll, value } from '@noeldemartin/utils';
import { toRaw } from 'vue';
import type { Falsy } from '@noeldemartin/utils';
import type { RouteLocationRaw } from 'vue-router';
Expand All @@ -9,6 +9,7 @@ import Events from '@/framework/core/facades/Events';
import Router from '@/framework/core/facades/Router';
import Service from '@/framework/core/Service';
import UI from '@/framework/core/facades/UI';
import { translate } from '@/framework/utils/translate';
import type { ComputedStateDefinitions, IService } from '@/framework/core/Service';

import Dish from '@/models/Dish';
Expand Down Expand Up @@ -121,7 +122,10 @@ export default class CookbookService extends Service<State, ComputedState, Persi
}

public async complete(dish: Dish): Promise<void> {
this.setState({ dishes: arrayWithout(this.dishes, dish) });
this.setState({
dishes: arrayWithout(this.dishes, dish),
timers: this.timers.filter(timer => !timer.hasDish(dish)),
});

if (this.dishes.length === 0) {
this.releaseScreen();
Expand Down Expand Up @@ -206,10 +210,12 @@ export default class CookbookService extends Service<State, ComputedState, Persi
}

protected async restorePersistedState(state: PersistedState): Promise<Partial<State>> {
const dishes = await Promise.all(state.dishes.map(json => Dish.fromJson(json)));

return {
...state,
dishes: await Promise.all(state.dishes.map(json => Dish.fromJson(json))),
timers: state.timers.map(json => Timer.fromJson(json)),
dishes,
timers: state.timers.map(json => Timer.fromJson(json, dishes)),
};
}

Expand All @@ -220,6 +226,24 @@ export default class CookbookService extends Service<State, ComputedState, Persi

this.dishes.push(dish);

recipe.sortedInstructions.forEach(({ position, text }) => {
const matches = stringMatchAll<2>(text, /(\d+)\s+minutes?/gi);

for (const match of matches) {
const timer = new Timer(
translate('kitchen.timers.new.name_dish', {
recipe: recipe.name,
step: position,
}),
parseInt(match[1]) * 60 * 1000,
);

timer.setDish(dish, position);

this.addTimer(timer);
}
});

if (this.dishes.length === 1) {
this.lockScreen();
}
Expand Down

0 comments on commit 3cea831

Please sign in to comment.