improve navigation in videos
This commit is contained in:
parent
2c7e6e53aa
commit
436ea7e89b
|
@ -16,26 +16,18 @@ object Video extends LilaController {
|
||||||
|
|
||||||
private def WithUserControl[A](f: UserControl => Fu[A])(implicit ctx: Context): Fu[A] = {
|
private def WithUserControl[A](f: UserControl => Fu[A])(implicit ctx: Context): Fu[A] = {
|
||||||
val reqTags = get("tags") ?? (_.split(',').toList.map(_.trim.toLowerCase))
|
val reqTags = get("tags") ?? (_.split(',').toList.map(_.trim.toLowerCase))
|
||||||
env.api.tag.pathsAnd(30, reqTags) map { tags =>
|
env.api.tag.paths(reqTags) map { tags =>
|
||||||
UserControl(
|
UserControl(filter = Filter(reqTags), tags = tags)
|
||||||
filter = Filter(reqTags),
|
|
||||||
tags = tags)
|
|
||||||
} flatMap f
|
} flatMap f
|
||||||
}
|
}
|
||||||
|
|
||||||
private def renderIndex(control: UserControl)(implicit ctx: Context) =
|
|
||||||
env.api.video.byTags(control.filter.tags, getInt("page") | 1) zip
|
|
||||||
env.api.video.count.apply map {
|
|
||||||
case (videos, count) =>
|
|
||||||
Ok(html.video.index(videos, count, control))
|
|
||||||
}
|
|
||||||
|
|
||||||
def index = Open { implicit ctx =>
|
def index = Open { implicit ctx =>
|
||||||
WithUserControl { control =>
|
WithUserControl { control =>
|
||||||
val filter = control.filter.copy(
|
env.api.video.byTags(control.filter.tags, getInt("page") | 1) zip
|
||||||
tags = get("tags") ?? (_.split(',').toList.map(_.trim.toLowerCase))
|
env.api.video.count.apply map {
|
||||||
)
|
case (videos, count) =>
|
||||||
renderIndex(control.copy(filter = filter))
|
Ok(html.video.index(videos, count, control))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@if(control.filter.tags.nonEmpty) {
|
||||||
|
<div class="under_tags">
|
||||||
|
<a class="button" href="@routes.Video.index">Clear search</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,10 +47,6 @@ final class Env(
|
||||||
youtube.updateAll logFailure "video youtube"
|
youtube.updateAll logFailure "video youtube"
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduler.once(15 seconds) {
|
|
||||||
sheet.fetchAll >> youtube.updateAll logFailure "video boot"
|
|
||||||
}
|
|
||||||
|
|
||||||
private[video] lazy val videoColl = db(CollectionVideo)
|
private[video] lazy val videoColl = db(CollectionVideo)
|
||||||
private[video] lazy val viewColl = db(CollectionView)
|
private[video] lazy val viewColl = db(CollectionView)
|
||||||
private[video] lazy val filterColl = db(CollectionFilter)
|
private[video] lazy val filterColl = db(CollectionFilter)
|
||||||
|
|
|
@ -4,11 +4,11 @@ import org.joda.time.DateTime
|
||||||
import reactivemongo.bson._
|
import reactivemongo.bson._
|
||||||
import reactivemongo.core.commands._
|
import reactivemongo.core.commands._
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import spray.caching.{ LruCache, Cache }
|
|
||||||
|
|
||||||
import lila.common.paginator._
|
import lila.common.paginator._
|
||||||
import lila.db.paginator.BSONAdapter
|
import lila.db.paginator.BSONAdapter
|
||||||
import lila.db.Types.Coll
|
import lila.db.Types.Coll
|
||||||
|
import lila.memo.AsyncCache
|
||||||
import lila.user.{ User, UserRepo }
|
import lila.user.{ User, UserRepo }
|
||||||
|
|
||||||
private[video] final class VideoApi(
|
private[video] final class VideoApi(
|
||||||
|
@ -104,13 +104,14 @@ private[video] final class VideoApi(
|
||||||
}
|
}
|
||||||
|
|
||||||
object count {
|
object count {
|
||||||
private val cache: Cache[Int] = LruCache(timeToLive = 1.day)
|
|
||||||
|
|
||||||
def clearCache = fuccess(cache.clear)
|
private val cache = AsyncCache.single(
|
||||||
|
f = videoColl.db command Count(videoColl.name, none),
|
||||||
|
timeToLive = 1.day)
|
||||||
|
|
||||||
def apply: Fu[Int] = cache(true) {
|
def clearCache = cache.clear
|
||||||
videoColl.db command Count(videoColl.name, none)
|
|
||||||
}
|
def apply: Fu[Int] = cache apply true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,54 +134,58 @@ private[video] final class VideoApi(
|
||||||
|
|
||||||
object tag {
|
object tag {
|
||||||
|
|
||||||
private val nbCache: Cache[List[TagNb]] = LruCache(timeToLive = 1.day)
|
def paths(filterTags: List[Tag]): Fu[List[TagNb]] = pathsCache(filterTags.sorted)
|
||||||
|
|
||||||
def clearCache = fuccess {
|
def clearCache = pathsCache.clear >> popularCache.clear
|
||||||
nbCache.clear
|
|
||||||
}
|
|
||||||
|
|
||||||
def pathsAnd(max: Int, forced: List[Tag]): Fu[List[TagNb]] =
|
private val max = 25
|
||||||
popular zip paths(forced) map {
|
|
||||||
case (all, paths) =>
|
private val pathsCache = AsyncCache[List[Tag], List[TagNb]](
|
||||||
val tags = all take max map { t =>
|
f = filterTags => {
|
||||||
paths find (_._id == t._id) getOrElse TagNb(t._id, 0)
|
val allPaths =
|
||||||
|
if (filterTags.isEmpty) popularCache(true)
|
||||||
|
else {
|
||||||
|
val command = Aggregate(videoColl.name, Seq(
|
||||||
|
Match(BSONDocument("tags" -> BSONDocument("$all" -> filterTags))),
|
||||||
|
Project("tags" -> BSONBoolean(true)),
|
||||||
|
Unwind("tags"),
|
||||||
|
GroupField("tags")("nb" -> SumValue(1))
|
||||||
|
))
|
||||||
|
videoColl.db.command(command) map {
|
||||||
|
_.toList.flatMap(_.asOpt[TagNb])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val missing = forced filterNot { t =>
|
popularCache(true) zip allPaths map {
|
||||||
tags exists (_.tag == t)
|
case (all, paths) =>
|
||||||
}
|
val tags = all map { t =>
|
||||||
tags.take(max - missing.size) ::: missing.flatMap { t =>
|
paths find (_._id == t._id) getOrElse TagNb(t._id, 0)
|
||||||
all find (_.tag == t)
|
} filterNot (_.empty) take max
|
||||||
}
|
val missing = filterTags filterNot { t =>
|
||||||
}
|
tags exists (_.tag == t)
|
||||||
|
}
|
||||||
|
val list = tags.take(max - missing.size) ::: missing.flatMap { t =>
|
||||||
|
all find (_.tag == t)
|
||||||
|
}
|
||||||
|
list.sortBy { t =>
|
||||||
|
if (filterTags contains t.tag) Int.MinValue
|
||||||
|
else -t.nb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeToLive = 1.day)
|
||||||
|
|
||||||
def popular: Fu[List[TagNb]] = nbCache("") {
|
private val popularCache = AsyncCache.single[List[TagNb]](
|
||||||
import reactivemongo.core.commands._
|
f = {
|
||||||
val command = Aggregate(videoColl.name, Seq(
|
|
||||||
Project("tags" -> BSONBoolean(true)),
|
|
||||||
Unwind("tags"),
|
|
||||||
GroupField("tags")("nb" -> SumValue(1)),
|
|
||||||
Sort(Seq(Descending("nb")))
|
|
||||||
))
|
|
||||||
videoColl.db.command(command) map {
|
|
||||||
_.toList.flatMap(_.asOpt[TagNb])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def paths(tags: List[Tag]): Fu[List[TagNb]] =
|
|
||||||
if (tags.isEmpty) popular
|
|
||||||
else nbCache(tags.sorted.mkString(",")) {
|
|
||||||
import reactivemongo.core.commands._
|
|
||||||
val command = Aggregate(videoColl.name, Seq(
|
val command = Aggregate(videoColl.name, Seq(
|
||||||
Match(BSONDocument("tags" -> BSONDocument("$all" -> tags))),
|
|
||||||
Project("tags" -> BSONBoolean(true)),
|
Project("tags" -> BSONBoolean(true)),
|
||||||
Unwind("tags"),
|
Unwind("tags"),
|
||||||
// Match(BSONDocument("tags" -> BSONDocument("$nin" -> tags))),
|
|
||||||
GroupField("tags")("nb" -> SumValue(1)),
|
GroupField("tags")("nb" -> SumValue(1)),
|
||||||
Sort(Seq(Descending("nb")))
|
Sort(Seq(Descending("nb")))
|
||||||
))
|
))
|
||||||
videoColl.db.command(command) map {
|
videoColl.db.command(command) map {
|
||||||
_.toList.flatMap(_.asOpt[TagNb])
|
_.toList.flatMap(_.asOpt[TagNb])
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
timeToLive = 1.day)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import org.joda.time.DateTime
|
||||||
case class TagNb(_id: Tag, nb: Int) {
|
case class TagNb(_id: Tag, nb: Int) {
|
||||||
|
|
||||||
def tag = _id
|
def tag = _id
|
||||||
|
|
||||||
|
def empty = nb == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Filter(tags: List[String]) {
|
case class Filter(tags: List[String]) {
|
||||||
|
|
|
@ -147,6 +147,10 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
#video_side .under_tags {
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
#video .not_found {
|
#video .not_found {
|
||||||
margin-top: 200px;
|
margin-top: 200px;
|
||||||
|
|
Loading…
Reference in a new issue