diff --git a/core/src/main/scala/ApiImpl.scala b/core/src/main/scala/ApiImpl.scala index 8b7809bf2..5dff40b79 100644 --- a/core/src/main/scala/ApiImpl.scala +++ b/core/src/main/scala/ApiImpl.scala @@ -62,6 +62,7 @@ class ApiImpl(dsl: GuardDsl, db: Db)(implicit ec: ExecutionContext) extends Api[ } changes.addNodes.forall { case node:Node.Content => allPostsWithAuthor.contains(node.id) + case node:Node.Info => allPostsWithAuthor.contains(node.id) case _ => false } } diff --git a/graph/src/main/scala/Edge.scala b/graph/src/main/scala/Edge.scala index e3870af93..bcde890ee 100644 --- a/graph/src/main/scala/Edge.scala +++ b/graph/src/main/scala/Edge.scala @@ -15,6 +15,7 @@ sealed trait Edge { } object Edge { + sealed trait Content extends Edge case class Member(userId: UserId, data: EdgeData.Member, channelId: NodeId) extends Edge { def sourceId = userId @@ -25,7 +26,7 @@ object Edge { def targetId = nodeId } - case class Parent(childId: NodeId, data: EdgeData.Parent, parentId: NodeId) extends Edge { + case class Parent(childId: NodeId, data: EdgeData.Parent, parentId: NodeId) extends Content { def sourceId = childId def targetId = parentId } @@ -40,14 +41,13 @@ object Edge { def data = EdgeData.Expanded } - case class Notify(nodeId: NodeId, userId: UserId) - extends Edge { + case class Notify(nodeId: NodeId, userId: UserId) extends Edge { def sourceId = nodeId def targetId = userId def data = EdgeData.Notify } - case class Label(sourceId: NodeId, data: EdgeData.Label, targetId: NodeId) extends Edge + case class Label(sourceId: NodeId, data: EdgeData.Label, targetId: NodeId) extends Content case class Pinned(userId: UserId, nodeId: NodeId) extends Edge { def sourceId = userId @@ -55,7 +55,7 @@ object Edge { def data = EdgeData.Pinned } - case class Before(nodeId: NodeId, data: EdgeData.Before, afterId: NodeId) extends Edge { + case class Before(nodeId: NodeId, data: EdgeData.Before, afterId: NodeId) extends Content { def sourceId = nodeId def targetId = afterId } diff --git a/graph/src/main/scala/GraphChanges.scala b/graph/src/main/scala/GraphChanges.scala index 4ab2355be..7d5ea8bd7 100644 --- a/graph/src/main/scala/GraphChanges.scala +++ b/graph/src/main/scala/GraphChanges.scala @@ -5,6 +5,8 @@ import wust.util.collection.RichCollection import scala.collection.breakOut +//TODO: only Edge.Content and Node.Content? +//TODO: not sets! what about an addnode with a succeeding edit, who wins? case class GraphChanges( addNodes: collection.Set[Node] = Set.empty, addEdges: collection.Set[Edge] = Set.empty, diff --git a/graph/src/main/scala/Node.scala b/graph/src/main/scala/Node.scala index 428a20f24..e760e99ad 100644 --- a/graph/src/main/scala/Node.scala +++ b/graph/src/main/scala/Node.scala @@ -28,11 +28,25 @@ sealed trait Node { } object Node { - // TODO: we cannot set the nodemeta here, but there is changeable data in the db class + //TODO: noderole/meta is not changeable for users, but is in db. case class User(id: UserId, data: NodeData.User, meta: NodeMeta) extends Node { @inline def name: String = data.name - def role: NodeRole = NodeRole.default + def role: NodeRole = NodeRole.Message } + + //TODO: noderole/meta is not changeable for infos, but is in db. + case class Info(id: NodeId, data: NodeData.Info, meta: NodeMeta) extends Node { + def role: NodeRole = NodeRole.Message + } + object Info { + @inline def apply(data: NodeData.Info): Info = { + new Info(NodeId.fresh, data, NodeMeta.default) + } + @inline def apply(id: NodeId, data: NodeData.Info): Info = { + new Info(id, data, NodeMeta.default) + } + } + case class Content(id: NodeId, data: NodeData.Content, role: NodeRole, meta: NodeMeta) extends Node object Content { @inline def apply(data: NodeData.Content, role: NodeRole, meta: NodeMeta): Content = { diff --git a/ids/src/main/scala/NodeData.scala b/ids/src/main/scala/NodeData.scala index d49d4aad0..6c4c83aa7 100644 --- a/ids/src/main/scala/NodeData.scala +++ b/ids/src/main/scala/NodeData.scala @@ -26,6 +26,16 @@ object NodeData { } object PlainText extends Named + sealed trait Info extends NodeData { def str = "Info" } + object Info { + case class EditNode(id: NodeId, oldData: NodeData, data: NodeData) extends Named with Info + object EditNode extends Named + case class AddParent(id: NodeId, parentId: NodeId) extends Named with Info + object AddParent extends Named + case class RemoveParent(id: NodeId, parentId: NodeId) extends Named with Info + object RemoveParent extends Named + } + object User extends Named case class User(name: String, isImplicit: Boolean, revision: Int) extends Named with NodeData { def str = name diff --git a/ids/src/main/scala/serialize/Circe.scala b/ids/src/main/scala/serialize/Circe.scala index dfd16b80c..40c769185 100644 --- a/ids/src/main/scala/serialize/Circe.scala +++ b/ids/src/main/scala/serialize/Circe.scala @@ -42,6 +42,8 @@ trait Circe { implicit val postContentEncoder2: Encoder[NodeData.Content] = deriveEncoder[NodeData.Content] implicit val postContentDecoder3: Decoder[NodeData.User] = deriveDecoder[NodeData.User] implicit val postContentEncoder3: Encoder[NodeData.User] = deriveEncoder[NodeData.User] + implicit val postContentDecoder4: Decoder[NodeData.Info] = deriveDecoder[NodeData.Info] + implicit val postContentEncoder4: Encoder[NodeData.Info] = deriveEncoder[NodeData.Info] implicit val postContentDecoder: Decoder[NodeData] = deriveDecoder[NodeData] implicit val postContentEncoder: Encoder[NodeData] = deriveEncoder[NodeData] diff --git a/sdk/shared/src/main/scala/EventInterpreter.scala b/sdk/shared/src/main/scala/EventInterpreter.scala new file mode 100644 index 000000000..f460a5fc7 --- /dev/null +++ b/sdk/shared/src/main/scala/EventInterpreter.scala @@ -0,0 +1,42 @@ +package wust.sdk + +import akka.io.UdpConnected.Disconnect +import wust.api.ApiEvent +import wust.graph.{Edge, Graph, GraphChanges, Node} +import wust.ids.{NodeData, NodeId} + +import scala.collection.mutable + +object EventInterpreter { + import NodeData.Info._ + + def apply(graph: Graph, changes: GraphChanges): Array[NodeData.Info] = { + val c = changes.consistent + val builder = Array.newBuilder[NodeData.Info] + val newNodesBuilder = mutable.HashSet.newBuilder[NodeId] + c.addNodes.foreach { + case n: Node.Content => + val oldNode = graph.nodesByIdGet(n.id) + oldNode match { + case None => newNodesBuilder += n.id + case Some(oldNode) => builder += EditNode(n.id, oldNode.data, n.data) + } + case _ => + } + + val newNodes = newNodesBuilder.result() + + c.addEdges.foreach { + case e: Edge.Parent => + if (!newNodes(e.childId)) builder += AddParent(e.childId, e.parentId) + case _ => + } + c.delEdges.foreach { + case e: Edge.Parent => + builder += RemoveParent(e.childId, e.parentId) + case _ => + } + + builder.result() + } +} diff --git a/sdk/shared/src/main/scala/EventProcessor.scala b/sdk/shared/src/main/scala/EventProcessor.scala index a0c9efc90..06c2a7b3a 100644 --- a/sdk/shared/src/main/scala/EventProcessor.scala +++ b/sdk/shared/src/main/scala/EventProcessor.scala @@ -5,7 +5,7 @@ import monix.reactive.{Observable, OverflowStrategy} import monix.reactive.subjects.{PublishSubject, PublishToOneSubject} import wust.api.ApiEvent._ import wust.api._ -import wust.ids.NodeId +import wust.ids.{NodeId, NodeRole, NodeData} import wust.graph._ import scala.concurrent.Future @@ -96,7 +96,22 @@ class EventProcessor private ( val rawGraphWithInit = sharedRawGraph.startWith(Seq(Graph.empty)) val enrichedChanges = enriched.changes.withLatestFrom(rawGraphWithInit)(enrichChanges) - val allChanges = Observable(enrichedChanges, changes).merge + val allChanges = Observable(enrichedChanges, changes).merge.withLatestFrom(rawGraphWithInit) { (changes, graph) => + val events = EventInterpreter(graph, changes) + val statusChanges = events.map { + case e: NodeData.Info.EditNode => + val statusNode = Node.Info(e) + graph.parents(e.id).map(GraphChanges.addNodeWithParent(statusNode, _)).foldLeft(GraphChanges.empty)(_ merge _) + case e: NodeData.Info.AddParent => + val statusNode = Node.Info(e) + GraphChanges.addNodeWithParent(statusNode, e.parentId) + case e: NodeData.Info.RemoveParent => + val statusNode = Node.Info(e) + GraphChanges.addNodeWithParent(statusNode, e.parentId) + } + + statusChanges.foldLeft(changes)(_ merge _) + } val localChanges = allChanges.withLatestFrom(currentUser.startWith(Seq(initialAuth.user)))((g, u) => (g, u)).collect { case (changes, user) if changes.nonEmpty => changes.consistent.withAuthor(user.id) diff --git a/webApp/src/main/scala/views/Components.scala b/webApp/src/main/scala/views/Components.scala index 840c52627..067db15aa 100644 --- a/webApp/src/main/scala/views/Components.scala +++ b/webApp/src/main/scala/views/Components.scala @@ -55,6 +55,7 @@ object Components { case NodeData.Markdown(content) => markdownVNode(trimToMaxLength(content, maxLength)) case NodeData.PlainText(content) => div(trimToMaxLength(content, maxLength)) case user: NodeData.User => div(displayUserName(user)) + case e: NodeData.Info => div(e.toString) } private val woostPathCurve = "m51.843 221.96c81.204 0-6.6913-63.86 18.402 13.37 25.093 77.23 58.666-26.098-7.029 21.633-65.695 47.73 42.949 47.73-22.746 0-65.695-47.731-32.122 55.597-7.029-21.633 25.093-77.23-62.802-13.37 18.402-13.37z"