|
@@ -1,370 +1,109 @@
|
|
|
-// package com.weEat.view
|
|
|
|
|
-
|
|
|
|
|
-// import com.raquo.laminar.api.L._
|
|
|
|
|
-// import io.laminext.syntax.core._
|
|
|
|
|
-// import com.raquo.airstream.ownership.ManualOwner
|
|
|
|
|
-
|
|
|
|
|
-// import com.weEat.controllers.{FoodController,USDAController}
|
|
|
|
|
-// import com.weEat.modules._
|
|
|
|
|
-// import com.weEat.shared.models.UnitType._
|
|
|
|
|
-// import com.weEat.shared.models._
|
|
|
|
|
-// import org.scalajs.dom.{Event,KeyboardEvent,HTMLInputElement,HTMLElement}
|
|
|
|
|
-// import com.tflucke.sortable.{Sortable, SortableOptions, SortableEvent}
|
|
|
|
|
-// import com.tflucke.typeahead._
|
|
|
|
|
-// import com.tflucke.typeahead.Dataset.Templates
|
|
|
|
|
-// import org.scalajs.dom.document
|
|
|
|
|
-// import gov.usda.nal.fdc.models.DataType._
|
|
|
|
|
-// import scala.concurrent.Future
|
|
|
|
|
-// import scala.util.{Success,Failure}
|
|
|
|
|
-
|
|
|
|
|
-// // TODO: prevent user from not having any of discreet/mass/volume input
|
|
|
|
|
-// // TODO: Save recipe node in cookie until ready to use
|
|
|
|
|
-// object RecipeEdit extends View {
|
|
|
|
|
-// import com.weEat.Main.headers
|
|
|
|
|
-
|
|
|
|
|
-// implicit val ec = com.weEat.shared.ctx
|
|
|
|
|
-
|
|
|
|
|
-// val tag = "viewRecipe"
|
|
|
|
|
-
|
|
|
|
|
-// val title = "Edit Recipe"
|
|
|
|
|
-
|
|
|
|
|
-// val ENTER_KEY_CODE = 13
|
|
|
|
|
-
|
|
|
|
|
-// override def permissions = Set("user")
|
|
|
|
|
-
|
|
|
|
|
-// private val _nameIn = input(idAttr := "name",
|
|
|
|
|
-// cls := "form-control",
|
|
|
|
|
-// required := true
|
|
|
|
|
-// )
|
|
|
|
|
-// private val _name: Signal[String] = _nameIn.value
|
|
|
|
|
-
|
|
|
|
|
-// private val internalFoodDS = Dataset(
|
|
|
|
|
-// { _ => Nil},
|
|
|
|
|
-// Some({ (str: String) => FoodController.query(str.toLowerCase)() }),
|
|
|
|
|
-// display = { food: FoodNodeId => food.name }
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// private val usdaFoodDS = Dataset(
|
|
|
|
|
-// { _ => Nil},
|
|
|
|
|
-// Some({ str: String =>
|
|
|
|
|
-// USDAController.getFoodsSearch(str, Seq(
|
|
|
|
|
-// Foundation, Survey, SRLegacy
|
|
|
|
|
-// ).map(_.toString))().map(_.foods.map(USDANodeNoId.fromSearchResult))
|
|
|
|
|
-// }),
|
|
|
|
|
-// templates = Some(Templates({x: USDANodeNoId => x.name}).copy(
|
|
|
|
|
-// header = Some({ (_: String, _: Seq[USDANodeNoId], _: String) =>
|
|
|
|
|
-// val html = document.createElement("div").asInstanceOf[HTMLElement]
|
|
|
|
|
-// html.innerText = "Unoffical Foods"
|
|
|
|
|
-// html
|
|
|
|
|
-// })
|
|
|
|
|
-// )),
|
|
|
|
|
-// display = {x: USDANodeNoId => x.name}
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// private val _ingredientSearch = input(typ := "text",
|
|
|
|
|
-// cls := "form-control input-sm",
|
|
|
|
|
-// onMountCallback({(ctx) =>
|
|
|
|
|
-// val elm = ctx.thisNode
|
|
|
|
|
-// TypeaheadElement[FoodNode](
|
|
|
|
|
-// elm.ref.asInstanceOf[HTMLInputElement],
|
|
|
|
|
-// minLength = 3
|
|
|
|
|
-// )(Seq(internalFoodDS, usdaFoodDS))
|
|
|
|
|
-// elm.amend(
|
|
|
|
|
-// TypeaheadRx.onSelected[FoodNode] --> { (e: CursorEvent[FoodNode]) =>
|
|
|
|
|
-// e.selectable.map(_.data).foreach({ node =>
|
|
|
|
|
-// // TODO: default unit
|
|
|
|
|
-// _editIngredient(Ingredient.fromFoodNode(node, 0, Gram)) { in =>
|
|
|
|
|
-// _ingredients.update(_ :+ in)
|
|
|
|
|
-// elm.ref.value = ""
|
|
|
|
|
-// Future.successful(())
|
|
|
|
|
-// }
|
|
|
|
|
-// })
|
|
|
|
|
-// }
|
|
|
|
|
-// )
|
|
|
|
|
-// })
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// private val _stepIn = input(idAttr := "step",
|
|
|
|
|
-// cls := "form-control",
|
|
|
|
|
-// onKeyPress --> { (e: KeyboardEvent) => if (e.keyCode == ENTER_KEY_CODE) {
|
|
|
|
|
-// val elm = e.target.asInstanceOf[HTMLInputElement]
|
|
|
|
|
-// _steps.update(_ :+ elm.value)
|
|
|
|
|
-// elm.value = ""
|
|
|
|
|
-// } }
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// private val _ingredients = Var[Seq[Ingredient]](Nil)
|
|
|
|
|
-// private val _steps = Var[Seq[String]](Nil)
|
|
|
|
|
-
|
|
|
|
|
-// private val _servingsIn = input(idAttr := "serv",
|
|
|
|
|
-// typ := "number",
|
|
|
|
|
-// cls := "form-control",
|
|
|
|
|
-// defaultValue := "4",
|
|
|
|
|
-// stepAttr := "0.1",
|
|
|
|
|
-// required := true
|
|
|
|
|
-// )
|
|
|
|
|
-// private val _servings = _servingsIn.value
|
|
|
|
|
-
|
|
|
|
|
-// private val _discreetIn = input(idAttr := "discreet",
|
|
|
|
|
-// typ := "checkbox",
|
|
|
|
|
-// cls := "form-check-input",
|
|
|
|
|
-// defaultChecked := true
|
|
|
|
|
-// )
|
|
|
|
|
-// private val _discreet = _discreetIn.checked
|
|
|
|
|
-
|
|
|
|
|
-// // TODO: Try autopopulate ingredients.sum(_.massPerUnit)
|
|
|
|
|
-// private val _massPIn = input(idAttr := "massP",
|
|
|
|
|
-// cls := "form-control",
|
|
|
|
|
-// typ := "number",
|
|
|
|
|
-// minAttr := "1",
|
|
|
|
|
-// stepAttr := "0.1",
|
|
|
|
|
-// value := "100"
|
|
|
|
|
-// )
|
|
|
|
|
-// private val _massP = _massPIn.value
|
|
|
|
|
-
|
|
|
|
|
-// // TODO: Try autopopulate ingredients.averge(_.density)
|
|
|
|
|
-// private val _volPIn = input(idAttr := "volP",
|
|
|
|
|
-// cls := "form-control",
|
|
|
|
|
-// typ := "number",
|
|
|
|
|
-// minAttr := "0",
|
|
|
|
|
-// stepAttr := "0.1"
|
|
|
|
|
-// )
|
|
|
|
|
-// private val _volP = _volPIn.value
|
|
|
|
|
-
|
|
|
|
|
-// private val _volPUnit = Var[MeasureUnit](Milliliter)
|
|
|
|
|
-// private val _volPUnitIn = select(cls := "custom-select",
|
|
|
|
|
-// MeasureUnit.units.zipWithIndex
|
|
|
|
|
-// .filter({ case (u, _) => u.typ == VOLUME })
|
|
|
|
|
-// .map({ case (unit, idx) =>
|
|
|
|
|
-// // TODO: default selected dynamic
|
|
|
|
|
-// option(
|
|
|
|
|
-// value := idx.toString,
|
|
|
|
|
-// selected := unit.abr == Milliliter.abr,
|
|
|
|
|
-// unit.abr
|
|
|
|
|
-// )
|
|
|
|
|
-// }),
|
|
|
|
|
-// onChange.mapToValue.map(_.toInt).map(MeasureUnit.units(_)) --> _volPUnit
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// val recipieNode = _ingredients.signal.combineWithFn(
|
|
|
|
|
-// _steps,
|
|
|
|
|
-// _name,
|
|
|
|
|
-// _discreet,
|
|
|
|
|
-// _servings,
|
|
|
|
|
-// _volP,
|
|
|
|
|
-// _volPUnit,
|
|
|
|
|
-// _massP
|
|
|
|
|
-// ) { case (ingredients, steps, name, discreet, servings, volP, volPUnit,
|
|
|
|
|
-// massP) =>
|
|
|
|
|
-// val gPServ = massP.toFloatOption
|
|
|
|
|
-// val mLPServ = volP.toFloatOption
|
|
|
|
|
-// .map( _ * volPUnit.conversionRatio )
|
|
|
|
|
-// val (servFactor, servUnit) = gPServ.zip(Some(Gram))
|
|
|
|
|
-// .getOrElse(
|
|
|
|
|
-// if (discreet) (1.0f, Count)
|
|
|
|
|
-// else mLPServ.zip(Some(Milliliter)).getOrElse(???)
|
|
|
|
|
-// ).asInstanceOf[(Float, MeasureUnit)]
|
|
|
|
|
-// val numServings = servings.toFloatOption.getOrElse(1.0f)
|
|
|
|
|
-// val stdQtiesPServing = servFactor / servUnit.typ.standardQuanity
|
|
|
|
|
-// val stdQties = numServings * stdQtiesPServing
|
|
|
|
|
-
|
|
|
|
|
-// RecipeNodeNoId(
|
|
|
|
|
-// name,
|
|
|
|
|
-// stdQties,
|
|
|
|
|
-// stdQtiesPServing,
|
|
|
|
|
-// servUnit.typ,
|
|
|
|
|
-// ingredients,
|
|
|
|
|
-// steps,
|
|
|
|
|
-// gPServ.zip(mLPServ).map({ case (m, v) => (m/v).toFloat }),
|
|
|
|
|
-// gPServ
|
|
|
|
|
-// )
|
|
|
|
|
-// }
|
|
|
|
|
-
|
|
|
|
|
-// private def _editIngredient(ing: Ingredient)(callback: (Ingredient => Future[_])) = {
|
|
|
|
|
-// val amount = Var[Float](ing.amount)
|
|
|
|
|
-// val unit = Var[MeasureUnit](Gram)
|
|
|
|
|
-
|
|
|
|
|
-// Overlay.confirmFuture(ing.food.map({ (food) =>
|
|
|
|
|
-// val amountIn = input(typ := "number",
|
|
|
|
|
-// cls := "form-control input-sm col-9",
|
|
|
|
|
-// minAttr := "0",
|
|
|
|
|
-// defaultValue := ing.amount.toString,
|
|
|
|
|
-// onInput.mapToValue.map(_.toFloat) --> amount
|
|
|
|
|
-// )
|
|
|
|
|
-// val unitIn = select(cls := "col-3 custom-select",
|
|
|
|
|
-// MeasureUnit.units.zipWithIndex.map({ case (unit, idx) =>
|
|
|
|
|
-// // TODO: default selected dynamic
|
|
|
|
|
-// option(value := idx.toString,
|
|
|
|
|
-// selected := unit.abr == Gram.abr,
|
|
|
|
|
-// unit.name
|
|
|
|
|
-// )
|
|
|
|
|
-// }),
|
|
|
|
|
-// onChange.mapToValue.map(_.toInt).map(MeasureUnit.units(_)) --> unit
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// div(cls := "row", amountIn, unitIn)
|
|
|
|
|
-// }),
|
|
|
|
|
-// amount.signal.recoverToTry.map(_.isSuccess) &&
|
|
|
|
|
-// unit.signal.recoverToTry.map(_.isSuccess)
|
|
|
|
|
-// ) { () =>
|
|
|
|
|
-// callback(ing.copy(
|
|
|
|
|
-// amount = amount.now(),
|
|
|
|
|
-// unit = unit.now()
|
|
|
|
|
-// ))
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
-
|
|
|
|
|
-// private def _removeFromList[T](s: Seq[T], idx: Int) =
|
|
|
|
|
-// s.take(idx) ++ s.drop(idx + 1)
|
|
|
|
|
-
|
|
|
|
|
-// private val _stepList = ol(
|
|
|
|
|
-// listStyleType := "none",
|
|
|
|
|
-// paddingLeft := "0",
|
|
|
|
|
-// children <-- _steps.signal.map(_.zipWithIndex.map((presentStep _).tupled)),
|
|
|
|
|
-// onMountCallback({(ctx) =>
|
|
|
|
|
-// Sortable.create(ctx.thisNode.ref, SortableOptions.onUpdate({
|
|
|
|
|
-// event: SortableEvent =>
|
|
|
|
|
-// _steps.update({ steps =>
|
|
|
|
|
-// (event.oldIndex.toOption, event.newIndex.toOption) match {
|
|
|
|
|
-// case (Some(old), Some(ne)) if (old < ne) =>
|
|
|
|
|
-// moveBackwards(steps, old, ne)
|
|
|
|
|
-// case (Some(old), Some(ne)) =>
|
|
|
|
|
-// moveBackwards(
|
|
|
|
|
-// steps.reverse,
|
|
|
|
|
-// steps.size - old - 1,
|
|
|
|
|
-// steps.size - ne - 1
|
|
|
|
|
-// ).reverse
|
|
|
|
|
-// case (None, Some(ne)) =>
|
|
|
|
|
-// val (first, second) = steps.splitAt(ne)
|
|
|
|
|
-// (first :+ event.item.innerText) ++ second
|
|
|
|
|
-// case (Some(old), None) =>
|
|
|
|
|
-// _removeFromList(steps, old)
|
|
|
|
|
-// }
|
|
|
|
|
-// })
|
|
|
|
|
-// // If the added element is still in the parent, remove it since the
|
|
|
|
|
-// // Rx.update will have generated a new one.
|
|
|
|
|
-// Option(event.item.parentElement).map(_.removeChild(event.item))
|
|
|
|
|
-
|
|
|
|
|
-// def moveBackwards(steps: Seq[String], old: Int, ne: Int) =
|
|
|
|
|
-// (steps.take(old) ++
|
|
|
|
|
-// steps.take(ne+1).drop(old + 1) :+
|
|
|
|
|
-// steps(old)) ++
|
|
|
|
|
-// steps.drop(ne+1)
|
|
|
|
|
-// }))
|
|
|
|
|
-// })
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// def presentFoodNode(idx: Int)(ingredientSig: Signal[Ingredient]) =
|
|
|
|
|
-// li(children <-- ingredientSig.flatMap { (ingredient: Ingredient) =>
|
|
|
|
|
-// Signal.fromFuture(ingredient.food).optionMap { (food) =>
|
|
|
|
|
-// Seq(
|
|
|
|
|
-// span(cls := "ui-icon ui-icon-pencil",
|
|
|
|
|
-// onClick --> { (e: Event) =>
|
|
|
|
|
-// _editIngredient(ingredient) { (in) =>
|
|
|
|
|
-// _ingredients.update(_.updated(idx, in))
|
|
|
|
|
-// Future.successful(())
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
-// ),
|
|
|
|
|
-// span(cls := "ui-icon ui-icon-close",
|
|
|
|
|
-// onClick --> { (e: Event) =>
|
|
|
|
|
-// _ingredients.update(_.filterNot(_ == ingredient))
|
|
|
|
|
-// }
|
|
|
|
|
-// ),
|
|
|
|
|
-// span(f"${ingredient.amount}%.02f${ingredient.unit.abr} ${food.name}")
|
|
|
|
|
-// )
|
|
|
|
|
-// } withDefault(Nil)
|
|
|
|
|
-// })
|
|
|
|
|
-
|
|
|
|
|
-// def presentStep(step: String, idx: Int) = li(
|
|
|
|
|
-// span(cls := "ui-icon ui-icon-close",
|
|
|
|
|
-// onClick --> { (e: Event) =>
|
|
|
|
|
-// _steps.update(_removeFromList(_, idx))
|
|
|
|
|
-// }
|
|
|
|
|
-// ),
|
|
|
|
|
-// step
|
|
|
|
|
-// )
|
|
|
|
|
-
|
|
|
|
|
-// def content = div(
|
|
|
|
|
-// h2("Recipe Editor"),
|
|
|
|
|
-// div(cls := "form-group",
|
|
|
|
|
-// div(cls := "container",
|
|
|
|
|
-// div(cls := "row",
|
|
|
|
|
-// div(cls := "col-md-12",
|
|
|
|
|
-// label("Name: "),
|
|
|
|
|
-// _nameIn
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "row",
|
|
|
|
|
-// div(cls := "col-md-3 form-check",
|
|
|
|
|
-// div(cls := "form-check",
|
|
|
|
|
-// _discreetIn,
|
|
|
|
|
-// label(cls := "form-check-label",
|
|
|
|
|
-// forId := "discreet",
|
|
|
|
|
-// "Descrete Servings"
|
|
|
|
|
-// )
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "col-md-3 input-group",
|
|
|
|
|
-// _servingsIn,
|
|
|
|
|
-// div(cls := "input-group-append",
|
|
|
|
|
-// label(cls := "input-group-text", "Servings")
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "col-md-3 input-group",
|
|
|
|
|
-// _massPIn,
|
|
|
|
|
-// div(cls := "input-group-append",
|
|
|
|
|
-// label(cls := "input-group-text", "g/Serving")
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "col-md-3 input-group",
|
|
|
|
|
-// _volPIn,
|
|
|
|
|
-// _volPUnitIn,
|
|
|
|
|
-// div(cls := "input-group-append",
|
|
|
|
|
-// label(cls := "input-group-text", "/Serving"),
|
|
|
|
|
-// ),
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "row",
|
|
|
|
|
-// div(cls := "col-md-5",
|
|
|
|
|
-// h2("Ingredients"),
|
|
|
|
|
-// _ingredientSearch,
|
|
|
|
|
-// ul(
|
|
|
|
|
-// listStyleType := "none",
|
|
|
|
|
-// paddingLeft := "0",
|
|
|
|
|
-// children <-- _ingredients.signal.splitByIndex {
|
|
|
|
|
-// case (idx, _, ingredientStream) =>
|
|
|
|
|
-// presentFoodNode(idx)(ingredientStream)
|
|
|
|
|
-// }
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "col-md-5",
|
|
|
|
|
-// h2("Steps"),
|
|
|
|
|
-// _stepIn,
|
|
|
|
|
-// _stepList
|
|
|
|
|
-// ),
|
|
|
|
|
-// div(cls := "col-md-2",
|
|
|
|
|
-// NutritionPane(recipieNode).render
|
|
|
|
|
-// )
|
|
|
|
|
-// )
|
|
|
|
|
-// ),
|
|
|
|
|
-// button(cls := "btn",
|
|
|
|
|
-// onClick --> { (e: Event) =>
|
|
|
|
|
-// implicit val owner = new ManualOwner()
|
|
|
|
|
-
|
|
|
|
|
-// // import play.api.libs.json.Json
|
|
|
|
|
-// // println(recipieNode.value)
|
|
|
|
|
-// // println(Json.toJson(recipieNode.value))
|
|
|
|
|
-// // println(Json.stringify(Json.toJson(recipieNode.value)))
|
|
|
|
|
-// FoodController.add()(recipieNode.observe.now()).onComplete {
|
|
|
|
|
-// case Success(_) => println("Success!")
|
|
|
|
|
-// case Failure(ex) =>
|
|
|
|
|
-// println("Could not add recipe")
|
|
|
|
|
-// throw ex
|
|
|
|
|
-// }
|
|
|
|
|
-// },
|
|
|
|
|
-// "Add"
|
|
|
|
|
-// )
|
|
|
|
|
-// )
|
|
|
|
|
-// )
|
|
|
|
|
-// }
|
|
|
|
|
|
|
+package com.weEat.view
|
|
|
|
|
+
|
|
|
|
|
+import com.raquo.laminar.api.L._
|
|
|
|
|
+import io.laminext.syntax.core._
|
|
|
|
|
+import com.raquo.waypoint._
|
|
|
|
|
+
|
|
|
|
|
+import play.api.libs.json.{JsValue,Json}
|
|
|
|
|
+import com.weEat.controllers.FoodController
|
|
|
|
|
+import com.weEat.modules._
|
|
|
|
|
+import com.weEat.shared.models._
|
|
|
|
|
+import com.weEat.shared.models.UnitType._
|
|
|
|
|
+
|
|
|
|
|
+// TODO: prevent user from not having any of discreet/mass/volume input
|
|
|
|
|
+// TODO: Save recipe node in cookie until ready to use
|
|
|
|
|
+object RecipeView extends View[String] {
|
|
|
|
|
+ import com.weEat.Main.headers
|
|
|
|
|
+
|
|
|
|
|
+ implicit val ec = com.weEat.shared.ctx
|
|
|
|
|
+
|
|
|
|
|
+ val navName = None
|
|
|
|
|
+ val tag = "viewRecipe"
|
|
|
|
|
+
|
|
|
|
|
+ case class ViewPage(val id: String) extends P {
|
|
|
|
|
+ // TODO: Add recipe name to title
|
|
|
|
|
+ val title = "Recipe"
|
|
|
|
|
+ def jsonValue = Json.toJson(id)
|
|
|
|
|
+ }
|
|
|
|
|
+ def parseJson(jsVal: JsValue) = ViewPage(jsVal.as[String])
|
|
|
|
|
+ def route = Route.onlyQuery(
|
|
|
|
|
+ encode = (page: ViewPage) => page.id,
|
|
|
|
|
+ decode = (id: String) => ViewPage(id = id),
|
|
|
|
|
+ pattern = (root / tag / endOfSegments) ? param[String]("id")
|
|
|
|
|
+ )
|
|
|
|
|
+ def defaultPage = ???
|
|
|
|
|
+
|
|
|
|
|
+ val ENTER_KEY_CODE = 13
|
|
|
|
|
+
|
|
|
|
|
+ def presentFoodNode(ingredient: Ingredient): Node =
|
|
|
|
|
+ li(child <-- Signal.fromFuture(ingredient.food).optionMap({ (food) =>
|
|
|
|
|
+ span(f"${ingredient.amount}%.02f${ingredient.unit.abr} ${food.name}")
|
|
|
|
|
+ }).withDefault(span())
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ def content(page: Signal[ViewPage]) = {
|
|
|
|
|
+ val food: Signal[Option[RecipeNodeId]] = page.flatMap((s) => Signal.fromFuture(
|
|
|
|
|
+ FoodController.get(s.id)().map(_.asInstanceOf[RecipeNodeId])
|
|
|
|
|
+ ))
|
|
|
|
|
+
|
|
|
|
|
+ div(
|
|
|
|
|
+ h2("Recipe"),
|
|
|
|
|
+ div(cls := "form-group",
|
|
|
|
|
+ div(cls := "container",
|
|
|
|
|
+ div(cls := "row",
|
|
|
|
|
+ div(cls := "col-md-12",
|
|
|
|
|
+ child.text <-- food.optionMap(_.name).withDefault("loading...")
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ div(cls := "row",
|
|
|
|
|
+ div(cls := "col-md-3",
|
|
|
|
|
+ "Servings: ",
|
|
|
|
|
+ child.text <-- food.optionMap(_.numServings.toString).withDefault("loading...")
|
|
|
|
|
+ ),
|
|
|
|
|
+ child <-- food.optionFlatMap((f) =>
|
|
|
|
|
+ f.massPerUnit
|
|
|
|
|
+ .map(div(cls := "col-md-3",
|
|
|
|
|
+ "Serving Weight: ", _, UnitType.defaultUnit(MASS).abr
|
|
|
|
|
+ ))
|
|
|
|
|
+ ).withDefault(div(cls := "col-md-3")),
|
|
|
|
|
+ child <-- food.optionFlatMap((f) =>
|
|
|
|
|
+ f.massPerUnit
|
|
|
|
|
+ .flatMap((mpu) => f.density.map(mpu / _))
|
|
|
|
|
+ .map((v) => div(cls := "col-md-3",
|
|
|
|
|
+ "Servings Volume: ",
|
|
|
|
|
+ f"$v%.1f",
|
|
|
|
|
+ UnitType.defaultUnit(VOLUME).abr
|
|
|
|
|
+ ))
|
|
|
|
|
+ ).withDefault(div(cls := "col-md-3"))
|
|
|
|
|
+ ),
|
|
|
|
|
+ div(cls := "row",
|
|
|
|
|
+ div(cls := "col-md-5",
|
|
|
|
|
+ h2("Ingredients"),
|
|
|
|
|
+ ul(
|
|
|
|
|
+ listStyleType := "none",
|
|
|
|
|
+ paddingLeft := "0",
|
|
|
|
|
+ children <-- food
|
|
|
|
|
+ .optionMap(_.ingredients.map(presentFoodNode(_)))
|
|
|
|
|
+ .withDefault(Seq(span("loading...")))
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ div(cls := "col-md-5",
|
|
|
|
|
+ h2("Steps"),
|
|
|
|
|
+ ol(
|
|
|
|
|
+ listStyleType := "none",
|
|
|
|
|
+ paddingLeft := "0",
|
|
|
|
|
+ children <-- food
|
|
|
|
|
+ .optionMap(_.steps.map(li(_)))
|
|
|
|
|
+ .withDefault(Seq(li("loading...")))
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ div(cls := "col-md-2",
|
|
|
|
|
+ child <-- food.optionMap((r) => NutritionPane(Val(r)).render)
|
|
|
|
|
+ .withDefault(div())
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|