WIP
This commit is contained in:
parent
263a609084
commit
40ea76895e
10 changed files with 149 additions and 36 deletions
|
|
@ -4,7 +4,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,16 @@ import scala.concurrent.Future
|
||||||
import scala.scalajs.js
|
import scala.scalajs.js
|
||||||
|
|
||||||
import model.Entry
|
import model.Entry
|
||||||
|
import model.EntryId
|
||||||
|
import rdts.base.Lattice
|
||||||
|
import rdts.datatypes.LastWriterWins
|
||||||
|
import scala.scalajs.js.Date
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Success
|
||||||
|
|
||||||
object DexieDB {
|
object DexieDB {
|
||||||
|
|
||||||
private val schemaVersion = 1.0
|
private val schemaVersion = 1.1
|
||||||
|
|
||||||
private val dexieDB: Dexie = new Dexie.^("fahrtenbuch")
|
private val dexieDB: Dexie = new Dexie.^("fahrtenbuch")
|
||||||
dexieDB
|
dexieDB
|
||||||
|
|
@ -31,9 +37,62 @@ object DexieDB {
|
||||||
val entriesObservable: Observable[Future[Seq[Entry]]] =
|
val entriesObservable: Observable[Future[Seq[Entry]]] =
|
||||||
liveQuery(() => getAllEntries())
|
liveQuery(() => getAllEntries())
|
||||||
|
|
||||||
def insertEntry(entry: Entry): Future[Any] = {
|
def getEntry(id: EntryId): Future[Option[Entry]] =
|
||||||
println(s"inserting Entry $entry")
|
entriesTable
|
||||||
|
.get(id.delegate)
|
||||||
|
.toFuture
|
||||||
|
.map(_.toOption.map(NativeConverter[Entry].fromNative(_)))
|
||||||
|
|
||||||
|
def insertEntry(entry: Entry): Unit = {
|
||||||
|
val e: Future[Option[Entry]] = getEntry(entry.id)
|
||||||
|
e.flatMap {
|
||||||
|
case Some(oldEntry) =>
|
||||||
|
println(s"found old: $oldEntry")
|
||||||
|
println(s"found new: $entry")
|
||||||
|
println(oldEntry.id == entry.id)
|
||||||
|
val newEntry = Lattice[Entry].merge(entry, entry)
|
||||||
|
val newEntry2 = Lattice[Entry].merge(oldEntry, oldEntry)
|
||||||
|
println(oldEntry.id)
|
||||||
|
println(entry.id)
|
||||||
|
val test =
|
||||||
|
Lattice[Entry].merge(
|
||||||
|
Entry(
|
||||||
|
EntryId("1"),
|
||||||
|
LastWriterWins.now(0),
|
||||||
|
LastWriterWins.now(2),
|
||||||
|
LastWriterWins.now(""),
|
||||||
|
LastWriterWins.now(false),
|
||||||
|
LastWriterWins.now("Dirk"),
|
||||||
|
LastWriterWins.now(new Date())
|
||||||
|
),
|
||||||
|
Entry(
|
||||||
|
EntryId("1"),
|
||||||
|
LastWriterWins.now(0),
|
||||||
|
LastWriterWins.now(2),
|
||||||
|
LastWriterWins.now(""),
|
||||||
|
LastWriterWins.now(false),
|
||||||
|
LastWriterWins.now("Dirk"),
|
||||||
|
LastWriterWins.now(new Date())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val newEntry3 =
|
||||||
|
Lattice[Entry].merge(oldEntry, entry.copy(id = oldEntry.id))
|
||||||
|
println("yolo")
|
||||||
|
Future.unit
|
||||||
|
// entriesTable.put(newEntry.toNative).toFuture
|
||||||
|
case _ =>
|
||||||
entriesTable.put(entry.toNative).toFuture
|
entriesTable.put(entry.toNative).toFuture
|
||||||
|
}.onComplete {
|
||||||
|
case Failure(exception) => println(s"failed with $exception")
|
||||||
|
case Success(value) => ()
|
||||||
|
}
|
||||||
|
// .toFuture
|
||||||
|
// .map(e =>
|
||||||
|
// if e.isUndefined then entriesTable.put(entry.toNative).toFuture
|
||||||
|
// else
|
||||||
|
// val dbEntry = NativeConverter[Entry].fromNative(e)
|
||||||
|
// Lattice[Entry].merge(entry, dbEntry)
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
def getAllEntries(): Future[Seq[Entry]] = {
|
def getAllEntries(): Future[Seq[Entry]] = {
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import components.AppComponent
|
||||||
|
|
||||||
@main
|
@main
|
||||||
def Fahrtenbuch(): Unit =
|
def Fahrtenbuch(): Unit =
|
||||||
val appComponent = AppComponent(Main.allEntries)
|
val appComponent = AppComponent(Main.allEntries, Trystero.onlineStatus)
|
||||||
|
|
||||||
renderOnDomContentLoaded(
|
renderOnDomContentLoaded(
|
||||||
dom.document.getElementById("app"),
|
dom.document.getElementById("app"),
|
||||||
AppComponent(Main.allEntries).render()
|
appComponent.render()
|
||||||
)
|
)
|
||||||
|
|
||||||
object Main {
|
object Main {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fahrtenbuch
|
package fahrtenbuch
|
||||||
|
|
||||||
import com.raquo.laminar.api.L.*
|
import com.raquo.laminar.api.L.*
|
||||||
|
import org.scalajs.dom
|
||||||
import org.scalajs.dom.RTCConfiguration
|
import org.scalajs.dom.RTCConfiguration
|
||||||
import org.scalajs.dom.RTCIceServer
|
import org.scalajs.dom.RTCIceServer
|
||||||
import org.scalajs.dom.RTCPeerConnection
|
import org.scalajs.dom.RTCPeerConnection
|
||||||
|
|
@ -12,6 +13,10 @@ import typings.trystero.mod.joinRoom
|
||||||
import typings.trystero.mod.selfId
|
import typings.trystero.mod.selfId
|
||||||
|
|
||||||
import scala.scalajs.js
|
import scala.scalajs.js
|
||||||
|
import typings.trystero.mod.ActionProgress
|
||||||
|
import typings.trystero.mod.ActionSender
|
||||||
|
import typings.trystero.mod.ActionReceiver
|
||||||
|
import model.Entry
|
||||||
|
|
||||||
object Trystero:
|
object Trystero:
|
||||||
private val eturn = new RTCIceServer:
|
private val eturn = new RTCIceServer:
|
||||||
|
|
@ -35,11 +40,13 @@ object Trystero:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
val room: Room = joinRoom(MyConfig, "fahrtenbuch")
|
val roomId = dom.window.location.hash
|
||||||
val peerList: Var[List[(String, RTCPeerConnection)]] = Var(List.empty)
|
val room: Room = joinRoom(MyConfig, roomId)
|
||||||
|
println(s"joining room $roomId")
|
||||||
val userId: Var[String] = Var(selfId)
|
val userId: Var[String] = Var(selfId)
|
||||||
|
|
||||||
// listen for incoming messages
|
// track online peers
|
||||||
|
val peerList: Var[List[(String, RTCPeerConnection)]] = Var(List.empty)
|
||||||
def updatePeers(): Unit =
|
def updatePeers(): Unit =
|
||||||
peerList.set(room.getPeers().toList)
|
peerList.set(room.getPeers().toList)
|
||||||
println(s"my peer ID is $selfId")
|
println(s"my peer ID is $selfId")
|
||||||
|
|
@ -51,3 +58,14 @@ object Trystero:
|
||||||
println(s"$peerId left")
|
println(s"$peerId left")
|
||||||
updatePeers()
|
updatePeers()
|
||||||
)
|
)
|
||||||
|
val onlineStatus: Signal[Boolean] = peerList.signal.map(_.nonEmpty)
|
||||||
|
|
||||||
|
object Actions:
|
||||||
|
// setup actions
|
||||||
|
private val entryAction: js.Tuple3[ActionSender[js.Any], ActionReceiver[
|
||||||
|
js.Any
|
||||||
|
], ActionProgress] = Trystero.room.makeAction[js.Any]("entry")
|
||||||
|
private val trysteroReceiveEntry: ActionReceiver[js.Any] = entryAction._2
|
||||||
|
|
||||||
|
def sendEntry(entry: Entry): Unit =
|
||||||
|
entryAction._1(entry.toNative)
|
||||||
|
|
|
||||||
7
src/main/scala/fahrtenbuch/Sync.scala
Normal file
7
src/main/scala/fahrtenbuch/Sync.scala
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fahrtenbuch
|
||||||
|
import com.raquo.laminar.api.L.*
|
||||||
|
import fahrtenbuch.model.Entry
|
||||||
|
|
||||||
|
object Sync:
|
||||||
|
val entrySyncOut =
|
||||||
|
Observer[Entry](onNext = Actions.sendEntry(_))
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
package fahrtenbuch.components
|
package fahrtenbuch.components
|
||||||
|
|
||||||
import com.raquo.laminar.api.L.*
|
import com.raquo.laminar.api.L.*
|
||||||
import fahrtenbuch.model.Entry
|
import fahrtenbuch.model.{Entry, EntryId}
|
||||||
import fahrtenbuch.Main.entryEditBus
|
import fahrtenbuch.Main.entryEditBus
|
||||||
import rdts.base.Uid
|
|
||||||
|
|
||||||
class AppComponent(allEntries: Signal[Set[Entry]]):
|
class AppComponent(
|
||||||
|
allEntries: Signal[Set[Entry]],
|
||||||
|
onlineStatus: Signal[Boolean]
|
||||||
|
):
|
||||||
// tracks whenever a user clicks on an edit button
|
// tracks whenever a user clicks on an edit button
|
||||||
val editClickBus = new EventBus[(Uid, Boolean)]
|
val editClickBus = new EventBus[(EntryId, Boolean)]
|
||||||
|
|
||||||
// tracks which entries are currently being edited
|
// tracks which entries are currently being edited
|
||||||
val editStateSignal: Signal[Map[Uid, Boolean]] =
|
val editStateSignal: Signal[Map[EntryId, Boolean]] =
|
||||||
editClickBus.stream.foldLeft(Map.empty[Uid, Boolean]) {
|
editClickBus.stream.foldLeft(Map.empty[EntryId, Boolean]) {
|
||||||
case (acc, (id, value)) =>
|
case (acc, (id, value)) =>
|
||||||
acc + (id -> value)
|
acc + (id -> value)
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +39,7 @@ class AppComponent(allEntries: Signal[Set[Entry]]):
|
||||||
def render(): HtmlElement =
|
def render(): HtmlElement =
|
||||||
div(
|
div(
|
||||||
cls := "app content",
|
cls := "app content",
|
||||||
h1("Fahrtenbuch"),
|
h1("Fahrtenbuch", OnlineStatusComponent(onlineStatus).render()),
|
||||||
table(
|
table(
|
||||||
cls := "table",
|
cls := "table",
|
||||||
thead(
|
thead(
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
package fahrtenbuch.components
|
package fahrtenbuch.components
|
||||||
import fahrtenbuch.model.Entry
|
import fahrtenbuch.model.{Entry, EntryId}
|
||||||
import com.raquo.laminar.nodes.ReactiveHtmlElement
|
import com.raquo.laminar.nodes.ReactiveHtmlElement
|
||||||
import org.scalajs.dom.HTMLTableRowElement
|
import org.scalajs.dom.HTMLTableRowElement
|
||||||
import com.raquo.laminar.api.L.*
|
import com.raquo.laminar.api.L.*
|
||||||
import com.raquo.laminar.api.features.unitArrows
|
import com.raquo.laminar.api.features.unitArrows
|
||||||
import rdts.base.Uid
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
class EntryComponent(
|
class EntryComponent(
|
||||||
entry: Entry,
|
entry: Entry,
|
||||||
editMode: Boolean,
|
editMode: Boolean,
|
||||||
editClickBus: EventBus[(Uid, Boolean)],
|
editClickBus: EventBus[(EntryId, Boolean)],
|
||||||
entryEditBus: EventBus[Entry]
|
entryEditBus: EventBus[Entry]
|
||||||
):
|
):
|
||||||
def render: ReactiveHtmlElement[HTMLTableRowElement] = {
|
def render: ReactiveHtmlElement[HTMLTableRowElement] = {
|
||||||
|
|
@ -93,11 +92,12 @@ class EntryComponent(
|
||||||
editClickBus.emit(entry.id, false)
|
editClickBus.emit(entry.id, false)
|
||||||
entryEditBus.emit(
|
entryEditBus.emit(
|
||||||
entry.copy(
|
entry.copy(
|
||||||
startKm =
|
driver = entry.driver.write(driverInput.ref.value)
|
||||||
entry.startKm.write(startKmInput.ref.value.toDouble),
|
// startKm =
|
||||||
endKm = entry.endKm.write(endKmInput.ref.value.toDouble),
|
// entry.startKm.write(startKmInput.ref.value.toDouble),
|
||||||
animal = entry.animal.write(animalInput.ref.value),
|
// endKm = entry.endKm.write(endKmInput.ref.value.toDouble),
|
||||||
paid = entry.paid.write(paidCheckbox.ref.checked)
|
// animal = entry.animal.write(animalInput.ref.value),
|
||||||
|
// paid = entry.paid.write(paidCheckbox.ref.checked)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ import com.raquo.laminar.api.L.*
|
||||||
|
|
||||||
import com.raquo.laminar.api.features.unitArrows
|
import com.raquo.laminar.api.features.unitArrows
|
||||||
import fahrtenbuch.Main.entryEditBus
|
import fahrtenbuch.Main.entryEditBus
|
||||||
import fahrtenbuch.model.Entry
|
import fahrtenbuch.model.{Entry, EntryId}
|
||||||
import rdts.base.Uid
|
|
||||||
import rdts.datatypes.LastWriterWins
|
import rdts.datatypes.LastWriterWins
|
||||||
import scala.scalajs.js.Date
|
import scala.scalajs.js.Date
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
@ -77,7 +76,7 @@ class NewEntryInput(showNewEntryField: Var[Boolean]):
|
||||||
button(
|
button(
|
||||||
cls := "button is-success",
|
cls := "button is-success",
|
||||||
onClick --> {
|
onClick --> {
|
||||||
val id = Uid.gen()
|
val id = EntryId.gen()
|
||||||
val driver = LastWriterWins.now(newEntryDriver.ref.value)
|
val driver = LastWriterWins.now(newEntryDriver.ref.value)
|
||||||
val startKm = LastWriterWins.now(newEntryStartKm.ref.value.toDouble)
|
val startKm = LastWriterWins.now(newEntryStartKm.ref.value.toDouble)
|
||||||
val endKm = LastWriterWins.now(newEntryEndKm.ref.value.toDouble)
|
val endKm = LastWriterWins.now(newEntryEndKm.ref.value.toDouble)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fahrtenbuch.components
|
||||||
|
|
||||||
|
import com.raquo.laminar.api.L.*
|
||||||
|
import com.raquo.airstream.core.Signal
|
||||||
|
|
||||||
|
class OnlineStatusComponent(online: Signal[Boolean]):
|
||||||
|
def render(): HtmlElement = {
|
||||||
|
val status = online.map {
|
||||||
|
case true => "Online"
|
||||||
|
case false => "Offline"
|
||||||
|
}
|
||||||
|
|
||||||
|
span(
|
||||||
|
cls := "tag",
|
||||||
|
text <-- status
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,26 @@ import org.getshaka.nativeconverter.NativeConverter
|
||||||
import org.getshaka.nativeconverter.ParseState
|
import org.getshaka.nativeconverter.ParseState
|
||||||
import scala.scalajs.js
|
import scala.scalajs.js
|
||||||
import rdts.datatypes.LastWriterWins
|
import rdts.datatypes.LastWriterWins
|
||||||
|
import rdts.base.Lattice
|
||||||
|
|
||||||
|
opaque type EntryId = Uid
|
||||||
|
object EntryId:
|
||||||
|
def gen(): EntryId = Uid.gen()
|
||||||
|
def apply(id: String): EntryId = Uid.predefined(id)
|
||||||
|
extension (id: EntryId) def delegate: String = id.delegate
|
||||||
|
|
||||||
|
given NativeConverter[EntryId] with {
|
||||||
|
extension (a: EntryId)
|
||||||
|
override def toNative: js.Any =
|
||||||
|
a.delegate
|
||||||
|
override def fromNative(ps: ParseState): Uid =
|
||||||
|
Uid.predefined(ps.json.asInstanceOf[String])
|
||||||
|
}
|
||||||
|
|
||||||
|
given Lattice[EntryId] = Lattice.assertEquals
|
||||||
|
|
||||||
case class Entry(
|
case class Entry(
|
||||||
id: Uid,
|
id: EntryId,
|
||||||
startKm: LastWriterWins[Double],
|
startKm: LastWriterWins[Double],
|
||||||
endKm: LastWriterWins[Double],
|
endKm: LastWriterWins[Double],
|
||||||
animal: LastWriterWins[String],
|
animal: LastWriterWins[String],
|
||||||
|
|
@ -24,10 +41,4 @@ case class Entry(
|
||||||
def costTotal: Double = costGas + costWear
|
def costTotal: Double = costGas + costWear
|
||||||
|
|
||||||
object Entry:
|
object Entry:
|
||||||
given NativeConverter[Uid] with {
|
given Lattice[Entry] = Lattice.derived
|
||||||
extension (a: Uid)
|
|
||||||
override def toNative: js.Any =
|
|
||||||
a.delegate
|
|
||||||
override def fromNative(ps: ParseState): Uid =
|
|
||||||
Uid.predefined(ps.json.asInstanceOf[String])
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue