streamer show UI
parent
ae5e08a4ff
commit
d07da0e3e4
|
@ -20,6 +20,12 @@ trait ScalatagsAttrs {
|
|||
lazy val dataColor = attr("data-color")
|
||||
lazy val dataFen = attr("data-fen")
|
||||
lazy val novalidate = attr("novalidate")
|
||||
object frame {
|
||||
val frameborder = attr("frameborder")
|
||||
val scrolling = attr("scrolling")
|
||||
val allowfullscreen = attr("allowfullscreen")
|
||||
val autoplay = attr("autoplay")
|
||||
}
|
||||
}
|
||||
|
||||
// collection of lila snippets
|
||||
|
|
|
@ -28,7 +28,7 @@ object bits {
|
|||
}
|
||||
|
||||
def menu(active: String, s: Option[lila.streamer.Streamer.WithUser])(implicit ctx: Context) =
|
||||
st.nav(cls := "page-menu__menu subnav")(
|
||||
st.nav(cls := "subnav")(
|
||||
a(cls := active.active("index"), href := routes.Streamer.index())("All streamers"),
|
||||
s.map { st =>
|
||||
frag(
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package views.html.streamer
|
||||
|
||||
import controllers.routes
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
||||
object header {
|
||||
|
||||
def apply(s: lila.streamer.Streamer.WithUserAndStream, following: Option[Boolean])(implicit ctx: Context) =
|
||||
div(cls := "top")(
|
||||
bits.pic(s.streamer, s.user, 300),
|
||||
div(cls := "overview")(
|
||||
h1(
|
||||
titleTag(s.user.title),
|
||||
s.streamer.name
|
||||
),
|
||||
s.streamer.headline.map(_.value).map { d =>
|
||||
p(cls := s"headline ${if (d.size < 60) "small" else if (d.size < 120) "medium" else "large"}")(d)
|
||||
},
|
||||
div(cls := "services")(
|
||||
s.streamer.twitch.map { twitch =>
|
||||
a(
|
||||
cls := List(
|
||||
"service twitch" -> true,
|
||||
"live" -> s.stream.exists(_.twitch)
|
||||
),
|
||||
href := twitch.fullUrl
|
||||
)(bits.svg.twitch, " ", twitch.minUrl)
|
||||
},
|
||||
s.streamer.youTube.map { youTube =>
|
||||
a(
|
||||
cls := List(
|
||||
"service youTube" -> true,
|
||||
"live" -> s.stream.exists(_.twitch)
|
||||
),
|
||||
href := youTube.fullUrl
|
||||
)(bits.svg.youTube, " ", youTube.minUrl)
|
||||
},
|
||||
a(cls := "service lichess", href := routes.User.show(s.user.username))(
|
||||
bits.svg.lichess,
|
||||
" ",
|
||||
s"lichess.org/@/${s.user.username}"
|
||||
)
|
||||
),
|
||||
div(cls := "ats")(
|
||||
s.stream.map { s =>
|
||||
p(cls := "at")(
|
||||
"Currently streaming: ",
|
||||
strong(s.status)
|
||||
)
|
||||
} getOrElse frag(
|
||||
p(cls := "at")(trans.lastSeenActive.frag(momentFromNow(s.streamer.seenAt))),
|
||||
s.streamer.liveAt.map { liveAt =>
|
||||
p(cls := "at")("Last stream ", momentFromNow(liveAt))
|
||||
}
|
||||
)
|
||||
),
|
||||
following.map { f =>
|
||||
(ctx.isAuth && !ctx.is(s.user)) option
|
||||
button(attr("data-user") := s.user.id, cls := List(
|
||||
"follow icon button" -> true,
|
||||
"active" -> f
|
||||
), tpe := "submit")(
|
||||
span(cls := "text", dataIcon := "h")(trans.follow.frag())
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
@(s: lila.streamer.Streamer.WithUserAndStream, following: Option[Boolean])(implicit ctx: Context)
|
||||
<div class="top">
|
||||
@bits.pic(s.streamer, s.user, 250)
|
||||
<div class="overview">
|
||||
<h1>@titleTag(s.user.title)@s.streamer.name</h1>
|
||||
@s.streamer.headline.map(_.value).map { d =>
|
||||
<p class="headline @if(d.size < 60){small} else {@if(d.size < 120){medium}else{large}}">@d</p>
|
||||
}
|
||||
<div class="services">
|
||||
@s.streamer.twitch.map { twitch =>
|
||||
<a class="service twitch@if(s.stream.exists(_.twitch)){ live}" href="@twitch.fullUrl">
|
||||
@bits.svg.twitch
|
||||
@twitch.minUrl
|
||||
</a>
|
||||
}
|
||||
@s.streamer.youTube.map { youTube =>
|
||||
<a class="service youTube@if(s.stream.exists(_.youTube)){ live}" href="@youTube.fullUrl">
|
||||
@bits.svg.youTube
|
||||
@youTube.minUrl
|
||||
</a>
|
||||
}
|
||||
<a class="service lichess" href="@routes.User.show(s.user.username)">
|
||||
@bits.svg.lichess
|
||||
lichess.org/@@/@s.user.username
|
||||
</a>
|
||||
</div>
|
||||
<div class="metas">
|
||||
<div class="ats">
|
||||
@s.stream.map { s =>
|
||||
<p class="at">
|
||||
Currently streaming:<br />
|
||||
<strong>@s.status</strong>
|
||||
</p>
|
||||
}.getOrElse {
|
||||
<p class="at">@trans.lastSeenActive(momentFromNow(s.streamer.seenAt))</p>
|
||||
@s.streamer.liveAt.map { liveAt =>
|
||||
<p class="at">Last stream @momentFromNow(liveAt)</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@following.map { f =>
|
||||
@if(ctx.isAuth && !ctx.is(s.user)) {
|
||||
<button data-user="@s.user.id" class="follow icon button@if(f){ active}" type="submit">
|
||||
<span class="text" data-icon="h">@trans.follow()</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +1,5 @@
|
|||
package views.html.streamer
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import controllers.routes
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
|
@ -74,7 +72,7 @@ object index {
|
|||
moreJs = infiniteScrollTag
|
||||
) {
|
||||
main(cls := "page-menu")(
|
||||
bits.menu("index", none),
|
||||
bits.menu("index", none)(ctx)(cls := " page-menu__menu"),
|
||||
div(cls := "page-menu__content box streamer-list")(
|
||||
h1(dataIcon := "", cls := "text")(title),
|
||||
!requests option div(cls := "list live")(
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package views.html.streamer
|
||||
|
||||
import controllers.routes
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.String.html.richText
|
||||
import lila.streamer.Stream.{ Twitch, YouTube }
|
||||
|
||||
object show {
|
||||
|
||||
def apply(
|
||||
s: lila.streamer.Streamer.WithUserAndStream,
|
||||
activities: Vector[lila.activity.ActivityView],
|
||||
following: Boolean
|
||||
)(implicit ctx: Context) = views.html.base.layout(
|
||||
title = s"${s.titleName} streams chess",
|
||||
responsive = true,
|
||||
moreCss = responsiveCssTag("streamer.show"),
|
||||
moreJs = embedJs("""
|
||||
$(function() {
|
||||
$('button.follow').click(function() {
|
||||
var klass = 'active';
|
||||
$(this).toggleClass(klass);
|
||||
$.ajax({
|
||||
url: '/rel/' + ($(this).hasClass('active') ? 'follow/' : 'unfollow/') + $(this).data('user'),
|
||||
method:'post'
|
||||
});
|
||||
});
|
||||
});"""),
|
||||
openGraph = lila.app.ui.OpenGraph(
|
||||
title = s"${s.titleName} streams chess",
|
||||
description = shorten(~(s.streamer.headline.map(_.value) orElse s.streamer.description.map(_.value)), 152),
|
||||
url = s"$netBaseUrl${routes.Streamer.show(s.user.username)}",
|
||||
`type` = "video",
|
||||
image = s.streamer.picturePath.map(p => dbImageUrl(p.value))
|
||||
).some,
|
||||
csp = defaultCsp.withTwitch.some
|
||||
)(
|
||||
main(cls := "page-menu streamer-show")(
|
||||
st.aside(cls := "page-menu__menu")(
|
||||
s.streamer.approval.chatEnabled option div(cls := "streamer-chat")(
|
||||
s.stream match {
|
||||
case Some(YouTube.Stream(_, _, videoId, _)) => iframe(
|
||||
frame.frameborder := "0",
|
||||
frame.scrolling := "no",
|
||||
src := s"https://www.youtube.com/live_chat?v=$videoId&embed_domain=$netDomain"
|
||||
)
|
||||
case _ => s.streamer.twitch.map { twitch =>
|
||||
iframe(
|
||||
frame.frameborder := "0",
|
||||
frame.scrolling := "yes",
|
||||
src := s"https://twitch.tv/embed/${twitch.userId}/chat${(ctx.currentBg != "light") ?? "?darkpopout"}"
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
bits.menu("show", s.withoutStream.some),
|
||||
a(cls := "blocker button button-metal", href := "https://getublockorigin.com")(
|
||||
i(dataIcon := ""),
|
||||
strong("Install a malware blocker!"),
|
||||
"Be safe from ads and trackers", br,
|
||||
"infesting Twitch and YouTube.", br,
|
||||
"Lichess recommend uBlock Origin", br,
|
||||
"which is free and open-source."
|
||||
)
|
||||
),
|
||||
div(cls := "page-menu__content")(
|
||||
s.stream match {
|
||||
case Some(YouTube.Stream(_, _, videoId, _)) => div(cls := "box embed youTube")(
|
||||
iframe(
|
||||
src := s"https://www.youtube.com/embed/$videoId?autoplay=1",
|
||||
frame.frameborder := "0",
|
||||
frame.allowfullscreen := true
|
||||
)
|
||||
)
|
||||
case _ => s.streamer.twitch.map { twitch =>
|
||||
div(cls := "box embed twitch")(
|
||||
iframe(
|
||||
src := s"https://player.twitch.tv/?channel=${twitch.userId}",
|
||||
frame.allowfullscreen := true,
|
||||
frame.autoplay := true
|
||||
)
|
||||
)
|
||||
} getOrElse div(cls := "box embed")(div(cls := "nostream")("OFFLINE"))
|
||||
},
|
||||
div(cls := "box streamer")(
|
||||
header(s, following.some),
|
||||
div(cls := "description")(richText(s.streamer.description.fold("")(_.value))),
|
||||
a(cls := "ratings", href := routes.User.show(s.user.username))(
|
||||
s.user.best6Perfs.map { showPerfRating(s.user, _) }
|
||||
),
|
||||
views.html.activity(s.user, activities)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
@(s: lila.streamer.Streamer.WithUserAndStream, activities: Vector[lila.activity.ActivityView], following: Boolean)(implicit ctx: Context)
|
||||
|
||||
@import lila.streamer.Stream.{ Twitch, YouTube }
|
||||
|
||||
@side = {
|
||||
@if(s.streamer.approval.chatEnabled) {
|
||||
<div class="side streamer-side">
|
||||
@s.stream match {
|
||||
case Some(YouTube.Stream(_, _, videoId, _)) => {
|
||||
<iframe
|
||||
frameborder="0"
|
||||
scrolling="no"
|
||||
src="https://www.youtube.com/live_chat?v=@videoId&embed_domain=@netDomain"
|
||||
width="235"
|
||||
height="500"></iframe>
|
||||
}
|
||||
case _ => {
|
||||
@s.streamer.twitch.map { twitch =>
|
||||
<iframe
|
||||
frameborder="0"
|
||||
scrolling="yes"
|
||||
src="https://twitch.tv/embed/@twitch.userId/chat@if(ctx.currentBg != "light"){?darkpopout}"
|
||||
width="235"
|
||||
height="500"></iframe>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="side_menu">
|
||||
@bits.menu("show", s.withoutStream.some).toHtml
|
||||
</div>
|
||||
<a class="blocker button" href="https://getublockorigin.com">
|
||||
<i data-icon=""></i>
|
||||
<strong>Install a malware blocker!</strong>
|
||||
Be safe from ads and trackers<br />
|
||||
infesting Twitch and YouTube.<br />
|
||||
We recommend uBlock Origin<br />
|
||||
which is free and open-source.
|
||||
</a>
|
||||
}
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("streamer.show.css")
|
||||
@cssTag("activity.css")
|
||||
}
|
||||
|
||||
@moreJs = {
|
||||
@embedJs {
|
||||
$(function() {
|
||||
$('button.follow').click(function() {
|
||||
var klass = 'active';
|
||||
$(this).toggleClass(klass);
|
||||
$.ajax({
|
||||
url: '/rel/' + ($(this).hasClass('active') ? 'follow/' : 'unfollow/') + $(this).data('user'),
|
||||
method:'post'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@title = @{ s"${s.titleName} streams chess" }
|
||||
|
||||
@base.layout(title = title,
|
||||
side = side.some,
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
openGraph = lila.app.ui.OpenGraph(
|
||||
title = title,
|
||||
description = shorten(~(s.streamer.headline.map(_.value) orElse s.streamer.description.map(_.value)), 152),
|
||||
url = s"$netBaseUrl${routes.Streamer.show(s.user.username)}",
|
||||
`type` = "video",
|
||||
image = s.streamer.picturePath.map(p => dbImageUrl(p.value))).some,
|
||||
csp = defaultCsp.withTwitch.some) {
|
||||
@s.stream match {
|
||||
case Some(YouTube.Stream(_, _, videoId, _)) => {
|
||||
<div class="content_box no_padding livestream youTube">
|
||||
<iframe
|
||||
width="792"
|
||||
height="446"
|
||||
src="https://www.youtube.com/embed/@videoId?autoplay=1"
|
||||
frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
}
|
||||
case _ => {
|
||||
@s.streamer.twitch.map { twitch =>
|
||||
<div class="content_box no_padding livestream twitch">
|
||||
<iframe
|
||||
src="https://player.twitch.tv/?channel=@twitch.userId"
|
||||
width="792"
|
||||
height="446"
|
||||
allowfullscreen autoplay></iframe>
|
||||
</div>
|
||||
}.getOrElse {
|
||||
<div class="content_box no_padding nostream">OFFLINE</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
<div class="content_box no_padding streamer">
|
||||
@header(s, following.some)
|
||||
<div class="description">@richText(s.streamer.description.fold("")(_.value))</div>
|
||||
<a class="ratings" href="@routes.User.show(s.user.username)">
|
||||
@s.user.best6Perfs.map { pt =>
|
||||
@showPerfRating(s.user, pt)
|
||||
}
|
||||
</a>
|
||||
@views.html.activity(s.user, activities).toHtml
|
||||
</div>
|
||||
}.toHtml
|
|
@ -1,119 +0,0 @@
|
|||
.streamer-side {
|
||||
margin: 15px 0 0 -35px;
|
||||
}
|
||||
.streamer .top {
|
||||
display: flex;
|
||||
}
|
||||
.streamer img.picture {
|
||||
flex: 0 0 250px;
|
||||
}
|
||||
.streamer .overview {
|
||||
margin: 20px 10px 0 20px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.streamer .metas {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
}
|
||||
.streamer .follow:disabled {
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
div.content_box.streamer h1 {
|
||||
font-family: 'Roboto';
|
||||
font-size: 32px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
padding: 0!important;
|
||||
text-shadow: none!important;
|
||||
}
|
||||
.streamer .headline {
|
||||
font-family: 'PT Serif', 'Noto Sans', 'Lucida Grande';
|
||||
font-size: 17px;
|
||||
padding: 0!important;
|
||||
}
|
||||
.streamer .headline.medium {
|
||||
font-size: 15px;
|
||||
}
|
||||
.streamer .headline.large {
|
||||
font-size: 14px;
|
||||
}
|
||||
.streamer .services {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.streamer .service {
|
||||
display: flex;
|
||||
font-size: 1.5em;
|
||||
white-space: nowrap;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.streamer .service.live {
|
||||
color: #dc322f;
|
||||
}
|
||||
.streamer .service svg {
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
.streamer a.service:hover svg path {
|
||||
fill: #3893E8!important;
|
||||
}
|
||||
.livestream {
|
||||
margin-bottom: 0px;
|
||||
line-height: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.livestream iframe {
|
||||
border: none;
|
||||
}
|
||||
.nostream {
|
||||
height: 300px;
|
||||
background: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40px;
|
||||
letter-spacing: 10px;
|
||||
}
|
||||
.streamer .description {
|
||||
padding: 2.5em 50px 2.5em 65px;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.streamer .services a:hover,
|
||||
.streamer .description a {
|
||||
color: #3893E8;
|
||||
}
|
||||
.streamer .ratings {
|
||||
padding: 2em 50px 2em 58px;
|
||||
font-size: 1.6em;
|
||||
background: rgba(213,145,32,0.2);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.streamer .ratings span::before {
|
||||
font-size: 1.6em;
|
||||
margin-right: 0.1em;
|
||||
}
|
||||
|
||||
.blocker {
|
||||
margin-top: 30px;
|
||||
padding: 15px 0!important;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-weight: normal!important;
|
||||
}
|
||||
.blocker i {
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
body.dark .streamer .service svg path {
|
||||
fill: #aaa;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
@import '../../../common/css/plugin';
|
||||
@import '../user/activity';
|
||||
@import '../streamer/show';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/dark';
|
||||
@import 'streamer.show';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/light';
|
||||
@import 'streamer.show';
|
|
@ -0,0 +1,2 @@
|
|||
@import '../../../common/css/theme/transp';
|
||||
@import 'streamer.show';
|
|
@ -0,0 +1,129 @@
|
|||
.streamer-show {
|
||||
.streamer-chat {
|
||||
margin-right: $block-gap;
|
||||
display: none;
|
||||
@include breakpoint($mq-small) {
|
||||
display: block;
|
||||
}
|
||||
iframe {
|
||||
height: 500px;
|
||||
}
|
||||
}
|
||||
.top {
|
||||
display: flex;
|
||||
}
|
||||
.picture {
|
||||
flex: 0 0 300px;
|
||||
}
|
||||
.overview {
|
||||
margin: 20px 10px 0 20px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.metas {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
}
|
||||
.follow:disabled {
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
h1 {
|
||||
@extend %roboto;
|
||||
font-size: 2.2rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
margin-bottom: .7em;
|
||||
}
|
||||
.headline {
|
||||
font-style: italic;
|
||||
}
|
||||
.headline.medium {
|
||||
font-size: .95em;
|
||||
}
|
||||
.headline.large {
|
||||
font-size: .9em;
|
||||
}
|
||||
.services {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.service {
|
||||
display: flex;
|
||||
font-size: 1.2em;
|
||||
white-space: nowrap;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.service.live {
|
||||
color: $c-brag;
|
||||
}
|
||||
.service svg {
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
a.service:hover svg path {
|
||||
fill: $c-link;
|
||||
}
|
||||
.embed {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
margin-bottom: $block-gap;
|
||||
}
|
||||
.embed > * {
|
||||
@extend %abs-100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: none;
|
||||
}
|
||||
.nostream {
|
||||
background: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40px;
|
||||
letter-spacing: 10px;
|
||||
}
|
||||
.description {
|
||||
padding: 2.5em 50px 2.5em 65px;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.services a:hover,
|
||||
.description a {
|
||||
color: #3893E8;
|
||||
}
|
||||
.ratings {
|
||||
padding: 2em 50px 2em 58px;
|
||||
font-size: 1.6em;
|
||||
line-height: .9;
|
||||
background: mix($c-brag, $c-bg-box, 20%);
|
||||
color: $c-font;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
span::before {
|
||||
font-size: 1.6em;
|
||||
margin-right: 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.blocker {
|
||||
margin: $block-gap $block-gap 0 0;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
.blocker i {
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue