mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-30 21:49:50 -05:00
added split/merge steps to recipe view
This commit is contained in:
@@ -63,8 +63,8 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- <closable-help-alert :text="$t('RecipeStepsHelp')" :action-text="$t('Steps')" @click="tab='steps'"></closable-help-alert>-->
|
||||
<v-btn @click="tab='steps'" class="float-right" variant="tonal" append-icon="fa-solid fa-arrow-right">{{$t('Steps')}} </v-btn>
|
||||
<!-- <closable-help-alert :text="$t('RecipeStepsHelp')" :action-text="$t('Steps')" @click="tab='steps'"></closable-help-alert>-->
|
||||
<v-btn @click="tab='steps'" class="float-right" variant="tonal" append-icon="fa-solid fa-arrow-right">{{ $t('Steps') }}</v-btn>
|
||||
</v-form>
|
||||
|
||||
</v-tabs-window-item>
|
||||
@@ -77,12 +77,17 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-center">
|
||||
<v-btn-group density="compact">
|
||||
<v-btn-group density="compact" divided border>
|
||||
<v-btn color="success" prepend-icon="fa-solid fa-plus" @click="addStep()">{{ $t('Add_Step') }}</v-btn>
|
||||
<v-btn color="warning" @click="dialogStepManager = true">
|
||||
<v-icon icon="fa-solid fa-arrow-down-1-9"></v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn prepend-icon="fa-solid fa-maximize" @click="handleSplitAllSteps"><span v-if="!mobile">{{ $t('Split') }}</span></v-btn>
|
||||
<v-btn prepend-icon="fa-solid fa-minimize" @click="handleMergeAllSteps"><span v-if="!mobile">{{ $t('Merge') }}</span></v-btn>
|
||||
</v-btn-group>
|
||||
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -101,7 +106,7 @@
|
||||
|
||||
<v-text-field :label="$t('Imported_From')" v-model="editingObj.sourceUrl"></v-text-field>
|
||||
<v-checkbox :label="$t('Private_Recipe')" persistent-hint :hint="$t('Private_Recipe_Help')" v-model="editingObj._private"></v-checkbox>
|
||||
<model-select mode="tags" model="User" :label="$t('Share')" persistent-hint v-model="editingObj.shared"
|
||||
<model-select mode="tags" model="User" :label="$t('Share')" persistent-hint v-model="editingObj.shared"
|
||||
append-to-body v-if="editingObj._private"></model-select>
|
||||
|
||||
</v-form>
|
||||
@@ -110,7 +115,7 @@
|
||||
</v-card-text>
|
||||
<v-card-text v-if="isSpaceAtRecipeLimit(useUserPreferenceStore().activeSpace)">
|
||||
<v-alert color="warning" icon="fa-solid fa-triangle-exclamation">
|
||||
{{$t('SpaceLimitReached')}}
|
||||
{{ $t('SpaceLimitReached') }}
|
||||
<v-btn color="success" variant="flat" :to="{name: 'SpaceSettings'}">{{ $t('SpaceSettings') }}</v-btn>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
@@ -152,6 +157,7 @@ import {useDisplay} from "vuetify";
|
||||
import {isSpaceAtRecipeLimit} from "@/utils/logic_utils";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import SpaceSettings from "@/components/settings/SpaceSettings.vue";
|
||||
import {mergeAllSteps, splitAllSteps} from "@/utils/step_utils.ts";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
@@ -188,7 +194,7 @@ onMounted(() => {
|
||||
/**
|
||||
* component specific state setup logic
|
||||
*/
|
||||
function initializeEditor(){
|
||||
function initializeEditor() {
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.steps = [] as Step[]
|
||||
@@ -249,6 +255,18 @@ function deleteStepAtIndex(index: number) {
|
||||
editingObj.value.steps.splice(index, 1)
|
||||
}
|
||||
|
||||
function handleMergeAllSteps(): void {
|
||||
if (editingObj.value.steps) {
|
||||
mergeAllSteps(editingObj.value.steps)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSplitAllSteps(): void {
|
||||
if (editingObj.value.steps) {
|
||||
splitAllSteps(editingObj.value.steps, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -272,8 +272,8 @@
|
||||
<v-col class="text-center">
|
||||
<v-btn-group border divided>
|
||||
<v-btn prepend-icon="fa-solid fa-shuffle" @click="autoSortIngredients()"><span v-if="!mobile">{{ $t('Auto_Sort') }}</span></v-btn>
|
||||
<v-btn prepend-icon="fa-solid fa-maximize" @click="splitAllSteps('\n')"><span v-if="!mobile">{{ $t('Split') }}</span></v-btn>
|
||||
<v-btn prepend-icon="fa-solid fa-minimize" @click="mergeAllSteps()"><span v-if="!mobile">{{ $t('Merge') }}</span></v-btn>
|
||||
<v-btn prepend-icon="fa-solid fa-maximize" @click="handleSplitAllSteps()"><span v-if="!mobile">{{ $t('Split') }}</span></v-btn>
|
||||
<v-btn prepend-icon="fa-solid fa-minimize" @click="handleMergeAllSteps()"><span v-if="!mobile">{{ $t('Merge') }}</span></v-btn>
|
||||
</v-btn-group>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -566,6 +566,7 @@ import {DateTime} from "luxon";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
import bookmarkletJs from '@/assets/bookmarklet_v3?url'
|
||||
import StepIngredientSorterDialog from "@/components/dialogs/StepIngredientSorterDialog.vue";
|
||||
import {mergeAllSteps, splitAllSteps, splitStep} from "@/utils/step_utils.ts";
|
||||
|
||||
function doListImport() {
|
||||
urlList.value = urlListImportInput.value.split('\n')
|
||||
@@ -809,67 +810,15 @@ function deleteStep(step: SourceImportStep) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
||||
* @param step step to split
|
||||
* @param split_character character to use as a delimiter between steps
|
||||
*/
|
||||
function splitStepObject(step: SourceImportStep, split_character: string) {
|
||||
let steps: SourceImportStep[] = []
|
||||
step.instruction.split(split_character).forEach(part => {
|
||||
if (part.trim() !== '') {
|
||||
steps.push({instruction: part, ingredients: [], showIngredientsTable: useUserPreferenceStore().userSettings.showStepIngredients!})
|
||||
}
|
||||
})
|
||||
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
||||
return steps
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits all steps of a given recipe_json at the split character (e.g. \n or \n\n)
|
||||
* @param split_character character to split steps at
|
||||
*/
|
||||
function splitAllSteps(split_character: string) {
|
||||
let steps: SourceImportStep[] = []
|
||||
if (importResponse.value.recipe) {
|
||||
importResponse.value.recipe.steps.forEach(step => {
|
||||
steps = steps.concat(splitStepObject(step, split_character))
|
||||
})
|
||||
importResponse.value.recipe.steps = steps
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.ERROR, "no steps found to split")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given step at the split character (e.g. \n or \n\n)
|
||||
* @param step step to split
|
||||
* @param split_character character to use as a delimiter between steps
|
||||
*/
|
||||
function splitStep(step: SourceImportStep, split_character: string) {
|
||||
if (importResponse.value.recipe) {
|
||||
let old_index = importResponse.value.recipe.steps.findIndex(x => x === step)
|
||||
let new_steps = splitStepObject(step, split_character)
|
||||
importResponse.value.recipe.steps.splice(old_index, 1, ...new_steps)
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.ERROR, "no steps found to split")
|
||||
function handleMergeAllSteps(): void {
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps){
|
||||
mergeAllSteps(importResponse.value.recipe.steps)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge all steps of a given recipe_json into one
|
||||
*/
|
||||
function mergeAllSteps() {
|
||||
let step = {instruction: '', ingredients: [], showIngredientsTable: useUserPreferenceStore().userSettings.showStepIngredients!} as SourceImportStep
|
||||
if (importResponse.value.recipe) {
|
||||
importResponse.value.recipe.steps.forEach(s => {
|
||||
step.instruction += s.instruction + '\n'
|
||||
step.ingredients = step.ingredients.concat(s.ingredients)
|
||||
})
|
||||
importResponse.value.recipe.steps = [step]
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.ERROR, "no steps found to split")
|
||||
function handleSplitAllSteps(): void {
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps){
|
||||
splitAllSteps(importResponse.value.recipe.steps, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
73
vue3/src/utils/step_utils.ts
Normal file
73
vue3/src/utils/step_utils.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {Step} from "@/openapi";
|
||||
|
||||
interface StepLike {
|
||||
instruction?: string;
|
||||
ingredients?: Array<any>;
|
||||
showIngredientsTable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
||||
* @param step step to split
|
||||
* @param split_character character to use as a delimiter between steps
|
||||
*/
|
||||
function splitStepObject<T extends StepLike>(step: T, split_character: string) {
|
||||
let steps: T[] = []
|
||||
if (step.instruction){
|
||||
step.instruction.split(split_character).forEach(part => {
|
||||
if (part.trim() !== '') {
|
||||
steps.push({instruction: part, ingredients: [], time: 0, showIngredientsTable: useUserPreferenceStore().userSettings.showStepIngredients!})
|
||||
}
|
||||
})
|
||||
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
||||
}
|
||||
return steps
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits all steps of a given recipe_json at the split character (e.g. \n or \n\n)
|
||||
* @param split_character character to split steps at
|
||||
*/
|
||||
export function splitAllSteps<T extends StepLike>(orig_steps: T[], split_character: string) {
|
||||
let steps: T[] = []
|
||||
if (orig_steps) {
|
||||
orig_steps.forEach(step => {
|
||||
steps = steps.concat(splitStepObject(step, split_character))
|
||||
})
|
||||
orig_steps.splice(0, orig_steps.length, ...steps) // replace all steps with the split steps
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.ERROR, "no steps found to split")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given step at the split character (e.g. \n or \n\n)
|
||||
* @param step step to split
|
||||
* @param split_character character to use as a delimiter between steps
|
||||
*/
|
||||
export function splitStep<T extends StepLike>(steps: T[], step: T, split_character: string) {
|
||||
if (steps){
|
||||
let old_index = steps.findIndex(x => x === step)
|
||||
let new_steps = splitStepObject(step, split_character)
|
||||
steps.splice(old_index, 1, ...new_steps)
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.ERROR, "no steps found to split")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge all steps of a given recipe_json into one
|
||||
*/
|
||||
export function mergeAllSteps<T extends StepLike>(steps: T[]) {
|
||||
let step = {instruction: '', ingredients: [], showIngredientsTable: useUserPreferenceStore().userSettings.showStepIngredients!} as T
|
||||
if (steps) {
|
||||
step.instruction = steps.map(s => s.instruction).join('\n')
|
||||
step.ingredients = steps.flatMap(s => s.ingredients)
|
||||
steps.splice(0, steps.length, step) // replace all steps with the merged step
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.ERROR, "no steps found to split")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user