|
|
@@ -0,0 +1,370 @@
|
|
|
+// 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"
|
|
|
+// )
|
|
|
+// )
|
|
|
+// )
|
|
|
+// }
|