Compare commits
1 Commits
master
...
betterIngr
Author | SHA1 | Date | |
---|---|---|---|
9b86a4d827 |
@ -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`.
|
||||||
|
@ -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',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
description: String,
|
description: String,
|
||||||
value: Error,
|
value: [Error, Boolean],
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
error: {
|
error: {
|
||||||
|
44
cuizin/js_src/components/IngredientListTile.vue
Normal file
44
cuizin/js_src/components/IngredientListTile.vue
Normal 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>
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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 |
Loading…
Reference in New Issue
Block a user