mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
feat: cook timer (#2508)
* updated base button group * added kitchen timer * added missing icon * usability tweaks * for for menu rendering over app bar * clean up types * fix for mp3 loading, maybe? * spooky linter fixes * for real this time --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
parent
5c5432304f
commit
693608d59f
BIN
frontend/assets/audio/kitchen_alarm.mp3
Normal file
BIN
frontend/assets/audio/kitchen_alarm.mp3
Normal file
Binary file not shown.
@ -42,6 +42,12 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<RecipeTimerMenu
|
||||||
|
fab
|
||||||
|
color="info"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
|
||||||
<RecipeContextMenu
|
<RecipeContextMenu
|
||||||
show-print
|
show-print
|
||||||
:menu-top="false"
|
:menu-top="false"
|
||||||
@ -90,6 +96,7 @@
|
|||||||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
|
import RecipeTimerMenu from "./RecipeTimerMenu.vue";
|
||||||
import RecipeTimelineBadge from "./RecipeTimelineBadge.vue";
|
import RecipeTimelineBadge from "./RecipeTimelineBadge.vue";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
@ -100,7 +107,7 @@ const JSON_EVENT = "json";
|
|||||||
const OCR_EVENT = "ocr";
|
const OCR_EVENT = "ocr";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeContextMenu, RecipeFavoriteBadge, RecipeTimelineBadge },
|
components: { RecipeContextMenu, RecipeFavoriteBadge, RecipeTimerMenu, RecipeTimelineBadge },
|
||||||
props: {
|
props: {
|
||||||
recipe: {
|
recipe: {
|
||||||
required: true,
|
required: true,
|
||||||
|
317
frontend/components/Domain/Recipe/RecipeTimerMenu.vue
Normal file
317
frontend/components/Domain/Recipe/RecipeTimerMenu.vue
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<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>
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-item-group>
|
<v-item-group>
|
||||||
<template v-for="btn in buttons">
|
<template v-for="btn in buttons">
|
||||||
<v-menu v-if="btn.children" :key="'menu-' + btn.event" active-class="pa-0" offset-y top left>
|
<v-menu v-if="btn.children" :key="'menu-' + btn.event" active-class="pa-0" offset-y top left :style="stretch ? 'width: 100%;' : ''">
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn tile :large="large" icon v-bind="attrs" v-on="on">
|
<v-btn tile :large="large" icon v-bind="attrs" v-on="on">
|
||||||
<v-icon>
|
<v-icon>
|
||||||
@ -25,7 +25,17 @@
|
|||||||
content-class="text-caption"
|
content-class="text-caption"
|
||||||
>
|
>
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn tile :large="large" icon v-bind="attrs" @click="$emit(btn.event)" v-on="on" :disabled="btn.disabled">
|
<v-btn
|
||||||
|
tile
|
||||||
|
icon
|
||||||
|
:color="btn.color"
|
||||||
|
:large="large"
|
||||||
|
:disabled="btn.disabled"
|
||||||
|
:style="stretch ? `width: ${maxButtonWidth};` : ''"
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="on"
|
||||||
|
@click="$emit(btn.event)"
|
||||||
|
>
|
||||||
<v-icon> {{ btn.icon }} </v-icon>
|
<v-icon> {{ btn.icon }} </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -36,10 +46,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
export interface ButtonOption {
|
export interface ButtonOption {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
color?: string;
|
||||||
text: string;
|
text: string;
|
||||||
event: string;
|
event: string;
|
||||||
children?: ButtonOption[];
|
children?: ButtonOption[];
|
||||||
@ -56,6 +67,16 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
stretch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
setup(props) {
|
||||||
|
const maxButtonWidth = computed(() => `${100 / props.buttons.length}%`);
|
||||||
|
return {
|
||||||
|
maxButtonWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -480,6 +480,13 @@
|
|||||||
"increase-scale-label": "Increase Scale by 1",
|
"increase-scale-label": "Increase Scale by 1",
|
||||||
"locked": "Locked",
|
"locked": "Locked",
|
||||||
"public-link": "Public Link",
|
"public-link": "Public Link",
|
||||||
|
"timer": {
|
||||||
|
"kitchen-timer": "Kitchen Timer",
|
||||||
|
"start-timer": "Start Timer",
|
||||||
|
"pause-timer": "Pause Timer",
|
||||||
|
"resume-timer": "Resume Timer",
|
||||||
|
"stop-timer": "Stop Timer"
|
||||||
|
},
|
||||||
"edit-timeline-event": "Edit Timeline Event",
|
"edit-timeline-event": "Edit Timeline Event",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
||||||
|
@ -135,6 +135,12 @@ import {
|
|||||||
mdiDockTop,
|
mdiDockTop,
|
||||||
mdiDockBottom,
|
mdiDockBottom,
|
||||||
mdiCheckboxOutline,
|
mdiCheckboxOutline,
|
||||||
|
mdiTimer,
|
||||||
|
mdiTimerPlus,
|
||||||
|
mdiPause,
|
||||||
|
mdiStop,
|
||||||
|
mdiPlay,
|
||||||
|
mdiTimerPause,
|
||||||
mdiFlipHorizontal,
|
mdiFlipHorizontal,
|
||||||
mdiFlipVertical,
|
mdiFlipVertical,
|
||||||
mdiRotateLeft,
|
mdiRotateLeft,
|
||||||
@ -228,6 +234,8 @@ export const icons = {
|
|||||||
openInNew: mdiOpenInNew,
|
openInNew: mdiOpenInNew,
|
||||||
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
|
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
|
||||||
pageLayoutBody: mdiPageLayoutBody,
|
pageLayoutBody: mdiPageLayoutBody,
|
||||||
|
pause: mdiPause,
|
||||||
|
play: mdiPlay,
|
||||||
printer: mdiPrinter,
|
printer: mdiPrinter,
|
||||||
printerSettings: mdiPrinterPosCog,
|
printerSettings: mdiPrinterPosCog,
|
||||||
refreshCircle: mdiRefreshCircle,
|
refreshCircle: mdiRefreshCircle,
|
||||||
@ -247,8 +255,12 @@ export const icons = {
|
|||||||
sortClockAscending: mdiSortClockAscending,
|
sortClockAscending: mdiSortClockAscending,
|
||||||
sortClockDescending: mdiSortClockDescending,
|
sortClockDescending: mdiSortClockDescending,
|
||||||
star: mdiStar,
|
star: mdiStar,
|
||||||
|
stop: mdiStop,
|
||||||
testTube: mdiTestTube,
|
testTube: mdiTestTube,
|
||||||
timelineText: mdiTimelineText,
|
timelineText: mdiTimelineText,
|
||||||
|
timer: mdiTimer,
|
||||||
|
timerPause: mdiTimerPause,
|
||||||
|
timerPlus: mdiTimerPlus,
|
||||||
tools: mdiTools,
|
tools: mdiTools,
|
||||||
potSteam: mdiPotSteam,
|
potSteam: mdiPotSteam,
|
||||||
translate: mdiTranslate,
|
translate: mdiTranslate,
|
||||||
|
@ -385,6 +385,17 @@ export default {
|
|||||||
// ["@nuxtjs/composition-api/dist/babel-plugin"],
|
// ["@nuxtjs/composition-api/dist/babel-plugin"],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// audio file support
|
||||||
|
// https://v2.nuxt.com/docs/features/configuration/#extend-webpack-to-load-audio-files
|
||||||
|
extend(config, ctx) {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.(ogg|mp3|wav|mpe?g)$/i,
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[path][name].[ext]'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
transpile: process.env.NODE_ENV !== "production" ? [/@vue[\\/]composition-api/] : null,
|
transpile: process.env.NODE_ENV !== "production" ? [/@vue[\\/]composition-api/] : null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user