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")
|
||||
s"$url$fenParam"
|
||||
}
|
||||
GameRepo game id flatMap {
|
||||
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
|
||||
}
|
||||
}
|
||||
Env.explorer.importer(id) map2 redirectAtFen
|
||||
}
|
||||
}
|
||||
|
|
|
@ -272,7 +272,7 @@ lazy val challenge = module("challenge", Seq(common, db, hub, setup, game, relat
|
|||
)
|
||||
|
||||
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(
|
||||
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(
|
||||
play.api,
|
||||
reactivemongo.driver, reactivemongo.iteratees
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.typesafe.config.Config
|
|||
final class Env(
|
||||
config: Config,
|
||||
gameColl: lila.db.dsl.Coll,
|
||||
importer: lila.importer.Importer,
|
||||
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 {
|
||||
def receive = {
|
||||
case lila.game.actorApi.FinishGame(game, _, _) if !game.aborted => indexer(game)
|
||||
|
@ -44,6 +36,7 @@ object Env {
|
|||
lazy val current = "explorer" boot new Env(
|
||||
config = lila.common.PlayApp loadConfig "explorer",
|
||||
gameColl = lila.game.Env.current.gameColl,
|
||||
importer = lila.importer.Env.current.importer,
|
||||
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)
|
||||
}
|
||||
|
||||
case ("explorerGame", o) =>
|
||||
reading[actorApi.ExplorerGame](o) { data =>
|
||||
member.userId foreach { byUserId =>
|
||||
api.explorerGame(byUserId, studyId, data, uid)
|
||||
}
|
||||
}
|
||||
|
||||
case ("like", o) => for {
|
||||
byUserId <- member.userId
|
||||
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 setTagReader = Json.reads[actorApi.SetTag]
|
||||
private implicit val gamebookReader = Json.reads[Gamebook]
|
||||
private implicit val explorerGame = Json.reads[actorApi.ExplorerGame]
|
||||
|
||||
def join(
|
||||
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) {
|
||||
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;
|
||||
padding: 3px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Roboto Mono', 'Roboto';
|
||||
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 {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -17,13 +17,15 @@ function tablebaseRelevant(variant: string, fen: Fen) {
|
|||
}
|
||||
|
||||
export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
||||
const allowed = prop(allow);
|
||||
const enabled = root.embed ? prop(false) : storedProp('explorer.enabled', false);
|
||||
const allowed = prop(allow),
|
||||
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);
|
||||
const loading = prop(true);
|
||||
const failing = prop(false);
|
||||
const hovering = prop<Hovering | null>(null);
|
||||
const movesAway = prop(0);
|
||||
|
||||
let cache = {};
|
||||
function onConfigClose() {
|
||||
|
@ -62,6 +64,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
|||
|
||||
function setNode() {
|
||||
if (!enabled()) return;
|
||||
gameMenu(null);
|
||||
const node = root.node;
|
||||
if (node.ply > 50 && !tablebaseRelevant(effectiveVariant, node.fen)) {
|
||||
cache[node.fen] = empty;
|
||||
|
@ -87,6 +90,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
|||
movesAway,
|
||||
config,
|
||||
withGames,
|
||||
gameMenu,
|
||||
current: () => cache[root.node.fen],
|
||||
toggle() {
|
||||
movesAway(0);
|
||||
|
@ -97,6 +101,7 @@ export default function(root: AnalyseCtrl, opts, allow: boolean): ExplorerCtrl {
|
|||
disable() {
|
||||
if (enabled()) {
|
||||
enabled(false);
|
||||
gameMenu(null);
|
||||
root.autoScroll();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -79,6 +79,7 @@ function showResult(winner: Color): VNode {
|
|||
|
||||
function showGameTable(ctrl: AnalyseCtrl, title: string, games): VNode | null {
|
||||
if (!ctrl.explorer.withGames || !games.length) return null;
|
||||
const openedId = ctrl.explorer.gameMenu();
|
||||
return h('table.games', [
|
||||
h('thead', [
|
||||
h('tr', [
|
||||
|
@ -86,29 +87,46 @@ function showGameTable(ctrl: AnalyseCtrl, title: string, games): VNode | null {
|
|||
])
|
||||
]),
|
||||
h('tbody', {
|
||||
insert: vnode => {
|
||||
const el = vnode.elm as HTMLElement;
|
||||
el.addEventListener('click', e => {
|
||||
const $tr = $(e.target).parents('tr');
|
||||
if (!$tr.length) return;
|
||||
const orientation = ctrl.chessground.state.orientation;
|
||||
const fenParam = ctrl.node.ply > 0 ? ('?fen=' + ctrl.node.fen) : '';
|
||||
if (ctrl.explorer.config.data.db.selected() === 'lichess')
|
||||
window.open('/' + $tr.data('id') + '/' + orientation + fenParam, '_blank');
|
||||
else window.open('/import/master/' + $tr.data('id') + '/' + orientation + fenParam, '_blank');
|
||||
});
|
||||
// el.oncontextmenu = (e: MouseEvent) => {
|
||||
// const path = eventPath(e);
|
||||
// if (path !== null) contextMenu(e, {
|
||||
// path,
|
||||
// root: ctrl
|
||||
// });
|
||||
// ctrl.redraw();
|
||||
// return false;
|
||||
// };
|
||||
}
|
||||
}, games.map(function(game) {
|
||||
return h('tr', {
|
||||
hook: bind('click', e => {
|
||||
const $tr = $(e.target).parents('tr');
|
||||
if (!$tr.length) return;
|
||||
ctrl.explorer.gameMenu($tr.data('id'));
|
||||
ctrl.redraw();
|
||||
})
|
||||
}, games.map(game => {
|
||||
return openedId && openedId === game.id ? h('tr', [
|
||||
h('td.game_menu', {
|
||||
attrs: { colspan: 4 },
|
||||
}, [
|
||||
h('div.game_title', `${game.white.name} - ${game.black.name}, ${showResult(game.winner).text}, ${game.year}`),
|
||||
h('div.menu', [
|
||||
h('a.text', {
|
||||
attrs: dataIcon('v'),
|
||||
hook: bind('click', _ => {
|
||||
const orientation = ctrl.chessground.state.orientation,
|
||||
fenParam = ctrl.node.ply > 0 ? ('?fen=' + ctrl.node.fen) : '';
|
||||
if (ctrl.explorer.config.data.db.selected() === 'lichess')
|
||||
window.open('/' + openedId + '/' + orientation + fenParam, '_blank');
|
||||
else window.open('/import/master/' + openedId + '/' + orientation + fenParam, '_blank');
|
||||
})
|
||||
}, '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,
|
||||
attrs: { 'data-id': game.id }
|
||||
}, [
|
||||
|
|
|
@ -93,6 +93,7 @@ export interface ExplorerCtrl {
|
|||
movesAway: Prop<number>;
|
||||
config: ExplorerConfigCtrl;
|
||||
withGames: boolean;
|
||||
gameMenu: Prop<string | null>;
|
||||
current(): ExplorerData | undefined;
|
||||
hovering: Prop<Hovering | null>;
|
||||
setNode();
|
||||
|
|
|
@ -42,6 +42,7 @@ export interface StudyCtrl {
|
|||
mutateCgConfig(config: any): void;
|
||||
isUpdatedRecently(): boolean;
|
||||
setGamebookOverride(o: GamebookOverride): void;
|
||||
explorerGame(gameId: string, insert: boolean): void;
|
||||
redraw(): void;
|
||||
}
|
||||
|
||||
|
|
|
@ -247,6 +247,12 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
vm.updatedAt = Date.now();
|
||||
}
|
||||
|
||||
function withPosition(obj: any) {
|
||||
obj.ch = vm.chapterId;
|
||||
obj.path = ctrl.path;
|
||||
return obj;
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
form,
|
||||
|
@ -284,11 +290,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
if (practice) practice.onJump();
|
||||
if (gamebookPlay) gamebookPlay.onJump();
|
||||
},
|
||||
withPosition(obj) {
|
||||
obj.ch = vm.chapterId;
|
||||
obj.path = ctrl.path;
|
||||
return obj;
|
||||
},
|
||||
withPosition,
|
||||
setPath(path, node) {
|
||||
onSetPath(path);
|
||||
setTimeout(() => commentForm.onSetPath(path, node), 100);
|
||||
|
@ -347,6 +349,9 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
if (!o) xhrReload();
|
||||
},
|
||||
mutateCgConfig,
|
||||
explorerGame(gameId: string, insert: boolean) {
|
||||
makeChange('explorerGame', withPosition({ gameId, insert }));
|
||||
},
|
||||
redraw,
|
||||
socketHandlers: {
|
||||
path(d) {
|
||||
|
|
Loading…
Reference in New Issue