ui/simul typescript/snabbdom WIP
parent
dd298318a9
commit
858e78f6de
|
@ -23,7 +23,7 @@ object show {
|
|||
title = sim.fullName,
|
||||
moreJs = frag(
|
||||
jsModule("simul"),
|
||||
embedJsUnsafe(s"""lichess.simul=${safeJsonValue(
|
||||
embedJsUnsafe(s"""LichessSimul.start(${safeJsonValue(
|
||||
Json.obj(
|
||||
"data" -> data,
|
||||
"i18n" -> bits.jsI18n(),
|
||||
|
@ -39,7 +39,7 @@ object show {
|
|||
)
|
||||
}
|
||||
)
|
||||
)}""")
|
||||
)})""")
|
||||
)
|
||||
) {
|
||||
main(
|
||||
|
|
|
@ -104,9 +104,8 @@ final class JsonView(
|
|||
getLightUser(player.user) map { light =>
|
||||
Json
|
||||
.obj(
|
||||
"id" -> player.user,
|
||||
"variant" -> player.variant.key,
|
||||
"rating" -> player.rating
|
||||
"id" -> player.user,
|
||||
"rating" -> player.rating
|
||||
)
|
||||
.add("name" -> light.map(_.name))
|
||||
.add("title" -> light.map(_.title))
|
||||
|
@ -118,6 +117,7 @@ final class JsonView(
|
|||
playerJson(app.player) map { player =>
|
||||
Json.obj(
|
||||
"player" -> player,
|
||||
"variant" -> app.player.variant.key,
|
||||
"accepted" -> app.accepted
|
||||
)
|
||||
}
|
||||
|
@ -146,11 +146,10 @@ final class JsonView(
|
|||
playerJson(p.player) map { player =>
|
||||
Json
|
||||
.obj(
|
||||
"player" -> player,
|
||||
"hostColor" -> p.hostColor,
|
||||
"winnerColor" -> p.winnerColor,
|
||||
"wins" -> p.wins, // can't be normalized because BC
|
||||
"game" -> gameJson(hostId, game)
|
||||
"player" -> player,
|
||||
"variant" -> p.player.variant.key,
|
||||
"hostColor" -> p.hostColor,
|
||||
"game" -> gameJson(hostId, game)
|
||||
)
|
||||
.some
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"description": "lichess.org simul",
|
||||
"main": "src/main.js",
|
||||
"keywords": [
|
||||
"chess",
|
||||
"lichess",
|
||||
|
@ -12,12 +11,16 @@
|
|||
"author": "Thibault Duplessis",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@build/rollupProject": "2.0.0"
|
||||
"@build/rollupProject": "2.0.0",
|
||||
"@types/jquery": "^2.0",
|
||||
"@types/lichess": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chat": "2.0.0",
|
||||
"chessground": "^7.9.1",
|
||||
"common": "2.0.0",
|
||||
"game": "2.0.0",
|
||||
"mithril": "github:ornicar/mithril.js#lila-1"
|
||||
"snabbdom": "^0.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "rollup --config",
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
var socket = require('./socket');
|
||||
var simul = require('./simul');
|
||||
var xhr = require('./xhr');
|
||||
|
||||
module.exports = function(env) {
|
||||
|
||||
this.env = env;
|
||||
|
||||
this.data = env.data;
|
||||
|
||||
this.userId = env.userId;
|
||||
|
||||
this.socket = new socket(env.socketSend, this);
|
||||
|
||||
this.reload = function(data) {
|
||||
if (this.data.isCreated && !data.isCreated) {
|
||||
// hack to change parent class - remove me when moving to snabbdom
|
||||
$('main.simul-created').removeClass('simul-created');
|
||||
}
|
||||
data.team = this.data.simul; // reload data does not contain the simul anymore
|
||||
this.data = data;
|
||||
}.bind(this);
|
||||
|
||||
if (simul.createdByMe(this) && this.data.isCreated)
|
||||
lichess.storage.set('lichess.move_on', '1'); // hideous hack :D
|
||||
|
||||
this.trans = lichess.trans(env.i18n);
|
||||
|
||||
this.teamBlock = this.data.team && !this.data.team.isIn;
|
||||
|
||||
this.hostPing = () => {
|
||||
if (simul.createdByMe(this) && this.data.isCreated) {
|
||||
xhr.ping(this);
|
||||
setTimeout(this.hostPing, 10000);
|
||||
}
|
||||
};
|
||||
this.hostPing();
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
import { makeSocket, SimulSocket } from './socket';
|
||||
import xhr from './xhr';
|
||||
import simul from './simul';
|
||||
import * as status from 'game/status';
|
||||
import {
|
||||
SimulData,
|
||||
SimulOpts
|
||||
} from './interfaces';
|
||||
|
||||
export default class SimulCtrl {
|
||||
|
||||
data: SimulData;
|
||||
trans: Trans;
|
||||
socket: SimulSocket;
|
||||
|
||||
constructor(readonly opts: SimulOpts, readonly redraw: () => void) {
|
||||
this.trans = window.lichess.trans(opts.i18n);
|
||||
this.socket = makeSocket(opts.socketSend, this);
|
||||
if (simul.createdByMe(this) && this.data.isCreated)
|
||||
window.lichess.storage.set('lichess.move_on', '1'); // hideous hack :D
|
||||
this.hostPing();
|
||||
}
|
||||
|
||||
reload = (data: SimulData) => {
|
||||
this.data = {
|
||||
...data,
|
||||
team: this.data.team // reload data does not contain the team anymore
|
||||
}
|
||||
};
|
||||
|
||||
teamBlock = () => this.data.team && !this.data.team.isIn;
|
||||
|
||||
hostPing = () => {
|
||||
if (this.createdByMe() && this.data.isCreated) {
|
||||
xhr.ping(this);
|
||||
setTimeout(this.hostPing, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
createdByMe = () => this.opts.userId === this.data.host.id;
|
||||
candidates = () => this.data.applicants.filter(a => !a.accepted);
|
||||
accepted = () => this.data.applicants.filter(a => a.accepted);
|
||||
myCurrentPairing = () =>
|
||||
this.opts.userId ? this.data.pairings.find(p =>
|
||||
p.game.status < status.ids.mate && p.player.id === this.opts.userId
|
||||
) : null;
|
||||
acceptedContainsMe = () => this.accepted().some(a => a.player.id === this.opts.userId);
|
||||
applicantsContainsMe = () => this.candidates().some(a => a.player.id === this.opts.userId);
|
||||
containsMe = () => this.opts.userId && (this.applicantsContainsMe() || this.pairingsContainMe());
|
||||
pairingsContainMe = () => this.data.pairings.some(a => a.player.id === this.opts.userId);
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import { VNode } from 'snabbdom/vnode'
|
||||
|
||||
export type MaybeVNode = VNode | string | null | undefined;
|
||||
export type MaybeVNodes = MaybeVNode[];
|
||||
export type Redraw = () => void;
|
||||
|
||||
export interface SimulOpts {
|
||||
data: SimulData;
|
||||
userId?: string;
|
||||
element: HTMLElement;
|
||||
$side: JQuery;
|
||||
socketSend: SocketSend;
|
||||
chat: any;
|
||||
i18n: any;
|
||||
}
|
||||
|
||||
export interface SimulData {
|
||||
id: string;
|
||||
name: string;
|
||||
fullName: string;
|
||||
isCreated: boolean;
|
||||
isRunning: boolean;
|
||||
isFinished: boolean;
|
||||
text: string;
|
||||
host: {
|
||||
id: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
patron?: boolean;
|
||||
rating: number;
|
||||
gameId?: string;
|
||||
};
|
||||
applicants: Applicant[];
|
||||
pairings: Pairing[];
|
||||
quote?: {
|
||||
text: string;
|
||||
author: string;
|
||||
}
|
||||
team?: Team;
|
||||
}
|
||||
|
||||
export interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
isIn: boolean;
|
||||
}
|
||||
|
||||
export interface Player extends LightUser {
|
||||
rating: number;
|
||||
provisional?: boolean;
|
||||
}
|
||||
|
||||
export interface Applicant {
|
||||
player: Player;
|
||||
variant: VariantKey;
|
||||
accepted: boolean;
|
||||
}
|
||||
|
||||
export interface Pairing {
|
||||
player: Player;
|
||||
variant: VariantKey;
|
||||
hostColor: Color;
|
||||
game: Game;
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
id: string;
|
||||
status: number;
|
||||
fen: string;
|
||||
lastMove: string;
|
||||
orient: Color;
|
||||
clock?: {
|
||||
white: number;
|
||||
black: number;
|
||||
}
|
||||
winner?: Color;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
var m = require('mithril');
|
||||
|
||||
var ctrl = require('./ctrl');
|
||||
var view = require('./view/main');
|
||||
|
||||
module.exports = function(opts) {
|
||||
|
||||
var controller = new ctrl(opts);
|
||||
|
||||
m.module(opts.element, {
|
||||
controller: function() {
|
||||
return controller;
|
||||
},
|
||||
view: view
|
||||
});
|
||||
|
||||
return {
|
||||
socketReceive: controller.socket.receive
|
||||
};
|
||||
};
|
||||
|
||||
window.LichessChat = require('chat').default;
|
|
@ -0,0 +1,45 @@
|
|||
import { init } from 'snabbdom';
|
||||
import { VNode } from 'snabbdom/vnode'
|
||||
import cls from 'snabbdom/modules/class';
|
||||
import attributes from 'snabbdom/modules/attributes';
|
||||
import { Chessground } from 'chessground';
|
||||
import { SimulOpts } from './interfaces';
|
||||
import SimulCtrl from './ctrl';
|
||||
import LichessChat from 'chat';
|
||||
|
||||
const patch = init([cls, attributes]);
|
||||
|
||||
import view from './view/main';
|
||||
|
||||
export function start(opts: SimulOpts) {
|
||||
|
||||
const li = window.lichess;
|
||||
const element = document.querySelector('main.swiss') as HTMLElement;
|
||||
li.socket = li.StrongSocket(
|
||||
'/simul/' + opts.data.id, opts.data.socketVersion, {
|
||||
receive: (t: string, d: any) => ctrl.socket.receive(t, d)
|
||||
});
|
||||
opts.classes = element.getAttribute('class');
|
||||
opts.socketSend = li.socket.send;
|
||||
opts.element = element;
|
||||
opts.$side = $('.simul__side').clone();
|
||||
|
||||
let vnode: VNode;
|
||||
|
||||
function redraw() {
|
||||
vnode = patch(vnode, view(ctrl));
|
||||
}
|
||||
|
||||
const ctrl = new SimulCtrl(opts, redraw);
|
||||
|
||||
const blueprint = view(ctrl);
|
||||
element.innerHTML = '';
|
||||
vnode = patch(element, blueprint);
|
||||
|
||||
redraw();
|
||||
};
|
||||
|
||||
// that's for the rest of lichess to access chessground
|
||||
// without having to include it a second time
|
||||
window.Chessground = Chessground;
|
||||
window.LichessChat = LichessChat;
|
|
@ -1,43 +0,0 @@
|
|||
var status = require('game/status');
|
||||
|
||||
function applicantsContainMe(ctrl) {
|
||||
return ctrl.data.applicants.some(function(a) {
|
||||
return a.player.id === ctrl.userId;
|
||||
})
|
||||
}
|
||||
|
||||
function pairingsContainMe(ctrl) {
|
||||
return ctrl.data.pairings.some(function(a) {
|
||||
return a.player.id === ctrl.userId;
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createdByMe: function(ctrl) {
|
||||
return ctrl.userId && ctrl.userId === ctrl.data.host.id;
|
||||
},
|
||||
containsMe: function(ctrl) {
|
||||
return ctrl.userId && (applicantsContainMe(ctrl) || pairingsContainMe(ctrl));
|
||||
},
|
||||
candidates: function(ctrl) {
|
||||
return ctrl.data.applicants.filter(function(a) {
|
||||
return !a.accepted;
|
||||
});
|
||||
},
|
||||
accepted: function(ctrl) {
|
||||
return ctrl.data.applicants.filter(function(a) {
|
||||
return a.accepted;
|
||||
});
|
||||
},
|
||||
acceptedContainsMe: function(ctrl) {
|
||||
return ctrl.data.applicants.some(function(a) {
|
||||
return a.accepted && a.player.id === ctrl.userId;
|
||||
})
|
||||
},
|
||||
myCurrentPairing: function(ctrl) {
|
||||
if (!ctrl.userId) return null;
|
||||
return ctrl.data.pairings.find(function(p) {
|
||||
return p.game.status < status.ids.mate && p.player.id === ctrl.userId;
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
var m = require('mithril');
|
||||
|
||||
module.exports = function(send, ctrl) {
|
||||
|
||||
this.send = send;
|
||||
|
||||
var handlers = {
|
||||
reload: function(data) {
|
||||
ctrl.reload(data);
|
||||
m.redraw();
|
||||
},
|
||||
aborted: function() {
|
||||
lichess.reload();
|
||||
},
|
||||
hostGame: function(gameId) {
|
||||
ctrl.data.host.gameId = gameId;
|
||||
m.redraw();
|
||||
}
|
||||
};
|
||||
|
||||
this.receive = function(type, data) {
|
||||
if (handlers[type]) {
|
||||
handlers[type](data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}.bind(this);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
import SimulCtrl from './ctrl';
|
||||
import { SimulData } from './interfaces';
|
||||
|
||||
export interface SimulSocket {
|
||||
send: SocketSend;
|
||||
receive(tpe: string, data: any): void;
|
||||
}
|
||||
|
||||
export function makeSocket(send: SocketSend, ctrl: SimulCtrl) {
|
||||
|
||||
const handlers: any = {
|
||||
reload(data: SimulData) {
|
||||
ctrl.reload(data);
|
||||
ctrl.redraw();
|
||||
},
|
||||
aborted: window.lichess.reload,
|
||||
hostGame(gameId: string) {
|
||||
ctrl.data.host.gameId = gameId;
|
||||
ctrl.redraw();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
send,
|
||||
receive(tpe: string, data: any) {
|
||||
if (handlers[tpe]) return handlers[tpe](data);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitReturns": false
|
||||
}
|
||||
}
|
|
@ -199,7 +199,6 @@
|
|||
else if (lichess.relay) startRelay(lichess.relay);
|
||||
else if (lichess.puzzle) startPuzzle(lichess.puzzle);
|
||||
else if (lichess.tournament) startTournament(lichess.tournament);
|
||||
else if (lichess.simul) startSimul(lichess.simul);
|
||||
else if (lichess.team) startTeam(lichess.team);
|
||||
|
||||
// delay so round starts first (just for perceived perf)
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import makeSocket from './socket';
|
||||
import { makeSocket, SwissSocket } from './socket';
|
||||
import xhr from './xhr';
|
||||
import throttle from 'common/throttle';
|
||||
import { myPage, players } from './pagination';
|
||||
import { SwissData, SwissOpts, Pages, Standing, Player } from './interfaces';
|
||||
import { SwissSocket } from './socket';
|
||||
|
||||
export default class SwissCtrl {
|
||||
|
||||
opts: SwissOpts;
|
||||
data: SwissData;
|
||||
trans: Trans;
|
||||
socket: SwissSocket;
|
||||
|
@ -19,14 +17,11 @@ export default class SwissCtrl {
|
|||
playerInfoId?: string;
|
||||
disableClicks: boolean = true;
|
||||
searching: boolean = false;
|
||||
redraw: () => void;
|
||||
|
||||
private lastStorage = window.lichess.storage.make('last-redirect');
|
||||
|
||||
constructor(opts: SwissOpts, redraw: () => void) {
|
||||
this.opts = opts;
|
||||
constructor(readonly opts: SwissOpts, readonly redraw: () => void) {
|
||||
this.data = this.readData(opts.data);
|
||||
this.redraw = redraw;
|
||||
this.trans = window.lichess.trans(opts.i18n);
|
||||
this.socket = makeSocket(opts.socketSend, this);
|
||||
this.page = this.data.standing.page;
|
||||
|
|
|
@ -5,7 +5,7 @@ export interface SwissSocket {
|
|||
receive(tpe: string, data: any): void;
|
||||
}
|
||||
|
||||
export default function(send: SocketSend, ctrl: SwissCtrl) {
|
||||
export function makeSocket(send: SocketSend, ctrl: SwissCtrl) {
|
||||
|
||||
const handlers: any = {
|
||||
reload() {
|
||||
|
|
Loading…
Reference in New Issue