palantir WIP
parent
9a9add8d03
commit
99bd6bb539
|
@ -110,7 +110,7 @@ object home {
|
|||
ctx.blind option h2("Timeline"),
|
||||
views.html.timeline entries userTimeline,
|
||||
// userTimeline.size >= 8 option
|
||||
a(cls := "more", href := routes.Timeline.home)(trans.more(), " »")
|
||||
if (userTimeline.size > 0) a(cls := "more", href := routes.Timeline.home)(trans.more(), " »")
|
||||
)
|
||||
} getOrElse div(cls := "about-side")(
|
||||
ctx.blind option h2("About"),
|
||||
|
|
|
@ -34,7 +34,8 @@ object bits {
|
|||
playing = playing,
|
||||
robots = robots,
|
||||
deferJs = true,
|
||||
zoomable = true
|
||||
zoomable = true,
|
||||
csp = defaultCsp.withPeer.some
|
||||
)(body)
|
||||
|
||||
def crosstable(cross: Option[lila.game.Crosstable.WithMatchup], game: Game)(implicit ctx: Context) =
|
||||
|
|
|
@ -45,11 +45,13 @@ final class Env(
|
|||
|
||||
val panic = new ChatPanic
|
||||
|
||||
private val palantir = new Palantir
|
||||
|
||||
system.scheduler.schedule(TimeoutCheckEvery, TimeoutCheckEvery) {
|
||||
timeout.checkExpired foreach api.userChat.reinstate
|
||||
}
|
||||
|
||||
system.actorOf(Props(new FrontActor(api)), name = ActorName)
|
||||
system.actorOf(Props(new FrontActor(api, palantir)), name = ActorName)
|
||||
|
||||
private[chat] lazy val chatColl = db(CollectionChat)
|
||||
private[chat] lazy val timeoutColl = db(CollectionTimeout)
|
||||
|
|
|
@ -5,7 +5,10 @@ import chess.Color
|
|||
|
||||
import actorApi._
|
||||
|
||||
private[chat] final class FrontActor(api: ChatApi) extends Actor {
|
||||
private[chat] final class FrontActor(
|
||||
api: ChatApi,
|
||||
palantir: Palantir
|
||||
) extends Actor {
|
||||
|
||||
def receive = {
|
||||
|
||||
|
@ -20,5 +23,7 @@ private[chat] final class FrontActor(api: ChatApi) extends Actor {
|
|||
case Remove(chatId) => api remove chatId
|
||||
|
||||
case RemoveAll(chatIds) => api removeAll chatIds
|
||||
|
||||
case Palantir.Toggle(chatId, userId, sri, on) => palantir.toggle(chatId, userId, sri, on)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package lila.chat
|
||||
|
||||
import com.github.blemale.scaffeine.{ Cache, Scaffeine }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.socket.Socket.makeMessage
|
||||
import lila.socket.SocketMember
|
||||
import lila.user.User
|
||||
|
||||
private final class Palantir {
|
||||
|
||||
import Palantir._
|
||||
|
||||
private val stones: Cache[Chat.Id, Stone] = Scaffeine()
|
||||
.expireAfterWrite(1 minute)
|
||||
.build[Chat.Id, Stone]
|
||||
|
||||
def toggle(chatId: Chat.Id, userId: User.ID, member: SocketMember, on: Boolean): Unit = {
|
||||
val stone = stones.getIfPresent(chatId).getOrElse(emptyStone) |> { stone =>
|
||||
if (on) stone.add(userId, member)
|
||||
else stone remove userId
|
||||
}
|
||||
stones.put(chatId, stone)
|
||||
member.push(makeMessage("palantir", stone.userIds.filter(userId !=)))
|
||||
}
|
||||
}
|
||||
|
||||
private object Palantir {
|
||||
|
||||
case class Stone(members: Map[User.ID, SocketMember]) {
|
||||
def add(uid: User.ID, member: SocketMember) = copy(
|
||||
members = members + (uid -> member)
|
||||
)
|
||||
def remove(uid: User.ID) = copy(
|
||||
members = members - uid
|
||||
)
|
||||
def userIds = members.keys.toList
|
||||
}
|
||||
|
||||
val emptyStone = Stone(Map.empty)
|
||||
|
||||
case class Toggle(chatId: Chat.Id, userId: User.ID, member: SocketMember, on: Boolean)
|
||||
}
|
|
@ -30,6 +30,12 @@ object Socket {
|
|||
} canTimeout.??(_(userId)) foreach { localTimeout =>
|
||||
chat ! actorApi.Timeout(chatId, modId, userId, reason, local = localTimeout)
|
||||
}
|
||||
|
||||
case ("palantir", o) => for {
|
||||
data ← o obj "d"
|
||||
on <- data boolean "on"
|
||||
userId <- member.userId
|
||||
} chat ! Palantir.Toggle(chatId, userId, member, on)
|
||||
}
|
||||
|
||||
type Send = (String, JsValue, Boolean) => Unit
|
||||
|
|
|
@ -61,6 +61,10 @@ case class ContentSecurityPolicy(
|
|||
frameSrc = "https://www.google.com" :: frameSrc
|
||||
)
|
||||
|
||||
def withPeer = copy(
|
||||
connectSrc = "wss://0.peerjs.com" :: connectSrc
|
||||
)
|
||||
|
||||
private def withPrismicEditor(maybe: Boolean): ContentSecurityPolicy = if (maybe) copy(
|
||||
scriptSrc = "https://static.cdn.prismic.io" :: scriptSrc,
|
||||
frameSrc = "https://lichess.prismic.io" :: "https://lichess.cdn.prismic.io" :: frameSrc,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -68,6 +68,10 @@ interface LichessSpeech {
|
|||
step(s: { san?: San }, cut: boolean): void;
|
||||
}
|
||||
|
||||
interface PalantirOpts {
|
||||
uid: string;
|
||||
redraw(): void;
|
||||
}
|
||||
interface Palantir {
|
||||
button(): any;
|
||||
}
|
||||
|
@ -138,7 +142,7 @@ interface Window {
|
|||
hopscotch: any;
|
||||
LichessSpeech?: LichessSpeech;
|
||||
palantir?: {
|
||||
palantir(): Palantir
|
||||
palantir(opts: PalantirOpts): Palantir
|
||||
};
|
||||
|
||||
[key: string]: any; // TODO
|
||||
|
|
2
ui/build
2
ui/build
|
@ -13,7 +13,7 @@ mkdir -p public/compiled
|
|||
|
||||
ts_apps1="common chess"
|
||||
ts_apps2="ceval game tree chat nvui"
|
||||
apps="site chat cli challenge notify learn insight editor puzzle round analyse lobby tournament tournamentSchedule tournamentCalendar simul perfStat dasher speech"
|
||||
apps="site chat cli challenge notify learn insight editor puzzle round analyse lobby tournament tournamentSchedule tournamentCalendar simul perfStat dasher speech palantir"
|
||||
|
||||
if [ $mode == "upgrade" ]; then
|
||||
yarn upgrade --non-interactive
|
||||
|
|
|
@ -40,5 +40,26 @@
|
|||
}
|
||||
&__palantir {
|
||||
padding: 0 .6em;
|
||||
background: $c-primary;
|
||||
color: $c-primary-over;
|
||||
animation: glowing 1.5s ease-in-out infinite;
|
||||
&:hover {
|
||||
background: $c-bad !important;
|
||||
color: $c-bad-over !important;
|
||||
}
|
||||
}
|
||||
.pal-off {
|
||||
background: transparent;
|
||||
color: $c-font;
|
||||
animation: none;
|
||||
&:hover {
|
||||
background: $c-primary !important;
|
||||
color: $c-primary-over !important;
|
||||
}
|
||||
}
|
||||
.pal-on {
|
||||
background: $c-good;
|
||||
color: $c-good-over;
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,10 +121,14 @@ export default function(opts: ChatOpts, redraw: Redraw): Ctrl {
|
|||
const emitEnabled = () => li.pubsub.emit('chat.enabled', vm.enabled);
|
||||
emitEnabled();
|
||||
|
||||
li.loadScript(li.compiledScript('palantir')).then(() => {
|
||||
palantir.instance = window.Palantir!.palantir();
|
||||
console.log(palantir.instance);
|
||||
redraw();
|
||||
data.userId && li.loadScript('javascripts/vendor/peerjs.min.js').then(() => {
|
||||
li.loadScript(li.compiledScript('palantir')).then(() => {
|
||||
palantir.instance = window.Palantir!.palantir({
|
||||
uid: data.userId,
|
||||
redraw
|
||||
});
|
||||
redraw();
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -27,5 +27,6 @@
|
|||
"typescript": "^3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/webrtc": "^0.0.25"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const snabbdom_1 = require("snabbdom");
|
||||
function instance() {
|
||||
function palantir(opts) {
|
||||
// const peerId = `lichess:${opts.uid}`;
|
||||
console.log(opts);
|
||||
console.log(window.Peer);
|
||||
// const peer = new peerjs.Peer(peerId);
|
||||
// peer.on('open', id => {
|
||||
// console.log('My peer ID is: ' + id);
|
||||
// navigator.getUserMedia({video: false, audio: true}, function(stream) {
|
||||
// // var call = peer.call('another-peers-id', stream);
|
||||
// // call.on('stream', function(remoteStream) {
|
||||
// // // Show stream in some video/canvas element.
|
||||
// // });
|
||||
// }, function(err) {
|
||||
// console.log('Failed to get local stream' ,err);
|
||||
// });
|
||||
// });
|
||||
return {
|
||||
button() {
|
||||
return snabbdom_1.h('button.palantir', {
|
||||
return snabbdom_1.h('button.mchat__palantir.fbt', {
|
||||
attrs: { 'data-icon': '' }
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
exports.instance = instance;
|
||||
exports.palantir = palantir;
|
||||
|
|
|
@ -1,21 +1,147 @@
|
|||
import { h } from 'snabbdom'
|
||||
import { h } from 'snabbdom';
|
||||
|
||||
export function palantir() {
|
||||
const li = window.lichess;
|
||||
|
||||
navigator.getUserMedia({video: true, audio: true}, function(stream) {
|
||||
var call = peer.call('another-peers-id', stream);
|
||||
call.on('stream', function(remoteStream) {
|
||||
// Show stream in some video/canvas element.
|
||||
type State = 'off' | 'opening' | 'getting-media' | 'ready' | 'calling' | 'answering' | 'getting-stream' | 'on' | 'stopping';
|
||||
|
||||
export function palantir(opts: PalantirOpts) {
|
||||
// const peerId = `lichess:${opts.uid}`;
|
||||
|
||||
let state: State = 'off',
|
||||
peer: any | undefined,
|
||||
myStream: any | undefined,
|
||||
remoteStream: any | undefined;
|
||||
|
||||
function setState(s: State) {
|
||||
console.log(s);
|
||||
state = s;
|
||||
opts.redraw();
|
||||
}
|
||||
|
||||
function peerIdOf(uid: string) {
|
||||
// return `org-lichess-${uid}`;
|
||||
return `org-l-${uid}`;
|
||||
}
|
||||
|
||||
function callStart(s: any) {
|
||||
remoteStream = s;
|
||||
setState('on');
|
||||
}
|
||||
|
||||
function start() {
|
||||
setState('opening');
|
||||
peer = peer || new window['Peer'](peerIdOf(opts.uid));
|
||||
window.peer = peer;
|
||||
peer.on('open', () => {
|
||||
setState('getting-media');
|
||||
navigator.mediaDevices.getUserMedia({video: false, audio: true}).then((s: any) => {
|
||||
myStream = s;
|
||||
setState('on');
|
||||
notifyLichess();
|
||||
setInterval(notifyLichess, 10 * 1000);
|
||||
setState('ready');
|
||||
peer.on('call', (call: any) => {
|
||||
if (!peer.connections[call.peer].find((c: any) => c.open)) {
|
||||
setState('answering');
|
||||
monitorCall(call);
|
||||
call.answer(myStream);
|
||||
}
|
||||
});
|
||||
}, function(err) {
|
||||
console.log('Failed to get local stream' ,err);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
});
|
||||
}, function(err) {
|
||||
console.log('Failed to get local stream' ,err);
|
||||
peer.on('stream', s => {
|
||||
console.log('stream', s);
|
||||
});
|
||||
peer.on('connection', function (c) {
|
||||
console.log("Connected to: " + c.peer);
|
||||
});
|
||||
peer.on('disconnected', function() {
|
||||
if (state == 'stopping') {
|
||||
peer.destroy(); // 'off' means manual disconnect
|
||||
peer = undefined; // 'off' means manual disconnect
|
||||
setState('off');
|
||||
}
|
||||
else {
|
||||
setState('opening');
|
||||
peer.reconnect();
|
||||
}
|
||||
});
|
||||
peer.on('close', function() {
|
||||
console.log('Connection destroyed');
|
||||
});
|
||||
peer.on('error', function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
function monitorCall(call: any) {
|
||||
console.log(call, 'monitor');
|
||||
call
|
||||
.on('stream', callStart)
|
||||
.on('close', s => {
|
||||
console.log('call close', s);
|
||||
stop();
|
||||
})
|
||||
.on('error', s => {
|
||||
console.log('call error', s);
|
||||
stop();
|
||||
});
|
||||
}
|
||||
|
||||
function notifyLichess() {
|
||||
li.pubsub.emit('socket.send', 'palantir', { on: true });
|
||||
}
|
||||
|
||||
function call(uid: string) {
|
||||
const peerId = peerIdOf(uid);
|
||||
if (peer && myStream && peer.id < peerId && !peer.connections[peerId]) {
|
||||
setState('calling');
|
||||
monitorCall(peer.call(peerId, myStream));
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (peer && state != 'off') {
|
||||
setState('stopping');
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
let started = false;
|
||||
function click() {
|
||||
if (!started) {
|
||||
start();
|
||||
started = true;
|
||||
} else if (state == 'off') start();
|
||||
else stop();
|
||||
}
|
||||
|
||||
li.pubsub.on('socket.in.palantir', uids => {
|
||||
uids.forEach(call);
|
||||
});
|
||||
|
||||
return {
|
||||
button() {
|
||||
return h('button.mchat__palantir.fbt', {
|
||||
attrs: { 'data-icon': '' }
|
||||
});
|
||||
return h('button.mchat__palantir.fbt.pal-' + state, {
|
||||
attrs: {
|
||||
'data-icon': '',
|
||||
title: state
|
||||
},
|
||||
hook: {
|
||||
insert(vnode) { (vnode.elm as HTMLElement).addEventListener('click', click) }
|
||||
}
|
||||
}, [
|
||||
state == 'on' ? h('audio.palantir__audio', {
|
||||
attrs: { autoplay: true },
|
||||
hook: {
|
||||
insert(vnode) { (vnode.elm as HTMLAudioElement).srcObject = remoteStream }
|
||||
}
|
||||
}) : null
|
||||
]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
"extends": "../tsconfig.base.json",
|
||||
"include": ["src/*.ts"],
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
|
||||
integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==
|
||||
|
||||
"@types/webrtc@^0.0.25":
|
||||
version "0.0.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/webrtc/-/webrtc-0.0.25.tgz#bd2b4e7b4c13250b3d58439623f2b9adfd7dee9e"
|
||||
integrity sha512-ep/e+p2uUKV1h96GBgRhwomrBch/bPDHPOKbCHODLGRUDuuKe2s7sErlFVKw+5BYUzvpxSmUNqoadaZ44MePoQ==
|
||||
|
||||
JSONStream@^1.0.3:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||
|
|
Loading…
Reference in New Issue