mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-04 03:03:18 -05:00 
			
		
		
		
	
		
			
	
	
		
			318 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
		
		
			
		
	
	
			318 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| 
								 | 
							
								<template>
							 | 
						||
| 
								 | 
							
								  <div class="text-center">
							 | 
						||
| 
								 | 
							
								    <v-menu
							 | 
						||
| 
								 | 
							
								      v-model="showMenu"
							 | 
						||
| 
								 | 
							
								      offset-x
							 | 
						||
| 
								 | 
							
								      offset-overflow
							 | 
						||
| 
								 | 
							
								      left
							 | 
						||
| 
								 | 
							
								      allow-overflow
							 | 
						||
| 
								 | 
							
								      close-delay="125"
							 | 
						||
| 
								 | 
							
								      :close-on-content-click="false"
							 | 
						||
| 
								 | 
							
								      content-class="d-print-none"
							 | 
						||
| 
								 | 
							
								      :z-index="2"
							 | 
						||
| 
								 | 
							
								    >
							 | 
						||
| 
								 | 
							
								      <template #activator="{ on, attrs }">
							 | 
						||
| 
								 | 
							
								        <v-badge :value="timerEnded" overlap color="red" content="!">
							 | 
						||
| 
								 | 
							
								          <v-btn :fab="fab" :small="fab" :color="timerEnded ? 'secondary' : color" :icon="!fab" dark v-bind="attrs" v-on="on" @click.prevent>
							 | 
						||
| 
								 | 
							
								            <v-progress-circular
							 | 
						||
| 
								 | 
							
								              v-if="timerInitialized && !timerEnded"
							 | 
						||
| 
								 | 
							
								              :value="timerProgress"
							 | 
						||
| 
								 | 
							
								              :rotate="270"
							 | 
						||
| 
								 | 
							
								              :color="timerRunning ? undefined : 'primary'"
							 | 
						||
| 
								 | 
							
								            >
							 | 
						||
| 
								 | 
							
								              <v-icon small>{{ timerRunning ? $globals.icons.timer : $globals.icons.timerPause }}</v-icon>
							 | 
						||
| 
								 | 
							
								            </v-progress-circular>
							 | 
						||
| 
								 | 
							
								            <v-icon v-else>{{ $globals.icons.timer }}</v-icon>
							 | 
						||
| 
								 | 
							
								          </v-btn>
							 | 
						||
| 
								 | 
							
								        </v-badge>
							 | 
						||
| 
								 | 
							
								      </template>
							 | 
						||
| 
								 | 
							
								      <v-card>
							 | 
						||
| 
								 | 
							
								        <v-card-title>
							 | 
						||
| 
								 | 
							
								          <v-icon class="pr-2">{{ $globals.icons.timer }}</v-icon>
							 | 
						||
| 
								 | 
							
								          {{ $i18n.tc("recipe.timer.kitchen-timer") }}
							 | 
						||
| 
								 | 
							
								        </v-card-title>
							 | 
						||
| 
								 | 
							
								        <div class="mx-auto" style="width: fit-content;">
							 | 
						||
| 
								 | 
							
								          <v-progress-circular
							 | 
						||
| 
								 | 
							
								            :value="timerProgress"
							 | 
						||
| 
								 | 
							
								            :rotate="270"
							 | 
						||
| 
								 | 
							
								            color="primary"
							 | 
						||
| 
								 | 
							
								            class="mb-2"
							 | 
						||
| 
								 | 
							
								            :size="128"
							 | 
						||
| 
								 | 
							
								            :width="24"
							 | 
						||
| 
								 | 
							
								          >
							 | 
						||
| 
								 | 
							
								          <v-icon
							 | 
						||
| 
								 | 
							
								            v-if="timerInitialized && !timerRunning"
							 | 
						||
| 
								 | 
							
								            x-large
							 | 
						||
| 
								 | 
							
								            :color="timerEnded ? 'red' : 'primary'"
							 | 
						||
| 
								 | 
							
								            @click="() => timerEnded ? resetTimer() : resumeTimer()"
							 | 
						||
| 
								 | 
							
								          >
							 | 
						||
| 
								 | 
							
								            {{ timerEnded ? $globals.icons.stop : $globals.icons.pause }}
							 | 
						||
| 
								 | 
							
								          </v-icon>
							 | 
						||
| 
								 | 
							
								          </v-progress-circular>
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								        <v-container width="100%" fluid class="ma-0 px-auto py-2">
							 | 
						||
| 
								 | 
							
								          <v-row no-gutters justify="center">
							 | 
						||
| 
								 | 
							
								            <v-col cols="3" align-self="center">
							 | 
						||
| 
								 | 
							
								              <v-text-field
							 | 
						||
| 
								 | 
							
								                :value="timerHours"
							 | 
						||
| 
								 | 
							
								                :min="0"
							 | 
						||
| 
								 | 
							
								                outlined
							 | 
						||
| 
								 | 
							
								                single-line
							 | 
						||
| 
								 | 
							
								                solo
							 | 
						||
| 
								 | 
							
								                hide-details
							 | 
						||
| 
								 | 
							
								                type="number"
							 | 
						||
| 
								 | 
							
								                :disabled="timerInitialized"
							 | 
						||
| 
								 | 
							
								                class="centered-input my-0 py-0"
							 | 
						||
| 
								 | 
							
								                style="font-size: large; width: 100px;"
							 | 
						||
| 
								 | 
							
								                @input="(v) => timerHours = v.toString().padStart(2, '0')"
							 | 
						||
| 
								 | 
							
								              />
							 | 
						||
| 
								 | 
							
								            </v-col>
							 | 
						||
| 
								 | 
							
								            <v-col cols="1" align-self="center" style="text-align: center;">
							 | 
						||
| 
								 | 
							
								              <h1>:</h1>
							 | 
						||
| 
								 | 
							
								            </v-col>
							 | 
						||
| 
								 | 
							
								            <v-col cols="3" align-self="center">
							 | 
						||
| 
								 | 
							
								              <v-text-field
							 | 
						||
| 
								 | 
							
								                :value="timerMinutes"
							 | 
						||
| 
								 | 
							
								                :min="0"
							 | 
						||
| 
								 | 
							
								                outlined
							 | 
						||
| 
								 | 
							
								                single-line
							 | 
						||
| 
								 | 
							
								                solo
							 | 
						||
| 
								 | 
							
								                hide-details
							 | 
						||
| 
								 | 
							
								                type="number"
							 | 
						||
| 
								 | 
							
								                :disabled="timerInitialized"
							 | 
						||
| 
								 | 
							
								                class="centered-input my-0 py-0"
							 | 
						||
| 
								 | 
							
								                style="font-size: large; width: 100px;"
							 | 
						||
| 
								 | 
							
								                @input="(v) => timerMinutes = v.toString().padStart(2, '0')"
							 | 
						||
| 
								 | 
							
								              />
							 | 
						||
| 
								 | 
							
								            </v-col>
							 | 
						||
| 
								 | 
							
								            <v-col cols="1" align-self="center" style="text-align: center;" >
							 | 
						||
| 
								 | 
							
								              <h1>:</h1>
							 | 
						||
| 
								 | 
							
								            </v-col>
							 | 
						||
| 
								 | 
							
								            <v-col cols="3" align-self="center">
							 | 
						||
| 
								 | 
							
								              <v-text-field
							 | 
						||
| 
								 | 
							
								                :value="timerSeconds"
							 | 
						||
| 
								 | 
							
								                :min="0"
							 | 
						||
| 
								 | 
							
								                outlined
							 | 
						||
| 
								 | 
							
								                single-line
							 | 
						||
| 
								 | 
							
								                solo
							 | 
						||
| 
								 | 
							
								                hide-details
							 | 
						||
| 
								 | 
							
								                type="number"
							 | 
						||
| 
								 | 
							
								                :disabled="timerInitialized"
							 | 
						||
| 
								 | 
							
								                class="centered-input my-0 py-0"
							 | 
						||
| 
								 | 
							
								                style="font-size: large; width: 100px;"
							 | 
						||
| 
								 | 
							
								                @input="(v) => timerSeconds = v.toString().padStart(2, '0')"
							 | 
						||
| 
								 | 
							
								              />
							 | 
						||
| 
								 | 
							
								            </v-col>
							 | 
						||
| 
								 | 
							
								          </v-row>
							 | 
						||
| 
								 | 
							
								        </v-container>
							 | 
						||
| 
								 | 
							
								        <div class="mx-auto" style="width: 100%;">
							 | 
						||
| 
								 | 
							
								          <BaseButtonGroup
							 | 
						||
| 
								 | 
							
								            stretch
							 | 
						||
| 
								 | 
							
								            :buttons="timerButtons"
							 | 
						||
| 
								 | 
							
								            @initialize-timer="initializeTimer"
							 | 
						||
| 
								 | 
							
								            @pause-timer="pauseTimer"
							 | 
						||
| 
								 | 
							
								            @resume-timer="resumeTimer"
							 | 
						||
| 
								 | 
							
								            @stop-timer="resetTimer"
							 | 
						||
| 
								 | 
							
								          />
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								      </v-card>
							 | 
						||
| 
								 | 
							
								    </v-menu>
							 | 
						||
| 
								 | 
							
								  </div>
							 | 
						||
| 
								 | 
							
								</template>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<script lang="ts">
							 | 
						||
| 
								 | 
							
								import { computed, defineComponent, reactive, ref, toRefs, useContext, watch } from "@nuxtjs/composition-api";
							 | 
						||
| 
								 | 
							
								import { ButtonOption } from "~/components/global/BaseButtonGroup.vue";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// @ts-ignore typescript can't find our audio file, but it's there!
							 | 
						||
| 
								 | 
							
								import timerAlarmAudio from "~/assets/audio/kitchen_alarm.mp3";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export default defineComponent({
							 | 
						||
| 
								 | 
							
								  props: {
							 | 
						||
| 
								 | 
							
								    fab: {
							 | 
						||
| 
								 | 
							
								      type: Boolean,
							 | 
						||
| 
								 | 
							
								      default: false,
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    color: {
							 | 
						||
| 
								 | 
							
								      type: String,
							 | 
						||
| 
								 | 
							
								      default: "primary",
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								  },
							 | 
						||
| 
								 | 
							
								  setup() {
							 | 
						||
| 
								 | 
							
								    const { $globals, i18n } = useContext();
							 | 
						||
| 
								 | 
							
								    const state = reactive({
							 | 
						||
| 
								 | 
							
								      showMenu: false,
							 | 
						||
| 
								 | 
							
								      timerInitialized: false,
							 | 
						||
| 
								 | 
							
								      timerRunning: false,
							 | 
						||
| 
								 | 
							
								      timerEnded: false,
							 | 
						||
| 
								 | 
							
								      timerInitialValue: 0,
							 | 
						||
| 
								 | 
							
								      timerValue: 0,
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    watch(
							 | 
						||
| 
								 | 
							
								      () => state.showMenu,
							 | 
						||
| 
								 | 
							
								      () => {
							 | 
						||
| 
								 | 
							
								        if (state.showMenu && state.timerEnded) {
							 | 
						||
| 
								 | 
							
								          resetTimer();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // ts doesn't recognize timerAlarmAudio because it's a weird import
							 | 
						||
| 
								 | 
							
								    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
							 | 
						||
| 
								 | 
							
								    const timerAlarm = new Audio(timerAlarmAudio);
							 | 
						||
| 
								 | 
							
								    timerAlarm.loop = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const timerHours = ref<string | number>("00");
							 | 
						||
| 
								 | 
							
								    const timerMinutes = ref<string | number>("00");
							 | 
						||
| 
								 | 
							
								    const timerSeconds = ref<string | number>("00");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const initializeButton: ButtonOption = {
							 | 
						||
| 
								 | 
							
								      icon: $globals.icons.timerPlus,
							 | 
						||
| 
								 | 
							
								      text: i18n.tc("recipe.timer.start-timer"),
							 | 
						||
| 
								 | 
							
								      event: "initialize-timer",
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const pauseButton: ButtonOption = {
							 | 
						||
| 
								 | 
							
								      icon: $globals.icons.pause,
							 | 
						||
| 
								 | 
							
								      text: i18n.tc("recipe.timer.pause-timer"),
							 | 
						||
| 
								 | 
							
								      event: "pause-timer",
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const resumeButton: ButtonOption = {
							 | 
						||
| 
								 | 
							
								      icon: $globals.icons.play,
							 | 
						||
| 
								 | 
							
								      text: i18n.tc("recipe.timer.resume-timer"),
							 | 
						||
| 
								 | 
							
								      event: "resume-timer",
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const stopButton: ButtonOption = {
							 | 
						||
| 
								 | 
							
								      icon: $globals.icons.stop,
							 | 
						||
| 
								 | 
							
								      text: i18n.tc("recipe.timer.stop-timer"),
							 | 
						||
| 
								 | 
							
								      event: "stop-timer",
							 | 
						||
| 
								 | 
							
								      color: "red",
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const timerButtons = computed<ButtonOption[]>(() => {
							 | 
						||
| 
								 | 
							
								      const buttons: ButtonOption[] = [];
							 | 
						||
| 
								 | 
							
								      if (state.timerInitialized) {
							 | 
						||
| 
								 | 
							
								        if (state.timerEnded) {
							 | 
						||
| 
								 | 
							
								          buttons.push(stopButton);
							 | 
						||
| 
								 | 
							
								        } else if (state.timerRunning) {
							 | 
						||
| 
								 | 
							
								          buttons.push(pauseButton, stopButton);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          buttons.push(resumeButton, stopButton);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        buttons.push(initializeButton);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // I don't know why this is failing the frontend lint test ¯\_(ツ)_/¯
							 | 
						||
| 
								 | 
							
								      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
							 | 
						||
| 
								 | 
							
								      return buttons;
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const timerProgress = computed(() => {
							 | 
						||
| 
								 | 
							
								      if(state.timerInitialValue) {
							 | 
						||
| 
								 | 
							
								        return (state.timerValue / state.timerInitialValue) * 100;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        return 0;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let timerInterval: number | null = null;
							 | 
						||
| 
								 | 
							
								    function decrementTimer() {
							 | 
						||
| 
								 | 
							
								      if (state.timerValue > 0) {
							 | 
						||
| 
								 | 
							
								        state.timerValue -= 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        timerHours.value = Math.floor(state.timerValue / 3600).toString().padStart(2, "0");
							 | 
						||
| 
								 | 
							
								        timerMinutes.value = Math.floor(state.timerValue % 3600 / 60).toString().padStart(2, "0");
							 | 
						||
| 
								 | 
							
								        timerSeconds.value = Math.floor(state.timerValue % 3600 % 60).toString().padStart(2, "0");
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      else {
							 | 
						||
| 
								 | 
							
								        state.timerRunning = false;
							 | 
						||
| 
								 | 
							
								        state.timerEnded = true;
							 | 
						||
| 
								 | 
							
								        timerAlarm.currentTime = 0;
							 | 
						||
| 
								 | 
							
								        timerAlarm.play();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (timerInterval) {
							 | 
						||
| 
								 | 
							
								          clearInterval(timerInterval);
							 | 
						||
| 
								 | 
							
								          timerInterval = null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function initializeTimer() {
							 | 
						||
| 
								 | 
							
								      state.timerInitialized = true;
							 | 
						||
| 
								 | 
							
								      state.timerRunning = true;
							 | 
						||
| 
								 | 
							
								      state.timerEnded = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      console.log(timerSeconds.value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const hours = parseFloat(timerHours.value.toString()) > 0 ? parseFloat(timerHours.value.toString()) : 0;
							 | 
						||
| 
								 | 
							
								      const minutes = parseFloat(timerMinutes.value.toString()) > 0 ? parseFloat(timerMinutes.value.toString()) : 0;
							 | 
						||
| 
								 | 
							
								      const seconds = parseFloat(timerSeconds.value.toString()) > 0 ? parseFloat(timerSeconds.value.toString()) : 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      state.timerInitialValue = (hours * 3600) + (minutes * 60) + seconds;
							 | 
						||
| 
								 | 
							
								      state.timerValue = state.timerInitialValue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      timerInterval = setInterval(decrementTimer, 1000) as unknown as number;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      timerHours.value = Math.floor(state.timerValue / 3600).toString().padStart(2, "0");
							 | 
						||
| 
								 | 
							
								      timerMinutes.value = Math.floor(state.timerValue % 3600 / 60).toString().padStart(2, "0");
							 | 
						||
| 
								 | 
							
								      timerSeconds.value = Math.floor(state.timerValue % 3600 % 60).toString().padStart(2, "0");
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function pauseTimer() {
							 | 
						||
| 
								 | 
							
								      state.timerRunning = false;
							 | 
						||
| 
								 | 
							
								      if (timerInterval) {
							 | 
						||
| 
								 | 
							
								        clearInterval(timerInterval);
							 | 
						||
| 
								 | 
							
								        timerInterval = null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function resumeTimer() {
							 | 
						||
| 
								 | 
							
								      state.timerRunning = true;
							 | 
						||
| 
								 | 
							
								      timerInterval = setInterval(decrementTimer, 1000) as unknown as number;
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function resetTimer() {
							 | 
						||
| 
								 | 
							
								      state.timerInitialized = false;
							 | 
						||
| 
								 | 
							
								      state.timerRunning = false;
							 | 
						||
| 
								 | 
							
								      state.timerEnded = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      timerAlarm.pause();
							 | 
						||
| 
								 | 
							
								      timerAlarm.currentTime = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      timerHours.value = "00";
							 | 
						||
| 
								 | 
							
								      timerMinutes.value = "00";
							 | 
						||
| 
								 | 
							
								      timerSeconds.value = "00";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      state.timerValue = 0;
							 | 
						||
| 
								 | 
							
								      if (timerInterval) {
							 | 
						||
| 
								 | 
							
								        clearInterval(timerInterval);
							 | 
						||
| 
								 | 
							
								        timerInterval = null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return {
							 | 
						||
| 
								 | 
							
								      ...toRefs(state),
							 | 
						||
| 
								 | 
							
								      timerHours,
							 | 
						||
| 
								 | 
							
								      timerMinutes,
							 | 
						||
| 
								 | 
							
								      timerSeconds,
							 | 
						||
| 
								 | 
							
								      timerButtons,
							 | 
						||
| 
								 | 
							
								      timerProgress,
							 | 
						||
| 
								 | 
							
								      initializeTimer,
							 | 
						||
| 
								 | 
							
								      pauseTimer,
							 | 
						||
| 
								 | 
							
								      resumeTimer,
							 | 
						||
| 
								 | 
							
								      resetTimer,
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								  },
							 | 
						||
| 
								 | 
							
								});
							 | 
						||
| 
								 | 
							
								</script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<style scoped>
							 | 
						||
| 
								 | 
							
								    .centered-input >>> input {
							 | 
						||
| 
								 | 
							
								      text-align: center;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								</style>
							 |