user mod zone WIP
parent
75c7898008
commit
d4a3507070
|
@ -321,32 +321,38 @@ final class User(
|
|||
if (HTTPRequest isEventSource ctx.req) renderModZone(username)
|
||||
else fuccess(modC.redirect(username))
|
||||
|
||||
private def modZoneSegment[A](fu: Fu[Option[A]], name: String, user: UserModel): Source[A, _] =
|
||||
private def modZoneSegment(fu: Fu[Frag], name: String, user: UserModel): Source[Frag, _] =
|
||||
Source futureSource {
|
||||
fu.monSuccess(_.mod zoneSegment name)
|
||||
.logFailure(lila.log("modZoneSegment").branch(s"$name ${user.id}"))
|
||||
.map(_.fold(Source.empty[A])(Source.single))
|
||||
.map(Source.single)
|
||||
}
|
||||
|
||||
protected[controllers] def renderModZone(username: String)(implicit ctx: Context): Fu[Result] = {
|
||||
env.user.repo withEmails username orFail s"No such user $username" map {
|
||||
case UserModel.WithEmails(user, emails) =>
|
||||
val parts =
|
||||
env.mod.logApi.userHistory(user.id).logTimeIfGt(s"$username logApi.userHistory", 2 seconds) zip
|
||||
env.plan.api.recentChargesOf(user).logTimeIfGt(s"$username plan.recentChargesOf", 2 seconds) zip
|
||||
env.report.api.byAndAbout(user, 20).logTimeIfGt(s"$username report.byAndAbout", 2 seconds) zip
|
||||
env.pref.api.getPref(user).logTimeIfGt(s"$username pref.getPref", 2 seconds) zip
|
||||
env.playban.api.getRageSit(user.id) flatMap {
|
||||
case history ~ charges ~ reports ~ pref ~ rageSit =>
|
||||
env.user.lightUserApi
|
||||
.preloadMany(reports.userIds)
|
||||
.logTimeIfGt(s"$username lightUserApi.preloadMany", 2 seconds) inject
|
||||
html.user.mod.parts(user, history, charges, reports, pref, rageSit).some
|
||||
import html.user.{ mod => view }
|
||||
import lila.app.ui.ScalatagsExtensions.LilaFragZero
|
||||
|
||||
val modLog = env.mod.logApi.userHistory(user.id).map(view.modLog)
|
||||
|
||||
val plan = env.plan.api.recentChargesOf(user).map(view.plan).dmap(~_)
|
||||
|
||||
val reportLog = env.report.api
|
||||
.byAndAbout(user, 20)
|
||||
.flatMap { rs =>
|
||||
env.user.lightUserApi.preloadMany(rs.userIds) inject rs
|
||||
}
|
||||
.map(view.reportLog(user))
|
||||
|
||||
val prefs = env.pref.api.getPref(user).map(view.prefs)
|
||||
|
||||
val rageSit = env.playban.api.getRageSit(user.id).map(view.showRageSit)
|
||||
|
||||
val actions = env.user.repo.isErased(user) map { erased =>
|
||||
html.user.mod.actions(user, emails, erased).some
|
||||
html.user.mod.actions(user, emails, erased)
|
||||
}
|
||||
val spyFu = env.security.userSpy(user).logTimeIfGt(s"$username security.userSpy", 2 seconds)
|
||||
val spyFu = env.security.userSpy(user)
|
||||
val others = spyFu flatMap { spy =>
|
||||
val familyUserIds = user.id :: spy.otherUserIds.toList
|
||||
(isGranted(_.ModNote) ?? env.user.noteApi
|
||||
|
@ -355,29 +361,33 @@ final class User(
|
|||
env.playban.api.bans(familyUserIds).logTimeIfGt(s"$username playban.bans", 2 seconds) zip
|
||||
lila.security.UserSpy.withMeSortedWithEmails(env.user.repo, user, spy.otherUsers) map {
|
||||
case notes ~ bans ~ othersWithEmail =>
|
||||
html.user.mod.otherUsers(user, spy, othersWithEmail, notes, bans).some
|
||||
html.user.mod.otherUsers(user, spy, othersWithEmail, notes, bans)
|
||||
}
|
||||
}
|
||||
val identification = spyFu map { spy =>
|
||||
(isGranted(_.Doxing) || (user.lameOrAlt && !user.hasTitle)) option
|
||||
(isGranted(_.Doxing) || (user.lameOrAlt && !user.hasTitle)) ??
|
||||
html.user.mod.identification(spy, env.security.printBan.blocks)
|
||||
}
|
||||
val irwin = env.irwin.api.reports.withPovs(user) map {
|
||||
_ ?? { reps =>
|
||||
html.irwin.report(reps).some
|
||||
html.irwin.report(reps)
|
||||
}
|
||||
}
|
||||
val assess = env.mod.assessApi.getPlayerAggregateAssessmentWithGames(user.id) flatMap {
|
||||
_ ?? { as =>
|
||||
env.user.lightUserApi
|
||||
.preloadMany(as.games.flatMap(_.userIds)) inject html.user.mod.assessments(as).some
|
||||
.preloadMany(as.games.flatMap(_.userIds)) inject html.user.mod.assessments(as)
|
||||
}
|
||||
}
|
||||
implicit val extractor = EventSource.EventDataExtractor[Frag](_.render)
|
||||
Ok.chunked {
|
||||
Source.single(html.user.mod.menu(user)) merge
|
||||
modZoneSegment(parts, "parts", user) merge
|
||||
modZoneSegment(actions, "actions", user) merge
|
||||
modZoneSegment(modLog, "modLog", user) merge
|
||||
modZoneSegment(plan, "plan", user) merge
|
||||
modZoneSegment(reportLog, "reportLog", user) merge
|
||||
modZoneSegment(prefs, "prefs", user) merge
|
||||
modZoneSegment(rageSit, "rageSit", user) merge
|
||||
modZoneSegment(others, "others", user) merge
|
||||
modZoneSegment(identification, "identification", user) merge
|
||||
modZoneSegment(irwin, "irwin", user) merge
|
||||
|
|
|
@ -52,7 +52,7 @@ object Environment
|
|||
|
||||
def isChatPanicEnabled = env.chat.panic.enabled
|
||||
|
||||
def blockingReportNbOpen: Int = env.report.api.nbOpen.awaitOrElse(10.millis, "nbReports", 0)
|
||||
def blockingReportNbOpen: Int = env.report.api.nbOpen.awaitOrElse(20.millis, "nbReports", 0)
|
||||
|
||||
val spinner: Frag = raw(
|
||||
"""<div class="spinner"><svg viewBox="0 0 40 40"><circle cx=20 cy=20 r=18 fill="none"></circle></svg></div>"""
|
||||
|
|
|
@ -170,3 +170,5 @@ trait ScalatagsExtensions {
|
|||
|
||||
def titleOrText(v: String)(implicit ctx: Context): Modifier = titleOrText(ctx.blind, v)
|
||||
}
|
||||
|
||||
object ScalatagsExtensions extends ScalatagsExtensions
|
||||
|
|
|
@ -16,17 +16,13 @@ object mod {
|
|||
|
||||
def menu(u: User)(implicit ctx: Context) =
|
||||
div(id := "mz_menu")(
|
||||
a(href := "#mz_actions")("Actions"),
|
||||
canViewRoles(u) option a(href := "#mz_roles")("Roles"),
|
||||
a(href := "#mz_actions")("Overview"),
|
||||
a(href := "#mz_irwin")("Irwin"),
|
||||
a(href := "#mz_assessments")("Evaluation"),
|
||||
a(href := "#mz_plan", cls := "mz_plan")("Patron"),
|
||||
a(href := "#mz_mod_log")("Mod log"),
|
||||
a(href := "#mz_reports_out")("Reports sent"),
|
||||
a(href := "#mz_reports_in")("Reports received"),
|
||||
a(href := "#mz_reports")("Reports"),
|
||||
a(href := "#mz_others")("Accounts"),
|
||||
a(href := "#mz_identification")("Identification"),
|
||||
a(href := "#us_profile")("Profile")
|
||||
a(href := "#mz_identification")("Identification")
|
||||
)
|
||||
|
||||
def actions(u: User, emails: User.Emails, erased: User.Erased)(implicit ctx: Context): Frag =
|
||||
|
@ -189,23 +185,6 @@ object mod {
|
|||
)
|
||||
)
|
||||
|
||||
def parts(
|
||||
u: User,
|
||||
history: List[lila.mod.Modlog],
|
||||
charges: List[lila.plan.Charge],
|
||||
reports: lila.report.Report.ByAndAbout,
|
||||
pref: lila.pref.Pref,
|
||||
rageSit: RageSit
|
||||
)(implicit ctx: Context) =
|
||||
frag(
|
||||
roles(u),
|
||||
prefs(pref),
|
||||
plan(charges),
|
||||
showRageSit(rageSit),
|
||||
modLog(history),
|
||||
reportLog(u, reports)
|
||||
)
|
||||
|
||||
def roles(u: User)(implicit ctx: Context) =
|
||||
canViewRoles(u) option div(cls := "mz_roles")(
|
||||
(if (isGranted(_.ChangePermission)) a(href := routes.Mod.permissions(u.username)) else span)(
|
||||
|
@ -238,7 +217,7 @@ object mod {
|
|||
span(cls := "text inline")(rageSit.counterView)
|
||||
)
|
||||
|
||||
def plan(charges: List[lila.plan.Charge])(implicit ctx: Context) =
|
||||
def plan(charges: List[lila.plan.Charge])(implicit ctx: Context): Option[Frag] =
|
||||
charges.headOption.map { firstCharge =>
|
||||
div(id := "mz_plan")(
|
||||
strong(cls := "text", dataIcon := patronIconChar)(
|
||||
|
@ -287,9 +266,9 @@ object mod {
|
|||
)
|
||||
)
|
||||
|
||||
def reportLog(u: User, reports: lila.report.Report.ByAndAbout)(implicit lang: Lang) =
|
||||
frag(
|
||||
div(id := "mz_reports_out", cls := "mz_reports")(
|
||||
def reportLog(u: User)(reports: lila.report.Report.ByAndAbout)(implicit lang: Lang) =
|
||||
div(id := "mz_reports")(
|
||||
div(cls := "mz_reports mz_reports--out")(
|
||||
strong(cls := "text", dataIcon := "!")(
|
||||
s"Reports sent by ${u.username}",
|
||||
reports.by.isEmpty option ": nothing to show."
|
||||
|
@ -308,7 +287,7 @@ object mod {
|
|||
}
|
||||
}
|
||||
),
|
||||
div(id := "mz_reports_in", cls := "mz_reports")(
|
||||
div(cls := "mz_reports mz_reports--in")(
|
||||
strong(cls := "text", dataIcon := "!")(
|
||||
s"Reports concerning ${u.username}",
|
||||
reports.about.isEmpty option ": nothing to show."
|
||||
|
|
|
@ -93,7 +93,7 @@ object header {
|
|||
a(
|
||||
cls := "btn-rack__btn mod-zone-toggle",
|
||||
href := routes.User.mod(u.username),
|
||||
titleOrText("Mod zone"),
|
||||
titleOrText("Mod zone (Hotkey: m)"),
|
||||
dataIcon := ""
|
||||
),
|
||||
a(
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
%box-neat {
|
||||
@extend %box-shadow, %box-radius;
|
||||
}
|
||||
%box-neat-top {
|
||||
@extend %box-shadow, %box-radius-top;
|
||||
}
|
||||
%box-neat-force {
|
||||
@extend %box-shadow, %box-radius-force;
|
||||
}
|
||||
|
|
|
@ -13,41 +13,43 @@
|
|||
}
|
||||
}
|
||||
|
||||
#mz_actions { order: 1; }
|
||||
#mz_roles { order: 2; }
|
||||
#mz_preferences { order: 3; }
|
||||
#mz_irwin { order: 4; }
|
||||
#mz_assessments { order: 5; }
|
||||
#mz_plan { order: 6; }
|
||||
#mz_mod_log { order: 7; }
|
||||
#mz_reports_out { order: 8; }
|
||||
#mz_reports_in { order: 9; }
|
||||
#mz_others { order: 10; }
|
||||
#mz_identification { order: 11; }
|
||||
#mz_actions { order: 1 }
|
||||
#mz_sitdccounter { order: 2 }
|
||||
#mz_roles { order: 3 }
|
||||
#mz_preferences { order: 4 }
|
||||
#mz_plan { order: 5 }
|
||||
#mz_irwin { order: 6 }
|
||||
#mz_assessments { order: 7 }
|
||||
#mz_mod_log { order: 8 }
|
||||
#mz_reports { order: 9 }
|
||||
#mz_others { order: 10 }
|
||||
#mz_identification { order: 11 }
|
||||
|
||||
#mz_menu {
|
||||
@extend %flex-center;
|
||||
border-bottom: 3px solid $c-brag;
|
||||
height: 4em;
|
||||
@extend %flex-center, %box-neat-top;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background: $c-brag;
|
||||
z-index: 1;
|
||||
// border-top: 1px solid $c-brag;
|
||||
// border-width: 3px 2px 0 2px;
|
||||
padding: 0;
|
||||
a {
|
||||
padding: .9em 1.2em;
|
||||
color: $c-font;
|
||||
padding: 1.2em 1.2em;
|
||||
color: $c-brag-over;
|
||||
font-size: 1.1em;
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
font-weight: bold;
|
||||
opacity: 0.5;
|
||||
&.available {
|
||||
opacity: 1;
|
||||
}
|
||||
&:not(.disabled):hover {
|
||||
color: $c-brag;
|
||||
background: mix(white, $c-brag, 20%);
|
||||
}
|
||||
i {
|
||||
margin-right: .3em;
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
.mod-zone.stick-menu & {
|
||||
@extend %box-neat;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
height: auto;
|
||||
background: $c-bg-box;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,20 +174,26 @@
|
|||
color: $c-link;
|
||||
}
|
||||
}
|
||||
.mz_reports form {
|
||||
display: block;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.mz_reports button {
|
||||
padding: 0;
|
||||
margin-left: -2px;
|
||||
}
|
||||
.mz_reports button:not(:hover) {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
.mz_reports button strong {
|
||||
display: inline;
|
||||
#mz_reports {
|
||||
@extend %flex-wrap;
|
||||
.mz_reports {
|
||||
flex: 1 0 50ch;
|
||||
}
|
||||
form {
|
||||
display: block;
|
||||
padding: 0 10px;
|
||||
}
|
||||
button {
|
||||
padding: 0;
|
||||
margin-left: -2px;
|
||||
&:not(:hover) {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
strong {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
#mz_assessments table {
|
||||
margin-top: 10px;
|
||||
|
|
|
@ -1,41 +1,39 @@
|
|||
var tablesort = require('tablesort');
|
||||
|
||||
function streamLoad(opts) {
|
||||
var source = new EventSource(opts.url), first = true;
|
||||
source.addEventListener('message', function(e) {
|
||||
var newHtml = e.data;
|
||||
if (!newHtml) return;
|
||||
if (first) {
|
||||
first = false;
|
||||
opts.node.innerHTML = newHtml;
|
||||
} else {
|
||||
opts.node.innerHTML += newHtml;
|
||||
}
|
||||
let source = new EventSource(opts.url);
|
||||
source.addEventListener('message', e => {
|
||||
if (!e.data) return;
|
||||
opts.node.innerHTML += e.data;
|
||||
opts.callback();
|
||||
});
|
||||
source.onerror = function() { source.close(); };
|
||||
source.onerror = () => source.close();
|
||||
}
|
||||
|
||||
var $toggle = $('.user-show .mod-zone-toggle');
|
||||
var $zone = $('.user-show .mod-zone');
|
||||
let $toggle = $('.user-show .mod-zone-toggle');
|
||||
let $zone = $('.user-show .mod-zone');
|
||||
|
||||
function loadZone() {
|
||||
$('.user-show').css('overflow', 'visible'); // required for mz_menu to be displayed
|
||||
$zone.html(lichess.spinnerHtml).removeClass('none');
|
||||
$('#main-wrap').addClass('full-screen-force');
|
||||
$zone.html('');
|
||||
streamLoad({
|
||||
node: $zone[0],
|
||||
url: $toggle.attr('href'),
|
||||
callback: lichess.debounce(function() {
|
||||
userMod($zone);
|
||||
}, 300)
|
||||
callback: lichess.debounce(() => userMod($zone), 300)
|
||||
});
|
||||
window.addEventListener('scroll', onScroll);
|
||||
window.addEventListener('scroll', onScroll);
|
||||
scrollTo('.mod-zone');
|
||||
}
|
||||
function unloadZone() {
|
||||
$zone.addClass('none');
|
||||
$('#main-wrap').removeClass('full-screen-force');
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
scrollTo('#top');
|
||||
}
|
||||
|
||||
function scrollTo(el) {
|
||||
window.scrollTo(0, document.querySelector(el).offsetTop);
|
||||
}
|
||||
|
||||
$toggle.click(function() {
|
||||
|
@ -48,10 +46,20 @@ function userMod($zone) {
|
|||
|
||||
lichess.pubsub.emit('content_loaded');
|
||||
|
||||
$('#mz_menu .mz_plan').toggleClass('disabled', !$('#mz_plan').length);
|
||||
$('#mz_menu > a:not(.available)').each(function() {
|
||||
$(this).toggleClass('available', !!$($(this).attr('href')).length);
|
||||
});
|
||||
$('#mz_menu > a:not(.hotkey)').each(function(i) {
|
||||
const id = this.href.replace(/.+(#\w+)$/, '$1'), n = '' + (i + 1);
|
||||
$(this).addClass('hotkey').prepend(`<i>${n}</i>`);
|
||||
Mousetrap.bind(n, () => {
|
||||
console.log(id, n);
|
||||
scrollTo(id);
|
||||
});
|
||||
});
|
||||
|
||||
$zone.find('form.xhr').submit(function() {
|
||||
$(this).find('input').attr('disabled', true);
|
||||
$zone.find('form.xhr:not(.ready)').submit(function() {
|
||||
$(this).addClass('ready').find('input').attr('disabled', true);
|
||||
$.ajax({
|
||||
...lichess.formAjax($(this)),
|
||||
success: function(html) {
|
||||
|
@ -78,28 +86,6 @@ function userMod($zone) {
|
|||
});
|
||||
}
|
||||
|
||||
(function(){
|
||||
var cleanNumber = function(i) {
|
||||
return i.replace(/[^\-?0-9.]/g, '');
|
||||
},
|
||||
|
||||
compareNumber = function(a, b) {
|
||||
a = parseFloat(a);
|
||||
b = parseFloat(b);
|
||||
|
||||
a = isNaN(a) ? 0 : a;
|
||||
b = isNaN(b) ? 0 : b;
|
||||
|
||||
return a - b;
|
||||
};
|
||||
|
||||
tablesort.extend('number', function(item) {
|
||||
return item.match(/^[-+]?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/); // Number
|
||||
}, function(a, b) {
|
||||
return compareNumber(cleanNumber(b), cleanNumber(a));
|
||||
});
|
||||
}());
|
||||
|
||||
$zone.find('#mz_others table').each(function() {
|
||||
tablesort(this, {
|
||||
descending: true
|
||||
|
@ -109,7 +95,30 @@ function userMod($zone) {
|
|||
|
||||
const onScroll = e => requestAnimationFrame(() => {
|
||||
if ($zone.hasClass('none')) return;
|
||||
$zone.toggleClass('stick-menu', window.scrollY > 220);
|
||||
$zone.toggleClass('stick-menu', window.scrollY > 200);
|
||||
});
|
||||
|
||||
(function(){
|
||||
var cleanNumber = function(i) {
|
||||
return i.replace(/[^\-?0-9.]/g, '');
|
||||
},
|
||||
|
||||
compareNumber = function(a, b) {
|
||||
a = parseFloat(a);
|
||||
b = parseFloat(b);
|
||||
|
||||
a = isNaN(a) ? 0 : a;
|
||||
b = isNaN(b) ? 0 : b;
|
||||
|
||||
return a - b;
|
||||
};
|
||||
|
||||
tablesort.extend('number', function(item) {
|
||||
return item.match(/^[-+]?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/); // Number
|
||||
}, function(a, b) {
|
||||
return compareNumber(cleanNumber(b), cleanNumber(a));
|
||||
});
|
||||
}());
|
||||
|
||||
if (location.search.startsWith('?mod')) $toggle.click();
|
||||
Mousetrap.bind('m', () => $toggle.click());
|
||||
|
|
Loading…
Reference in New Issue