study explorer game insertion - WIP
parent
0b8340fa1f
commit
7866dd59ab
|
@ -54,16 +54,6 @@ object Importer extends LilaController {
|
||||||
val fenParam = get("fen").??(f => s"?fen=$f")
|
val fenParam = get("fen").??(f => s"?fen=$f")
|
||||||
s"$url$fenParam"
|
s"$url$fenParam"
|
||||||
}
|
}
|
||||||
GameRepo game id flatMap {
|
Env.explorer.importer(id) map2 redirectAtFen
|
||||||
case Some(game) if game.createdAt.isAfter(masterGameEncodingFixedAt) => fuccess(redirectAtFen(game))
|
|
||||||
case _ => (GameRepo remove id) >> Env.explorer.fetchPgn(id) flatMap {
|
|
||||||
case None => fuccess(NotFound)
|
|
||||||
case Some(pgn) => env.importer(
|
|
||||||
lila.importer.ImportData(pgn, none),
|
|
||||||
user = "lichess".some,
|
|
||||||
forceId = id.some
|
|
||||||
) map redirectAtFen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,7 +272,7 @@ lazy val challenge = module("challenge", Seq(common, db, hub, setup, game, relat
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val study = module("study", Seq(
|
lazy val study = module("study", Seq(
|
||||||
common, db, hub, socket, game, round, importer, notifyModule, relation, evalCache
|
common, db, hub, socket, game, round, importer, notifyModule, relation, evalCache, explorer
|
||||||
)).settings(
|
)).settings(
|
||||||
libraryDependencies ++= provided(play.api, reactivemongo.driver)
|
libraryDependencies ++= provided(play.api, reactivemongo.driver)
|
||||||
)
|
)
|
||||||
|
@ -370,7 +370,7 @@ lazy val report = module("report", Seq(common, db, user, game, security)).settin
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val explorer = module("explorer", Seq(common, db, game)).settings(
|
lazy val explorer = module("explorer", Seq(common, db, game, importer)).settings(
|
||||||
libraryDependencies ++= provided(
|
libraryDependencies ++= provided(
|
||||||
play.api,
|
play.api,
|
||||||
reactivemongo.driver, reactivemongo.iteratees
|
reactivemongo.driver, reactivemongo.iteratees
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.typesafe.config.Config
|
||||||
final class Env(
|
final class Env(
|
||||||
config: Config,
|
config: Config,
|
||||||
gameColl: lila.db.dsl.Coll,
|
gameColl: lila.db.dsl.Coll,
|
||||||
|
importer: lila.importer.Importer,
|
||||||
system: ActorSystem
|
system: ActorSystem
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -23,15 +24,6 @@ final class Env(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def fetchPgn(id: String): Fu[Option[String]] = {
|
|
||||||
import play.api.libs.ws.WS
|
|
||||||
import play.api.Play.current
|
|
||||||
WS.url(s"$InternalEndpoint/master/pgn/$id").get() map {
|
|
||||||
case res if res.status == 200 => res.body.some
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IndexFlow) system.lilaBus.subscribe(system.actorOf(Props(new Actor {
|
if (IndexFlow) system.lilaBus.subscribe(system.actorOf(Props(new Actor {
|
||||||
def receive = {
|
def receive = {
|
||||||
case lila.game.actorApi.FinishGame(game, _, _) if !game.aborted => indexer(game)
|
case lila.game.actorApi.FinishGame(game, _, _) if !game.aborted => indexer(game)
|
||||||
|
@ -44,6 +36,7 @@ object Env {
|
||||||
lazy val current = "explorer" boot new Env(
|
lazy val current = "explorer" boot new Env(
|
||||||
config = lila.common.PlayApp loadConfig "explorer",
|
config = lila.common.PlayApp loadConfig "explorer",
|
||||||
gameColl = lila.game.Env.current.gameColl,
|
gameColl = lila.game.Env.current.gameColl,
|
||||||
|
importer = lila.importer.Env.current.importer,
|
||||||
system = lila.common.PlayApp.system
|
system = lila.common.PlayApp.system
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package lila.explorer
|
||||||
|
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
|
import lila.game.{ Game, GameRepo }
|
||||||
|
import lila.importer.{ Importer, ImportData }
|
||||||
|
|
||||||
|
final class ExplorerImport(
|
||||||
|
endpoint: String,
|
||||||
|
importer: Importer
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val masterGameEncodingFixedAt = new DateTime(2016, 3, 9, 0, 0)
|
||||||
|
|
||||||
|
def apply(id: Game.ID): Fu[Option[Game]] =
|
||||||
|
GameRepo game id flatMap {
|
||||||
|
case Some(game) if game.createdAt.isAfter(masterGameEncodingFixedAt) => fuccess(game.some)
|
||||||
|
case _ => (GameRepo remove id) >> fetchPgn(id) flatMap {
|
||||||
|
case None => fuccess(none)
|
||||||
|
case Some(pgn) => importer(
|
||||||
|
ImportData(pgn, none),
|
||||||
|
user = "lichess".some,
|
||||||
|
forceId = id.some
|
||||||
|
) map some
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def fetchPgn(id: String): Fu[Option[String]] = {
|
||||||
|
import play.api.libs.ws.WS
|
||||||
|
import play.api.Play.current
|
||||||
|
WS.url(s"$endpoint/master/pgn/$id").get() map {
|
||||||
|
case res if res.status == 200 => res.body.some
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package lila.study
|
||||||
|
|
||||||
|
import lila.game.{ Game, Namer, PgnImport }
|
||||||
|
import lila.user.User
|
||||||
|
import chess.format.pgn.{ Parser, Reader, ParsedPgn, Tag, TagType }
|
||||||
|
|
||||||
|
private final class ExplorerGame(
|
||||||
|
importer: lila.explorer.ExplorerImport
|
||||||
|
) {
|
||||||
|
|
||||||
|
def quote(userId: User.ID, study: Study, chapter: Chapter, path: Path, gameId: Game.ID): Fu[Comment] =
|
||||||
|
importer(gameId) flatMap {
|
||||||
|
_.fold(false) { game =>
|
||||||
|
|
||||||
|
val comment = Comment(
|
||||||
|
id = Comment.Id.make,
|
||||||
|
text = game.pgnImport.fold(lichessTitle)(importTitle(game)),
|
||||||
|
by = Comment.Author.User(author.id, author.titleName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def importTitle(g: Game)(pgnImport: PgnImport): String =
|
||||||
|
Parser.full(pgnImport.pgn) flatMap {
|
||||||
|
case ParsedPgn(_, tags, _) =>
|
||||||
|
def tag(which: Tag.type => TagType): Option[String] =
|
||||||
|
tags find (_.name == which(Tag)) map (_.value)
|
||||||
|
|
||||||
|
ImportData(pgnImport.pgn, none).preprocess(none).fold(
|
||||||
|
_ => lichessTitle(g),
|
||||||
|
processed =>
|
||||||
|
val players = Namer.vsText(game, withRatings = true)
|
||||||
|
val result = chess.Color.showResult(game.winnerColor)
|
||||||
|
val text = s"$players, $result
|
||||||
|
}
|
||||||
|
|
||||||
|
def insert(userId: User.ID, study: Study, chapter: Chapter, gameId: Game.ID) = ???
|
||||||
|
}
|
|
@ -228,6 +228,13 @@ private[study] final class SocketHandler(
|
||||||
} api.toggleGlyph(userId, studyId, position.ref, glyph, uid)
|
} api.toggleGlyph(userId, studyId, position.ref, glyph, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ("explorerGame", o) =>
|
||||||
|
reading[actorApi.ExplorerGame](o) { data =>
|
||||||
|
member.userId foreach { byUserId =>
|
||||||
|
api.explorerGame(byUserId, studyId, data, uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case ("like", o) => for {
|
case ("like", o) => for {
|
||||||
byUserId <- member.userId
|
byUserId <- member.userId
|
||||||
v <- (o \ "d" \ "liked").asOpt[Boolean]
|
v <- (o \ "d" \ "liked").asOpt[Boolean]
|
||||||
|
@ -260,6 +267,7 @@ private[study] final class SocketHandler(
|
||||||
private implicit val StudyDataReader = Json.reads[Study.Data]
|
private implicit val StudyDataReader = Json.reads[Study.Data]
|
||||||
private implicit val setTagReader = Json.reads[actorApi.SetTag]
|
private implicit val setTagReader = Json.reads[actorApi.SetTag]
|
||||||
private implicit val gamebookReader = Json.reads[Gamebook]
|
private implicit val gamebookReader = Json.reads[Gamebook]
|
||||||
|
private implicit val explorerGame = Json.reads[actorApi.ExplorerGame]
|
||||||
|
|
||||||
def join(
|
def join(
|
||||||
studyId: Study.Id,
|
studyId: Study.Id,
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package lila.study
|
||||||
|
|
||||||
|
private final class StudyCommenter() {
|
||||||
|
|
||||||
|
def apply(chapter: Chapteer, position: Position, comment: Comment) =
|
||||||
|
chapter.setComment(comment, position.path) match {
|
||||||
|
case Some(newChapter) =>
|
||||||
|
studyRepo.updateNow(study)
|
||||||
|
newChapter.root.nodeAt(position.path) ?? { node =>
|
||||||
|
node.comments.findBy(comment.by) ?? { c =>
|
||||||
|
chapterRepo.setComments(newChapter, position.path, node.comments.filterEmpty) >>- {
|
||||||
|
sendTo(study, Socket.SetComment(position, c, uid))
|
||||||
|
indexStudy(study)
|
||||||
|
sendStudyEnters(study, userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,3 +6,4 @@ case class SaveStudy(study: Study)
|
||||||
case class SetTag(chapterId: Chapter.Id, name: String, value: String) {
|
case class SetTag(chapterId: Chapter.Id, name: String, value: String) {
|
||||||
def tag = chess.format.pgn.Tag(name, value take 140)
|
def tag = chess.format.pgn.Tag(name, value take 140)
|
||||||
}
|
}
|
||||||
|
case class ExplorerGame(chapterId: Chapter.Id, path: String, gameId: String, insert: Boolean)
|
||||||
|
|
|
@ -608,9 +608,30 @@ body.dark .explorer_box .black {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-family: 'Roboto Mono', 'Roboto';
|
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
.explorer_box td.game_menu {
|
||||||
|
background: #759900;
|
||||||
|
cursor: default;
|
||||||
|
padding: 5px 0 0 0;
|
||||||
|
}
|
||||||
|
.explorer_box .game_menu .game_title {
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.explorer_box .game_menu .menu {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.explorer_box .game_menu .menu a {
|
||||||
|
color: #fff;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.explorer_box .game_menu .menu a:hover {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
.explorer_box .tablebase {
|
.explorer_box .tablebase {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,15 @@ function tablebaseRelevant(variant: string, fen: Fen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
||||||
const allowed = prop(allow);
|
const allowed = prop(allow),
|
||||||
const enabled = root.embed ? prop(false) : storedProp('explorer.enabled', false);
|
enabled = root.embed ? prop(false) : storedProp('explorer.enabled', false),
|
||||||
|
loading = prop(true),
|
||||||
|
failing = prop(false),
|
||||||
|
hovering = prop<Hovering | null>(null),
|
||||||
|
movesAway = prop(0),
|
||||||
|
gameMenu = prop<string | null>(null);
|
||||||
|
|
||||||
if ((location.hash === '#explorer' || location.hash === '#opening') && !root.embed) enabled(true);
|
if ((location.hash === '#explorer' || location.hash === '#opening') && !root.embed) enabled(true);
|
||||||
const loading = prop(true);
|
|
||||||
const failing = prop(false);
|
|
||||||
const hovering = prop<Hovering | null>(null);
|
|
||||||
const movesAway = prop(0);
|
|
||||||
|
|
||||||
let cache = {};
|
let cache = {};
|
||||||
function onConfigClose() {
|
function onConfigClose() {
|
||||||
|
@ -62,6 +64,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
||||||
|
|
||||||
function setNode() {
|
function setNode() {
|
||||||
if (!enabled()) return;
|
if (!enabled()) return;
|
||||||
|
gameMenu(null);
|
||||||
const node = root.node;
|
const node = root.node;
|
||||||
if (node.ply > 50 && !tablebaseRelevant(effectiveVariant, node.fen)) {
|
if (node.ply > 50 && !tablebaseRelevant(effectiveVariant, node.fen)) {
|
||||||
cache[node.fen] = empty;
|
cache[node.fen] = empty;
|
||||||
|
@ -87,6 +90,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
||||||
movesAway,
|
movesAway,
|
||||||
config,
|
config,
|
||||||
withGames,
|
withGames,
|
||||||
|
gameMenu,
|
||||||
current: () => cache[root.node.fen],
|
current: () => cache[root.node.fen],
|
||||||
toggle() {
|
toggle() {
|
||||||
movesAway(0);
|
movesAway(0);
|
||||||
|
@ -97,6 +101,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
||||||
disable() {
|
disable() {
|
||||||
if (enabled()) {
|
if (enabled()) {
|
||||||
enabled(false);
|
enabled(false);
|
||||||
|
gameMenu(null);
|
||||||
root.autoScroll();
|
root.autoScroll();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -79,6 +79,7 @@ function showResult(winner: Color): VNode {
|
||||||
|
|
||||||
function showGameTable(ctrl: AnalyseCtrl, title: string, games): VNode | null {
|
function showGameTable(ctrl: AnalyseCtrl, title: string, games): VNode | null {
|
||||||
if (!ctrl.explorer.withGames || !games.length) return null;
|
if (!ctrl.explorer.withGames || !games.length) return null;
|
||||||
|
const openedId = ctrl.explorer.gameMenu();
|
||||||
return h('table.games', [
|
return h('table.games', [
|
||||||
h('thead', [
|
h('thead', [
|
||||||
h('tr', [
|
h('tr', [
|
||||||
|
@ -86,29 +87,46 @@ function showGameTable(ctrl: AnalyseCtrl, title: string, games): VNode | null {
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
h('tbody', {
|
h('tbody', {
|
||||||
insert: vnode => {
|
hook: bind('click', e => {
|
||||||
const el = vnode.elm as HTMLElement;
|
const $tr = $(e.target).parents('tr');
|
||||||
el.addEventListener('click', e => {
|
if (!$tr.length) return;
|
||||||
const $tr = $(e.target).parents('tr');
|
ctrl.explorer.gameMenu($tr.data('id'));
|
||||||
if (!$tr.length) return;
|
ctrl.redraw();
|
||||||
const orientation = ctrl.chessground.state.orientation;
|
})
|
||||||
const fenParam = ctrl.node.ply > 0 ? ('?fen=' + ctrl.node.fen) : '';
|
}, games.map(game => {
|
||||||
if (ctrl.explorer.config.data.db.selected() === 'lichess')
|
return openedId && openedId === game.id ? h('tr', [
|
||||||
window.open('/' + $tr.data('id') + '/' + orientation + fenParam, '_blank');
|
h('td.game_menu', {
|
||||||
else window.open('/import/master/' + $tr.data('id') + '/' + orientation + fenParam, '_blank');
|
attrs: { colspan: 4 },
|
||||||
});
|
}, [
|
||||||
// el.oncontextmenu = (e: MouseEvent) => {
|
h('div.game_title', `${game.white.name} - ${game.black.name}, ${showResult(game.winner).text}, ${game.year}`),
|
||||||
// const path = eventPath(e);
|
h('div.menu', [
|
||||||
// if (path !== null) contextMenu(e, {
|
h('a.text', {
|
||||||
// path,
|
attrs: dataIcon('v'),
|
||||||
// root: ctrl
|
hook: bind('click', _ => {
|
||||||
// });
|
const orientation = ctrl.chessground.state.orientation,
|
||||||
// ctrl.redraw();
|
fenParam = ctrl.node.ply > 0 ? ('?fen=' + ctrl.node.fen) : '';
|
||||||
// return false;
|
if (ctrl.explorer.config.data.db.selected() === 'lichess')
|
||||||
// };
|
window.open('/' + openedId + '/' + orientation + fenParam, '_blank');
|
||||||
}
|
else window.open('/import/master/' + openedId + '/' + orientation + fenParam, '_blank');
|
||||||
}, games.map(function(game) {
|
})
|
||||||
return h('tr', {
|
}, 'View'),
|
||||||
|
...(ctrl.study ? [
|
||||||
|
h('a.text', {
|
||||||
|
attrs: dataIcon('c'),
|
||||||
|
hook: bind('click', _ => ctrl.study!.explorerGame(openedId, false), ctrl.redraw)
|
||||||
|
}, 'Quote'),
|
||||||
|
h('a.text', {
|
||||||
|
attrs: dataIcon('O'),
|
||||||
|
hook: bind('click', _ => ctrl.study!.explorerGame(openedId, true), ctrl.redraw)
|
||||||
|
}, 'Insert')
|
||||||
|
] : []),
|
||||||
|
h('a.text', {
|
||||||
|
attrs: dataIcon('L'),
|
||||||
|
hook: bind('click', _ => ctrl.explorer.gameMenu(null), ctrl.redraw)
|
||||||
|
}, 'Close')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]) : h('tr', {
|
||||||
key: game.id,
|
key: game.id,
|
||||||
attrs: { 'data-id': game.id }
|
attrs: { 'data-id': game.id }
|
||||||
}, [
|
}, [
|
||||||
|
|
|
@ -93,6 +93,7 @@ export interface ExplorerCtrl {
|
||||||
movesAway: Prop<number>;
|
movesAway: Prop<number>;
|
||||||
config: ExplorerConfigCtrl;
|
config: ExplorerConfigCtrl;
|
||||||
withGames: boolean;
|
withGames: boolean;
|
||||||
|
gameMenu: Prop<string | null>;
|
||||||
current(): ExplorerData | undefined;
|
current(): ExplorerData | undefined;
|
||||||
hovering: Prop<Hovering | null>;
|
hovering: Prop<Hovering | null>;
|
||||||
setNode();
|
setNode();
|
||||||
|
|
|
@ -42,6 +42,7 @@ export interface StudyCtrl {
|
||||||
mutateCgConfig(config: any): void;
|
mutateCgConfig(config: any): void;
|
||||||
isUpdatedRecently(): boolean;
|
isUpdatedRecently(): boolean;
|
||||||
setGamebookOverride(o: GamebookOverride): void;
|
setGamebookOverride(o: GamebookOverride): void;
|
||||||
|
explorerGame(gameId: string, insert: boolean): void;
|
||||||
redraw(): void;
|
redraw(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -247,6 +247,12 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
vm.updatedAt = Date.now();
|
vm.updatedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withPosition(obj: any) {
|
||||||
|
obj.ch = vm.chapterId;
|
||||||
|
obj.path = ctrl.path;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
form,
|
form,
|
||||||
|
@ -284,11 +290,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
if (practice) practice.onJump();
|
if (practice) practice.onJump();
|
||||||
if (gamebookPlay) gamebookPlay.onJump();
|
if (gamebookPlay) gamebookPlay.onJump();
|
||||||
},
|
},
|
||||||
withPosition(obj) {
|
withPosition,
|
||||||
obj.ch = vm.chapterId;
|
|
||||||
obj.path = ctrl.path;
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
setPath(path, node) {
|
setPath(path, node) {
|
||||||
onSetPath(path);
|
onSetPath(path);
|
||||||
setTimeout(() => commentForm.onSetPath(path, node), 100);
|
setTimeout(() => commentForm.onSetPath(path, node), 100);
|
||||||
|
@ -347,6 +349,9 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
if (!o) xhrReload();
|
if (!o) xhrReload();
|
||||||
},
|
},
|
||||||
mutateCgConfig,
|
mutateCgConfig,
|
||||||
|
explorerGame(gameId: string, insert: boolean) {
|
||||||
|
makeChange('explorerGame', withPosition({ gameId, insert }));
|
||||||
|
},
|
||||||
redraw,
|
redraw,
|
||||||
socketHandlers: {
|
socketHandlers: {
|
||||||
path(d) {
|
path(d) {
|
||||||
|
|
Loading…
Reference in New Issue