Skip to content

Commit 41a7a63

Browse files
committed
WIP: ClientStorage.scala,GlobalState.scala,UrlConfig.scala,AuthControls.scala,Components.scala,CreateNewPrompt.scala,DoodleView.scala,MembersModal.scala,NewProjectPrompt.scala
1 parent 4e379dc commit 41a7a63

9 files changed

Lines changed: 76 additions & 35 deletions

File tree

webApp/src/main/scala/wust/webApp/ClientStorage.scala

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package wust.webApp
22

33
import wust.util.Memo
44
import wust.ids._
5+
import wust.graph.Graph
6+
import wust.graph.Page
57
import collection.mutable
68
import cats.effect.SyncIO
79
import io.circe._
@@ -15,22 +17,32 @@ import wust.api.Authentication
1517
import wust.api.serialize.Circe._
1618
import wust.graph.GraphChanges
1719
import wust.webUtil.outwatchHelpers._
20+
import wust.util.time.time
1821

19-
import scala.util.{Failure, Success, Try}
22+
import scala.util.{ Failure, Success, Try }
2023
import wust.facades.segment.Segment
2124

2225
class ClientStorage(implicit owner: Ctx.Owner) {
23-
import org.scalajs.dom.ext.{LocalStorage => internal}
26+
import org.scalajs.dom.ext.{ LocalStorage => internal }
2427

2528
object keys {
2629
val auth = "wust.auth"
2730
val sidebarOpen = "wust.sidebar.open"
2831
val sidebarWithProjects = "wust.sidebar.projects"
2932
val taglistOpen = "wust.taglist.open"
3033
val filterlistOpen = "wust.filterlist.open"
31-
def pendingChanges(userId:UserId) = s"wust.pendingchanges.${userId.toUuid.toString}"
32-
def pendingChangesInvalid(userId:UserId) = s"wust.pendingchanges.invalid.${userId.toUuid.toString}"
34+
def pendingChanges(userId: UserId) = s"wust.pendingchanges.${userId.toUuid.toString}"
35+
def pendingChangesInvalid(userId: UserId) = s"wust.pendingchanges.invalid.${userId.toUuid.toString}"
3336
val backendTimeDelta = "wust.backendtimedelta"
37+
def pageCache(page: Page) = new {
38+
val baseKey = page.parentId match {
39+
case Some(parentId) => s"wust.pageCache.${parentId.toUuid.toString}"
40+
case None => s"wust.pageCache.empty"
41+
}
42+
val graph = s"${baseKey}.graph"
43+
val time = s"${baseKey}.time"
44+
val keys = List(graph, time)
45+
}
3446
}
3547

3648
private def toJson[T: Encoder](value: T): String = value.asJson.noSpaces
@@ -51,8 +63,30 @@ class ClientStorage(implicit owner: Ctx.Owner) {
5163
}
5264
}
5365

66+
def getGraph(page: Page): Option[Graph] = {
67+
val storageKey = keys.pageCache(page)
68+
val encodedOpt = internal(storageKey.graph)
69+
encodedOpt flatMap { encoded =>
70+
decode[Graph](encoded) match {
71+
case Left(_) =>
72+
// cannot decode cache -> prune this entry
73+
storageKey.keys.foreach(internal.remove)
74+
None
75+
case Right(decoded) => Some(decoded)
76+
}
77+
}
78+
}
79+
80+
def updateGraph(page: Page, graph: Graph): Unit = {
81+
time(s"writing to cache: $page") {
82+
val storageKey = keys.pageCache(page)
83+
internal.update(storageKey.graph, toJson(graph))
84+
internal.update(storageKey.time, EpochMilli.now.toString)
85+
}
86+
}
87+
5488
val auth: Var[Option[Authentication]] = {
55-
if(canAccessLs) {
89+
if (canAccessLs) {
5690
LocalStorage
5791
.handlerWithoutEvents[SyncIO](keys.auth)
5892
.unsafeRunSync()
@@ -61,48 +95,47 @@ class ClientStorage(implicit owner: Ctx.Owner) {
6195
} else Var(None)
6296
}
6397

64-
def addPendingGraphChange(userId: UserId, change:GraphChanges) = {
98+
def addPendingGraphChange(userId: UserId, change: GraphChanges) = {
6599
pendingChanges(userId).update(_ :+ toJson(change))
66100
}
67101

68102
def clearPendingGraphChanges(userId: UserId) = {
69103
pendingChanges(userId).update(_ => List.empty)
70104
}
71105

72-
def getDecodablePendingGraphChanges(userId: UserId):List[GraphChanges] = {
106+
def getDecodablePendingGraphChanges(userId: UserId): List[GraphChanges] = {
73107
val all = pendingChanges(userId).now
74108
val validEncodedBuilder = mutable.ListBuffer.empty[String]
75109
val validDecodedBuilder = mutable.ListBuffer.empty[GraphChanges]
76110
val invalidBuilder = mutable.ListBuffer.empty[String]
77111
all.foreach { encoded =>
78112
decode[GraphChanges](encoded) match {
79-
case Left(error) =>
113+
case Left(error) =>
80114
invalidBuilder += encoded
81115
Segment.trackError("Failed to decode pending GraphChange", s"${error.toString}: ${encoded}")
82-
case Right(valid) =>
116+
case Right(valid) =>
83117
validEncodedBuilder += encoded
84118
validDecodedBuilder += valid
85119
}
86120
}
87121

88122
val invalid = invalidBuilder.result()
89123

90-
if(invalid.nonEmpty) {
124+
if (invalid.nonEmpty) {
91125
pendingChangesInvalid(userId).update(_ ++ invalid)
92126
pendingChanges(userId)() = validEncodedBuilder.result()
93127
}
94128

95129
validDecodedBuilder.result()
96130
}
97131

98-
99132
val pendingChanges = Memo.mutableHashMapMemo[UserId, Var[List[String]]](pendingChangesByUser)
100133
val pendingChangesInvalid = Memo.mutableHashMapMemo[UserId, Var[List[String]]](pendingChangesInvalidByUser)
101134

102135
//TODO: howto handle with events from other tabs?
103136
private def pendingChangesByUser(userId: UserId): Var[List[String]] = {
104137
val storageKey = keys.pendingChanges(userId)
105-
if(canAccessLs) {
138+
if (canAccessLs) {
106139
LocalStorage
107140
.handlerWithoutEvents[SyncIO](storageKey)
108141
.unsafeRunSync()
@@ -113,7 +146,7 @@ class ClientStorage(implicit owner: Ctx.Owner) {
113146

114147
private def pendingChangesInvalidByUser(userId: UserId): Var[List[String]] = {
115148
val storageKey = keys.pendingChangesInvalid(userId)
116-
if(canAccessLs) {
149+
if (canAccessLs) {
117150
LocalStorage
118151
.handlerWithoutEvents[SyncIO](storageKey)
119152
.unsafeRunSync()
@@ -122,9 +155,8 @@ class ClientStorage(implicit owner: Ctx.Owner) {
122155
} else Var(List.empty)
123156
}
124157

125-
126158
val sidebarOpen: Var[Option[Boolean]] = {
127-
if(canAccessLs) {
159+
if (canAccessLs) {
128160
LocalStorage
129161
.handlerWithoutEvents[SyncIO](keys.sidebarOpen)
130162
.unsafeRunSync()
@@ -134,7 +166,7 @@ class ClientStorage(implicit owner: Ctx.Owner) {
134166
}
135167

136168
val sidebarWithProjects: Var[Option[Boolean]] = {
137-
if(canAccessLs) {
169+
if (canAccessLs) {
138170
LocalStorage
139171
.handlerWithoutEvents[SyncIO](keys.sidebarWithProjects)
140172
.unsafeRunSync()
@@ -144,7 +176,7 @@ class ClientStorage(implicit owner: Ctx.Owner) {
144176
}
145177

146178
val taglistOpen: Var[Option[Boolean]] = {
147-
if(canAccessLs) {
179+
if (canAccessLs) {
148180
LocalStorage
149181
.handlerWithoutEvents[SyncIO](keys.taglistOpen)
150182
.unsafeRunSync()
@@ -154,7 +186,7 @@ class ClientStorage(implicit owner: Ctx.Owner) {
154186
}
155187

156188
val filterlistOpen: Var[Option[Boolean]] = {
157-
if(canAccessLs) {
189+
if (canAccessLs) {
158190
LocalStorage
159191
.handlerWithoutEvents[SyncIO](keys.filterlistOpen)
160192
.unsafeRunSync()
@@ -164,7 +196,7 @@ class ClientStorage(implicit owner: Ctx.Owner) {
164196
}
165197

166198
val backendTimeDelta: Var[Long] = {
167-
if(canAccessLs) {
199+
if (canAccessLs) {
168200
LocalStorage
169201
.handlerWithoutEvents[SyncIO](keys.backendTimeDelta)
170202
.unsafeRunSync()

webApp/src/main/scala/wust/webApp/state/GlobalState.scala

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package wust.webApp.state
22

33
// import acyclic.file
4+
import wust.webApp.ClientStorage
45
import com.github.ghik.silencer.silent
56
import org.scalajs.dom.experimental.permissions.PermissionState
67
import org.scalajs.dom.window
@@ -19,6 +20,7 @@ import wust.webApp.views._
1920
import wust.webApp.{ Client, WoostConfig }
2021
import wust.webUtil.outwatchHelpers._
2122
import wust.webUtil.{ BrowserDetect, ModalConfig, Ownable }
23+
import wust.webUtil.Elements.defer
2224
import wust.facades.segment.Segment
2325
import outwatch.dom._
2426
import outwatch.dom.dsl._
@@ -175,16 +177,23 @@ object GlobalState {
175177
val pageExistsInGraph: Rx[Boolean] = Rx{ page().parentId.exists(rawGraph().contains) }
176178
val showPageNotFound = Rx { !isLoading() && !pageExistsInGraph() && viewIsContent() }
177179

178-
def focus(nodeId: NodeId, needsGet: Boolean = true) = {
179-
val alreadyLoaded = (
180+
def focus(nodeId: NodeId, view: Option[View] = GlobalState.urlConfig.now.view, needsGet: Boolean = true): Unit = focusPage(Page(nodeId), needsGet = needsGet)
181+
def focusPage(page: Page, view: Option[View] = GlobalState.urlConfig.now.view, needsGet: Boolean = true): Unit = {
182+
@inline def nextPage = page
183+
val oldPage = GlobalState.page.now
184+
val oldGraph = graph.now
185+
val oldRawGraph = rawGraph.now
186+
def alreadyLoaded = (
180187
for {
181-
pageId <- page.now.parentId
182-
pageIdx <- graph.now.idToIdx(pageId)
183-
nodeIdx <- graph.now.idToIdx(nodeId)
184-
} yield dfs.exists(_(pageIdx), dfs.withStart, graph.now.childrenIdx, isFound = { _ == nodeIdx })
188+
oldPageId <- oldPage.parentId
189+
oldPageIdx <- oldGraph.idToIdx(oldPageId)
190+
nextPageId <- nextPage.parentId
191+
nextPageIdx <- oldGraph.idToIdx(nextPageId)
192+
} yield dfs.exists(_(oldPageIdx), dfs.withStart, oldGraph.childrenIdx, isFound = { _ == nextPageIdx })
185193
).getOrElse(false)
186194

187-
urlConfig.update(_.focus(Page(nodeId), needsGet = needsGet && !alreadyLoaded))
195+
defer { Client.storage.updateGraph(oldPage, oldRawGraph) }
196+
urlConfig.update(_.focus(nextPage, view, needsGet = needsGet && !alreadyLoaded))
188197
}
189198

190199
def focusSubPage(nodeIdOpt: Option[NodeId]) = {

webApp/src/main/scala/wust/webApp/state/UrlConfig.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ final case class UrlConfig(
3838

3939
def redirect: UrlConfig = copy(view = redirectTo, redirectTo = None)
4040

41-
@inline def focus(view: View): UrlConfig = focus(Some(view))
41+
@inline private[state] def focus(view: View): UrlConfig = focus(Some(view))
4242
@inline def focus(page: Page, view: View): UrlConfig = focus(page, Some(view))
4343
@inline def focus(view: Option[View]): UrlConfig = copy(view = view, redirectTo = None, focusId = None)
4444
@inline def focus(page: Page, view: View, needsGet: Boolean): UrlConfig = focus(page, Some(view), needsGet)
45-
def focus(page: Page, view: Option[View] = None, needsGet: Boolean = true): UrlConfig = copy(pageChange = PageChange(page, needsGet = needsGet), view = view, redirectTo = None, focusId = None, subPage = Page.empty)
45+
private[state] def focus(page: Page, view: Option[View] = None, needsGet: Boolean = true): UrlConfig = copy(pageChange = PageChange(page, needsGet = needsGet), view = view, redirectTo = None, focusId = None, subPage = Page.empty)
4646

4747
}
4848

webApp/src/main/scala/wust/webApp/views/AuthControls.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ object AuthControls {
7474
cls := s"tiny compact ui $buttonStyle button",
7575
onClick foreach {
7676
Client.auth.logout().foreach { _ =>
77-
GlobalState.urlConfig.update(_.focus(Page.empty, View.Login))
77+
GlobalState.focusPage(Page.empty, view = Some(View.Login))
7878
}
7979
FeatureState.use(Feature.ClickLogoutInAuthStatus)
8080
Segment.trackSignedOut()

webApp/src/main/scala/wust/webApp/views/Components.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ object Components {
151151
} // Max 1 dm node with this name
152152
previousDmNode match {
153153
case Some(dmNode) if graph.can_access_node(user.id, dmNode.id) =>
154-
GlobalState.urlConfig.update(_.focus(Page(dmNode.id), View.Conversation))
154+
GlobalState.focus(dmNode.id, view = Some(View.Conversation))
155155
case _ => // create a new channel, add user as member
156156
val nodeId = NodeId.fresh
157157
val change:GraphChanges =
@@ -163,7 +163,7 @@ object Components {
163163
))
164164

165165
GlobalState.submitChanges(change)
166-
GlobalState.urlConfig.update(_.focus(Page(nodeId), View.Chat, needsGet = false))
166+
GlobalState.focus(nodeId, Some(View.Chat), needsGet = false)
167167
()
168168
}
169169
Segment.trackEvent("Direct Message")

webApp/src/main/scala/wust/webApp/views/CreateNewPrompt.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ object CreateNewPrompt {
7272
if (addToChannels.now) {
7373
val channelChanges = GraphChanges.connect(Edge.Pinned)(newNode.id, GlobalState.user.now.id)
7474
GlobalState.submitChanges(changes merge channelChanges)
75-
GlobalState.urlConfig.update(_.focus(Page(newNode.id), needsGet = false))
75+
GlobalState.focus(newNode.id, needsGet = false)
7676
} else {
7777
GlobalState.submitChanges(changes).foreach { _ =>
7878
if(childNodes.now.nonEmpty)

webApp/src/main/scala/wust/webApp/views/DoodleView.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ object DoodleView extends AppDefinition {
268268
)).foreach {
269269
val nodeId = NodeId.fresh
270270
GlobalState.submitChanges(createNode(nodeId))
271-
GlobalState.urlConfig.update(_.focus(Page(nodeId), needsGet = false))
271+
GlobalState.focus(nodeId, needsGet = false)
272272
()
273273
}
274274

webApp/src/main/scala/wust/webApp/views/MembersModal.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ object MembersModal {
120120
if (membership.userId == GlobalState.user.now.id) {
121121
needAction() = Some(NeedAction(
122122
{ () =>
123-
GlobalState.urlConfig.update(_.focus(Page.empty))
123+
GlobalState.focusPage(Page.empty)
124124
GlobalState.uiModalClose.onNext(())
125125
action()
126126
},

webApp/src/main/scala/wust/webApp/views/NewProjectPrompt.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ object NewProjectPrompt {
5252
val views = if (selectedViews.now.isEmpty) None else Some(selectedViews.now.toList)
5353
GlobalState.submitChanges(GraphChanges.newProject(nodeId, GlobalState.user.now.id, newName, views) merge sub.changes(nodeId) merge extraChanges(nodeId))
5454

55-
if (focusNewProject) GlobalState.urlConfig.update(_.focus(Page(nodeId), needsGet = false))
55+
if (focusNewProject) GlobalState.focus(nodeId, needsGet = false)
5656

5757
FeatureState.use(Feature.CreateProject)
5858
selectedViews.now.foreach (ViewModificationMenu.trackAddViewFeature)

0 commit comments

Comments
 (0)