mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
feat: Print Preferences (#2131)
* added basic recipe print settings added print settings dialog refactored print view to live inside print container * removed unwanted padding * changed preferences layout
This commit is contained in:
parent
b25cc70963
commit
670907b563
@ -50,6 +50,7 @@
|
|||||||
fab
|
fab
|
||||||
color="info"
|
color="info"
|
||||||
:card-menu="false"
|
:card-menu="false"
|
||||||
|
:recipe="recipe"
|
||||||
:recipe-id="recipe.id"
|
:recipe-id="recipe.id"
|
||||||
:recipe-scale="recipeScale"
|
:recipe-scale="recipeScale"
|
||||||
:use-items="{
|
:use-items="{
|
||||||
@ -60,6 +61,7 @@
|
|||||||
mealplanner: true,
|
mealplanner: true,
|
||||||
shoppingList: true,
|
shoppingList: true,
|
||||||
print: true,
|
print: true,
|
||||||
|
printPreferences: true,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: recipe.settings ? recipe.settings.public : false,
|
publicUrl: recipe.settings ? recipe.settings.public : false,
|
||||||
}"
|
}"
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
mealplanner: true,
|
mealplanner: true,
|
||||||
shoppingList: true,
|
shoppingList: true,
|
||||||
print: false,
|
print: false,
|
||||||
|
printPreferences: false,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: false,
|
publicUrl: false,
|
||||||
}"
|
}"
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
mealplanner: true,
|
mealplanner: true,
|
||||||
shoppingList: true,
|
shoppingList: true,
|
||||||
print: false,
|
print: false,
|
||||||
|
printPreferences: false,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: false,
|
publicUrl: false,
|
||||||
}"
|
}"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- Recipe Share Dialog -->
|
<!-- Recipe Share Dialog -->
|
||||||
<RecipeDialogShare v-model="shareDialog" :recipe-id="recipeId" :name="name" />
|
<RecipeDialogShare v-model="shareDialog" :recipe-id="recipeId" :name="name" />
|
||||||
|
<RecipeDialogPrintPreferences v-model="printPreferencesDialog" :recipe="recipe" />
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-model="recipeDeleteDialog"
|
v-model="recipeDeleteDialog"
|
||||||
:title="$t('recipe.delete-recipe')"
|
:title="$t('recipe.delete-recipe')"
|
||||||
@ -115,10 +116,12 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, useContext, useRouter, ref } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, toRefs, useContext, useRouter, ref } from "@nuxtjs/composition-api";
|
||||||
|
import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
||||||
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { planTypeOptions } from "~/composables/use-group-mealplan";
|
import { planTypeOptions } from "~/composables/use-group-mealplan";
|
||||||
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
import { ShoppingListSummary } from "~/lib/api/types/group";
|
import { ShoppingListSummary } from "~/lib/api/types/group";
|
||||||
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
||||||
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
||||||
@ -131,6 +134,7 @@ export interface ContextMenuIncludes {
|
|||||||
mealplanner: boolean;
|
mealplanner: boolean;
|
||||||
shoppingList: boolean;
|
shoppingList: boolean;
|
||||||
print: boolean;
|
print: boolean;
|
||||||
|
printPreferences: boolean;
|
||||||
share: boolean;
|
share: boolean;
|
||||||
publicUrl: boolean;
|
publicUrl: boolean;
|
||||||
}
|
}
|
||||||
@ -144,6 +148,7 @@ export interface ContextMenuItem {
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
RecipeDialogPrintPreferences,
|
||||||
RecipeDialogShare,
|
RecipeDialogShare,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@ -157,6 +162,7 @@ export default defineComponent({
|
|||||||
mealplanner: true,
|
mealplanner: true,
|
||||||
shoppingList: true,
|
shoppingList: true,
|
||||||
print: true,
|
print: true,
|
||||||
|
printPreferences: true,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: false,
|
publicUrl: false,
|
||||||
}),
|
}),
|
||||||
@ -195,6 +201,10 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
recipe: {
|
||||||
|
type: Object as () => Recipe,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
recipeId: {
|
recipeId: {
|
||||||
required: true,
|
required: true,
|
||||||
type: String,
|
type: String,
|
||||||
@ -217,6 +227,7 @@ export default defineComponent({
|
|||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
printPreferencesDialog: false,
|
||||||
shareDialog: false,
|
shareDialog: false,
|
||||||
recipeDeleteDialog: false,
|
recipeDeleteDialog: false,
|
||||||
mealplannerDialog: false,
|
mealplannerDialog: false,
|
||||||
@ -278,6 +289,12 @@ export default defineComponent({
|
|||||||
color: undefined,
|
color: undefined,
|
||||||
event: "print",
|
event: "print",
|
||||||
},
|
},
|
||||||
|
printPreferences: {
|
||||||
|
title: i18n.tc("general.print-preferences"),
|
||||||
|
icon: $globals.icons.printerSettings,
|
||||||
|
color: undefined,
|
||||||
|
event: "printPreferences",
|
||||||
|
},
|
||||||
share: {
|
share: {
|
||||||
title: i18n.tc("general.share"),
|
title: i18n.tc("general.share"),
|
||||||
icon: $globals.icons.shareVariant,
|
icon: $globals.icons.shareVariant,
|
||||||
@ -382,6 +399,9 @@ export default defineComponent({
|
|||||||
mealplanner: () => {
|
mealplanner: () => {
|
||||||
state.mealplannerDialog = true;
|
state.mealplannerDialog = true;
|
||||||
},
|
},
|
||||||
|
printPreferences: () => {
|
||||||
|
state.printPreferencesDialog = true;
|
||||||
|
},
|
||||||
shoppingList: () => {
|
shoppingList: () => {
|
||||||
getShoppingLists();
|
getShoppingLists();
|
||||||
state.shoppingListDialog = true;
|
state.shoppingListDialog = true;
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<BaseDialog
|
||||||
|
v-model="dialog"
|
||||||
|
:icon="$globals.icons.printerSettings"
|
||||||
|
:title="$tc('general.print-preferences')"
|
||||||
|
width="70%"
|
||||||
|
>
|
||||||
|
<div class="pa-6">
|
||||||
|
<v-container class="print-config mb-3 pa-0">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="auto" align-self="center" class="text-center">
|
||||||
|
<div class="text-subtitle-2" style="text-align: center;">{{ $tc('recipe.recipe-image') }}</div>
|
||||||
|
<v-btn-toggle v-model="preferences.imagePosition" mandatory style="width: fit-content;">
|
||||||
|
<v-btn :value="ImagePosition.left">
|
||||||
|
<v-icon>{{ $globals.icons.dockLeft }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn :value="ImagePosition.right">
|
||||||
|
<v-icon>{{ $globals.icons.dockRight }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn :value="ImagePosition.hidden">
|
||||||
|
<v-icon>{{ $globals.icons.windowClose }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="auto" align-self="start">
|
||||||
|
<v-row no-gutters>
|
||||||
|
<v-switch v-model="preferences.showDescription" hide-details :label="$tc('recipe.description')" />
|
||||||
|
</v-row>
|
||||||
|
<v-row no-gutters>
|
||||||
|
<v-switch v-model="preferences.showNotes" hide-details :label="$tc('recipe.notes')" />
|
||||||
|
</v-row>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
<v-card
|
||||||
|
height="fit-content"
|
||||||
|
max-height="40vh"
|
||||||
|
width="100%"
|
||||||
|
class="print-preview"
|
||||||
|
style="overflow-y: auto;"
|
||||||
|
>
|
||||||
|
<RecipePrintView :recipe="recipe"/>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
|
import { ImagePosition, useUserPrintPreferences } from "~/composables/use-users/preferences";
|
||||||
|
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
RecipePrintView,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
recipe: {
|
||||||
|
type: Object as () => NoUndefinedField<Recipe>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
const preferences = useUserPrintPreferences();
|
||||||
|
|
||||||
|
// V-Model Support
|
||||||
|
const dialog = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.value;
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
context.emit("input", val);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dialog,
|
||||||
|
ImagePosition,
|
||||||
|
preferences,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -70,7 +70,7 @@
|
|||||||
:recipe="recipe"
|
:recipe="recipe"
|
||||||
class="px-1 my-4 d-print-none"
|
class="px-1 my-4 d-print-none"
|
||||||
/>
|
/>
|
||||||
<RecipePrintView :recipe="recipe" :scale="scale" />
|
<RecipePrintContainer :recipe="recipe" :scale="scale" />
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ import RecipePageOrganizers from "./RecipePageParts/RecipePageOrganizers.vue";
|
|||||||
import RecipePageScale from "./RecipePageParts/RecipePageScale.vue";
|
import RecipePageScale from "./RecipePageParts/RecipePageScale.vue";
|
||||||
import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue";
|
import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue";
|
||||||
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
|
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
|
||||||
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
import RecipePrintContainer from "~/components/Domain/Recipe/RecipePrintContainer.vue";
|
||||||
import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
@ -116,7 +116,7 @@ const EDITOR_OPTIONS = {
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipePageHeader,
|
RecipePageHeader,
|
||||||
RecipePrintView,
|
RecipePrintContainer,
|
||||||
RecipePageComments,
|
RecipePageComments,
|
||||||
RecipePageTitleContent,
|
RecipePageTitleContent,
|
||||||
RecipePageEditorToolbar,
|
RecipePageEditorToolbar,
|
||||||
|
56
frontend/components/Domain/Recipe/RecipePrintContainer.vue
Normal file
56
frontend/components/Domain/Recipe/RecipePrintContainer.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div class="print-container">
|
||||||
|
<RecipePrintView :recipe="recipe" :scale="scale" dense />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
||||||
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
RecipePrintView,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recipe: {
|
||||||
|
type: Object as () => Recipe,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media print {
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-container {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-main__wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.print-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,19 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="print-container">
|
<div :class="dense ? 'wrapper' : 'wrapper pa-3'">
|
||||||
<section>
|
<section>
|
||||||
<v-card-title class="headline pl-0">
|
<v-container class="ma-0 pa-0">
|
||||||
<v-icon left color="primary">
|
<v-row>
|
||||||
{{ $globals.icons.primary }}
|
<v-col
|
||||||
</v-icon>
|
v-if="preferences.imagePosition && preferences.imagePosition != ImagePosition.hidden"
|
||||||
{{ recipe.name }}
|
:order="preferences.imagePosition == ImagePosition.left ? -1 : 1"
|
||||||
</v-card-title>
|
cols="4"
|
||||||
<RecipeTimeCard :prep-time="recipe.prepTime" :total-time="recipe.totalTime" :perform-time="recipe.performTime" />
|
align-self="center"
|
||||||
|
>
|
||||||
|
<img :key="imageKey" :src="recipeImageUrl" style="min-height: 50; max-width: 100%;" />
|
||||||
|
</v-col>
|
||||||
|
<v-col order=0>
|
||||||
|
<v-card-title class="headline pl-0">
|
||||||
|
<v-icon left color="primary">
|
||||||
|
{{ $globals.icons.primary }}
|
||||||
|
</v-icon>
|
||||||
|
{{ recipe.name }}
|
||||||
|
</v-card-title>
|
||||||
|
<RecipeTimeCard :prep-time="recipe.prepTime" :total-time="recipe.totalTime" :perform-time="recipe.performTime" color="white" />
|
||||||
|
<v-card-text v-if="preferences.showDescription" class="px-0">
|
||||||
|
<SafeMarkdown :source="recipe.description" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<v-card-text class="px-0">
|
|
||||||
<SafeMarkdown :source="recipe.description" />
|
|
||||||
</v-card-text>
|
|
||||||
|
|
||||||
<!-- Ingredients -->
|
<!-- Ingredients -->
|
||||||
<section>
|
<section>
|
||||||
<v-card-title class="headline pl-0"> {{ $t("recipe.ingredients") }} </v-card-title>
|
<v-card-title class="headline pl-0"> {{ $t("recipe.ingredients") }} </v-card-title>
|
||||||
@ -30,6 +43,7 @@
|
|||||||
:style="{ gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
|
:style="{ gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
|
||||||
>
|
>
|
||||||
<template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients">
|
<template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients">
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
|
<p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -57,24 +71,30 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Notes -->
|
<!-- Notes -->
|
||||||
<v-divider v-if="hasNotes" class="grey my-4"></v-divider>
|
<div v-if="preferences.showNotes">
|
||||||
|
<v-divider v-if="hasNotes" class="grey my-4"></v-divider>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div v-for="(note, index) in recipe.notes" :key="index + 'note'">
|
<div v-for="(note, index) in recipe.notes" :key="index + 'note'">
|
||||||
<div class="print-section">
|
<div class="print-section">
|
||||||
<h4>{{ note.title }}</h4>
|
<h4>{{ note.title }}</h4>
|
||||||
<SafeMarkdown :source="note.text" class="note-body" />
|
<SafeMarkdown :source="note.text" class="note-body" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||||
|
import { useStaticRoutes } from "~/composables/api";
|
||||||
import { Recipe, RecipeIngredient, RecipeStep } from "~/lib/api/types/recipe";
|
import { Recipe, RecipeIngredient, RecipeStep } from "~/lib/api/types/recipe";
|
||||||
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
|
import { ImagePosition, useUserPrintPreferences } from "~/composables/use-users/preferences";
|
||||||
import { parseIngredientText } from "~/composables/recipes";
|
import { parseIngredientText } from "~/composables/recipes";
|
||||||
|
import { usePageState } from "~/composables/recipe-page/shared-state";
|
||||||
|
|
||||||
type IngredientSection = {
|
type IngredientSection = {
|
||||||
sectionName: string;
|
sectionName: string;
|
||||||
@ -93,15 +113,27 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
recipe: {
|
recipe: {
|
||||||
type: Object as () => Recipe,
|
type: Object as () => NoUndefinedField<Recipe>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
scale: {
|
scale: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
|
dense: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const preferences = useUserPrintPreferences();
|
||||||
|
const { recipeImage } = useStaticRoutes();
|
||||||
|
const { imageKey } = usePageState(props.recipe.slug);
|
||||||
|
|
||||||
|
const recipeImageUrl = computed(() => {
|
||||||
|
return recipeImage(props.recipe.id, props.recipe.image, imageKey.value);
|
||||||
|
});
|
||||||
|
|
||||||
// Group ingredients by section so we can style them independently
|
// Group ingredients by section so we can style them independently
|
||||||
const ingredientSections = computed<IngredientSection[]>(() => {
|
const ingredientSections = computed<IngredientSection[]>(() => {
|
||||||
if (!props.recipe.recipeIngredient) {
|
if (!props.recipe.recipeIngredient) {
|
||||||
@ -190,8 +222,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
hasNotes,
|
hasNotes,
|
||||||
|
imageKey,
|
||||||
|
ImagePosition,
|
||||||
parseText,
|
parseText,
|
||||||
parseIngredientText,
|
parseIngredientText,
|
||||||
|
preferences,
|
||||||
|
recipeImageUrl,
|
||||||
ingredientSections,
|
ingredientSections,
|
||||||
instructionSections,
|
instructionSections,
|
||||||
};
|
};
|
||||||
@ -199,38 +235,14 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
@media print {
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-container {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-main {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-main__wrap {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Makes all text solid black */
|
/* Makes all text solid black */
|
||||||
.print-container {
|
.wrapper {
|
||||||
display: none;
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-container,
|
.wrapper,
|
||||||
.print-container >>> * {
|
.wrapper >>> * {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
:small="$vuetify.breakpoint.smAndDown"
|
:small="$vuetify.breakpoint.smAndDown"
|
||||||
label
|
label
|
||||||
color="accent custom-transparent"
|
:color="color"
|
||||||
class="ma-1"
|
class="ma-1"
|
||||||
>
|
>
|
||||||
<v-icon left>
|
<v-icon left>
|
||||||
@ -34,6 +34,10 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "accent custom-transparent"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
import { Ref, useContext } from "@nuxtjs/composition-api";
|
import { Ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
|
|
||||||
|
export interface UserPrintPreferences {
|
||||||
|
imagePosition: string;
|
||||||
|
showDescription: boolean;
|
||||||
|
showNotes: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ImagePosition {
|
||||||
|
hidden = "hidden",
|
||||||
|
left = "left",
|
||||||
|
right = "right",
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserRecipePreferences {
|
export interface UserRecipePreferences {
|
||||||
orderBy: string;
|
orderBy: string;
|
||||||
orderDirection: string;
|
orderDirection: string;
|
||||||
@ -9,6 +21,22 @@ export interface UserRecipePreferences {
|
|||||||
useMobileCards: boolean;
|
useMobileCards: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
|
||||||
|
const fromStorage = useLocalStorage(
|
||||||
|
"recipe-print-preferences",
|
||||||
|
{
|
||||||
|
imagePosition: "left",
|
||||||
|
showDescription: true,
|
||||||
|
showNotes: true,
|
||||||
|
},
|
||||||
|
{ mergeDefaults: true }
|
||||||
|
// we cast to a Ref because by default it will return an optional type ref
|
||||||
|
// but since we pass defaults we know all properties are set.
|
||||||
|
) as unknown as Ref<UserPrintPreferences>;
|
||||||
|
|
||||||
|
return fromStorage;
|
||||||
|
}
|
||||||
|
|
||||||
export function useUserSortPreferences(): Ref<UserRecipePreferences> {
|
export function useUserSortPreferences(): Ref<UserRecipePreferences> {
|
||||||
const { $globals } = useContext();
|
const { $globals } = useContext();
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@
|
|||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"options": "Options:",
|
"options": "Options:",
|
||||||
"print": "Print",
|
"print": "Print",
|
||||||
|
"print-preferences": "Print Preferences",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"rating": "Rating",
|
"rating": "Rating",
|
||||||
"recent": "Recent",
|
"recent": "Recent",
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiPrinter,
|
mdiPrinter,
|
||||||
|
mdiPrinterPosCog,
|
||||||
mdiShareVariant,
|
mdiShareVariant,
|
||||||
mdiChevronDown,
|
mdiChevronDown,
|
||||||
mdiHeart,
|
mdiHeart,
|
||||||
@ -128,6 +129,10 @@ import {
|
|||||||
mdiMessageText,
|
mdiMessageText,
|
||||||
mdiChefHat,
|
mdiChefHat,
|
||||||
mdiContentDuplicate,
|
mdiContentDuplicate,
|
||||||
|
mdiDockLeft,
|
||||||
|
mdiDockRight,
|
||||||
|
mdiDockTop,
|
||||||
|
mdiDockBottom,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
@ -176,6 +181,10 @@ export const icons = {
|
|||||||
desktopTowerMonitor: mdiDesktopTowerMonitor,
|
desktopTowerMonitor: mdiDesktopTowerMonitor,
|
||||||
devTo: mdiDevTo,
|
devTo: mdiDevTo,
|
||||||
diceMultiple: mdiDiceMultiple,
|
diceMultiple: mdiDiceMultiple,
|
||||||
|
dockTop: mdiDockTop,
|
||||||
|
dockBottom: mdiDockBottom,
|
||||||
|
dockLeft: mdiDockLeft,
|
||||||
|
dockRight: mdiDockRight,
|
||||||
dotsHorizontal: mdiDotsHorizontal,
|
dotsHorizontal: mdiDotsHorizontal,
|
||||||
dotsVertical: mdiDotsVertical,
|
dotsVertical: mdiDotsVertical,
|
||||||
download: mdiDownload,
|
download: mdiDownload,
|
||||||
@ -211,6 +220,7 @@ export const icons = {
|
|||||||
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
|
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
|
||||||
pageLayoutBody: mdiPageLayoutBody,
|
pageLayoutBody: mdiPageLayoutBody,
|
||||||
printer: mdiPrinter,
|
printer: mdiPrinter,
|
||||||
|
printerSettings: mdiPrinterPosCog,
|
||||||
refreshCircle: mdiRefreshCircle,
|
refreshCircle: mdiRefreshCircle,
|
||||||
robot: mdiRobot,
|
robot: mdiRobot,
|
||||||
search: mdiMagnify,
|
search: mdiMagnify,
|
||||||
|
@ -246,6 +246,7 @@
|
|||||||
duplicate: false,
|
duplicate: false,
|
||||||
mealplanner: false,
|
mealplanner: false,
|
||||||
print: true,
|
print: true,
|
||||||
|
printPreferences: false,
|
||||||
share: false,
|
share: false,
|
||||||
shoppingList: true,
|
shoppingList: true,
|
||||||
publicUrl: false,
|
publicUrl: false,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user