Skip to content

Commit f2c0cc7

Browse files
authored
Merge pull request #3 from pme123/provide-simple-store
Provide simple store
2 parents 6e8a4de + fc72325 commit f2c0cc7

File tree

6 files changed

+113
-46
lines changed

6 files changed

+113
-46
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ case class AdapterNotRunning(logReport: Option[LogReport]) extends AdapterMsg
7070
// each LogEntry that is created by the AdapterProcess
7171
case class LogEntryMsg(logEntry: LogEntry) extends AdapterMsg
7272

73+
// sent when the Adapter Process is started
74+
case object RunStarted extends AdapterMsg
75+
7376
// sent when the Adapter Process finished
7477
case class RunFinished(logReport: LogReport) extends AdapterMsg
7578

@@ -87,7 +90,8 @@ The client is split in 3 classes:
8790
### AdapterClient
8891
The whole web page is here composed with `Binding.scala data-binding expressions`.
8992

90-
It is more or less HTML-snippets that contain dynamic content provided by `Binding.scala data sources`:
93+
It is more or less HTML-snippets that contain dynamic content provided by `Binding.scala data sources`.
94+
They are encapsulated in the **UIState**:
9195

9296
* `logData: Vars[LogEntry]` a list of LogEntries that from the active- or last Adapter run.
9397
* `isRunning: Var[Boolean]` is true if the Adapter process is running at the moment.
@@ -112,6 +116,16 @@ My solution is taken from here: [github.com/vmunier/play-scalajs.g8](https://git
112116
* add to the end of the `routes`-file `-> /webjars webjars.Routes`
113117
* now you can use them like `<img src={"" + g.jsRoutes.controllers.Assets.versioned("images/favicon.png").url}></img>`
114118
Don't forget `import scala.scalajs.js.Dynamic.{global => g}`
119+
120+
### UIStore
121+
I like the concepts of Redux.
122+
However with `Binding.scala` most of the patterns are obsolete.
123+
What I wanted to achieve was to manipulate the UIState in just one place.
124+
My solution (for now) is to have a UIStore trait, that provides for each manipulation-action
125+
a function.
126+
To have a general reducer seems to be an unnecessary overhead and even worse
127+
the payload is not type safe.
128+
115129
### ClientWebsocket
116130
Encapsulates the communication with the server. The internal communication with the AdapterClient
117131
is done via the `Binding.scala data sources`.
@@ -207,3 +221,14 @@ $ sbt
207221
> run
208222
```
209223
open [http://localhost:9000](http://localhost:9000) in a browser.
224+
225+
# This is the end;)
226+
227+
Please create an issue, if you...
228+
229+
* have some suggestions
230+
* find a bug
231+
* don't understand a part
232+
* have a question
233+
234+

client/src/main/scala/client/AdapterClient.scala

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package client
22

3-
import com.thoughtworks.binding.Binding.{Constants, Var, Vars}
3+
import com.thoughtworks.binding.Binding.Constants
44
import com.thoughtworks.binding.{Binding, dom}
55
import org.scalajs.dom.document
66
import org.scalajs.dom.raw._
@@ -11,18 +11,17 @@ import scala.language.implicitConversions
1111
import scala.scalajs.js
1212
import scala.scalajs.js.Dynamic.{global => g}
1313

14-
object AdapterClient extends js.JSApp {
14+
object AdapterClient
15+
extends js.JSApp
16+
with UIStore {
1517

16-
private val logData = Vars[LogEntry]()
17-
private val isRunning = Var[Boolean](false)
18-
private val filterText = Var[String]("")
19-
private val filterLevel = Var[LogLevel](LogLevel.DEBUG)
20-
private val lastLogLevel = Var[Option[LogLevel]](None)
21-
private lazy val socket = ClientWebsocket(logData, isRunning, filterText, filterLevel, lastLogLevel)
18+
val uiState = UIState()
19+
20+
private lazy val socket = ClientWebsocket(uiState)
2221

2322
def main(): Unit = {
2423
dom.render(document.body, render)
25-
socket.connectWS() // initial population
24+
socket.connectWS()
2625
import SemanticUI.jq2semantic
2726
jQuery(".ui.dropdown").dropdown(js.Dynamic.literal(on = "hover"))
2827
}
@@ -54,7 +53,7 @@ object AdapterClient extends js.JSApp {
5453

5554
@dom
5655
private def lastLevel = {
57-
val logLevel = lastLogLevel.bind
56+
val logLevel = uiState.lastLogLevel.bind
5857

5958
@dom
6059
def logImage(levelClass: String): Binding[HTMLElement] =
@@ -89,7 +88,8 @@ object AdapterClient extends js.JSApp {
8988
<input id="filterInput"
9089
type="text"
9190
placeholder="Filter..."
92-
onkeyup={event: Event => filterText.value = s"${filterInput.value}"}>
91+
onkeyup={_: Event =>
92+
changeFilterText(s"${filterInput.value}")}>
9393
</input>
9494
</div>
9595
</div>
@@ -106,8 +106,8 @@ object AdapterClient extends js.JSApp {
106106
data:data-position="bottom right">
107107
<select id="filterSelect"
108108
class="ui compact dropdown"
109-
onchange={event: Event =>
110-
filterLevel.value = LogLevel.fromLevel(s"${filterSelect.value}").get}>
109+
onchange={_: Event =>
110+
changeFilterLevel(LogLevel.fromLevel(s"${filterSelect.value}").get)}>
111111
<option value="ERROR">ERROR</option>
112112
<option value="WARN">WARN</option>
113113
<option value="INFO">INFO</option>
@@ -118,7 +118,7 @@ object AdapterClient extends js.JSApp {
118118

119119
@dom
120120
private def runAdapterButton = {
121-
val runDisabled = isRunning.bind
121+
val runDisabled = uiState.isRunning.bind
122122

123123
<div class="ui item">
124124
<button class="ui basic icon button"
@@ -135,7 +135,7 @@ object AdapterClient extends js.JSApp {
135135
private def clearButton = {
136136
<div class="ui item">
137137
<button class="ui basic icon button"
138-
onclick={_: Event => logData.value.clear()}
138+
onclick={_: Event => clearLogData()}
139139
data:data-tooltip="Clear the console"
140140
data:data-position="bottom right">
141141
<i class="remove circle outline icon large"></i>
@@ -145,9 +145,9 @@ object AdapterClient extends js.JSApp {
145145

146146
@dom
147147
private def adapterContainer = {
148-
val logEntries = logData.bind
149-
val text = filterText.bind
150-
val level = filterLevel.bind
148+
val logEntries = uiState.logData.bind
149+
val text = uiState.filterText.bind
150+
val level = uiState.filterLevel.bind
151151
val filteredLE =
152152
logEntries
153153
.filter(le => le.level >= level)

client/src/main/scala/client/ClientWebsocket.scala

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
package client
22

3-
import com.thoughtworks.binding.Binding.{Var, Vars}
43
import org.scalajs.dom.raw._
54
import org.scalajs.dom.{document, window}
65
import play.api.libs.json.{JsError, JsSuccess, Json}
76
import shared._
87

98
import scala.scalajs.js.timers.setTimeout
109

11-
case class ClientWebsocket(logData: Vars[LogEntry]
12-
, isRunning: Var[Boolean]
13-
, filterText: Var[String]
14-
, filterLevel: Var[LogLevel]
15-
, lastLogLevel: Var[Option[LogLevel]]) {
10+
case class ClientWebsocket(uiState: UIState)
11+
extends UIStore {
1612

1713
private lazy val wsURL = s"ws://${window.location.host}/ws"
1814

@@ -25,21 +21,21 @@ case class ClientWebsocket(logData: Vars[LogEntry]
2521
val message = Json.parse(e.data.toString)
2622
message.validate[AdapterMsg] match {
2723
case JsSuccess(AdapterRunning(logReport), _) =>
28-
println(s"Adapter running")
29-
isRunning.value = true
30-
addLogEntries(logReport)
24+
changeIsRunning(true)
25+
newLogEntries(logReport)
3126
case JsSuccess(AdapterNotRunning(logReport), _) =>
32-
println(s"Adapter NOT running")
33-
isRunning.value = false
34-
lastLogLevel.value = logReport.map(_.maxLevel())
35-
logReport.foreach(addLogEntries)
27+
changeIsRunning(false)
28+
logReport.foreach { lr =>
29+
changeLastLogLevel(lr)
30+
newLogEntries(lr)
31+
}
3632
case JsSuccess(LogEntryMsg(le), _) =>
37-
isRunning.value = true
38-
addLogEntry(le)
33+
newLogEntry(le)
34+
case JsSuccess(RunStarted, _) =>
35+
changeIsRunning(true)
3936
case JsSuccess(RunFinished(logReport), _) =>
40-
println("Run Finished")
41-
isRunning.value = false
42-
lastLogLevel.value = Some(logReport.maxLevel())
37+
changeIsRunning(false)
38+
changeLastLogLevel(logReport)
4339
case JsSuccess(other, _) =>
4440
println(s"Other message: $other")
4541
case JsError(errors) =>
@@ -50,9 +46,9 @@ case class ClientWebsocket(logData: Vars[LogEntry]
5046
println(s"exception with websocket: ${e.message}!")
5147
socket.close(0, e.message)
5248
}
53-
socket.onopen = { (e: Event) =>
49+
socket.onopen = { (_: Event) =>
5450
println("websocket open!")
55-
logData.value.clear()
51+
clearLogData()
5652
}
5753
socket.onclose = { (e: CloseEvent) =>
5854
println("closed socket" + e.reason)
@@ -67,15 +63,12 @@ case class ClientWebsocket(logData: Vars[LogEntry]
6763
socket.send(Json.toJson(RunAdapter()).toString())
6864
}
6965

70-
private def addLogEntries(logReport: LogReport): Unit = {
71-
logReport.logEntries.foreach(addLogEntry)
66+
private def newLogEntries(logReport: LogReport) {
67+
logReport.logEntries.foreach(newLogEntry)
7268
}
7369

74-
private def addLogEntry(logEntry: LogEntry): Unit = {
75-
logData.value += logEntry
76-
logData.value
77-
.filter(le => le.level >= filterLevel.value)
78-
.filter(le => le.msg.contains(filterText.value))
70+
private def newLogEntry(logEntry: LogEntry) {
71+
addLogEntry(logEntry)
7972

8073
val objDiv = document.getElementById("log-panel")
8174
objDiv.scrollTop = objDiv.scrollHeight - 20
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package client
2+
3+
import com.thoughtworks.binding.Binding.{Var, Vars}
4+
import shared.{LogEntry, LogLevel, LogReport}
5+
6+
trait UIStore {
7+
protected def uiState: UIState
8+
9+
protected def clearLogData() {
10+
println("UIStore: clearLogData")
11+
uiState.logData.value.clear()
12+
}
13+
14+
protected def addLogEntry(logEntry: LogEntry) {
15+
println(s"UIStore: addLogEntry $logEntry")
16+
uiState.logData.value += logEntry
17+
}
18+
19+
protected def changeFilterText(text: String) {
20+
println(s"UIStore: changeFilterText $text")
21+
uiState.filterText.value = text
22+
}
23+
24+
protected def changeFilterLevel(logLevel: LogLevel) {
25+
println(s"UIStore: changeFilterLevel $logLevel")
26+
uiState.filterLevel.value = logLevel
27+
}
28+
29+
protected def changeIsRunning(running: Boolean) {
30+
println(s"UIStore: changeIsRunning $running")
31+
uiState.isRunning.value = running
32+
}
33+
34+
protected def changeLastLogLevel(report: LogReport) {
35+
println(s"UIStore: changeLastLogLevel ${report.maxLevel()}")
36+
uiState.lastLogLevel.value = Some(report.maxLevel())
37+
}
38+
}
39+
40+
case class UIState(logData: Vars[LogEntry] = Vars[LogEntry]()
41+
, isRunning: Var[Boolean] = Var[Boolean](false)
42+
, filterText: Var[String] = Var[String]("")
43+
, filterLevel: Var[LogLevel] = Var[LogLevel](LogLevel.DEBUG)
44+
, lastLogLevel: Var[Option[LogLevel]] = Var[Option[LogLevel]](None)
45+
)

server/app/actors/AdapterActor.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class AdapterActor @Inject()(implicit mat: Materializer, ec: ExecutionContext)
6767
logService = LogService("Demo Adapter Process", user)
6868
isRunning = true
6969
Future {
70+
sendToSubscriber(RunStarted)
7071
sendToSubscriber(logService.startLogging())
7172
for (i <- 0 to 10) {
7273
Thread.sleep(750)

shared/src/main/scala/shared/SharedMessages.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ case class AdapterNotRunning(logReport: Option[LogReport]) extends AdapterMsg
2828
// each LogEntry that is created by the AdapterProcess
2929
case class LogEntryMsg(logEntry: LogEntry) extends AdapterMsg
3030

31+
// sent when the Adapter Process is started
32+
case object RunStarted extends AdapterMsg
33+
3134
// sent when the Adapter Process finished
3235
case class RunFinished(logReport: LogReport) extends AdapterMsg
3336

0 commit comments

Comments
 (0)