display viewed videos

pull/437/head
Thibault Duplessis 2015-04-15 11:51:53 +02:00
parent a20c1ecadc
commit e91060252e
9 changed files with 67 additions and 29 deletions

View File

@ -24,10 +24,10 @@ object Video extends LilaController {
def index = Open { implicit ctx =>
WithUserControl { control =>
control.query match {
case Some(query) => env.api.video.search(query, getInt("page") | 1) map { videos =>
case Some(query) => env.api.video.search(ctx.me, query, getInt("page") | 1) map { videos =>
Ok(html.video.search(videos, control))
}
case None => env.api.video.byTags(control.filter.tags, getInt("page") | 1) zip
case None => env.api.video.byTags(ctx.me, control.filter.tags, getInt("page") | 1) zip
env.api.video.count.apply map {
case (videos, count) =>
Ok(html.video.index(videos, count, control))
@ -40,7 +40,7 @@ object Video extends LilaController {
WithUserControl { control =>
env.api.video.find(id) flatMap {
case None => fuccess(NotFound(html.video.notFound(control)))
case Some(video) => env.api.video.similar(video, 9) zip
case Some(video) => env.api.video.similar(ctx.me, video, 9) zip
ctx.userId.?? { userId =>
env.api.view.add(View.make(videoId = video.id, userId = userId))
} map {
@ -53,7 +53,7 @@ object Video extends LilaController {
def author(author: String) = Open { implicit ctx =>
WithUserControl { control =>
env.api.video.byAuthor(author, getInt("page") | 1) map { videos =>
env.api.video.byAuthor(ctx.me, author, getInt("page") | 1) map { videos =>
Ok(html.video.author(author, videos, control))
}
}

View File

@ -1,4 +1,4 @@
@(author: String, videos: Paginator[lila.video.Video], control: lila.video.UserControl)(implicit ctx: Context)
@(author: String, videos: Paginator[lila.video.VideoView], control: lila.video.UserControl)(implicit ctx: Context)
@layout(
title = s"$author • Free Chess Videos",

View File

@ -1,16 +1,19 @@
@(video: lila.video.Video, control: lila.video.UserControl)(implicit ctx: Context)
@(vv: lila.video.VideoView, control: lila.video.UserControl)(implicit ctx: Context)
<a class="card paginated_element" href="@routes.Video.show(video.id)?@control.queryString">
<span class="img" style="background-image: url(@video.thumbnail)"></span>
<a class="card paginated_element" href="@routes.Video.show(vv.video.id)?@control.queryString">
@if(ctx.isAuth) {
<span class="view@if(vv.view){ yes}" data-icon="v"></span>
}
<span class="img" style="background-image: url(@vv.video.thumbnail)"></span>
<span class="info">
<span class="title">@video.title</span>
<span class="title">@vv.video.title</span>
</span>
<span class="reveal">
<span class="full-title">@video.title</span>
<span class="author">@video.author</span>
<span class="target">@video.targets.map(lila.video.Target.name).mkString(", ")</span>
<span class="full-title">@vv.video.title</span>
<span class="author">@vv.video.author</span>
<span class="target">@vv.video.targets.map(lila.video.Target.name).mkString(", ")</span>
<span class="tags">
@video.tags.map { tag =>
@vv.video.tags.map { tag =>
<span data-icon="o">@tag.capitalize</span>
}
</span>

View File

@ -1,4 +1,4 @@
@(videos: Paginator[lila.video.Video], count: Int, control: lila.video.UserControl)(implicit ctx: Context)
@(videos: Paginator[lila.video.VideoView], count: Int, control: lila.video.UserControl)(implicit ctx: Context)
@layout(
title = "Free Chess Videos",

View File

@ -1,4 +1,4 @@
@(videos: Paginator[lila.video.Video], control: lila.video.UserControl)(implicit ctx: Context)
@(videos: Paginator[lila.video.VideoView], control: lila.video.UserControl)(implicit ctx: Context)
@layout(
title = s"${control.query.getOrElse("Search")} • Free Chess Videos",

View File

@ -1,4 +1,4 @@
@(video: lila.video.Video, similar: List[lila.video.Video], control: lila.video.UserControl)(implicit ctx: Context)
@(video: lila.video.Video, similar: Seq[lila.video.VideoView], control: lila.video.UserControl)(implicit ctx: Context)
@layout(
title = s"${video.title} • Free Chess Videos",
@ -24,8 +24,8 @@ control = control) {
}
</div>
<div class="similar list">
@similar.map { video =>
@card(video, control)
@similar.map { vv =>
@card(vv, control)
}
</div>
</div>

View File

@ -26,6 +26,17 @@ private[video] final class VideoApi(
private implicit val TagNbBSONHandler = Macros.handler[TagNb]
import View.viewBSONHandler
private def videoViews(userOption: Option[User])(videos: Seq[Video]): Fu[Seq[VideoView]] = userOption match {
case None => fuccess {
videos map { VideoView(_, false) }
}
case Some(user) => view.seenVideoIds(user, videos) map { ids =>
videos.map { v =>
VideoView(v, ids contains v.id)
}
}
}
object video {
private val maxPerPage = 18
@ -33,7 +44,7 @@ private[video] final class VideoApi(
def find(id: Video.ID): Fu[Option[Video]] =
videoColl.find(BSONDocument("_id" -> id)).one[Video]
def search(query: String, page: Int): Fu[Paginator[Video]] = {
def search(user: Option[User], query: String, page: Int): Fu[Paginator[VideoView]] = {
val q = query.split(' ').map { word => s""""$word"""" } mkString " "
val textScore = BSONDocument("score" -> BSONDocument("$meta" -> "textScore"))
Paginator(
@ -44,7 +55,7 @@ private[video] final class VideoApi(
),
projection = textScore,
sort = textScore
),
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
}
@ -75,18 +86,18 @@ private[video] final class VideoApi(
doc flatMap (_.getAs[String]("_id"))
}
def popular(page: Int): Fu[Paginator[Video]] = Paginator(
def popular(user: Option[User], page: Int): Fu[Paginator[VideoView]] = Paginator(
adapter = new BSONAdapter[Video](
collection = videoColl,
selector = BSONDocument(),
projection = BSONDocument(),
sort = BSONDocument("metadata.likes" -> -1)
),
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
def byTags(tags: List[Tag], page: Int): Fu[Paginator[Video]] =
if (tags.isEmpty) popular(page)
def byTags(user: Option[User], tags: List[Tag], page: Int): Fu[Paginator[VideoView]] =
if (tags.isEmpty) popular(user, page)
else Paginator(
adapter = new BSONAdapter[Video](
collection = videoColl,
@ -95,11 +106,11 @@ private[video] final class VideoApi(
),
projection = BSONDocument(),
sort = BSONDocument("metadata.likes" -> -1)
),
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
def byAuthor(author: String, page: Int): Fu[Paginator[Video]] =
def byAuthor(user: Option[User], author: String, page: Int): Fu[Paginator[VideoView]] =
Paginator(
adapter = new BSONAdapter[Video](
collection = videoColl,
@ -108,11 +119,11 @@ private[video] final class VideoApi(
),
projection = BSONDocument(),
sort = BSONDocument("metadata.likes" -> -1)
),
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
def similar(video: Video, max: Int): Fu[List[Video]] =
def similar(user: Option[User], video: Video, max: Int): Fu[Seq[VideoView]] =
videoColl.find(BSONDocument(
"tags" -> BSONDocument("$in" -> video.tags),
"_id" -> BSONDocument("$ne" -> video.id)
@ -120,7 +131,7 @@ private[video] final class VideoApi(
.cursor[Video]
.collect[List]().map { videos =>
videos.sortBy { v => -v.similarity(video) } take max
}
} flatMap videoViews(user)
object count {
@ -149,6 +160,18 @@ private[video] final class VideoApi(
viewColl.db command Count(viewColl.name, BSONDocument(
View.BSONFields.id -> View.makeId(video.id, user.id)
).some) map (0!=)
def seenVideoIds(user: User, videos: Seq[Video]): Fu[Set[Video.ID]] =
viewColl.find(
BSONDocument(
"_id" -> BSONDocument("$in" -> videos.map { v =>
View.makeId(v.id, user.id)
})
),
BSONDocument(View.BSONFields.videoId -> true, "_id" -> false)
).cursor[BSONDocument].collect[List]() map { docs =>
docs.flatMap(_.getAs[String](View.BSONFields.videoId)).toSet
}
}
object tag {

View File

@ -8,6 +8,8 @@ case class View(
userId: String,
date: DateTime)
case class VideoView(video: Video, view: Boolean)
object View {
def makeId(videoId: Video.ID, userId: String) = s"$videoId/$userId"

View File

@ -90,6 +90,16 @@
transform: translateY(-100%);
opacity: 1;
}
#video .card .view {
position: absolute;
bottom: 5px;
right: 8px;
z-index: 2;
opacity: 0.3;
}
#video .card .view.yes {
opacity: 1;
}
#video .card .full-title {
font-weight: bold;
display: block;