From 323d16b465468db7cd49e0a2ca77d54b1d8a6f8f Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 2 Jul 2025 00:52:59 -0700 Subject: [PATCH] nove components to separate package --- .../fahrtenbuch/Components/AppComponent.scala | 71 +++++++++++ .../Components/EntryComponent.scala | 75 +++++++++++ .../Components/NewEntryInput.scala | 51 ++++++++ src/main/scala/fahrtenbuch/Database.scala | 2 + src/main/scala/fahrtenbuch/Entry.scala | 68 ---------- src/main/scala/fahrtenbuch/main.scala | 119 ++---------------- 6 files changed, 211 insertions(+), 175 deletions(-) create mode 100644 src/main/scala/fahrtenbuch/Components/AppComponent.scala create mode 100644 src/main/scala/fahrtenbuch/Components/EntryComponent.scala create mode 100644 src/main/scala/fahrtenbuch/Components/NewEntryInput.scala diff --git a/src/main/scala/fahrtenbuch/Components/AppComponent.scala b/src/main/scala/fahrtenbuch/Components/AppComponent.scala new file mode 100644 index 0000000..2f101cb --- /dev/null +++ b/src/main/scala/fahrtenbuch/Components/AppComponent.scala @@ -0,0 +1,71 @@ +package fahrtenbuch.components + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.api.features.unitArrows +import fahrtenbuch.Entry +import fahrtenbuch.Main.allEntries +import fahrtenbuch.Main.entryEditBus +import rdts.base.Uid + +class AppComponent(allEntries: Signal[List[Entry]]): + // tracks whenever a user clicks on an edit button + val editClickBus = new EventBus[(Uid, Boolean)] + + // tracks which entries are currently being edited + val editStateSignal: Signal[Map[Uid, Boolean]] = + editClickBus.stream.foldLeft(Map.empty[Uid, Boolean]) { + case (acc, (id, value)) => + acc + (id -> value) + } + + val entryComponents: Signal[List[EntryComponent]] = + allEntries + .combineWith(editStateSignal) + .map { case (entries, editState) => + entries.toList + .sortBy(_.id) + .map(entry => + EntryComponent( + entry, + editState.getOrElse(entry.id, false), + editClickBus, + entryEditBus + ) + ) + } + + val showNewEntryField = Var(false) + + def render(): HtmlElement = + div( + cls := "app content", + h1("Fahrtenbuch"), + table( + cls := "table", + thead( + tr( +// th("Date"), + th("Fahrer*in"), + th("Start Km"), + th("Ende Km"), + th("Tier"), + th("Abnutzung"), + th("Gesamtkosten"), + th("Bezahlt"), + th() + ) + ), + tbody( + children <-- entryComponents.map(_.map(_.render)), + child(NewEntryInput(showNewEntryField).render) <-- showNewEntryField + ) + ), + button( + cls := "button is-primary", + onClick --> { _ => + showNewEntryField.set(true) + }, + "Eintrag hinzufügen" + ) + ) +end AppComponent diff --git a/src/main/scala/fahrtenbuch/Components/EntryComponent.scala b/src/main/scala/fahrtenbuch/Components/EntryComponent.scala new file mode 100644 index 0000000..435403b --- /dev/null +++ b/src/main/scala/fahrtenbuch/Components/EntryComponent.scala @@ -0,0 +1,75 @@ +package fahrtenbuch.components +import fahrtenbuch.Entry +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.HTMLTableRowElement +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.api.features.unitArrows +import rdts.base.Uid + +class EntryComponent( + entry: Entry, + editMode: Boolean, + editClickBus: EventBus[(Uid, Boolean)], + entryEditBus: EventBus[Entry] +): + def render: ReactiveHtmlElement[HTMLTableRowElement] = { + if editMode then + val driverInput = input(cls := "input", value := entry.driver) + val startKmInput = + input(cls := "input", value := entry.startKm.toString()) + val endKmInput = input(cls := "input", value := entry.endKm.toString()) + val animalInput = input(cls := "input", value := entry.animal) + val costWearInput = + input(cls := "input", value := entry.costWear.toString()) + val costTotalInput = + input(cls := "input", value := entry.costTotal.toString()) + val paidCheckbox = input(`type` := "checkbox", checked := entry.paid) + tr( +// td(input(cls := "input", value := entry.date.toDateString())), + td(driverInput), + td(startKmInput), + td(endKmInput), + td(animalInput), + td(), + td(), + td(paidCheckbox), + td( + button( + cls := "button is-success", + onClick --> { + editClickBus.emit(entry.id, false) + entryEditBus.emit( + entry.copy( + startKm = startKmInput.ref.value.toDouble, + endKm = endKmInput.ref.value.toDouble, + animal = animalInput.ref.value, + paid = paidCheckbox.ref.checked + ) + ) + }, + span( + cls := "icon edit", + i(cls := "mdi mdi-18px mdi-check-bold") + ) + ) + ) + ) + else + tr( + // td(entry.date.toDateString()), + td(entry.driver), + td(entry.startKm), + td(entry.endKm), + td(entry.animal), + td(s"${entry.costWear}€"), + td(s"${entry.costTotal}€"), + td(if entry.paid then "Ja" else "Nein"), + td( + button( + cls := "button is-link", + onClick --> editClickBus.emit(entry.id, true), + span(cls := "icon edit", i(cls := "mdi mdi-18px mdi-pencil")) + ) + ) + ) + } diff --git a/src/main/scala/fahrtenbuch/Components/NewEntryInput.scala b/src/main/scala/fahrtenbuch/Components/NewEntryInput.scala new file mode 100644 index 0000000..2e04eba --- /dev/null +++ b/src/main/scala/fahrtenbuch/Components/NewEntryInput.scala @@ -0,0 +1,51 @@ +package fahrtenbuch.components + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.api.features.unitArrows +import fahrtenbuch.Entry +import fahrtenbuch.Main.entryEditBus +import rdts.base.Uid + +class NewEntryInput(showNewEntryField: Var[Boolean]): + val newEntryDriver = input(cls := "input") + val newEntryStartKm = input(cls := "input") + val newEntryEndKm = input(cls := "input") + val newEntryAnimal = input(cls := "input") + val newEntryPaid = input(`type` := "checkbox") + + def render = + tr( + td(newEntryDriver), + td(newEntryStartKm), + td(newEntryEndKm), + td(newEntryAnimal), + td(), + td(), + td(newEntryPaid), + td( + button( + cls := "button is-success", + onClick --> { + val id = Uid.gen() + val driver = newEntryDriver.ref.value + val startKm = newEntryStartKm.ref.value.toDouble + val endKm = newEntryEndKm.ref.value.toDouble + val animal = newEntryAnimal.ref.value + val paid = newEntryPaid.ref.checked + entryEditBus.emit( + Entry(id, startKm, endKm, animal, paid, driver) + ) + showNewEntryField.set(false) + newEntryDriver.ref.value = "" + newEntryStartKm.ref.value = "" + newEntryEndKm.ref.value = "" + newEntryAnimal.ref.value = "" + newEntryPaid.ref.checked = false + }, + span( + cls := "icon edit", + i(cls := "mdi mdi-18px mdi-check-bold") + ) + ) + ) + ) diff --git a/src/main/scala/fahrtenbuch/Database.scala b/src/main/scala/fahrtenbuch/Database.scala index 2d02be9..72db3da 100644 --- a/src/main/scala/fahrtenbuch/Database.scala +++ b/src/main/scala/fahrtenbuch/Database.scala @@ -8,6 +8,8 @@ import scala.scalajs.js import org.getshaka.nativeconverter.NativeConverter import scala.concurrent.ExecutionContext.Implicits.global import typings.dexie.mod.Observable +import com.raquo.airstream.core.Signal +import com.raquo.airstream.core.EventStream object DexieDB { diff --git a/src/main/scala/fahrtenbuch/Entry.scala b/src/main/scala/fahrtenbuch/Entry.scala index 97fcb61..d18e74b 100644 --- a/src/main/scala/fahrtenbuch/Entry.scala +++ b/src/main/scala/fahrtenbuch/Entry.scala @@ -6,7 +6,6 @@ import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.api.features.unitArrows import org.scalajs.dom.HTMLTableRowElement import com.raquo.laminar.nodes.ReactiveHtmlElement -import fahrtenbuch.Main.editClickBus import scala.annotation.threadUnsafe import fahrtenbuch.Main.entryEditBus import org.getshaka.nativeconverter.NativeConverter @@ -14,73 +13,6 @@ import fahrtenbuch.Main.entryPrinter import org.getshaka.nativeconverter.ParseState import scala.scalajs.js -case class EntryComponent( - entry: Entry, - editMode: Boolean -): - def render: ReactiveHtmlElement[HTMLTableRowElement] = { - if editMode then - val driverInput = input(cls := "input", value := entry.driver) - val startKmInput = - input(cls := "input", value := entry.startKm.toString()) - val endKmInput = input(cls := "input", value := entry.endKm.toString()) - val animalInput = input(cls := "input", value := entry.animal) - val costWearInput = - input(cls := "input", value := entry.costWear.toString()) - val costTotalInput = - input(cls := "input", value := entry.costTotal.toString()) - val paidCheckbox = input(`type` := "checkbox", checked := entry.paid) - tr( -// td(input(cls := "input", value := entry.date.toDateString())), - td(driverInput), - td(startKmInput), - td(endKmInput), - td(animalInput), - td(), - td(), - td(paidCheckbox), - td( - button( - cls := "button is-success", - onClick --> { - editClickBus.emit(entry.id, false) - entryEditBus.emit( - entry.copy( - startKm = startKmInput.ref.value.toDouble, - endKm = endKmInput.ref.value.toDouble, - animal = animalInput.ref.value, - paid = paidCheckbox.ref.checked - ) - ) - }, - entryEditBus.stream --> entryPrinter, - span( - cls := "icon edit", - i(cls := "mdi mdi-18px mdi-check-bold") - ) - ) - ) - ) - else - tr( - // td(entry.date.toDateString()), - td(entry.driver), - td(entry.startKm), - td(entry.endKm), - td(entry.animal), - td(s"${entry.costWear}€"), - td(s"${entry.costTotal}€"), - td(if entry.paid then "Ja" else "Nein"), - td( - button( - cls := "button is-link", - onClick --> editClickBus.emit(entry.id, true), - span(cls := "icon edit", i(cls := "mdi mdi-18px mdi-pencil")) - ) - ) - ) - } - case class Entry( id: Uid, startKm: Double, diff --git a/src/main/scala/fahrtenbuch/main.scala b/src/main/scala/fahrtenbuch/main.scala index ec9710c..24bc16b 100644 --- a/src/main/scala/fahrtenbuch/main.scala +++ b/src/main/scala/fahrtenbuch/main.scala @@ -1,39 +1,31 @@ package fahrtenbuch -import scala.scalajs.js -import scala.scalajs.js.annotation.* -import org.scalajs.dom import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.api.features.unitArrows -import scala.scalajs.js.Date -import rdts.base.Uid import fahrtenbuch.DexieDB.entriesObservable +import org.scalajs.dom +import rdts.base.Uid + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.scalajs.js +import scala.scalajs.js.Date +import scala.scalajs.js.annotation.* import scala.util.Failure import scala.util.Success -import scala.concurrent.ExecutionContext.Implicits.global -// import javascriptLogo from "/javascript.svg" -//@js.native @JSImport("/javascript.svg", JSImport.Default) -//val javascriptLogo: String = js.native +import components.AppComponent @main def Fahrtenbuch(): Unit = + val appComponent = AppComponent(Main.allEntries) + renderOnDomContentLoaded( dom.document.getElementById("app"), - Main.appElement() + AppComponent(Main.allEntries).render() ) - println("Hello, Worlds!") object Main { - // tracks whenever a user clicks on an edit button - val editClickBus = new EventBus[(Uid, Boolean)] - val editStateSignal: Signal[Map[Uid, Boolean]] = - editClickBus.stream.foldLeft(Map.empty[Uid, Boolean]) { - case (acc, (id, value)) => - acc + (id -> value) - } - // track changes to entries val entryEditBus = new EventBus[Entry] val entryObserver = @@ -52,98 +44,11 @@ object Main { case Success(value) => allEntriesVar.set(value.toSet) } ) - val allEntries = allEntriesVar.signal + val allEntries: Signal[List[Entry]] = allEntriesVar.signal.map(_.toList) // val allEntries = entryEditBus.stream.foldLeft(Map.empty[Uid, Entry]) { // case (acc, entry) => // acc + (entry.id -> entry) // } entryEditBus.stream.addObserver(entryObserver)(using unsafeWindowOwner) - val entryComponents: Signal[List[EntryComponent]] = - allEntries - .combineWith(editStateSignal) - .map { case (entries, editState) => - entries.toList - .sortBy(_.id) - .map(entry => - EntryComponent(entry, editState.getOrElse(entry.id, false)) - ) - } - - val showNewEntryField = Var(false) - - val newEntryInput = - val newEntryDriver = input(cls := "input") - val newEntryStartKm = input(cls := "input") - val newEntryEndKm = input(cls := "input") - val newEntryAnimal = input(cls := "input") - val newEntryPaid = input(`type` := "checkbox") - tr( - td(newEntryDriver), - td(newEntryStartKm), - td(newEntryEndKm), - td(newEntryAnimal), - td(), - td(), - td(newEntryPaid), - td( - button( - cls := "button is-success", - onClick --> { - val id = Uid.gen() - val driver = newEntryDriver.ref.value - val startKm = newEntryStartKm.ref.value.toDouble - val endKm = newEntryEndKm.ref.value.toDouble - val animal = newEntryAnimal.ref.value - val paid = newEntryPaid.ref.checked - entryEditBus.emit( - Entry(id, startKm, endKm, animal, paid, driver) - ) - showNewEntryField.set(false) - newEntryDriver.ref.value = "" - newEntryStartKm.ref.value = "" - newEntryEndKm.ref.value = "" - newEntryAnimal.ref.value = "" - newEntryPaid.ref.checked = false - }, - span( - cls := "icon edit", - i(cls := "mdi mdi-18px mdi-check-bold") - ) - ) - ) - ) - - def appElement(): HtmlElement = - div( - cls := "app content", - h1("Fahrtenbuch"), - table( - cls := "table", - thead( - tr( -// th("Date"), - th("Fahrer*in"), - th("Start Km"), - th("Ende Km"), - th("Tier"), - th("Abnutzung"), - th("Gesamtkosten"), - th("Bezahlt"), - th() - ) - ), - tbody( - children <-- entryComponents.map(_.map(_.render)), - child(newEntryInput) <-- showNewEntryField - ) - ), - button( - cls := "button is-primary", - onClick --> { _ => - showNewEntryField.set(true) - }, - "Eintrag hinzufügen" - ) - ) }