Compare commits

..

1 Commits

Author SHA1 Message Date
9b86a4d827 Attempt to improve ingredients edition 2018-03-11 18:13:16 +01:00
12 changed files with 81 additions and 48 deletions

View File

@ -5,10 +5,6 @@ Cuizin is a tool wrapping around [Web Outside Of Browsers](http://weboob.org/)
to help you sort and organize recipes you find online. You can also manually to help you sort and organize recipes you find online. You can also manually
add new recipes. add new recipes.
![Homepage](screenshots/home.png)
More screenshots are available in the [`screenshots` folder](screenshots/).
## Installation ## Installation
@ -37,7 +33,7 @@ WSGI to serve the application directly (`application` variable is exported in
You can customize the behavior of the app by passing environment variables: You can customize the behavior of the app by passing environment variables:
* `CUIZIN_HOST` to set the host on which the webserver should listen to * `CUIZIN_HOST` to set the host on which the webserver should listen to
(defaults to `localhost` only). Use `CUIZIN_HOST=0.0.0.0` to make it (defaults to `localhost` only). Use `HOST=0.0.0.0` to make it
world-accessible. world-accessible.
* `CUIZIN_PORT` to set the port on which the webserver should listen. Defaults * `CUIZIN_PORT` to set the port on which the webserver should listen. Defaults
to `8080`. to `8080`.

View File

@ -1,14 +1,6 @@
import * as constants from '@/constants'; import * as constants from '@/constants';
function fetchAPI(endpoint, params = {}) {
return fetch(
`${constants.API_URL}${endpoint}`,
Object.assign({}, { credentials: 'same-origin' }, params),
);
}
function loadJSON(response) { function loadJSON(response) {
return response.json(); return response.json();
} }
@ -24,9 +16,9 @@ function _postProcessRecipes(response) {
parsed.recipes = parsed.recipes.map(item => Object.assign( parsed.recipes = parsed.recipes.map(item => Object.assign(
item, item,
{ {
instructions: item.instructions ? item.instructions.split(/[\r\n]\n/).map( instructions: item.instructions.split(/[\r\n]\n/).map(
line => line.trim(), line => line.trim(),
) : [], ),
}, },
)); ));
} }
@ -37,25 +29,27 @@ function _postProcessRecipes(response) {
export function loadRecipes() { export function loadRecipes() {
return fetchAPI('api/v1/recipes') return fetch(`${constants.API_URL}api/v1/recipes`)
.then(_postProcessRecipes); .then(_postProcessRecipes);
} }
export function loadRecipe(id) { export function loadRecipe(id) {
return fetchAPI(`api/v1/recipe/${id}`) return fetch(`${constants.API_URL}api/v1/recipe/${id}`)
.then(_postProcessRecipes); .then(_postProcessRecipes);
} }
export function refetchRecipe(id) { export function refetchRecipe(id) {
return fetchAPI(`api/v1/recipe/${id}/refetch`) return fetch(`${constants.API_URL}api/v1/recipe/${id}/refetch`, {
method: 'GET',
})
.then(_postProcessRecipes); .then(_postProcessRecipes);
} }
export function postRecipeByUrl(recipe) { export function postRecipeByUrl(recipe) {
return fetchAPI('api/v1/recipes/by_url', { return fetch(`${constants.API_URL}api/v1/recipes/by_url`, {
method: 'POST', method: 'POST',
body: JSON.stringify(recipe), body: JSON.stringify(recipe),
}) })
@ -64,7 +58,7 @@ export function postRecipeByUrl(recipe) {
export function postRecipeManually(recipe) { export function postRecipeManually(recipe) {
return fetchAPI('api/v1/recipes/manually', { return fetch(`${constants.API_URL}api/v1/recipes/manually`, {
method: 'POST', method: 'POST',
body: JSON.stringify(recipe), body: JSON.stringify(recipe),
}) })
@ -73,7 +67,7 @@ export function postRecipeManually(recipe) {
export function editRecipe(id, recipe) { export function editRecipe(id, recipe) {
return fetchAPI(`api/v1/recipe/${id}`, { return fetch(`${constants.API_URL}api/v1/recipe/${id}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(recipe), body: JSON.stringify(recipe),
}) })
@ -82,7 +76,7 @@ export function editRecipe(id, recipe) {
export function deleteRecipe(id) { export function deleteRecipe(id) {
return fetchAPI(`api/v1/recipe/${id}`, { return fetch(`${constants.API_URL}api/v1/recipe/${id}`, {
method: 'DELETE', method: 'DELETE',
}); });
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<v-flex xs12> <v-flex xs12>
<ErrorDialog v-model="error" :description="$t('error.title')" /> <ErrorDialog :v-model="error" :description="$t('error.title')" />
<h2 v-if="recipe">{{ $t('new.edit_recipe') }}</h2> <h2 v-if="recipe">{{ $t('new.edit_recipe') }}</h2>
<h2 v-else>{{ $t('new.add_manually') }}</h2> <h2 v-else>{{ $t('new.add_manually') }}</h2>
@ -23,7 +23,7 @@
<v-text-field <v-text-field
:label="$t('new.short_description')" :label="$t('new.short_description')"
v-model="short_description" v-model="short_description"
textarea multi-line
></v-text-field> ></v-text-field>
<v-layout row wrap> <v-layout row wrap>
<v-flex xs12 md5> <v-flex xs12 md5>
@ -51,16 +51,12 @@
<v-flex xs12 class="text-xs-left"> <v-flex xs12 class="text-xs-left">
<h3>{{ $t('new.ingredients') }}</h3> <h3>{{ $t('new.ingredients') }}</h3>
<v-list v-if="ingredients.length" class="transparent"> <v-list v-if="ingredients.length" class="transparent">
<v-list-tile v-for="ingredient in ingredients" :key="ingredient"> <IngredientListTile
<v-list-tile-action> v-for="(ingredient, index) in ingredients" :key="index"
<v-btn flat icon color="red" v-on:click="() => removeIngredient(ingredient)"> :ingredient="ingredient"
<v-icon>delete</v-icon> :onDelete="() => removeIngredient(index)"
</v-btn> :onEdit="(value) => editIngredient(index, value)"
</v-list-tile-action> ></IngredientListTile>
<v-list-tile-content>
<v-list-tile-title>{{ ingredient }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list> </v-list>
<p class="ml-5 my-3" v-else>{{ $t('new.none') }}</p> <p class="ml-5 my-3" v-else>{{ $t('new.none') }}</p>
<v-text-field <v-text-field
@ -74,7 +70,7 @@
:label="$t('new.instructions')" :label="$t('new.instructions')"
v-model="instructions" v-model="instructions"
:rules="[v => !!v || $t('new.instructions_are_required')]" :rules="[v => !!v || $t('new.instructions_are_required')]"
textarea multi-line
required required
></v-text-field> ></v-text-field>
@ -97,14 +93,18 @@
</template> </template>
<script> <script>
import Vue from 'vue';
import * as api from '@/api'; import * as api from '@/api';
import * as rules from '@/rules'; import * as rules from '@/rules';
import ErrorDialog from '@/components/ErrorDialog'; import ErrorDialog from '@/components/ErrorDialog';
import IngredientListTile from '@/components/IngredientListTile';
export default { export default {
components: { components: {
ErrorDialog, ErrorDialog,
IngredientListTile,
}, },
props: { props: {
recipe: { recipe: {
@ -157,11 +157,11 @@ export default {
this.ingredients.push(this.new_ingredient); this.ingredients.push(this.new_ingredient);
this.new_ingredient = null; this.new_ingredient = null;
}, },
removeIngredient(ingredient) { removeIngredient(index) {
const index = this.ingredients.indexOf(ingredient); Vue.delete(this.ingredients, index);
if (index !== -1) { },
this.ingredients.splice(index, 1); editIngredient(index, value) {
} Vue.set(this.ingredients, index, value);
}, },
submitAdd() { submitAdd() {
this.isImporting = true; this.isImporting = true;

View File

@ -17,7 +17,7 @@
export default { export default {
props: { props: {
description: String, description: String,
value: Error, value: [Error, Boolean],
}, },
computed: { computed: {
error: { error: {

View File

@ -0,0 +1,44 @@
<template>
<v-list-tile>
<v-list-tile-action>
<v-btn flat icon color="red" v-on:click="onDelete">
<v-icon>delete</v-icon>
</v-btn>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-on:click="(editable === false) && (editable = true)">
<v-text-field v-if="editable"
v-model="updatedIngredient"
single-line
solo
@keyup.enter.native="editIngredient"
></v-text-field>
<template v-else>
{{ ingredient }}
</template>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template>
<script>
export default {
props: {
ingredient: String,
onDelete: Function,
onEdit: Function,
},
data() {
return {
editable: false,
updatedIngredient: this.ingredient,
};
},
methods: {
editIngredient() {
this.editable = !this.editable;
this.onEdit(this.updatedIngredient);
},
},
};
</script>

View File

@ -2,9 +2,9 @@
<v-container fluid grid-list-md> <v-container fluid grid-list-md>
<Loader v-if="isLoading"></Loader> <Loader v-if="isLoading"></Loader>
<v-layout row wrap v-else> <v-layout row wrap v-else>
<ErrorDialog v-model="error" :description="$t('error.unable_load_recipes')" /> <ErrorDialog :v-model="error" :description="$t('error.unable_load_recipes')" />
<v-flex xs12 v-if="!error && !recipes.length" class="text-xs-center"> <v-flex xs12 v-if="!recipes.length" class="text-xs-center">
<p>{{ $t('home.onboarding') }}</p> <p>{{ $t('home.onboarding') }}</p>
</v-flex> </v-flex>
<v-flex <v-flex

View File

@ -2,9 +2,9 @@
<v-container grid-list-md class="panel"> <v-container grid-list-md class="panel">
<Loader v-if="isLoading"></Loader> <Loader v-if="isLoading"></Loader>
<v-layout row v-else> <v-layout row v-else>
<ErrorDialog v-model="errorDelete" :description="$t('error.unable_delete_recipe')" /> <ErrorDialog :v-model="errorDelete" :description="$t('error.unable_delete_recipe')" />
<ErrorDialog v-model="errorFetch" :description="$t('error.unable_fetch_recipe')" /> <ErrorDialog :v-model="errorFetch" :description="$t('error.unable_fetch_recipe')" />
<ErrorDialog v-model="errorRefetch" :description="$t('error.unable_refetch_recipe')" /> <ErrorDialog :v-model="errorRefetch" :description="$t('error.unable_refetch_recipe')" />
<v-dialog v-model="refetchConfirm" max-width="500px"> <v-dialog v-model="refetchConfirm" max-width="500px">
<v-card> <v-card>

View File

@ -12,8 +12,7 @@ from weboob.core.ouiboube import WebNip
from cuizin import db from cuizin import db
# List of backends with recipe abilities in Weboob # List of backends with recipe abilities in Weboob
BACKENDS = ['750g', 'allrecipes', 'cuisineaz', 'journaldesfemmes', 'marmiton', BACKENDS = ['750g', 'allrecipes', 'cuisineaz', 'marmiton', 'supertoinette']
'supertoinette']
def fetch_recipe(url, recipe=None): def fetch_recipe(url, recipe=None):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 KiB