Tag editor; ingredient editor

This commit is contained in:
Dan Elbert 2018-04-03 18:31:20 -05:00
parent a07f037b8f
commit c13b3a09bd
21 changed files with 672 additions and 78 deletions

View File

@ -2,7 +2,7 @@ class IngredientsController < ApplicationController
before_action :set_ingredient, only: [:show, :edit, :update, :destroy]
before_action :ensure_valid_user, except: [:index]
before_action :ensure_valid_user, except: [:index, :show]
# GET /ingredients
# GET /ingredients.json
@ -39,7 +39,7 @@ class IngredientsController < ApplicationController
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully created.' }
format.json { render :show, status: :created, location: @ingredient }
format.json { render json: { success: true }, status: :created, location: @ingredient }
else
format.html { render :new }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
@ -58,7 +58,7 @@ class IngredientsController < ApplicationController
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
format.json { render :show, status: :ok, location: @ingredient }
format.json { render json: { success: true }, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
@ -92,9 +92,7 @@ class IngredientsController < ApplicationController
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
respond_to do |format|
format.js {}
end
render :show
end
def prefetch
@ -134,7 +132,7 @@ class IngredientsController < ApplicationController
# Never trust parameters from the scary internet, only allow the white list through.
def ingredient_params
params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ash, :iron, :magnesium, :phosphorus, :potassium, :zinc, :copper, :manganese, :vit_c, :vit_b6, :vit_b12, :vit_a, :vit_e, :vit_d, :cholesterol, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
end
def conversion_params

View File

@ -60,8 +60,6 @@ class RecipesController < ApplicationController
if @recipe.update(recipe_params)
render json: { success: true }
else
puts '===='
puts @recipe.recipe_ingredients.map { |ri| ri.ingredient_id.class }.join("|")
render json: @recipe.errors, status: :unprocessable_entity
end
end

View File

@ -0,0 +1,94 @@
<template>
<span :title="fullString">{{ friendlyString }}</span>
</template>
<script>
export default {
props: {
dateTime: {
required: true,
type: [Date, String]
},
showDate: {
required: false,
type: Boolean,
default: true
},
showTime: {
required: false,
type: Boolean,
default: true
}
},
computed: {
dateObj() {
if (this.dateTime instanceof Date) {
return this.dateTime;
} else if (this.dateTime === null || this.dateTime.length === 0) {
return null;
} else {
return new Date(this.dateTime);
}
},
friendlyString() {
const parts = [];
if (this.showDate) {
parts.push(this.formatDate());
}
if (this.showTime) {
parts.push(this.formatTime(true));
}
return parts.join(" ");
},
fullString() {
return this.formatDate() + " " + this.formatTime(false);
}
},
methods: {
formatDate() {
if (this.dateObj) {
return [this.dateObj.getMonth() + 1, this.dateObj.getDate(), this.dateObj.getFullYear() % 100].join("/");
} else {
return "";
}
},
formatTime(use12hour) {
if (this.dateObj) {
let h = this.dateObj.getHours();
const m = this.dateObj.getMinutes().toString().padStart(2, "0");
let meridiem = "";
if (use12hour) {
meridiem = " am";
if (h === 0) {
h = 12;
} else if (h > 12) {
h = h - 12;
meridiem = " pm";
}
} else {
h = h.toString().padStart(2, "0");
}
return h + ":" + m + " " + meridiem;
} else {
return "";
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -10,9 +10,12 @@
import CaretTop from "open-iconic/svg/caret-top";
import Check from "open-iconic/svg/check";
import CircleCheck from "open-iconic/svg/circle-check.svg";
import LinkBroken from "open-iconic/svg/link-broken";
import LinkIntact from "open-iconic/svg/link-intact";
import LockLocked from "open-iconic/svg/lock-locked";
import LockUnlocked from "open-iconic/svg/lock-unlocked";
import Person from "open-iconic/svg/person";
import Pencil from "open-iconic/svg/pencil";
import Star from "open-iconic/svg/star";
import X from "open-iconic/svg/x";
@ -21,8 +24,11 @@
'caret-top': CaretTop,
check: Check,
'circle-check': CircleCheck,
'link-broken': LinkBroken,
'link-intact': LinkIntact,
'lock-locked': LockLocked,
'lock-unlocked': LockUnlocked,
pencil: Pencil,
person: Person,
star: Star,
x: X
@ -85,24 +91,29 @@
fill: currentColor;
&.sm {
width: 0.6em;
height: 0.6em;
}
&.md {
width: 1em;
height: 1em;
}
&.md {
&.lg {
width: 1.33em;
height: 1.33em;
}
&.lg {
&.xl {
width: 2em;
height: 2em;
}
&.xl {
width: 3em;
height: 3em;
}
/*&.xl {*/
/*width: 3em;*/
/*height: 3em;*/
/*}*/
}
}

View File

@ -0,0 +1,100 @@
<template>
<nav v-show="totalPages > 1" class="pagination" role="navigation" :aria-label="pagedItemName + ' page navigation'">
<a class="pagination-previous" :title="isFirstPage ? 'This is the first page' : ''" :disabled="isFirstPage" @click.prevent="changePage(currentPage - 1)">Previous</a>
<a class="pagination-next" :title="isLastPage ? 'This is the last page' : ''" :disabled="isLastPage" @click.prevent="changePage(currentPage + 1)">Next page</a>
<ul class="pagination-list">
<li v-for="page in pageItems" :key="page">
<a v-if="page > 0" class="pagination-link" :class="{'is-current': page === currentPage}" href="#" @click.prevent="changePage(page)">{{page}}</a>
<span v-else class="pagination-ellipsis">&hellip;</span>
</li>
</ul>
</nav>
</template>
<script>
export default {
props: {
pagedItemName: {
required: false,
type: String,
default: ''
},
currentPage: {
required: true,
type: Number
},
totalPages: {
required: true,
type: Number
},
pageWindow: {
required: false,
type: Number,
default: 4
},
pageOuterWindow: {
required: false,
type: Number,
default: 1
}
},
computed: {
pageItems() {
const items = new Set();
for (let x = 0; x < this.pageOuterWindow; x++) {
items.add(x + 1);
items.add(this.totalPages - x);
}
const start = this.currentPage - Math.ceil(this.pageWindow / 2);
const end = this.currentPage + Math.floor(this.pageWindow / 2);
for (let x = start; x <= end; x++) {
items.add(x);
}
let emptySpace = -1;
const finalList = [];
[...items.values()].filter(p => p > 0 && p <= this.totalPages).sort((a, b) => a - b).forEach((p, idx, list) => {
finalList.push(p);
if (list[idx + 1] && list[idx + 1] !== p + 1) {
finalList.push(emptySpace--);
}
});
return finalList;
},
isLastPage() {
return this.currentPage === this.totalPages;
},
isFirstPage() {
return this.currentPage === 1;
}
},
methods: {
changePage(idx) {
this.$emit("changePage", idx);
}
}
}
</script>
<style lang="scss" scoped>
ul.pagination {
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<div class="tag-editor control">
<input type="text" class="input" v-model="tagText">
<div class="tags">
<span v-for="t in value" :key="t" class="tag">{{t}}</span>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
required: true,
type: Array
}
},
// data() {
// return {
// valueCopy: []
// };
// },
computed: {
tagText: {
get: function() {
return this.value.join(" ");
},
set: function(str) {
const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))];
if (!this.arraysEqual(newTags, this.value)) {
this.$emit("input", newTags);
}
}
}
},
watch: {
// value(newVal) {
// console.log(newVal);
// if (!this.arraysEqual(newVal, this.valueCopy)) {
// console.log("diff");
// this.valueCopy = newVal;
// }
// }
},
methods: {
arraysEqual(arr1, arr2) {
if(arr1.length !== arr2.length)
return false;
for(let i = arr1.length; i--;) {
if(arr1[i] !== arr2[i])
return false;
}
return true;
}
}
}
</script>

View File

@ -9,10 +9,28 @@
</div>
</div>
<div class="field">
<label class="label is-small-mobile">Nutrient Databank Number</label>
<label class="label is-small-mobile">Nutrient Databank Number</label>
<div class="field has-addons">
<div class="control">
<input type="text" class="input is-small-mobile" v-model="ingredient.ndbn">
<button type="button" class="button" :class="{'is-primary': hasNdbn}"><app-icon :icon="hasNdbn ? 'link-intact' : 'link-broken'" size="sm"></app-icon><span>{{ingredient.ndbn}}</span></button>
</div>
<div class="control is-expanded">
<app-autocomplete
:inputClass="'is-small-mobile'"
ref="autocomplete"
v-model="ingredient.usda_food_name"
:minLength="2"
valueAttribute="name"
labelAttribute="description"
placeholder=""
@optionSelected="searchItemSelected"
:onGetOptions="updateSearchItems"
>
</app-autocomplete>
</div>
<div v-if="hasNdbn" class="control">
<button type="button" class="button is-danger" @click="removeNdbn">X</button>
</div>
</div>
@ -23,13 +41,6 @@
</div>
</div>
<fieldset>
<legent>Ingredient Units</legent>
<button class="button" type="button">Add Unit</button>
</fieldset>
<div class="field">
<label class="label is-small-mobile">Notes</label>
<div class="control">
@ -37,31 +48,96 @@
</div>
</div>
<fieldset>
<legend>Nutrition per 100 grams</legend>
<div class="columns">
<div class="column">
<div class="message">
<div class="message-header">
Custom Units
</div>
<div class="columns is-mobile is-multiline">
<div v-for="(nutrient, name) in nutrients" :key="name" class="column is-half-mobile is-one-third-tablet">
<label class="label is-small-mobile">{{nutrient.label}}</label>
<div class="field has-addons">
<div class="control">
<input type="text" class="input is-small-mobile" v-model="ingredient[name]">
</div>
<div class="control">
<button type="button" class="unit-label button is-static is-small-mobile">{{nutrient.unit}}</button>
</div>
<div class="message-body">
<button class="button" type="button" @click="addUnit">Add Unit</button>
<table class="table">
<tr>
<th>Name</th>
<th>Grams</th>
<th></th>
</tr>
<tr v-for="unit in visibleIngredientUnits" :key="unit.id">
<td>
<div class="control">
<input type="text" class="input is-small-mobile" v-model="unit.name">
</div>
</td>
<td>
<div class="control">
<input type="text" class="input is-small-mobile" v-model="unit.gram_weight">
</div>
</td>
<td>
<button type="button" class="button is-danger" @click="removeUnit(unit)">X</button>
</td>
</tr>
</table>
</div>
</div>
</div>
</fieldset>
<div class="column">
<div class="message">
<div class="message-header">
NDBN Units
</div>
<div class="message-body">
<table class="table">
<tr>
<th>Name</th>
<th>Grams</th>
</tr>
<tr v-for="unit in ingredient.ndbn_units">
<td>{{unit.description}}</td>
<td>{{unit.gram_weight}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="message">
<div class="message-header">
Nutrition per 100 grams
</div>
<div class="message-body">
<div class="columns is-mobile is-multiline">
<div v-for="(nutrient, name) in nutrients" :key="name" class="column is-half-mobile is-one-third-tablet">
<label class="label is-small-mobile">{{nutrient.label}}</label>
<div class="field has-addons">
<div class="control is-expanded">
<input type="text" class="input is-small-mobile" :disabled="hasNdbn" v-model="ingredient[name]">
</div>
<div class="control">
<button type="button" tabindex="-1" class="unit-label button is-static is-small-mobile">{{nutrient.unit}}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import api from "../lib/Api";
import AppAutocomplete from "./AppAutocomplete";
import AppIcon from "./AppIcon";
export default {
props: {
@ -109,8 +185,67 @@
};
},
computed: {
visibleIngredientUnits() {
return this.ingredient.ingredient_units.filter(iu => iu._destroy !== true);
},
hasNdbn() {
return this.ingredient.ndbn !== null;
}
},
methods: {
addUnit() {
this.ingredient.ingredient_units.push({
id: null,
name: null,
gram_weight: null
});
},
removeUnit(unit) {
if (unit.id) {
unit._destroy = true;
} else {
const idx = this.ingredient.ingredient_units.findIndex(i => i === unit);
this.ingredient.ingredient_units.splice(idx, 1);
}
},
removeNdbn() {
this.ingredient.ndbn = null;
this.ingredient.usda_food_name = null;
this.ingredient.ndbn_units = [];
},
updateSearchItems(text) {
return api.getUsdaFoodSearch(text)
.then(data => data.map(f => {
return {
name: f.name,
ndbn: f.ndbn,
description: ["#", f.ndbn, ", Cal:", f.kcal, ", Carbs:", f.carbohydrates, ", Fat:", f.lipid, ", Protein:", f.protein].join("")
}
}));
},
searchItemSelected(food) {
this.ingredient.ndbn = food.ndbn;
this.ingredient.usda_food_name = food.name;
this.ingredient.ndbn_units = [];
this.loadResource(
api.postIngredientSelectNdbn(this.ingredient)
.then(i => Object.assign(this.ingredient, i))
);
},
},
components: {
AppAutocomplete
AppAutocomplete,
AppIcon
}
}

View File

@ -31,9 +31,7 @@
<div class="field">
<label class="label is-small-mobile">Tags</label>
<div class="control">
<input class="input is-small-mobile" type="text" placeholder="tags">
</div>
<app-tag-editor v-model="recipe.tags"></app-tag-editor>
</div>
<div class="columns">
@ -93,6 +91,7 @@
import api from "../lib/Api";
import RecipeEditIngredientEditor from "./RecipeEditIngredientEditor";
import AppTagEditor from "./AppTagEditor";
export default {
props: {
@ -143,6 +142,7 @@
},
components: {
AppTagEditor,
RecipeEditIngredientEditor
}
}

View File

@ -37,7 +37,9 @@
</div>
<div class="column is-narrow">
<span class="label is-small-mobile" v-if="showLabels">&nbsp;</span>
<button type="button" class="button is-danger is-small-mobile" @click="deleteIngredient(ingredient)">X</button>
<button type="button" class="button is-danger is-small-mobile" @click="deleteIngredient(ingredient)">
<app-icon icon="x"></app-icon>
</button>
</div>
</div>
</template>
@ -45,6 +47,7 @@
<script>
import AppAutocomplete from "./AppAutocomplete";
import AppIcon from "./AppIcon";
import api from "../lib/Api";
export default {
@ -92,7 +95,8 @@
},
components: {
AppAutocomplete
AppAutocomplete,
AppIcon
}
}

View File

@ -7,7 +7,7 @@
<ingredient-show :ingredient="ingredient"></ingredient-show>
</div>
<router-link class="button" :to="{name: 'edit_ingredient', params: { id: ingredientId }}">Edit</router-link>
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_ingredient', params: { id: ingredientId }}">Edit</router-link>
<router-link class="button" to="/ingredients">Back</router-link>
</div>
</template>

View File

@ -20,7 +20,36 @@
data() {
return {
ingredient: {
name: null
name: null,
notes: null,
ndbn: null,
density: null,
water: null,
ash: null,
protein: null,
kcal: null,
fiber: null,
sugar: null,
carbohydrates: null,
calcium: null,
iron: null,
magnesium: null,
phosphorus: null,
potassium: null,
sodium: null,
zinc: null,
copper: null,
manganese: null,
vit_c: null,
vit_b6: null,
vit_b12: null,
vit_a: null,
vit_e: null,
vit_d: null,
vit_k: null,
cholesterol: null,
lipids: null,
ingredient_units: []
},
validationErrors: null
}

View File

@ -1,7 +1,7 @@
<template>
<div>
<div v-if="recipe === null">
<div v-if="ingredient === null">
Loading...
</div>
<div v-else>

View File

@ -2,7 +2,9 @@
<div>
<h1 class="title">Ingredients</h1>
<router-link :to="{name: 'new_ingredient'}" class="button is-primary">Create Ingredient</router-link>
<router-link v-if="isLoggedIn" :to="{name: 'new_ingredient'}" class="button is-primary">Create Ingredient</router-link>
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="ingredient" @changePage="changePage"></app-pager>
<table class="table">
<thead>
@ -10,7 +12,7 @@
<th>Name</th>
<th>USDA</th>
<th>KCal per 100g</th>
<th>Density</th>
<th>Density (oz/cup)</th>
<th></th>
</tr>
<tr>
@ -31,12 +33,19 @@
<td>{{i.kcal}}</td>
<td>{{i.density}}</td>
<td>
<button type="button" class="button is-danger">X</button>
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_ingredient', params: { id: i.id } }">
<app-icon icon="pencil"></app-icon>
</router-link>
<button v-if="isLoggedIn" type="button" class="button is-danger">
<app-icon icon="x"></app-icon>
</button>
</td>
</tr>
</tbody>
</table>
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="ingredient" @changePage="changePage"></app-pager>
</div>
</template>
@ -45,6 +54,7 @@
import api from "../lib/Api";
import debounce from "lodash/debounce";
import AppIcon from "./AppIcon";
import AppPager from "./AppPager";
export default {
data() {
@ -65,10 +75,27 @@
} else {
return [];
}
},
totalPages() {
if (this.ingredientData) {
return this.ingredientData.total_pages
}
return 0;
},
currentPage() {
if (this.ingredientData) {
return this.ingredientData.current_page
}
return 0;
}
},
methods: {
changePage(idx) {
this.search.page = idx;
},
getList: debounce(function() {
this.loadResource(
@ -89,7 +116,8 @@
},
components: {
AppIcon
AppIcon,
AppPager
}
}

View File

@ -7,7 +7,7 @@
<recipe-show :recipe="recipe"></recipe-show>
</div>
<router-link class="button" :to="{name: 'edit_recipe', params: { id: recipeId }}">Edit</router-link>
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_recipe', params: { id: recipeId }}">Edit</router-link>
<router-link class="button" to="/">Back</router-link>
</div>
</template>

View File

@ -2,7 +2,9 @@
<div>
<h1 class="title">Recipes</h1>
<router-link :to="{name: 'new_recipe'}" class="button is-primary">Create Recipe</router-link>
<router-link v-if="isLoggedIn" :to="{name: 'new_recipe'}" class="button is-primary">Create Recipe</router-link>
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager>
<table class="table">
<thead>
@ -48,15 +50,21 @@
</td>
<td>{{ r.yields }}</td>
<td>{{ formatRecipeTime(r.total_time, r.active_time) }}</td>
<td>{{ formatDate(r.updated_at) }}</td>
<td><app-date-time :date-time="r.updated_at" :show-time="false"></app-date-time></td>
<td>
<router-link :to="{name: 'edit_recipe', params: { id: r.id } }" class="button">Edit</router-link>
<button type="button" class="button">Delete</button>
<router-link v-if="isLoggedIn" :to="{name: 'edit_recipe', params: { id: r.id } }" class="button">
<app-icon icon="pencil" size="md"></app-icon>
</router-link>
<button v-if="isLoggedIn" type="button" class="button is-danger">
<app-icon icon="x" size="md"></app-icon>
</button>
</td>
</tr>
</tbody>
</table>
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager>
</div>
</template>
@ -64,7 +72,9 @@
import api from "../lib/Api";
import debounce from "lodash/debounce";
import AppDateTime from "./AppDateTime";
import AppIcon from "./AppIcon";
import AppPager from "./AppPager";
import AppRating from "./AppRating";
export default {
@ -100,10 +110,28 @@
{name: 'total_time', label: 'Time', sort: true},
{name: 'created_at', label: 'Created', sort: true}
]
},
totalPages() {
if (this.recipeData) {
return this.recipeData.total_pages;
}
return 0;
},
currentPage() {
if (this.recipeData) {
return this.recipeData.current_page;
}
return 0;
}
},
methods: {
changePage(idx) {
this.search.page = idx;
},
setSort(col) {
if (this.search.sortColumn === col) {
this.search.sortDirection = this.search.sortDirection === "desc" ? "asc" : "desc";
@ -120,15 +148,6 @@
);
}, 500, {leading: true, trailing: true}),
formatDate(dateStr) {
if (dateStr && dateStr.length) {
const date = new Date(dateStr);
return [date.getMonth() + 1, date.getDate(), date.getFullYear() % 100].join("/");
} else {
return "";
}
},
formatRecipeTime(total, active) {
let str = "";
@ -160,7 +179,9 @@
components: {
AppRating,
AppIcon
AppIcon,
AppDateTime,
AppPager
}
}

View File

@ -181,6 +181,76 @@ class Api {
return this.get("/ingredients/" + id);
}
buildIngredientParams(ingredient) {
return {
ingredient: {
name: ingredient.name,
notes: ingredient.notes,
ndbn: ingredient.ndbn,
density: ingredient.density,
water: ingredient.water,
ash: ingredient.ash,
protein: ingredient.protein,
kcal: ingredient.kcal,
fiber: ingredient.fiber,
sugar: ingredient.sugar,
carbohydrates: ingredient.carbohydrates,
calcium: ingredient.calcium,
iron: ingredient.iron,
magnesium: ingredient.magnesium,
phosphorus: ingredient.phosphorus,
potassium: ingredient.potassium,
sodium: ingredient.sodium,
zinc: ingredient.zinc,
copper: ingredient.copper,
manganese: ingredient.manganese,
vit_c: ingredient.vit_c,
vit_b6: ingredient.vit_b6,
vit_b12: ingredient.vit_b12,
vit_a: ingredient.vit_a,
vit_e: ingredient.vit_e,
vit_d: ingredient.vit_d,
vit_k: ingredient.vit_k,
cholesterol: ingredient.cholesterol,
lipids: ingredient.lipids,
ingredient_units_attributes: ingredient.ingredient_units.map(iu => {
if (iu._destroy) {
return {
id: iu.id,
_destroy: true
};
} else {
return {
id: iu.id,
name: iu.name,
gram_weight: iu.gram_weight
};
}
})
}
}
}
postIngredient(ingredient) {
return this.post("/ingredients/", this.buildIngredientParams(ingredient));
}
patchIngredient(ingredient) {
return this.patch("/ingredients/" + ingredient.id, this.buildIngredientParams(ingredient));
}
postIngredientSelectNdbn(ingredient) {
const url = ingredient.id ? "/ingredients/" + ingredient.id + "/select_ndbn" : "/ingredients/select_ndbn";
return this.post(url, this.buildIngredientParams(ingredient));
}
getUsdaFoodSearch(query) {
return this.get("/ingredients/usda_food_search", {query: query});
}
postLogin(username, password) {
const params = {
username: username,

View File

@ -1,6 +1,7 @@
// Bluma default overrides
$green: #79A736;
$red: #FF0000;
$primary: $green;
$modal-content-width: 750px;

View File

@ -6,6 +6,7 @@
@import "~bulma/sass/components/level";
@import "~bulma/sass/components/message";
@import "~bulma/sass/components/modal";
@import "~bulma/sass/components/pagination";
@import "~bulma/sass/elements/_all";
@import "~bulma/sass/grid/columns";
@import "~bulma/sass/layout/section";

View File

@ -3,6 +3,7 @@ json.extract! @ingredient,
:id,
:name,
:ndbn,
:usda_food_name,
:notes,
:density,
:water,
@ -30,3 +31,16 @@ json.extract! @ingredient,
:vit_k,
:cholesterol,
:lipids
if @ingredient.ndbn.present?
json.ndbn_units @ingredient.usda_food.usda_food_weights do |fw|
json.extract! fw, :amount, :description, :gram_weight
end
else
json.ndbn_units []
end
json.ingredient_units @ingredient.ingredient_units do |iu|
json.extract! iu, :id, :name, :gram_weight
json._destroy false
end

View File

@ -1,7 +1,8 @@
json.array! @foods do |f|
json.extract! f, :ndbn
json.extract! f, :ndbn, :kcal, :carbohydrates, :lipid, :protein
json.name f.long_description
end

View File

@ -1,30 +1,36 @@
Arguments:
/home/dan/.nvm/versions/node/v8.7.0/bin/node /home/dan/.nvm/versions/node/v8.7.0/bin/yarn add svg-router
/Users/delbert/.nvm/versions/node/v8.9.0/bin/node /usr/local/Cellar/yarn/1.5.1_1/libexec/bin/yarn.js check --integrity
PATH:
/home/dan/.rvm/gems/ruby-2.5.1/bin:/home/dan/.rvm/gems/ruby-2.5.1@global/bin:/home/dan/.rvm/rubies/ruby-2.5.1/bin:/home/dan/.rvm/bin:/home/dan/.cargo/bin:/home/dan/.nvm/versions/node/v8.7.0/bin:/home/dan/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
/Users/delbert/.rvm/gems/ruby-2.4.3/bin:/Users/delbert/.rvm/gems/ruby-2.4.3@global/bin:/Users/delbert/.rvm/rubies/ruby-2.4.3/bin:/Users/delbert/.rvm/bin:/Users/delbert/.cargo/bin:/Users/delbert/.nvm/versions/node/v8.9.0/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin
Yarn version:
1.5.1
Node version:
8.7.0
8.9.0
Platform:
linux x64
darwin x64
npm manifest:
{
"dependencies": {
"@rails/webpacker": "^3.4.1",
"autosize": "^4.0.1",
"bulma": "^0.6.2",
"caniuse-lite": "^1.0.30000815",
"css-loader": "^0.28.11",
"lodash": "^4.17.5",
"open-iconic": "^1.1.1",
"svg-loader": "^0.0.2",
"vue": "^2.5.16",
"vue-loader": "^14.2.2",
"vue-progressbar": "^0.7.4",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.16",
"vuex": "^3.0.1",
"vuex-router-sync": "^5.0.0",
"webpack": "^3.11.0"
},
"devDependencies": {
@ -318,6 +324,10 @@ Lockfile:
postcss "^6.0.17"
postcss-value-parser "^3.2.3"
autosize@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.1.tgz#4e2f89d00e290dd98e1f95555a8ccb91e9c7a41a"
aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@ -3312,7 +3322,7 @@ Lockfile:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@~4.17.4:
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.4:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
@ -3817,6 +3827,10 @@ Lockfile:
version "3.0.5"
resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-3.0.5.tgz#36eff32201379efdf1180fb445e51a8e2425f9f6"
open-iconic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/open-iconic/-/open-iconic-1.1.1.tgz#9dcfc8c7cd3c61cdb4a236b1a347894c97adc0c6"
opn@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
@ -5554,6 +5568,10 @@ Lockfile:
dependencies:
has-flag "^3.0.0"
svg-loader@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/svg-loader/-/svg-loader-0.0.2.tgz#601ab2fdaa1dadae3ca9975b550de92a07e1d92b"
svgo@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
@ -5903,6 +5921,10 @@ Lockfile:
vue-style-loader "^4.0.1"
vue-template-es2015-compiler "^1.6.0"
vue-progressbar@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/vue-progressbar/-/vue-progressbar-0.7.4.tgz#435dd9cb3707e29a1b18063afed44aeff371e606"
vue-router@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
@ -5929,6 +5951,10 @@ Lockfile:
version "2.5.16"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.16.tgz#07edb75e8412aaeed871ebafa99f4672584a0085"
vuex-router-sync@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/vuex-router-sync/-/vuex-router-sync-5.0.0.tgz#1a225c17a1dd9e2f74af0a1b2c62072e9492b305"
vuex@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
@ -6188,11 +6214,10 @@ Lockfile:
window-size "0.1.0"
Trace:
Error: Couldn't find package "svg-router" on the "npm" registry.
at new MessageError (/home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:186:110)
at NpmResolver.<anonymous> (/home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:51625:15)
Error: Found 1 errors.
at new MessageError (/usr/local/Cellar/yarn/1.5.1_1/libexec/lib/cli.js:186:110)
at /usr/local/Cellar/yarn/1.5.1_1/libexec/lib/cli.js:59470:13
at Generator.next (<anonymous>)
at step (/home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:98:30)
at /home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:109:13
at step (/usr/local/Cellar/yarn/1.5.1_1/libexec/lib/cli.js:98:30)
at /usr/local/Cellar/yarn/1.5.1_1/libexec/lib/cli.js:109:13
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)