Refactor/composables-folder (#787)

* move api clients and rename

* organize recipes composables

* rewrite useRecipeContext

* refactor(frontend): ♻️ abstract common ingredient functionality.

* feat(frontend):  add scale, and back to recipe button + hide ingredients if none

* update regex to mach 11. instead of just 1.

* minor UX improvements

Co-authored-by: Hayden K <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-11-06 11:28:47 -08:00 committed by GitHub
parent 095d3bda3f
commit 788e176b16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 330 additions and 245 deletions

View file

@ -71,7 +71,7 @@
</template>
<script>
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default {
props: {
slug: {
@ -88,7 +88,7 @@ export default {
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -18,8 +18,7 @@
</template>
<script>
import { useStaticRoutes } from "~/composables/api";
import { useApiSingleton } from "~/composables/use-api";
import { useStaticRoutes, useUserApi } from "~/composables/api";
export default {
props: {
tiny: {
@ -52,7 +51,7 @@ export default {
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
const { recipeImage, recipeSmallImage, recipeTinyImage } = useStaticRoutes();

View file

@ -67,7 +67,7 @@
import { defineComponent } from "@nuxtjs/composition-api";
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
import RecipeContextMenu from "./RecipeContextMenu";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default defineComponent({
components: {
RecipeFavoriteBadge,
@ -104,7 +104,7 @@ export default defineComponent({
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -103,7 +103,7 @@
<script>
import RecipeCard from "./RecipeCard";
import RecipeCardMobile from "./RecipeCardMobile";
import { useSorter } from "~/composables/use-recipes";
import { useSorter } from "~/composables/recipes";
const SORT_EVENT = "sort";
export default {

View file

@ -36,7 +36,7 @@
<script>
import { defineComponent } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
const CREATED_ITEM_EVENT = "created-item";
export default defineComponent({
props: {
@ -58,7 +58,7 @@ export default defineComponent({
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -44,8 +44,8 @@
<script>
import RecipeCategoryTagDialog from "./RecipeCategoryTagDialog";
import { useApiSingleton } from "~/composables/use-api";
import { useTags, useCategories } from "~/composables/use-tags-categories";
import { useUserApi } from "~/composables/api";
import { useTags, useCategories } from "~/composables/recipes";
const MOUNTED_EVENT = "mounted";
export default {
components: {
@ -91,7 +91,7 @@ export default {
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
const { allTags, useAsyncGetAll: getAllTags } = useTags();
const { allCategories, useAsyncGetAll: getAllCategories } = useCategories();

View file

@ -50,7 +50,7 @@
</template>
<script>
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
const NEW_COMMENT_EVENT = "new-comment";
const UPDATE_COMMENT_EVENT = "update-comment";
export default {
@ -65,7 +65,7 @@ export default {
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -75,7 +75,7 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
import { useClipboard, useShare } from "@vueuse/core";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
export interface ContextMenuIncludes {
@ -147,7 +147,7 @@ export default defineComponent({
},
},
setup(props, context) {
const api = useApiSingleton();
const api = useUserApi();
const state = reactive({
loading: false,

View file

@ -39,7 +39,7 @@
import { computed, defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import RecipeChip from "./RecipeChips.vue";
import { Recipe } from "~/types/api-types/recipe";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { UserOut } from "~/types/api-types/user";
const INPUT_EVENT = "input";
@ -114,7 +114,7 @@ export default defineComponent({
// ============
// Group Members
const api = useApiSingleton();
const api = useUserApi();
const members = ref<UserOut[] | null[]>([]);
async function refreshMembers() {

View file

@ -82,7 +82,7 @@ export default defineComponent({
.join("\n");
}
const numberedLineRegex = /\d[.):] /gm;
const numberedLineRegex = /\d+[.):] /gm;
function splitByNumberedLine() {
// Split inputText by numberedLineRegex

View file

@ -22,7 +22,7 @@
<script>
import { defineComponent } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default defineComponent({
props: {
slug: {
@ -39,7 +39,7 @@ export default defineComponent({
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -41,7 +41,7 @@
<script>
import { defineComponent } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
const REFRESH_EVENT = "refresh";
const UPLOAD_EVENT = "upload";
export default defineComponent({
@ -52,7 +52,7 @@ export default defineComponent({
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -95,8 +95,7 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api";
import { useFoods } from "~/composables/use-recipe-foods";
import { useUnits } from "~/composables/use-recipe-units";
import { useFoods, useUnits } from "~/composables/recipes";
import { validators } from "~/composables/use-validators";
export default defineComponent({

View file

@ -2,7 +2,7 @@
<div v-if="value && value.length > 0">
<div class="d-flex justify-start">
<h2 class="mb-4 mt-1">{{ $t("recipe.ingredients") }}</h2>
<AppButtonCopy btn-class="ml-auto" :copy-text="ingredientCopyText" />
<AppButtonCopy btn-class="ml-auto" :copy-text="ingredientCopyText" />
</div>
<div>
<div v-for="(ingredient, index) in value" :key="'ingredient' + index">
@ -11,7 +11,10 @@
<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="parseIngredientText(ingredient)">
<VueMarkdown
class="ma-0 pa-0 text-subtitle-1 dense-markdown"
:source="parseIngredientText(ingredient, disableAmount, scale)"
>
</VueMarkdown>
</v-list-item-content>
</v-list-item>
@ -23,7 +26,7 @@
<script>
import { computed, defineComponent } from "@nuxtjs/composition-api";
import VueMarkdown from "@adapttive/vue-markdown";
import { useFraction } from "@/composables/use-fraction";
import { parseIngredientText } from "~/composables/recipes";
export default defineComponent({
components: {
VueMarkdown,
@ -43,37 +46,10 @@ export default defineComponent({
},
},
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 * props.scale;
}
return `${return_qty} ${unit?.name || " "} ${food?.name || " "} ${note}`;
}
const ingredientCopyText = computed(() => {
// Returns a string of all ingredients in markdown list format -[ ]
return props.value
.map((ingredient) => {
return `- [ ] ${parseIngredientText(ingredient)}`;
return `- [ ] ${parseIngredientText(ingredient, props.disableAmount, props.scale)}`;
})
.join("\n");
});

View file

@ -17,13 +17,31 @@
</p>
<v-divider class="mb-4"></v-divider>
<v-checkbox
v-for="ing in ingredients"
v-for="ing in unusedIngredients"
:key="ing.referenceId"
v-model="activeRefs"
:label="ing.note"
:value="ing.referenceId"
class="mb-n2 mt-n2"
></v-checkbox>
>
<template #label>
<div v-html="parseIngredientText(ing, disableAmount)"></div>
</template>
</v-checkbox>
<template v-if="usedIngredients.length > 0">
<h4 class="py-3 ml-1">Linked to other step</h4>
<v-checkbox
v-for="ing in usedIngredients"
:key="ing.referenceId"
v-model="activeRefs"
:value="ing.referenceId"
class="mb-n2 mt-n2"
>
<template #label>
<div v-html="parseIngredientText(ing, disableAmount)"></div>
</template>
</v-checkbox>
</template>
</v-card-text>
<v-divider></v-divider>
@ -111,17 +129,16 @@
<v-card-text v-if="edit">
<v-textarea :key="'instructions' + index" v-model="value[index]['text']" auto-grow dense rows="4">
</v-textarea>
<div v-for="ing in step.ingredientReferences" :key="ing.referenceId">
{{ getIngredientByRefId(ing.referenceId).note }}
</div>
<div
v-for="ing in step.ingredientReferences"
:key="ing.referenceId"
v-html="getIngredientByRefId(ing.referenceId)"
/>
</v-card-text>
<v-expand-transition>
<div v-show="!isChecked(index) && !edit" class="m-0 p-0">
<v-card-text>
<VueMarkdown :source="step.text"> </VueMarkdown>
<div v-for="ing in step.ingredientReferences" :key="ing.referenceId">
{{ getIngredientByRefId(ing.referenceId).note }}
</div>
</v-card-text>
</div>
</v-expand-transition>
@ -138,6 +155,7 @@ import draggable from "vuedraggable";
import VueMarkdown from "@adapttive/vue-markdown";
import { ref, toRefs, reactive, defineComponent, watch, onMounted } from "@nuxtjs/composition-api";
import { RecipeStep, IngredientToStepRef, RecipeIngredient } from "~/types/api-types/recipe";
import { parseIngredientText } from "~/composables/recipes";
interface MergerHistory {
target: number;
@ -164,12 +182,18 @@ export default defineComponent({
type: Array as () => RecipeIngredient[],
default: () => [],
},
disableAmount: {
type: Boolean,
default: false,
},
},
setup(props, context) {
const state = reactive({
dialog: false,
disabledSteps: [] as number[],
unusedIngredients: [] as RecipeIngredient[],
usedIngredients: [] as RecipeIngredient[],
});
const showTitleEditor = ref<boolean[]>([]);
@ -245,6 +269,7 @@ export default defineComponent({
const activeText = ref("");
function openDialog(idx: number, refs: IngredientToStepRef[], text: string) {
setUsedIngredients();
activeText.value = text;
activeIndex.value = idx;
state.dialog = true;
@ -261,6 +286,24 @@ export default defineComponent({
state.dialog = false;
}
function setUsedIngredients() {
const usedRefs: { [key: string]: boolean } = {};
props.value.forEach((element) => {
element.ingredientReferences.forEach((ref) => {
usedRefs[ref.referenceId] = true;
});
});
state.usedIngredients = props.ingredients.filter((ing) => {
return ing.referenceId in usedRefs;
});
state.unusedIngredients = props.ingredients.filter((ing) => {
return !(ing.referenceId in usedRefs);
});
}
function autoSetReferences() {
// Ingore matching blacklisted words when auto-linking - This is kind of a cludgey implementation. We're blacklisting common words but
// other common phrases trigger false positives and I'm not sure how else to approach this. In the future I maybe look at looking directly
@ -294,10 +337,9 @@ export default defineComponent({
}
props.ingredients.forEach((ingredient) => {
if (
ingredient.note.toLowerCase().includes(" " + word) &&
!activeRefs.value.includes(ingredient.referenceId)
) {
const searchText = parseIngredientText(ingredient, props.disableAmount);
if (searchText.toLowerCase().includes(" " + word) && !activeRefs.value.includes(ingredient.referenceId)) {
console.info("Word Matched", `'${word}'`, ingredient.note);
activeRefs.value.push(ingredient.referenceId);
}
@ -306,7 +348,11 @@ export default defineComponent({
}
function getIngredientByRefId(refId: String) {
return props.ingredients.find((ing) => ing.referenceId === refId) || "";
const ing = props.ingredients.find((ing) => ing.referenceId === refId) || "";
if (ing === "") {
return "";
}
return parseIngredientText(ing, props.disableAmount);
}
// ===============================================================
@ -365,6 +411,7 @@ export default defineComponent({
toggleShowTitle,
updateIndex,
autoSetReferences,
parseIngredientText,
};
},
});

View file

@ -18,7 +18,7 @@
<script>
import { defineComponent } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default defineComponent({
props: {
emitOnly: {
@ -43,7 +43,7 @@ export default defineComponent({
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -140,7 +140,7 @@
// import AppButtonUpload from "@/components/UI/Buttons/AppButtonUpload.vue";
import { defineComponent, ref } from "@nuxtjs/composition-api";
import { fieldTypes } from "~/composables/forms";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators";
export default defineComponent({
@ -160,7 +160,7 @@ export default defineComponent({
const domImportFromUrlDialog = ref(null);
const domImportFromUrlForm = ref<VForm | null>(null);
const api = useApiSingleton();
const api = useUserApi();
return {
domCreateDialog,

View file

@ -68,8 +68,8 @@ export default {
this.show = true;
const copyText = this.copyText;
navigator.clipboard.writeText(copyText).then(
() => console.log("Copied", copyText),
() => console.log("Copied Failed", copyText)
() => console.log(`Copied\n${copyText}`),
() => console.log(`Copied Failed\n${copyText}`)
);
setTimeout(() => {
this.toggleBlur();

View file

@ -11,7 +11,7 @@
</template>
<script>
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
const UPLOAD_EVENT = "uploaded";
export default {
props: {
@ -45,7 +45,7 @@ export default {
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -29,7 +29,7 @@
</template>
<script>
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default {
name: "BaseButton",
props: {
@ -107,7 +107,7 @@ export default {
},
},
setup() {
const api = useApiSingleton();
const api = useUserApi();
return { api };
},

View file

@ -59,7 +59,7 @@ export const useAdminApi = function (): AdminAPI {
return new AdminAPI(requests);
};
export const useApiSingleton = function (): Api {
export const useUserApi = function (): Api {
const { $axios } = useContext();
const requests = getRequests($axios);

View file

@ -1 +1,2 @@
export { useStaticRoutes } from "./static-routes";
export { useAdminApi, useUserApi } from "./api-client";

View file

@ -0,0 +1,7 @@
export { useFraction } from "./use-fraction";
export { useRecipe } from "./use-recipe";
export { useFoods } from "./use-recipe-foods";
export { useUnits } from "./use-recipe-units";
export { useRecipes, recentRecipes, allRecipes, useLazyRecipes, useSorter } from "./use-recipes";
export { useTags, useCategories, allCategories, allTags } from "./use-tags-categories";
export { parseIngredientText } from "./use-recipe-ingredients";

View file

@ -1,12 +1,12 @@
import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useAsyncKey } from "../use-utils";
import { useUserApi } from "~/composables/api";
import { Food } from "~/api/class-interfaces/recipe-foods";
let foodStore: Ref<Food[] | null> | null = null;
export const useFoods = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
const deleteTargetId = ref(0);
const validForm = ref(true);

View file

@ -0,0 +1,28 @@
import { useFraction } from "./use-fraction";
import { RecipeIngredient } from "~/types/api-types/recipe";
const { frac } = useFraction();
export function parseIngredientText(ingredient: RecipeIngredient, disableAmount: boolean, scale: number = 1): string {
if (disableAmount) {
return ingredient.note;
}
const { quantity, food, unit, note } = ingredient;
let returnQty = "";
if (unit?.fraction) {
const fraction = frac(quantity * scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
returnQty += fraction[0];
}
if (fraction[1] > 0) {
returnQty += ` <sup>${fraction[1]}</sup>&frasl;<sub>${fraction[2]}</sub>`;
}
} else {
returnQty = (quantity * scale).toString();
}
return `${returnQty} ${unit?.name || " "} ${food?.name || " "} ${note}`.replace(/ {2,}/g, " ");
}

View file

@ -1,12 +1,12 @@
import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useAsyncKey } from "../use-utils";
import { useUserApi } from "~/composables/api";
import { Unit } from "~/api/class-interfaces/recipe-units";
let unitStore: Ref<Unit[] | null> | null = null;
export const useUnits = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
const deleteTargetId = ref(0);
const validForm = ref(true);

View file

@ -0,0 +1,47 @@
import { ref, onMounted } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { Recipe } from "~/types/api-types/recipe";
export const useRecipe = function (slug: string, eager: boolean = true) {
const api = useUserApi();
const loading = ref(false);
const recipe = ref<Recipe | null>(null);
async function fetchRecipe() {
loading.value = true;
const { data } = await api.recipes.getOne(slug);
loading.value = false;
if (data) {
recipe.value = data;
}
}
async function deleteRecipe() {
loading.value = true;
const { data } = await api.recipes.deleteOne(slug);
loading.value = false;
return data;
}
async function updateRecipe(recipe: Recipe) {
loading.value = true;
const { data } = await api.recipes.updateOne(slug, recipe);
loading.value = false;
return data;
}
onMounted(() => {
if (eager) {
fetchRecipe();
}
});
return {
recipe,
loading,
fetchRecipe,
deleteRecipe,
updateRecipe,
};
};

View file

@ -1,7 +1,7 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { set } from "@vueuse/core";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useAsyncKey } from "../use-utils";
import { useUserApi } from "~/composables/api";
import { Recipe } from "~/types/api-types/recipe";
export const allRecipes = ref<Recipe[] | null>([]);
@ -58,7 +58,7 @@ export const useSorter = () => {
};
export const useLazyRecipes = function () {
const api = useApiSingleton();
const api = useUserApi();
const recipes = ref<Recipe[] | null>([]);
@ -78,7 +78,7 @@ export const useLazyRecipes = function () {
};
export const useRecipes = (all = false, fetchRecipes = true) => {
const api = useApiSingleton();
const api = useUserApi();
// recipes is non-reactive!!
const { recipes, start, end } = (() => {

View file

@ -1,6 +1,6 @@
import { Ref, ref, useAsync } from "@nuxtjs/composition-api";
import { useApiSingleton } from "./use-api";
import { useAsyncKey } from "./use-utils";
import { useUserApi } from "../api";
import { useAsyncKey } from "../use-utils";
import { CategoriesAPI, Category } from "~/api/class-interfaces/categories";
import { Tag, TagsAPI } from "~/api/class-interfaces/tags";
@ -45,14 +45,14 @@ function baseTagsCategories(reference: Ref<Category[] | null> | Ref<Tag[] | null
}
export const useTags = function () {
const api = useApiSingleton();
const api = useUserApi();
return {
allTags,
...baseTagsCategories(allTags, api.tags),
};
};
export const useCategories = function () {
const api = useApiSingleton();
const api = useUserApi();
return {
allCategories,
...baseTagsCategories(allCategories, api.categories),

View file

@ -2,7 +2,7 @@ import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
import { set } from "@vueuse/core";
import { toastLoading, loader } from "./use-toast";
import { AllBackups, ImportBackup } from "~/api/class-interfaces/backups";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
const backups = ref<AllBackups>({
imports: [],
@ -32,7 +32,7 @@ function optionsFactory() {
}
export const useBackups = function (fetch = true) {
const api = useApiSingleton();
const api = useUserApi();
const backupOptions = reactive(optionsFactory());
const deleteTarget = ref("");

View file

@ -1,13 +1,13 @@
import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { CookBook } from "~/api/class-interfaces/group-cookbooks";
let cookbookStore: Ref<CookBook[] | null> | null = null;
export const useCookbook = function () {
function getOne(id: string | number) {
const api = useApiSingleton();
const api = useUserApi();
const units = useAsync(async () => {
const { data } = await api.cookbooks.getOne(id);
@ -22,7 +22,7 @@ export const useCookbook = function () {
};
export const useCookbooks = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
const deleteTargetId = ref(0);
const validForm = ref(true);

View file

@ -1,11 +1,11 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { addDays, subDays, format } from "date-fns";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { CreateMealPlan, UpdateMealPlan } from "~/api/class-interfaces/group-mealplan";
export const useMealplans = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
const validForm = ref(true);

View file

@ -1,10 +1,10 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { GroupWebhook } from "~/api/class-interfaces/group-webhooks";
export const useGroupWebhooks = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
const validForm = ref(true);

View file

@ -1,10 +1,10 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { CreateGroup } from "~/api/class-interfaces/groups";
export const useGroupSelf = function () {
const api = useApiSingleton();
const api = useUserApi();
const actions = {
get() {
@ -35,7 +35,7 @@ export const useGroupSelf = function () {
};
export const useGroupCategories = function () {
const api = useApiSingleton();
const api = useUserApi();
const actions = {
getAll() {
@ -61,7 +61,7 @@ export const useGroupCategories = function () {
};
export const useGroups = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
function getAllGroups() {

View file

@ -1,13 +1,12 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { CreateEventNotification } from "@/api/class-interfaces/event-notifications";
import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { CreateEventNotification } from "@/api/class-interfaces/event-notifications";
import { useUserApi } from "~/composables/api";
const notificationTypes = ["General", "Discord", "Gotify", "Pushover", "Home Assistant"];
export const useNotifications = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
const createNotificationData = ref<CreateEventNotification>({
@ -23,7 +22,7 @@ export const useNotifications = function () {
notificationUrl: "",
});
const deleteTarget = ref(0)
const deleteTarget = ref(0);
function getNotifications() {
loading.value = true;
@ -63,13 +62,13 @@ export const useNotifications = function () {
}
async function testById(id: number) {
const {data} = await api.notifications.testNotification(id, null)
console.log(data)
const { data } = await api.notifications.testNotification(id, null);
console.log(data);
}
async function testByUrl(testUrl: string) {
const {data} = await api.notifications.testNotification(null, testUrl)
console.log(data)
const { data } = await api.notifications.testNotification(null, testUrl);
console.log(data);
}
const notifications = getNotifications();

View file

@ -1,43 +0,0 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { Recipe } from "~/types/api-types/recipe";
export const useRecipeContext = function () {
const api = useApiSingleton();
const loading = ref(false);
function getBySlug(slug: string) {
loading.value = true;
const recipe = useAsync(async () => {
const { data } = await api.recipes.getOne(slug);
return data;
}, slug);
loading.value = false;
return recipe;
}
async function fetchRecipe(slug: string) {
loading.value = true;
const { data } = await api.recipes.getOne(slug);
loading.value = false;
return data;
}
async function deleteRecipe(slug: string) {
loading.value = true;
const { data } = await api.recipes.deleteOne(slug);
loading.value = false;
return data;
}
async function updateRecipe(slug: string, recipe: Recipe) {
loading.value = true;
const { data } = await api.recipes.updateOne(slug, recipe);
loading.value = false;
return data;
}
return { loading, getBySlug, deleteRecipe, updateRecipe, fetchRecipe };
};

View file

@ -1,5 +1,5 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { UserIn, UserOut } from "~/types/api-types/user";
/*
@ -9,7 +9,7 @@ to control whether the object is substantiated... but some of the others rely on
*/
export const useAllUsers = function () {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
function getAllUsers() {
@ -37,7 +37,7 @@ export const useAllUsers = function () {
};
export const useUser = function (refreshFunc: CallableFunction | null = null) {
const api = useApiSingleton();
const api = useUserApi();
const loading = ref(false);
function getUser(id: string) {

View file

@ -1,3 +1,10 @@
export const useAsyncKey = function () {
return String(Date.now());
};
export function uuid4() {
// @ts-ignore
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
);
}

View file

@ -1,12 +0,0 @@
const max = 1000000;
export function uniqueId() {
return Date.now() + Math.random() * max;
}
export function uuid4() {
// @ts-ignore
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
);
}

View file

@ -42,7 +42,7 @@
<script lang="ts">
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import { ServerTask } from "~/api/types/server-task";
import { useAdminApi } from "~/composables/use-api";
import { useAdminApi } from "~/composables/api";
export default defineComponent({
layout: "admin",

View file

@ -92,14 +92,14 @@
<script lang="ts">
import { defineComponent, useAsync } from "@nuxtjs/composition-api";
import AdminEventViewer from "@/components/Domain/Admin/AdminEventViewer.vue";
import { useAdminApi, useApiSingleton } from "~/composables/use-api";
import { useAdminApi, useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
export default defineComponent({
components: { AdminEventViewer },
layout: "admin",
setup() {
const api = useApiSingleton();
const api = useUserApi();
const adminApi = useAdminApi();

View file

@ -72,14 +72,14 @@
<script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api";
import { fieldTypes } from "~/composables/forms";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { useUser, useAllUsers } from "~/composables/use-user";
export default defineComponent({
layout: "admin",
setup() {
const api = useApiSingleton();
const api = useUserApi();
const refUserDialog = ref();
const { groups } = useGroups();

View file

@ -69,12 +69,12 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api";
import { Confidence, Parser } from "~/api/class-interfaces/recipes";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default defineComponent({
layout: "admin",
setup() {
const api = useApiSingleton();
const api = useUserApi();
const state = reactive({
loading: false,

View file

@ -102,7 +102,7 @@ import {
useContext,
} from "@nuxtjs/composition-api";
import { CheckAppConfig } from "~/api/admin/admin-about";
import { useAdminApi, useApiSingleton } from "~/composables/use-api";
import { useAdminApi, useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators";
import { useAsyncKey } from "~/composables/use-utils";
@ -130,7 +130,7 @@ export default defineComponent({
isSiteSecure: false,
});
const api = useApiSingleton();
const api = useUserApi();
const adminApi = useAdminApi();
onMounted(async () => {

View file

@ -65,7 +65,7 @@
<script lang="ts">
import { defineComponent, reactive, toRefs, ref, computed } from "@nuxtjs/composition-api";
import { useFoods } from "~/composables/use-recipe-foods";
import { useFoods } from "~/composables/recipes";
import { validators } from "~/composables/use-validators";
export default defineComponent({
layout: "admin",

View file

@ -66,7 +66,7 @@
<script lang="ts">
import { defineComponent, reactive, toRefs, ref, computed } from "@nuxtjs/composition-api";
import { useUnits } from "~/composables/use-recipe-units";
import { useUnits } from "~/composables/recipes";
import { validators } from "~/composables/use-validators";
export default defineComponent({
layout: "admin",

View file

@ -35,7 +35,7 @@
<script lang="ts">
import { defineComponent, toRefs, reactive } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
export default defineComponent({
layout: "basic",
@ -47,7 +47,7 @@ export default defineComponent({
error: false,
});
const api = useApiSingleton();
const api = useUserApi();
async function requestLink() {
state.loading = true;

View file

@ -11,7 +11,7 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useRecipes, recentRecipes } from "~/composables/use-recipes";
import { useRecipes, recentRecipes } from "~/composables/recipes";
import { useStaticRoutes } from "~/composables/api";
export default defineComponent({

View file

@ -152,7 +152,7 @@ import { isSameDay, addDays, subDays, parseISO, format } from "date-fns";
import { SortableEvent } from "sortablejs"; // eslint-disable-line
import draggable from "vuedraggable";
import { useMealplans } from "~/composables/use-group-mealplan";
import { useRecipes, allRecipes } from "~/composables/use-recipes";
import { useRecipes, allRecipes } from "~/composables/recipes";
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
export default defineComponent({

View file

@ -8,12 +8,30 @@
<v-card-title>
<h1 class="headline">{{ recipe.name }}</h1>
</v-card-title>
<v-stepper v-model="activeStep" flat>
<v-toolbar class="ma-1 elevation-2 rounded">
<v-toolbar-title class="headline">
Step {{ activeStep }} of {{ recipe.recipeInstructions.length }}</v-toolbar-title
>
</v-toolbar>
<div class="d-flex mt-3 px-2">
<BaseButton color="primary" @click="$router.go(-1)">
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
To Recipe
</BaseButton>
<v-btn rounded icon color="primary" class="ml-auto" small @click="scale > 1 ? scale-- : null">
<v-icon>
{{ $globals.icons.minus }}
</v-icon>
</v-btn>
<v-btn rounded color="primary" small> Scale: {{ scale }} </v-btn>
<v-btn rounded icon color="primary" small @click="scale++">
<v-icon>
{{ $globals.icons.createAlt }}
</v-icon>
</v-btn>
</div>
<v-stepper-items>
<template v-for="(step, index) in recipe.recipeInstructions">
<v-stepper-content :key="index + 1 + '-content'" :step="index + 1" class="pa-0 mt-2 elevation-0">
@ -21,11 +39,17 @@
<v-card-text>
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
<VueMarkdown :source="step.text"> </VueMarkdown>
<v-divider></v-divider>
<h2 class="mb-4 mt-4">{{ $t("recipe.ingredients") }}</h2>
<div v-for="ing in step.ingredientReferences" :key="ing.referenceId">
{{ getIngredientByRefId(ing.referenceId).note }}
</div>
<template v-if="step.ingredientReferences.length > 0">
<v-divider></v-divider>
<div>
<h2 class="mb-4 mt-4">{{ $t("recipe.ingredients") }}</h2>
<div
v-for="ing in step.ingredientReferences"
:key="ing.referenceId"
v-html="getIngredientByRefId(ing.referenceId)"
></div>
</div>
</template>
</v-card-text>
</v-card>
<v-card-actions class="justify-center">
@ -33,6 +57,7 @@
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
Back
</BaseButton>
<BaseButton
icon-right
:disabled="index + 1 == recipe.recipeInstructions.length"
@ -55,25 +80,35 @@ import { defineComponent, useRoute, ref } from "@nuxtjs/composition-api";
// @ts-ignore
import VueMarkdown from "@adapttive/vue-markdown";
import { useStaticRoutes } from "~/composables/api";
import { useRecipeContext } from "~/composables/use-recipe-context";
import { parseIngredientText, useRecipe } from "~/composables/recipes";
export default defineComponent({
components: { VueMarkdown },
setup() {
const route = useRoute();
const slug = route.value.params.slug;
const activeStep = ref(1);
const scale = ref(1);
const { getBySlug } = useRecipeContext();
const { recipe } = useRecipe(slug);
const { recipeImage } = useStaticRoutes();
const recipe = getBySlug(slug);
function getIngredientByRefId(refId: String) {
return recipe.value?.recipeIngredient.find((ing) => ing.referenceId === refId) || "";
if (!recipe.value) {
return;
}
const ing = recipe?.value.recipeIngredient.find((ing) => ing.referenceId === refId) || "";
if (ing === "") {
return "";
}
return parseIngredientText(ing, recipe?.value?.settings?.disableAmount || false, scale.value);
}
return {
scale,
getIngredientByRefId,
activeStep,
slug,

View file

@ -233,6 +233,7 @@
<RecipeInstructions
v-model="recipe.recipeInstructions"
:ingredients="recipe.recipeIngredient"
:disable-amount="recipe.settings.disableAmount"
:edit="form"
/>
<div v-if="form" class="d-flex">
@ -289,9 +290,9 @@ import VueMarkdown from "@adapttive/vue-markdown";
import draggable from "vuedraggable";
import RecipeCategoryTagSelector from "@/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
import RecipeDialogBulkAdd from "@/components/Domain/Recipe//RecipeDialogBulkAdd.vue";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi, useStaticRoutes } from "~/composables/api";
import { validators } from "~/composables/use-validators";
import { useRecipeContext } from "~/composables/use-recipe-context";
import { useRecipe } from "~/composables/recipes";
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
@ -307,8 +308,7 @@ import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientE
import RecipeIngredientParserMenu from "~/components/Domain/Recipe/RecipeIngredientParserMenu.vue";
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
import { Recipe } from "~/types/api-types/recipe";
import { useStaticRoutes } from "~/composables/api";
import { uuid4 } from "~/composables/use-uuid";
import { uuid4 } from "~/composables/use-utils";
export default defineComponent({
components: {
@ -335,7 +335,7 @@ export default defineComponent({
const route = useRoute();
const router = useRouter();
const slug = route.value.params.slug;
const api = useApiSingleton();
const api = useUserApi();
const state = reactive({
form: false,
@ -352,15 +352,13 @@ export default defineComponent({
},
});
const { getBySlug, loading, fetchRecipe } = useRecipeContext();
const { recipe, loading, fetchRecipe } = useRecipe(slug);
const { recipeImage } = useStaticRoutes();
// @ts-ignore
const { $vuetify } = useContext();
const recipe = getBySlug(slug);
// ===========================================================================
// Layout Helpers
@ -399,7 +397,7 @@ export default defineComponent({
async function closeEditor() {
state.form = false;
state.jsonEditor = false;
recipe.value = await fetchRecipe(slug);
await fetchRecipe();
}
function toggleJson() {

View file

@ -75,10 +75,8 @@
import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
import { Food, ParsedIngredient, Parser } from "~/api/class-interfaces/recipes";
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
import { useApiSingleton } from "~/composables/use-api";
import { useRecipeContext } from "~/composables/use-recipe-context";
import { useFoods } from "~/composables/use-recipe-foods";
import { useUnits } from "~/composables/use-recipe-units";
import { useUserApi } from "~/composables/api";
import { useRecipe, useFoods, useUnits } from "~/composables/recipes";
import { RecipeIngredientUnit } from "~/types/api-types/recipe";
interface Error {
@ -99,11 +97,9 @@ export default defineComponent({
const route = useRoute();
const router = useRouter();
const slug = route.value.params.slug;
const api = useApiSingleton();
const api = useUserApi();
const { getBySlug, loading } = useRecipeContext();
const recipe = getBySlug(slug);
const { recipe, loading } = useRecipe(slug);
const ingredients = ref<any[]>([]);

View file

@ -313,7 +313,7 @@
import { defineComponent, reactive, toRefs, ref, useRouter, useContext } from "@nuxtjs/composition-api";
// @ts-ignore No Types for v-jsoneditor
import VJsoneditor from "v-jsoneditor";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
import { validators } from "~/composables/use-validators";
import { Recipe } from "~/types/api-types/recipe";
@ -357,7 +357,7 @@ export default defineComponent({
},
];
const api = useApiSingleton();
const api = useUserApi();
const router = useRouter();
function handleResponse(response: any, edit: Boolean = false) {

View file

@ -17,7 +17,7 @@
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import { useThrottleFn } from "@vueuse/core";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useLazyRecipes } from "~/composables/use-recipes";
import { useLazyRecipes } from "~/composables/recipes";
export default defineComponent({
components: { RecipeCardSection },

View file

@ -13,13 +13,13 @@
<script lang="ts">
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { Recipe } from "~/types/api-types/recipe";
export default defineComponent({
components: { RecipeCardSection },
setup() {
const api = useApiSingleton();
const api = useUserApi();
const route = useRoute();
const slug = route.value.params.slug;

View file

@ -28,12 +28,12 @@
<script lang="ts">
import { computed, defineComponent, useAsync } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
export default defineComponent({
setup() {
const api = useApiSingleton();
const api = useUserApi();
const categories = useAsync(async () => {
const { data } = await api.categories.getAll();

View file

@ -13,13 +13,13 @@
<script lang="ts">
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { Recipe } from "~/types/api-types/admin";
export default defineComponent({
components: { RecipeCardSection },
setup() {
const api = useApiSingleton();
const api = useUserApi();
const route = useRoute();
const slug = route.value.params.slug;

View file

@ -28,12 +28,12 @@
<script lang="ts">
import { defineComponent, useAsync, computed } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
export default defineComponent({
setup() {
const api = useApiSingleton();
const api = useUserApi();
const tags = useAsync(async () => {
const { data } = await api.tags.getAll();

View file

@ -101,14 +101,14 @@
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, ref, useRouter, watch } from "@nuxtjs/composition-api";
import { validators } from "@/composables/use-validators";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { useRouterQuery } from "@/composables/use-router";
export default defineComponent({
layout: "basic",
setup() {
const api = useApiSingleton();
const api = useUserApi();
const state = reactive({
joinGroup: false,
loggingIn: false,

View file

@ -68,7 +68,7 @@
<script lang="ts">
import { defineComponent, toRefs, reactive } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { validators } from "@/composables/use-validators";
import { useRouteQuery } from "~/composables/use-router";
@ -92,7 +92,7 @@ export default defineComponent({
// ===================
// API
const api = useApiSingleton();
const api = useUserApi();
async function requestLink() {
state.loading = true;
// TODO: Fix Response to send meaningful error

View file

@ -70,7 +70,7 @@ import { defineComponent } from "@nuxtjs/composition-api";
import RecipeSearchFilterSelector from "~/components/Domain/Recipe/RecipeSearchFilterSelector.vue";
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useRecipes, allRecipes } from "~/composables/use-recipes";
import { useRecipes, allRecipes } from "~/composables/recipes";
export default defineComponent({
components: {

View file

@ -64,12 +64,12 @@
<script lang="ts">
import { defineComponent, ref, onMounted, useContext } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { UserOut } from "~/types/api-types/user";
export default defineComponent({
setup() {
const api = useApiSingleton();
const api = useUserApi();
const { i18n } = useContext();

View file

@ -96,8 +96,8 @@
import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
import RecipeDataTable from "~/components/Domain/Recipe/RecipeDataTable.vue";
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
import { useApiSingleton } from "~/composables/use-api";
import { useRecipes, allRecipes } from "~/composables/use-recipes";
import { useUserApi } from "~/composables/api";
import { useRecipes, allRecipes } from "~/composables/recipes";
import { Recipe } from "~/types/api-types/recipe";
const MODES = {
@ -169,7 +169,7 @@ export default defineComponent({
},
];
const api = useApiSingleton();
const api = useUserApi();
function exportSelected() {
console.log("Export Selected");

View file

@ -65,7 +65,7 @@
<script lang="ts">
import { computed, defineComponent, useContext, ref } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default defineComponent({
setup() {
@ -75,7 +75,7 @@ export default defineComponent({
return nuxtContext.$auth.user;
});
const api = useApiSingleton();
const api = useUserApi();
const domNewTokenForm = ref<VForm | null>(null);

View file

@ -104,7 +104,7 @@
<script lang="ts">
import { ref, reactive, defineComponent, computed, useContext, watch } from "@nuxtjs/composition-api";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
export default defineComponent({
setup() {
@ -117,7 +117,7 @@ export default defineComponent({
const userCopy = ref({ ...user.value });
const api = useApiSingleton();
const api = useUserApi();
const domUpdatePassword = ref<VForm | null>(null);
const password = reactive({

View file

@ -125,7 +125,7 @@
<script lang="ts">
import { computed, defineComponent, useContext, ref, toRefs, reactive } from "@nuxtjs/composition-api";
import UserProfileLinkCard from "@/components/Domain/User/UserProfileLinkCard.vue";
import { useApiSingleton } from "~/composables/use-api";
import { useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators";
import { alert } from "~/composables/use-toast";
@ -141,7 +141,7 @@ export default defineComponent({
const generatedLink = ref("");
const token = ref("");
const api = useApiSingleton();
const api = useUserApi();
async function getSignupLink() {
const { data } = await api.groups.createInvitation({ uses: 1 });
if (data) {

View file

@ -91,6 +91,7 @@ export interface RecipeIngredient {
export interface RecipeIngredientUnit {
name?: string;
description?: string;
fraction?: boolean;
}
export interface RecipeIngredientFood {
name?: string;