1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

feat(frontend): Fractional Scaling

This commit is contained in:
hay-kot 2021-08-27 20:05:02 -08:00
parent d1a7ec3b95
commit 5ba337ab11
11 changed files with 306 additions and 126 deletions

View file

@ -27,7 +27,7 @@
</v-text-field>
</v-col>
<v-col v-if="!disableAmount && units" sm="12" md="3" cols="12">
<v-select
<v-autocomplete
v-model="value.unit"
hide-details
dense
@ -38,10 +38,13 @@
class="mx-1"
placeholder="Choose Unit"
>
</v-select>
<template #no-data>
<RecipeIngredientUnitDialog class="mx-2" block small />
</template>
</v-autocomplete>
</v-col>
<v-col v-if="!disableAmount && foods" m="12" md="3" cols="12" class="">
<v-select
<v-autocomplete
v-model="value.food"
hide-details
dense
@ -52,7 +55,10 @@
class="mx-1 py-0"
placeholder="Choose Food"
>
</v-select>
<template #no-data>
<RecipeIngredientFoodDialog class="mx-2" block small />
</template>
</v-autocomplete>
</v-col>
<v-col sm="12" md="" cols="12">
<v-text-field v-model="value.note" hide-details dense solo class="mx-1" placeholder="Notes">
@ -81,10 +87,14 @@
<script lang="ts">
import { defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
import RecipeIngredientUnitDialog from "./RecipeIngredientUnitDialog.vue";
import RecipeIngredientFoodDialog from "./RecipeIngredientFoodDialog.vue";
import { useFoods } from "~/composables/use-recipe-foods";
import { useUnits } from "~/composables/use-recipe-units";
import { validators } from "~/composables/use-validators";
export default defineComponent({
components: { RecipeIngredientUnitDialog, RecipeIngredientFoodDialog },
props: {
value: {
type: Object,
@ -99,7 +109,7 @@ export default defineComponent({
const { value } = props;
const { foods } = useFoods();
const { units } = useUnits();
const { units, workingUnitData, actions: unitActions } = useUnits();
const state = reactive({
showTitle: false,
@ -115,8 +125,14 @@ export default defineComponent({
}
}
return { foods, units, ...toRefs(state), toggleTitle };
return { workingUnitData, unitActions, validators, foods, units, ...toRefs(state), toggleTitle };
},
});
</script>
<style >
.v-input__append-outer {
margin: 0 !important;
padding: 0 !important;
}
</style>

View file

@ -0,0 +1,38 @@
<template>
<BaseDialog
title="Create Food"
:icon="$globals.icons.foods"
:keep-open="!validForm"
@submit="actions.createOne(domCreateFoodForm)"
>
<v-card-text>
<v-form ref="domCreateFoodForm">
<v-text-field v-model="workingFoodData.name" label="Name" :rules="[validators.required]"></v-text-field>
<v-text-field v-model="workingFoodData.description" label="Description"></v-text-field>
</v-form>
</v-card-text>
<template #activator="{ open }">
<BaseButton
v-bind="$attrs"
@click="
actions.resetWorking();
open();
"
></BaseButton>
</template>
</BaseDialog>
</template>
<script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api";
import { useFoods } from "~/composables/use-recipe-foods";
import { validators } from "~/composables/use-validators";
export default defineComponent({
setup() {
const domCreateFoodForm = ref(null);
const { workingFoodData, actions, validForm } = useFoods();
return { validators, workingFoodData, actions, domCreateFoodForm, validForm };
},
});
</script>

View file

@ -0,0 +1,40 @@
<template>
<BaseDialog
title="Create Unit"
:icon="$globals.icons.units"
:keep-open="!validForm"
@submit="actions.createOne(domCreateUnitForm)"
>
<v-card-text>
<v-form ref="domCreateUnitForm">
<v-text-field v-model="workingUnitData.name" label="Name" :rules="[validators.required]"></v-text-field>
<v-text-field v-model="workingUnitData.abbreviation" label="Abbreviation"></v-text-field>
<v-text-field v-model="workingUnitData.description" label="Description"></v-text-field>
<v-switch v-model="workingUnitData.fraction" label="Display as Fraction"></v-switch>
</v-form>
</v-card-text>
<template #activator="{ open }">
<BaseButton
v-bind="$attrs"
@click="
actions.resetWorking();
open();
"
></BaseButton>
</template>
</BaseDialog>
</template>
<script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api";
import { useUnits } from "~/composables/use-recipe-units";
import { validators } from "~/composables/use-validators";
export default defineComponent({
setup() {
const domCreateUnitForm = ref(null);
const { workingUnitData, actions, validForm } = useUnits();
return { validators, workingUnitData, actions, validForm, domCreateUnitForm };
},
});
</script>

View file

@ -1,68 +1,15 @@
<template>
<div v-if="edit || (value && value.length > 0)">
<div v-if="value && value.length > 0">
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
<div v-if="edit">
<draggable :value="value" handle=".handle" @input="updateIndex" @start="drag = true" @end="drag = false">
<transition-group type="transition" :name="!drag ? 'flip-list' : null">
<div v-for="(ingredient, index) in value" :key="generateKey('ingredient', index)">
<v-row align="center">
<v-text-field
v-if="edit && showTitleEditor[index]"
v-model="value[index].title"
class="mx-3 mt-3"
dense
:label="$t('recipe.section-title')"
>
</v-text-field>
<v-textarea
v-model="value[index].note"
class="mr-2"
:label="$t('recipe.ingredient')"
auto-grow
solo
dense
rows="1"
>
<template slot="append">
<v-tooltip right nudge-right="10">
<template #activator="{ on, attrs }">
<v-btn icon small class="mt-n1" v-bind="attrs" v-on="on" @click="toggleShowTitle(index)">
<v-icon>{{ showTitleEditor[index] ? $globals.icons.minus : $globals.icons.createAlt }}</v-icon>
</v-btn>
</template>
<span>{{
showTitleEditor[index] ? $t("recipe.remove-section") : $t("recipe.insert-section")
}}</span>
</v-tooltip>
</template>
<template slot="append-outer">
<v-icon class="handle">{{ $globals.icons.arrowUpDown }}</v-icon>
</template>
<v-icon slot="prepend" class="mr-n1" color="error" @click="removeByIndex(value, index)">
{{ $globals.icons.delete }}
</v-icon>
</v-textarea>
</v-row>
</div>
</transition-group>
</draggable>
<div class="d-flex row justify-end">
<RecipeDialogBulkAdd class="mr-2" @bulk-data="addIngredient" />
<v-btn color="secondary" dark class="mr-4" @click="addIngredient">
<v-icon>{{ $globals.icons.create }}</v-icon>
</v-btn>
</div>
</div>
<div v-else>
<div>
<div v-for="(ingredient, index) in value" :key="generateKey('ingredient', index)">
<h3 v-if="showTitleEditor[index]" class="mt-2">{{ ingredient.title }}</h3>
<v-divider v-if="showTitleEditor[index]"></v-divider>
<v-list-item dense @click="toggleChecked(index)">
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox>
<v-list-item-content>
<VueMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredient.note"> </VueMarkdown>
<VueMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="parseIngredientText(ingredient)">
</VueMarkdown>
</v-list-item-content>
</v-list-item>
</div>
@ -72,13 +19,10 @@
<script>
import VueMarkdown from "@adapttive/vue-markdown";
import draggable from "vuedraggable";
import { useFraction } from "@/composables/use-fraction";
import { utils } from "@/utils";
import RecipeDialogBulkAdd from "./RecipeDialogBulkAdd";
export default {
components: {
RecipeDialogBulkAdd,
draggable,
VueMarkdown,
},
props: {
@ -86,11 +30,42 @@ export default {
type: Array,
default: () => [],
},
edit: {
disableAmount: {
type: Boolean,
default: false,
},
scale: {
type: Number,
default: 1,
},
},
setup(props) {
const { frac } = useFraction();
function parseIngredientText(ingredient) {
if (props.disableAmount) {
return ingredient.note;
}
const { quantity, food, unit, note } = ingredient;
let return_qty = "";
if (unit?.fraction) {
const fraction = frac(quantity * props.scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
return_qty += fraction[0];
}
if (fraction[1] > 0) {
return_qty += ` <sup>${fraction[1]}</sup>&frasl;<sub>${fraction[2]}</sub>`;
}
} else {
return_qty = quantity;
}
return `${return_qty} ${unit?.name || " "} ${food?.name || " "} ${note}`;
}
return { parseIngredientText };
},
data() {
return {
@ -111,42 +86,14 @@ export default {
this.showTitleEditor = this.value.map((x) => this.validateTitle(x.title));
},
methods: {
addIngredient(ingredients = null) {
if (ingredients.length) {
const newIngredients = ingredients.map((x) => {
return {
title: null,
note: x,
unit: null,
food: null,
disableAmount: true,
quantity: 1,
};
});
this.value.push(...newIngredients);
} else {
this.value.push({
title: null,
note: "",
unit: null,
food: null,
disableAmount: true,
quantity: 1,
});
}
},
generateKey(item, index) {
return utils.generateUniqueKey(item, index);
},
updateIndex(data) {
this.$emit("input", data);
},
toggleChecked(index) {
this.$set(this.checked, index, !this.checked[index]);
},
removeByIndex(list, index) {
list.splice(index, 1);
},
validateTitle(title) {
return !(title === null || title === "");
},