study: store shapes per node
parent
036524a9a9
commit
c583ab4c6f
|
@ -165,19 +165,19 @@ object BSON extends Handlers {
|
|||
def byteArrayO(b: ByteArray): Option[BSONBinary] =
|
||||
if (b.isEmpty) None else ByteArray.ByteArrayBSONHandler.write(b).some
|
||||
def bytesO(b: Array[Byte]): Option[BSONBinary] = byteArrayO(ByteArray(b))
|
||||
def listO(list: List[String]): Option[List[String]] = list match {
|
||||
def strListO(list: List[String]): Option[List[String]] = list match {
|
||||
case Nil => None
|
||||
case List("") => None
|
||||
case List("", "") => None
|
||||
case List(a, "") => Some(List(a))
|
||||
case full => Some(full)
|
||||
}
|
||||
def listO[A](list: List[A])(implicit writer: BSONWriter[A, _ <: BSONValue]): Option[Barr] =
|
||||
if (list.isEmpty) None
|
||||
else Some(BSONArray(list map writer.write))
|
||||
def docO(o: Bdoc): Option[Bdoc] = if (o.isEmpty) None else Some(o)
|
||||
def double(i: Double): BSONDouble = BSONDouble(i)
|
||||
def doubleO(i: Double): Option[BSONDouble] = if (i != 0) Some(BSONDouble(i)) else None
|
||||
def intsO(l: List[Int]): Option[Barr] =
|
||||
if (l.isEmpty) None
|
||||
else Some(BSONArray(l map BSONInteger.apply))
|
||||
|
||||
import scalaz.Functor
|
||||
def map[M[_]: Functor, A, B <: BSONValue](a: M[A])(implicit writer: BSONWriter[A, B]): M[B] =
|
||||
|
|
|
@ -101,7 +101,7 @@ object BSONHandlers {
|
|||
def writes(w: BSON.Writer, o: Game) = BSONDocument(
|
||||
id -> o.id,
|
||||
playerIds -> (o.whitePlayer.id + o.blackPlayer.id),
|
||||
playerUids -> w.listO(List(~o.whitePlayer.userId, ~o.blackPlayer.userId)),
|
||||
playerUids -> w.strListO(List(~o.whitePlayer.userId, ~o.blackPlayer.userId)),
|
||||
whitePlayer -> w.docO(playerBSONHandler write ((_: Color) => (_: Player.Id) => (_: Player.UserId) => (_: Player.Win) => o.whitePlayer)),
|
||||
blackPlayer -> w.docO(playerBSONHandler write ((_: Color) => (_: Player.Id) => (_: Player.UserId) => (_: Player.Win) => o.blackPlayer)),
|
||||
binaryPieces -> o.binaryPieces,
|
||||
|
|
|
@ -75,7 +75,7 @@ case object Perf {
|
|||
def writes(w: BSON.Writer, o: Perf) = BSONDocument(
|
||||
"gl" -> o.glicko,
|
||||
"nb" -> w.int(o.nb),
|
||||
"re" -> w.intsO(o.recent),
|
||||
"re" -> w.listO(o.recent),
|
||||
"la" -> o.latest.map(w.date))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ sealed trait Node {
|
|||
def drops: Option[List[Pos]]
|
||||
def eval: Option[Node.Eval]
|
||||
def comments: List[String]
|
||||
def shapes: List[Node.Shape]
|
||||
def children: List[Branch]
|
||||
def opening: Option[FullOpening]
|
||||
def crazyData: Option[Crazyhouse.Data]
|
||||
|
@ -42,6 +43,7 @@ case class Root(
|
|||
drops: Option[List[Pos]] = None,
|
||||
eval: Option[Node.Eval] = None,
|
||||
comments: List[String] = Nil,
|
||||
shapes: List[Node.Shape] = Nil,
|
||||
children: List[Branch] = Nil,
|
||||
opening: Option[FullOpening] = None,
|
||||
crazyData: Option[Crazyhouse.Data]) extends Node {
|
||||
|
@ -66,6 +68,7 @@ case class Branch(
|
|||
eval: Option[Node.Eval] = None,
|
||||
nag: Option[String] = None,
|
||||
comments: List[String] = Nil,
|
||||
shapes: List[Node.Shape] = Nil,
|
||||
children: List[Branch] = Nil,
|
||||
opening: Option[FullOpening] = None,
|
||||
crazyData: Option[Crazyhouse.Data]) extends Node {
|
||||
|
@ -79,6 +82,14 @@ case class Branch(
|
|||
|
||||
object Node {
|
||||
|
||||
sealed trait Shape
|
||||
object Shape {
|
||||
type ID = String
|
||||
type Brush = String
|
||||
case class Circle(brush: Brush, orig: Pos) extends Shape
|
||||
case class Arrow(brush: Brush, orig: Pos, dest: Pos) extends Shape
|
||||
}
|
||||
|
||||
case class Eval(
|
||||
cp: Option[Int] = None,
|
||||
mate: Option[Int] = None,
|
||||
|
@ -109,6 +120,16 @@ object Node {
|
|||
"name" -> o.name)
|
||||
}
|
||||
|
||||
private implicit val posWrites: Writes[Pos] = Writes[Pos] { p =>
|
||||
JsString(p.key)
|
||||
}
|
||||
private implicit val shapeCircleWrites = Json.writes[Shape.Circle]
|
||||
private implicit val shapeArrowWrites = Json.writes[Shape.Arrow]
|
||||
implicit val shapeWrites: Writes[Shape] = Writes[Shape] {
|
||||
case s: Shape.Circle => shapeCircleWrites writes s
|
||||
case s: Shape.Arrow => shapeArrowWrites writes s
|
||||
}
|
||||
|
||||
implicit val nodeJsonWriter: Writes[Node] = Writes { node =>
|
||||
import node._
|
||||
(
|
||||
|
@ -119,6 +140,7 @@ object Node {
|
|||
add("eval", eval) _ compose
|
||||
add("nag", nag) _ compose
|
||||
add("comments", comments, comments.nonEmpty) _ compose
|
||||
add("shapes", shapes, shapes.nonEmpty) _ compose
|
||||
add("opening", opening) _ compose
|
||||
add("dests", dests.map {
|
||||
_.map {
|
||||
|
|
|
@ -9,6 +9,7 @@ import lila.db.BSON
|
|||
import lila.db.BSON._
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.common.LightUser
|
||||
import lila.socket.tree.Node.Shape
|
||||
|
||||
private object BSONHandlers {
|
||||
|
||||
|
@ -71,6 +72,7 @@ private object BSONHandlers {
|
|||
move = WithSan(r.get[Uci]("u"), r.str("s")),
|
||||
fen = r.get[FEN]("f"),
|
||||
check = r boolD "c",
|
||||
shapes = r.getsD[Shape]("h"),
|
||||
by = r str "b",
|
||||
children = r.get[Node.Children]("n"))
|
||||
def writes(w: Writer, s: Node) = BSONDocument(
|
||||
|
@ -80,6 +82,7 @@ private object BSONHandlers {
|
|||
"s" -> s.move.san,
|
||||
"f" -> s.fen,
|
||||
"c" -> w.boolO(s.check),
|
||||
"h" -> w.listO(s.shapes),
|
||||
"b" -> s.by,
|
||||
"n" -> s.children)
|
||||
}
|
||||
|
@ -89,11 +92,13 @@ private object BSONHandlers {
|
|||
ply = r int "p",
|
||||
fen = r.get[FEN]("f"),
|
||||
check = r boolD "c",
|
||||
shapes = r.getsD[Shape]("h"),
|
||||
children = r.get[Node.Children]("n"))
|
||||
def writes(w: Writer, s: Root) = BSONDocument(
|
||||
"p" -> s.ply,
|
||||
"f" -> s.fen,
|
||||
"c" -> w.boolO(s.check),
|
||||
"h" -> w.listO(s.shapes),
|
||||
"n" -> s.children)
|
||||
}
|
||||
implicit val ChildrenBSONHandler = new BSONHandler[BSONArray, Node.Children] {
|
||||
|
|
|
@ -4,6 +4,8 @@ import chess.Color
|
|||
import chess.variant.Variant
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.socket.tree.Node.Shape
|
||||
|
||||
case class Chapter(
|
||||
_id: Chapter.ID,
|
||||
studyId: Study.ID,
|
||||
|
@ -22,6 +24,11 @@ case class Chapter(
|
|||
updateRoot { root =>
|
||||
root.withChildren(_.addNodeAt(node, path))
|
||||
}
|
||||
|
||||
def setShapes(path: Path, shapes: List[Shape]): Option[Chapter] =
|
||||
updateRoot { root =>
|
||||
root.withChildren(_.setShapesAt(shapes, path))
|
||||
}
|
||||
}
|
||||
|
||||
object Chapter {
|
||||
|
|
|
@ -18,11 +18,13 @@ private final class ChapterMaker(domain: String) {
|
|||
ply = sit.turns,
|
||||
fen = FEN(Forsyth.>>(sit)),
|
||||
check = sit.situation.check,
|
||||
shapes = Nil,
|
||||
children = Node.emptyChildren)
|
||||
case None => Node.Root(
|
||||
ply = 0,
|
||||
fen = FEN(variant.initialFen),
|
||||
check = false,
|
||||
shapes = Nil,
|
||||
children = Node.emptyChildren)
|
||||
}
|
||||
Chapter.make(
|
||||
|
|
|
@ -7,6 +7,7 @@ import play.api.libs.json._
|
|||
import lila.common.LightUser
|
||||
import lila.common.PimpedJson._
|
||||
import lila.socket.Socket.Uid
|
||||
import lila.socket.tree.Node.Shape
|
||||
|
||||
object JsonView {
|
||||
|
||||
|
@ -19,9 +20,6 @@ object JsonView {
|
|||
private implicit val uciCharPairWrites: Writes[UciCharPair] = Writes[UciCharPair] { u =>
|
||||
JsString(u.toString)
|
||||
}
|
||||
private implicit val posWrites: Writes[Pos] = Writes[Pos] { p =>
|
||||
JsString(p.key)
|
||||
}
|
||||
private implicit val posReader: Reads[Pos] = Reads[Pos] { v =>
|
||||
(v.asOpt[String] flatMap Pos.posAt).fold[JsResult[Pos]](JsError(Nil))(JsSuccess(_))
|
||||
}
|
||||
|
@ -43,15 +41,10 @@ object JsonView {
|
|||
"ply" -> n.ply,
|
||||
"fen" -> n.fen,
|
||||
"check" -> n.check,
|
||||
"shapes" -> n.shapes,
|
||||
"children" -> n.children.nodes)
|
||||
}
|
||||
|
||||
private implicit val shapeCircleWrites = Json.writes[Shape.Circle]
|
||||
private implicit val shapeArrowWrites = Json.writes[Shape.Arrow]
|
||||
private[study] implicit val shapeWrites: Writes[Shape] = Writes[Shape] {
|
||||
case s: Shape.Circle => shapeCircleWrites writes s
|
||||
case s: Shape.Arrow => shapeArrowWrites writes s
|
||||
}
|
||||
private[study] implicit val shapeReader: Reads[Shape] = Reads[Shape] { js =>
|
||||
js.asOpt[JsObject].flatMap { o =>
|
||||
for {
|
||||
|
@ -83,7 +76,6 @@ object JsonView {
|
|||
"id" -> s.id,
|
||||
"members" -> s.members,
|
||||
"position" -> s.position,
|
||||
"shapes" -> s.shapes,
|
||||
"ownerId" -> s.ownerId,
|
||||
"createdAt" -> s.createdAt)
|
||||
}
|
||||
|
@ -99,6 +91,7 @@ object JsonView {
|
|||
"san" -> n.move.san,
|
||||
"fen" -> fenWriter.writes(n.fen),
|
||||
"check" -> n.check,
|
||||
"shapes" -> n.shapes,
|
||||
"by" -> n.by,
|
||||
"children" -> n.children.nodes)
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ package lila.study
|
|||
import chess.format.{ Uci, UciCharPair, Forsyth, FEN }
|
||||
|
||||
import lila.user.User
|
||||
import lila.socket.tree.Node.Shape
|
||||
|
||||
sealed trait RootOrNode {
|
||||
val ply: Int
|
||||
val fen: FEN
|
||||
val check: Boolean
|
||||
val shapes: List[Shape]
|
||||
val children: Node.Children
|
||||
}
|
||||
|
||||
|
@ -17,6 +19,7 @@ case class Node(
|
|||
move: Uci.WithSan,
|
||||
fen: FEN,
|
||||
check: Boolean,
|
||||
shapes: List[Shape],
|
||||
by: User.ID,
|
||||
children: Node.Children) extends RootOrNode {
|
||||
|
||||
|
@ -59,6 +62,12 @@ object Node {
|
|||
case Some((head, tail)) => updateChildren(head, _.promoteNodeAt(tail))
|
||||
}
|
||||
|
||||
def setShapesAt(shapes: List[Shape], path: Path): Option[Children] = path.split match {
|
||||
case None => none
|
||||
case Some((head, Path(Nil))) => updateWith(head, _.copy(shapes = shapes).some)
|
||||
case Some((head, tail)) => updateChildren(head, _.setShapesAt(shapes, tail))
|
||||
}
|
||||
|
||||
def get(id: UciCharPair): Option[Node] = nodes.find(_.id == id)
|
||||
|
||||
def has(id: UciCharPair): Boolean = nodes.exists(_.id == id)
|
||||
|
@ -81,6 +90,7 @@ object Node {
|
|||
ply: Int,
|
||||
fen: FEN,
|
||||
check: Boolean,
|
||||
shapes: List[Shape],
|
||||
children: Children) extends RootOrNode {
|
||||
|
||||
def withChildren(f: Children => Option[Children]) =
|
||||
|
@ -100,12 +110,14 @@ object Node {
|
|||
ply = 0,
|
||||
fen = FEN(Forsyth.initial),
|
||||
check = false,
|
||||
shapes = Nil,
|
||||
children = emptyChildren)
|
||||
|
||||
def fromRootBy(userId: User.ID)(b: lila.socket.tree.Root): Root = Root(
|
||||
ply = b.ply,
|
||||
fen = FEN(b.fen),
|
||||
check = b.check,
|
||||
shapes = Nil,
|
||||
children = Children(b.children.toVector.map(fromBranchBy(userId))))
|
||||
}
|
||||
|
||||
|
@ -115,6 +127,7 @@ object Node {
|
|||
move = b.move,
|
||||
fen = FEN(b.fen),
|
||||
check = b.check,
|
||||
shapes = Nil,
|
||||
by = userId,
|
||||
children = Children(b.children.toVector.map(fromBranchBy(userId))))
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package lila.study
|
||||
|
||||
import chess.Pos
|
||||
|
||||
sealed trait Shape
|
||||
|
||||
object Shape {
|
||||
|
||||
type ID = String
|
||||
type Brush = String
|
||||
|
||||
case class Circle(brush: Brush, orig: Pos) extends Shape
|
||||
case class Arrow(brush: Brush, orig: Pos, dest: Pos) extends Shape
|
||||
}
|
|
@ -8,6 +8,7 @@ import scala.concurrent.duration._
|
|||
import lila.hub.TimeBomb
|
||||
import lila.socket.actorApi.{ Connected => _, _ }
|
||||
import lila.socket.Socket.Uid
|
||||
import lila.socket.tree.Node.Shape
|
||||
import lila.socket.{ SocketActor, History, Historical, AnaDests }
|
||||
import lila.user.User
|
||||
|
||||
|
@ -55,7 +56,8 @@ private final class Socket(
|
|||
|
||||
case ReloadAll(study, chapters) => notifyVersion("reload", JsNull, Messadata())
|
||||
|
||||
case ReloadShapes(shapes, uid) => notifyVersion("shapes", Json.obj(
|
||||
case SetShapes(pos, shapes, uid) => notifyVersion("shapes", Json.obj(
|
||||
"p" -> pos,
|
||||
"s" -> shapes,
|
||||
"w" -> who(uid)
|
||||
), Messadata())
|
||||
|
@ -130,7 +132,7 @@ private object Socket {
|
|||
case class DelNode(position: Position.Ref, uid: Uid)
|
||||
case class SetPath(position: Position.Ref, uid: Uid)
|
||||
case class ReloadMembers(members: StudyMembers)
|
||||
case class ReloadShapes(shapes: List[Shape], uid: Uid)
|
||||
case class SetShapes(position: Position.Ref, shapes: List[Shape], uid: Uid)
|
||||
case class ReloadChapters(chapters: List[Chapter.Metadata])
|
||||
case class ReloadAll(study: Study, chapters: List[Chapter.Metadata])
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import lila.hub.actorApi.map._
|
|||
import lila.socket.actorApi.{ Connected => _, _ }
|
||||
import lila.socket.Socket.makeMessage
|
||||
import lila.socket.Socket.Uid
|
||||
import lila.socket.tree.Node.Shape
|
||||
import lila.socket.{ Handler, AnaMove, AnaDests }
|
||||
import lila.user.User
|
||||
import makeTimeout.short
|
||||
|
@ -77,23 +78,23 @@ private[study] final class SocketHandler(
|
|||
}
|
||||
}
|
||||
case ("setPath", o) => AnaRateLimit(uid.value) {
|
||||
reading[AtPath](o) { d =>
|
||||
reading[AtPosition](o) { position =>
|
||||
member.userId foreach { userId =>
|
||||
api.setPath(userId, studyId, Position.Ref(d.chapterId, Path(d.path)), uid)
|
||||
api.setPath(userId, studyId, position.ref, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
case ("deleteVariation", o) => AnaRateLimit(uid.value) {
|
||||
reading[AtPath](o) { d =>
|
||||
reading[AtPosition](o) { position =>
|
||||
member.userId foreach { userId =>
|
||||
api.deleteNodeAt(userId, studyId, Position.Ref(d.chapterId, Path(d.path)), uid)
|
||||
api.deleteNodeAt(userId, studyId, position.ref, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
case ("promoteVariation", o) => AnaRateLimit(uid.value) {
|
||||
reading[AtPath](o) { d =>
|
||||
reading[AtPosition](o) { position =>
|
||||
member.userId foreach { userId =>
|
||||
api.promoteNodeAt(userId, studyId, Position.Ref(d.chapterId, Path(d.path)), uid)
|
||||
api.promoteNodeAt(userId, studyId, position.ref, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,9 +116,11 @@ private[study] final class SocketHandler(
|
|||
} api.kick(byUserId, studyId, userId)
|
||||
|
||||
case ("shapes", o) =>
|
||||
(o \ "d").asOpt[List[Shape]] foreach { shapes =>
|
||||
member.userId foreach { userId =>
|
||||
api.setShapes(userId, studyId, shapes, uid)
|
||||
reading[AtPosition](o) { position =>
|
||||
(o \ "d" \ "shapes").asOpt[List[Shape]] foreach { shapes =>
|
||||
member.userId foreach { userId =>
|
||||
api.setShapes(userId, studyId, position.ref, shapes take 16, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,8 +153,10 @@ private[study] final class SocketHandler(
|
|||
private def reading[A](o: JsValue)(f: A => Unit)(implicit reader: Reads[A]): Unit =
|
||||
o obj "d" flatMap { d => reader.reads(d).asOpt } foreach f
|
||||
|
||||
private case class AtPath(path: String, chapterId: String)
|
||||
private implicit val atPathReader = Json.reads[AtPath]
|
||||
private case class AtPosition(path: String, chapterId: String) {
|
||||
def ref = Position.Ref(chapterId, Path(path))
|
||||
}
|
||||
private implicit val atPositionReader = Json.reads[AtPosition]
|
||||
private case class SetRole(userId: String, role: String)
|
||||
private implicit val SetRoleReader = Json.reads[SetRole]
|
||||
private implicit val ChapterDataReader = Json.reads[ChapterMaker.Data]
|
||||
|
|
|
@ -8,7 +8,6 @@ case class Study(
|
|||
_id: Study.ID,
|
||||
members: StudyMembers,
|
||||
position: Position.Ref,
|
||||
shapes: List[Shape],
|
||||
ownerId: User.ID,
|
||||
createdAt: DateTime) {
|
||||
|
||||
|
@ -24,10 +23,7 @@ case class Study(
|
|||
|
||||
def withChapter(c: Chapter.Like) =
|
||||
if (c.id == position.chapterId) this
|
||||
else copy(
|
||||
position = Position.Ref(chapterId = c.id, path = Path.root),
|
||||
shapes = Nil
|
||||
)
|
||||
else copy(position = Position.Ref(chapterId = c.id, path = Path.root))
|
||||
}
|
||||
|
||||
object Study {
|
||||
|
@ -47,7 +43,6 @@ object Study {
|
|||
_id = scala.util.Random.alphanumeric take idSize mkString,
|
||||
members = StudyMembers(Map(user.id -> owner)),
|
||||
position = Position.Ref("", Path.root),
|
||||
shapes = Nil,
|
||||
ownerId = user.id,
|
||||
createdAt = DateTime.now)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package lila.study
|
||||
|
||||
import akka.actor.{ ActorRef, ActorSelection }
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeHtml4
|
||||
|
||||
import chess.format.{ Forsyth, FEN }
|
||||
import lila.chat.actorApi.SystemTalk
|
||||
import lila.hub.actorApi.map.Tell
|
||||
import lila.hub.Sequencer
|
||||
import lila.socket.Socket.Uid
|
||||
import lila.socket.tree.Node.Shape
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class StudyApi(
|
||||
|
@ -118,10 +120,19 @@ final class StudyApi(
|
|||
} >>- reloadMembers(study)
|
||||
}
|
||||
|
||||
def setShapes(userId: User.ID, studyId: Study.ID, shapes: List[Shape], uid: Uid) = sequenceStudy(studyId) { study =>
|
||||
def setShapes(userId: User.ID, studyId: Study.ID, position: Position.Ref, shapes: List[Shape], uid: Uid) = sequenceStudy(studyId) { study =>
|
||||
Contribute(userId, study) {
|
||||
studyRepo.setShapes(study, shapes)
|
||||
} >>- reloadShapes(study, uid)
|
||||
chapterRepo.byIdAndStudy(position.chapterId, study.id) flatMap {
|
||||
_ ?? { chapter =>
|
||||
chapter.setShapes(position.path, shapes) match {
|
||||
case Some(newChapter) =>
|
||||
chapterRepo.update(newChapter) >>-
|
||||
sendTo(study.id, Socket.SetShapes(position, shapes, uid).pp)
|
||||
case None => fufail(s"Invalid setShapes $position $shapes") >>- reloadUid(study, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addChapter(byUserId: User.ID, studyId: Study.ID, data: ChapterMaker.Data, socket: ActorRef) = sequenceStudy(studyId) { study =>
|
||||
|
@ -148,9 +159,7 @@ final class StudyApi(
|
|||
studyRepo.update(study withChapter chapter) >>- {
|
||||
reloadAll(study)
|
||||
study.members.get(byUserId).foreach { member =>
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeHtml4
|
||||
val message = s"${member.user.name} switched to ${escapeHtml4(chapter.name)}"
|
||||
chat ! SystemTalk(study.id, message, socket)
|
||||
chat ! SystemTalk(study.id, escapeHtml4(chapter.name), socket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,11 +209,6 @@ final class StudyApi(
|
|||
sendTo(study.id, Socket.ReloadAll(study, chapters))
|
||||
}
|
||||
|
||||
private def reloadShapes(study: Study, uid: Uid) =
|
||||
studyRepo.getShapes(study.id).foreach { shapes =>
|
||||
sendTo(study.id, Socket.ReloadShapes(shapes, uid))
|
||||
}
|
||||
|
||||
private def sequenceStudy(studyId: String)(f: Study => Funit): Funit =
|
||||
byId(studyId) flatMap {
|
||||
_ ?? { study =>
|
||||
|
|
|
@ -5,6 +5,7 @@ import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSO
|
|||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.socket.tree.Node.Shape
|
||||
import lila.user.User
|
||||
|
||||
private final class StudyRepo(coll: Coll) {
|
||||
|
@ -31,15 +32,6 @@ private final class StudyRepo(coll: Coll) {
|
|||
)
|
||||
).void
|
||||
|
||||
def getShapes(studyId: Study.ID): Fu[List[Shape]] =
|
||||
coll.primitiveOne[List[Shape]]($id(studyId), "shapes") map (~_)
|
||||
|
||||
def setShapes(study: Study, shapes: List[Shape]): Funit =
|
||||
coll.update(
|
||||
$id(study.id),
|
||||
$set("shapes" -> shapes)
|
||||
).void
|
||||
|
||||
def addMember(study: Study, member: StudyMember): Funit =
|
||||
coll.update(
|
||||
$id(study.id),
|
||||
|
|
|
@ -15,6 +15,7 @@ object TreeBuilder {
|
|||
ply = root.ply,
|
||||
fen = root.fen.value,
|
||||
check = root.check,
|
||||
shapes = root.shapes,
|
||||
children = toBranches(root.children),
|
||||
crazyData = none)
|
||||
|
||||
|
@ -24,6 +25,7 @@ object TreeBuilder {
|
|||
move = node.move,
|
||||
fen = node.fen.value,
|
||||
check = node.check,
|
||||
shapes = node.shapes,
|
||||
children = toBranches(node.children),
|
||||
crazyData = none)
|
||||
|
||||
|
|
|
@ -24,6 +24,18 @@
|
|||
max-height: 160px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
.study_box .list .loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.study_box .list .elem {
|
||||
display: flex;
|
||||
|
|
|
@ -120,7 +120,7 @@ module.exports = function(opts) {
|
|||
onChange();
|
||||
if (!dests) getDests();
|
||||
this.setAutoShapes();
|
||||
if (this.study) this.study.onShowGround();
|
||||
if (node.shapes) this.chessground.setShapes(node.shapes);
|
||||
}.bind(this);
|
||||
|
||||
var getDests = throttle(800, false, function() {
|
||||
|
|
|
@ -108,6 +108,7 @@ module.exports = {
|
|||
});
|
||||
}
|
||||
}, [
|
||||
ctrl.vm.loading ? m('div.loading', m.trust(lichess.spinnerHtml)) : null,
|
||||
ctrl.chapters.list().map(function(chapter) {
|
||||
var confing = ctrl.chapters.vm.confing === chapter.id;
|
||||
var active = ctrl.position().chapterId === chapter.id;
|
||||
|
|
|
@ -52,13 +52,7 @@ module.exports = {
|
|||
req.chapterId = data.position.chapterId;
|
||||
return req;
|
||||
}
|
||||
|
||||
var updateShapes = function() {
|
||||
var shapes = ctrl.vm.path === data.position.path ? data.shapes : [];
|
||||
ctrl.chessground.setShapes(shapes);
|
||||
}
|
||||
ctrl.userJump(data.position.path);
|
||||
updateShapes();
|
||||
|
||||
var samePosition = function(p1, p2) {
|
||||
return p1.chapterId === p2.chapterId && p1.path === p2.path;
|
||||
|
@ -71,7 +65,9 @@ module.exports = {
|
|||
members.set(s.members);
|
||||
chapters.set(s.chapters);
|
||||
ctrl.reloadData(d.analysis);
|
||||
ctrl.chessground.set({orientation: d.analysis.orientation});
|
||||
ctrl.chessground.set({
|
||||
orientation: d.analysis.orientation
|
||||
});
|
||||
vm.loading = false;
|
||||
};
|
||||
|
||||
|
@ -83,7 +79,13 @@ module.exports = {
|
|||
ctrl.chessground.set({
|
||||
drawable: {
|
||||
onChange: function(shapes) {
|
||||
if (members.canContribute()) send("shapes", shapes);
|
||||
if (members.canContribute()) {
|
||||
ctrl.tree.setShapes(shapes, ctrl.vm.path);
|
||||
send("shapes", addChapterId({
|
||||
path: ctrl.vm.path,
|
||||
shapes: shapes
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -116,14 +118,12 @@ module.exports = {
|
|||
},
|
||||
setChapter: function(id) {
|
||||
send("setChapter", id);
|
||||
vm.loading = true;
|
||||
},
|
||||
setTab: function(tab) {
|
||||
vm.tab(tab);
|
||||
m.redraw.strategy("all");
|
||||
},
|
||||
onShowGround: function() {
|
||||
updateShapes();
|
||||
},
|
||||
socketHandlers: {
|
||||
path: function(d) {
|
||||
var position = d.p,
|
||||
|
@ -177,9 +177,11 @@ module.exports = {
|
|||
m.redraw();
|
||||
},
|
||||
shapes: function(d) {
|
||||
members.setActive(d.w.u);
|
||||
data.shapes = d.s;
|
||||
updateShapes();
|
||||
var position = d.p,
|
||||
who = d.w;
|
||||
members.setActive(who.u);
|
||||
if (who.s === sri) return;
|
||||
if (position.chapterId !== data.position.chapterId) return;
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ module.exports = {
|
|||
|
||||
main: function(ctrl) {
|
||||
|
||||
if (ctrl.vm.loading) return m.trust(lichess.spinnerHtml);
|
||||
|
||||
var activeTab = ctrl.vm.tab();
|
||||
|
||||
var makeTab = function(key, name) {
|
||||
|
|
|
@ -148,6 +148,11 @@ module.exports = function(root) {
|
|||
if (opening) node.opening = opening;
|
||||
});
|
||||
},
|
||||
setShapes: function(shapes, path) {
|
||||
return updateAt(path, function(node) {
|
||||
node.shapes = shapes;
|
||||
});
|
||||
},
|
||||
pathIsMainline: pathIsMainline,
|
||||
pathExists: pathExists,
|
||||
deleteNodeAt: deleteNodeAt,
|
||||
|
|
Loading…
Reference in New Issue