diff options
| author | RblSb <msrblsb@gmail.com> | 2020-02-22 06:35:57 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2020-02-22 06:35:57 +0300 |
| commit | 08d068181045f91f0aa160f9b088a18048b948ec (patch) | |
| tree | a995230c279a5a1fd849dc5202301d15474bca61 /src | |
| parent | 4e0859a0f902e84cfafb38103e0be9f5b81d7abe (diff) | |
More client buttons
Diffstat (limited to 'src')
| -rw-r--r-- | src/Client.hx | 49 | ||||
| -rw-r--r-- | src/ClientTools.hx | 6 | ||||
| -rw-r--r-- | src/Types.hx | 2 | ||||
| -rw-r--r-- | src/client/Buttons.hx | 156 | ||||
| -rw-r--r-- | src/client/Main.hx | 197 | ||||
| -rw-r--r-- | src/client/MobileView.hx | 13 | ||||
| -rw-r--r-- | src/client/Player.hx | 2 | ||||
| -rw-r--r-- | src/client/Split.hx | 7 | ||||
| -rw-r--r-- | src/server/Main.hx | 37 |
9 files changed, 334 insertions, 135 deletions
diff --git a/src/Client.hx b/src/Client.hx index e44417a..7aa14c5 100644 --- a/src/Client.hx +++ b/src/Client.hx @@ -5,33 +5,70 @@ import js.npm.ws.WebSocket; #elseif js import js.html.WebSocket; #end +import haxe.EnumFlags; + +enum ClientGroup { + User; + Leader; + Admin; +} typedef ClientData = { name:String, - isLeader:Bool + group:Int } class Client { + #if nodejs public final ws:WebSocket; + public final id:Int; + #end public var name:String; - public var isLeader:Bool; + public var group:EnumFlags<ClientGroup>; + public var isLeader(get, set):Bool; + public var isAdmin(get, set):Bool; - public function new(?ws:WebSocket, name:String, isLeader = false) { + public function new(?ws:WebSocket, ?id:Int, name:String, group:Int) { + #if nodejs this.ws = ws; + this.id = id; + #end this.name = name; - this.isLeader = isLeader; + this.group = new EnumFlags(group); + } + + inline function get_isLeader():Bool { + return group.has(Leader); + } + + inline function set_isLeader(flag:Bool):Bool { + return setGroupFlag(Leader, flag); + } + + inline function get_isAdmin():Bool { + return group.has(Admin); + } + + inline function set_isAdmin(flag:Bool):Bool { + return setGroupFlag(Admin, flag); + } + + function setGroupFlag(type:ClientGroup, flag:Bool):Bool { + if (flag) group.set(type); + else group.unset(type); + return flag; } public function getData():ClientData { return { name: name, - isLeader: isLeader + group: group.toInt() } } public static function fromData(data:ClientData):Client { - return new Client(data.name, data.isLeader); + return new Client(data.name, data.group); } } diff --git a/src/ClientTools.hx b/src/ClientTools.hx index 4503ada..26faab6 100644 --- a/src/ClientTools.hx +++ b/src/ClientTools.hx @@ -16,11 +16,13 @@ class ClientTools { return false; } - public static function getByName(clients:Array<Client>, name:String):Null<Client> { + public static function getByName( + clients:Array<Client>, name:String, ?def:Client + ):Null<Client> { for (client in clients) { if (client.name == name) return client; } - return null; + return def; } } diff --git a/src/Types.hx b/src/Types.hx index 03f18bd..8a0bde2 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -54,6 +54,7 @@ typedef WsEvent = { ?isUnknownClient:Bool, }, ?logout:{ + oldClientName:String, clientName:String, clients:Array<ClientData>, }, @@ -105,4 +106,5 @@ enum abstract WsEventType(String) { var GetTime; var SetTime; var SetLeader; + var ClearChat; } diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx new file mode 100644 index 0000000..a5fbfa4 --- /dev/null +++ b/src/client/Buttons.hx @@ -0,0 +1,156 @@ +package client; + +import js.html.KeyboardEvent; +import js.html.InputElement; +import js.html.ButtonElement; +import js.html.Element; +import client.Main.ge; +import js.Browser.window; +import js.html.Event; + +class Buttons { + + static final personalHistory:Array<String> = []; + static var personalHistoryId = -1; + static var split:Split; + + public static function init(main:Main):Void { + initChatInput(main); + + final smilesBtn = ge("#smilesbtn"); + smilesBtn.onclick = e -> { + smilesBtn.classList.toggle("active"); + final smilesWrap = ge("#smileswrap"); + if (smilesBtn.classList.contains("active")) + smilesWrap.style.display = "block"; + else smilesWrap.style.display = "none"; + } + + ge("#clearchatbtn").style.display = "inline-block"; + ge("#clearchatbtn").onclick = e -> { + if (main.isAdmin()) main.send({type: ClearChat}); + } + final userList = ge("#userlist"); + userList.onclick = e -> { + if (!main.isAdmin()) return; + var el:Element = cast e.target; + if (userList == el) return; + if (!el.classList.contains("userlist_item")) + el = el.parentElement; + var name = ""; + if (el.children.length == 1) { + name = el.lastElementChild.innerText; + } + main.send({ + type: SetLeader, + setLeader: { + clientName: name + } + }); + } + + split = new Split(["#chatwrap", "#videowrap"], { + sizes: [40, 60], + onDragEnd: () -> { + window.dispatchEvent(new Event("resize")); + }, + minSize: 185, + snapOffset: 0 + }); + + final userlistToggle = ge("#userlisttoggle"); + userlistToggle.onclick = e -> { + final style = ge("#userlist").style; + if (style.display == "none") { + userlistToggle.classList.add("glyphicon-chevron-down"); + userlistToggle.classList.remove("glyphicon-chevron-right"); + style.display = "block"; + } else { + userlistToggle.classList.add("glyphicon-chevron-right"); + userlistToggle.classList.remove("glyphicon-chevron-down"); + style.display = "none"; + } + } + ge("#usercount").onclick = userlistToggle.onclick; + + final extendPlayer = ge("#extendplayer"); + extendPlayer.onclick = e -> { + if (extendPlayer.classList.contains("active")) { + split.setSizes([40, 60]); + ge('#userlist').style.width = "90px"; + } else { + split.setSizes([20, 80]); + ge('#userlist').style.width = "80px"; + } + extendPlayer.classList.toggle("active"); + window.dispatchEvent(new Event('resize')); + } + + final showMediaUrl:ButtonElement = cast ge("#showmediaurl"); + showMediaUrl.onclick = e -> { + ge("#showmediaurl").classList.toggle("collapsed"); + ge("#showmediaurl").classList.toggle("active"); + ge("#addfromurl").classList.toggle("collapse"); + } + + window.onresize = onVideoResize; + window.dispatchEvent(new Event("resize")); + } + + static function onVideoResize():Void { + final player = ge("#ytapiplayer"); + final height = player.offsetHeight - ge("#chatline").offsetHeight; + ge("#messagebuffer").style.height = '${height}px'; + ge("#userlist").style.height = '${height}px'; + } + + + static function initChatInput(main:Main):Void { + final guestName:InputElement = cast ge("#guestname"); + guestName.onkeydown = e -> { + if (guestName.value.length == 0) return; + if (e.keyCode == 13) main.send({ + type: Login, + login: { + clientName: guestName.value + } + }); + } + + final chatLine:InputElement = cast ge("#chatline"); + chatLine.onkeydown = function(e:KeyboardEvent) { + switch (e.keyCode) { + case 13: // Enter + if (chatLine.value.length == 0) return; + main.send({ + type: Message, + message: { + clientName: "", + text: chatLine.value + } + }); + personalHistory.push(chatLine.value); + if (personalHistory.length > 50) personalHistory.shift(); + personalHistoryId = -1; + chatLine.value = ""; + case 38: // Up + personalHistoryId--; + if (personalHistoryId == -2) { + personalHistoryId = personalHistory.length - 1; + if (personalHistoryId == -1) return; + } else if (personalHistoryId == -1) personalHistoryId++; + chatLine.value = personalHistory[personalHistoryId]; + case 40: // Down + if (personalHistoryId == -1) return; + personalHistoryId++; + if (personalHistoryId > personalHistory.length - 1) { + personalHistoryId = -1; + chatLine.value = ""; + return; + } + chatLine.value = personalHistory[personalHistoryId]; + } + } + } + +} diff --git a/src/client/Main.hx b/src/client/Main.hx index 5b2dc4c..f9232eb 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -19,12 +19,10 @@ using ClientTools; class Main { final clients:Array<Client> = []; - final personalHistory:Array<String> = []; var pageTitle = document.title; var config:Null<Config>; final filters:Array<{regex:EReg, replace:String}> = []; - var personal:Null<Client>; - var personalHistoryId = -1; + var personal = new Client("Unknown", 0); var isConnected = false; var ws:WebSocket; final player:Player; @@ -70,67 +68,13 @@ class Main { } function initListeners():Void { - final smilesBtn = ge("#smilesbtn"); - smilesBtn.onclick = e -> { - final smilesWrap = ge("#smileswrap"); - if (smilesWrap.style.display == "") - smilesWrap.style.display = "block"; - else smilesWrap.style.display = ""; - } - - final guestName:InputElement = cast ge("#guestname"); - guestName.onkeydown = (e:KeyboardEvent) -> { - if (guestName.value.length == 0) return; - if (e.keyCode == 13) send({ - type: Login, - login: { - clientName: guestName.value - } - }); - } - - final chatLine:InputElement = cast ge("#chatline"); - chatLine.onkeydown = function(e:KeyboardEvent) { - switch (e.keyCode) { - case 13: // Enter - if (chatLine.value.length == 0) return; - send({ - type: Message, - message: { - clientName: "", - text: chatLine.value - } - }); - personalHistory.push(chatLine.value); - if (personalHistory.length > 50) personalHistory.shift(); - personalHistoryId = -1; - chatLine.value = ""; - case 38: // Up - personalHistoryId--; - if (personalHistoryId == -2) { - personalHistoryId = personalHistory.length - 1; - if (personalHistoryId == -1) return; - } else if (personalHistoryId == -1) personalHistoryId++; - chatLine.value = personalHistory[personalHistoryId]; - case 40: // Down - if (personalHistoryId == -1) return; - personalHistoryId++; - if (personalHistoryId > personalHistory.length - 1) { - personalHistoryId = -1; - chatLine.value = ""; - return; - } - chatLine.value = personalHistory[personalHistoryId]; - } - } - + Buttons.init(this); MobileView.init(); - final leaderBtn:InputElement = cast ge("#leader_btn"); + final leaderBtn = ge("#leader_btn"); leaderBtn.onclick = (e) -> { - if (personal == null) return; - if (!personal.isLeader) leaderBtn.classList.add('label-success'); - else leaderBtn.classList.remove('label-success'); + // change button style before answer + setLeaderButton(!personal.isLeader); final name = personal.isLeader ? "" : personal.name; send({ type: SetLeader, @@ -140,12 +84,7 @@ class Main { }); } - final showMediaUrl:ButtonElement = cast ge("#showmediaurl"); - showMediaUrl.onclick = (e:MouseEvent) -> { - ge("#showmediaurl").classList.toggle("collapsed"); - ge("#showmediaurl").classList.toggle("active"); - ge("#addfromurl").classList.toggle("collapse"); - } + // TODO next/end ge("#queue_next").onclick = (e:MouseEvent) -> addVideoUrl(); ge("#queue_end").onclick = (e:MouseEvent) -> addVideoUrl(); ge("#mediaurl").onkeydown = function(e:KeyboardEvent) { @@ -153,26 +92,32 @@ class Main { } } - public function isLeader():Bool { - return personal != null && personal.isLeader; + public inline function isLeader():Bool { + return personal.isLeader; + } + + public inline function isAdmin():Bool { + return personal.isAdmin; } function addVideoUrl():Void { final mediaUrl:InputElement = cast ge("#mediaurl"); final url = mediaUrl.value; - final name = personal == null ? "Unknown" : personal.name; + var name = url.substr(url.lastIndexOf('/') + 1); + final matchName = ~/^(.+)\./; + if (matchName.match(name)) name = matchName.matched(1); + else name = Lang.get("rawVideo"); + getRemoteVideoDuration(mediaUrl.value, (duration:Float) -> { send({ - type: AddVideo, - addVideo: { + type: AddVideo, addVideo: { item: { url: url, - title: Lang.get("rawVideo"), - author: name, + title: name, + author: personal.name, duration: duration } - } - }); + }}); }); mediaUrl.value = ""; } @@ -181,8 +126,11 @@ class Main { final player:Element = ge("#ytapiplayer"); final video = document.createVideoElement(); video.src = src; + // TODO catch errors on AddVideo and getRemoteVideoDuration + video.onerror = e -> { + callback(0); + } video.onloadedmetadata = () -> { - trace(video.duration); player.removeChild(video); callback(video.duration); } @@ -201,30 +149,7 @@ class Main { trace('Event: ${data.type}', untyped data[t]); switch (data.type) { case Connected: - setConfig(data.connected.config); - if (data.connected.isUnknownClient) { - updateClients(data.connected.clients); - ge("#guestlogin").style.display = "block"; - ge("#chatline").style.display = "none"; - } else { - onLogin(data.connected.clients, data.connected.clientName); - } - final guestName:InputElement = cast ge("#guestname"); - if (guestName.value.length > 0) send({ - type: Login, - login: { - clientName: guestName.value - } - }); - for (message in data.connected.history) { - addMessage(message.name, message.text, message.time); - } - final list = data.connected.videoList; - if (list.length == 0) return; - player.setVideo(list[0]); - for (video in data.connected.videoList) { - player.addVideoItem(video); - } + onConnected(data); case Login: onLogin(data.login.clients, data.login.clientName); case LoginError: @@ -233,12 +158,11 @@ class Main { serverMessage(4, text); case Logout: updateClients(data.logout.clients); - personal = null; - ge("#guestlogin").style.display = "block"; - ge("#chatline").style.display = "none"; + personal = new Client(data.logout.clientName, 0); + showGuestLoginPanel(); case UpdateClients: updateClients(data.updateClients.clients); - if (personal != null) personal = clients.getByName(personal.name); + personal = clients.getByName(personal.name, personal); case Message: addMessage(data.message.clientName, data.message.text); case AddVideo: @@ -280,10 +204,38 @@ class Main { case SetLeader: clients.setLeader(data.setLeader.clientName); updateUserList(); - final leaderBtn:InputElement = cast ge("#leader_btn"); - if (isLeader()) leaderBtn.classList.add('label-success'); - else leaderBtn.classList.remove('label-success'); + setLeaderButton(isLeader()); if (isLeader()) player.setTime(player.getTime(), false); + case ClearChat: + ge("#messagebuffer").innerHTML = ""; + } + } + + function onConnected(data:WsEvent):Void { + final connected = data.connected; + setConfig(connected.config); + if (connected.isUnknownClient) { + updateClients(connected.clients); + personal = clients.getByName(connected.clientName, personal); + showGuestLoginPanel(); + } else { + onLogin(connected.clients, connected.clientName); + } + final guestName:InputElement = cast ge("#guestname"); + if (guestName.value.length > 0) send({ + type: Login, + login: { + clientName: guestName.value + } + }); + for (message in connected.history) { + addMessage(message.name, message.text, message.time); + } + final list = connected.videoList; + if (list.length == 0) return; + player.setVideo(list[0]); + for (video in connected.videoList) { + player.addVideoItem(video); } } @@ -327,8 +279,18 @@ class Main { function onLogin(data:Array<ClientData>, clientName:String):Void { updateClients(data); - personal = clients.getByName(clientName); - if (personal == null) return; + final newPersonal = clients.getByName(clientName); + if (newPersonal == null) return; + personal = newPersonal; + hideGuestLoginPanel(); + } + + function showGuestLoginPanel():Void { + ge("#guestlogin").style.display = "block"; + ge("#chatline").style.display = "none"; + } + + function hideGuestLoginPanel():Void { ge("#guestlogin").style.display = "none"; ge("#chatline").style.display = "block"; } @@ -376,10 +338,10 @@ class Main { final list = new StringBuf(); for (client in clients) { - // final klass = client.isLeader ? "userlist_owner" : "userlist_item"; - final klass = "userlist_item"; + list.add('<div class="userlist_item">'); if (client.isLeader) list.add('<span class="glyphicon glyphicon-star-empty"></span>'); - list.add('<span class="$klass">${client.name}</span></br>'); + final klass = client.isAdmin ? "userlist_owner" : ""; + list.add('<span class="$klass">${client.name}</span></div>'); } final userlist = ge("#userlist"); userlist.innerHTML = list.toString(); @@ -418,7 +380,7 @@ class Main { while (msgBuf.children.length > 200) msgBuf.removeChild(msgBuf.firstChild); msgBuf.scrollTop = msgBuf.scrollHeight; } - if (personal != null && personal.name == name) { + if (personal.name == name) { msgBuf.scrollTop = msgBuf.scrollHeight; } if (document.hidden && onBlinkTab == null) { @@ -432,6 +394,13 @@ class Main { } } + function setLeaderButton(flag:Bool):Void { + final leaderBtn = ge("#leader_btn"); + if (isLeader()) { + leaderBtn.classList.add("label-success"); + } else leaderBtn.classList.remove("label-success"); + } + function escapeRegExp(regex:String):String { return ~/([.*+?^${}()|[\]\\])/g.replace(regex, "\\$1"); } diff --git a/src/client/MobileView.hx b/src/client/MobileView.hx index 558df61..976958b 100644 --- a/src/client/MobileView.hx +++ b/src/client/MobileView.hx @@ -1,25 +1,24 @@ package client; -import js.html.InputElement; import js.Browser.document; import client.Main.ge; class MobileView { public static function init():Void { - final mvbtn:InputElement = cast ge("#mv_btn"); - mvbtn.onclick = (e) -> { - final mobile_view = toggleFullScreen(); - if (mobile_view) { + final mvbtn = ge("#mv_btn"); + mvbtn.onclick = e -> { + final mobileView = toggleFullScreen(); + if (mobileView) { document.body.classList.add('mobile-view'); - mvbtn.classList.add('label-success'); + mvbtn.classList.add('active'); final vwrap = ge("#videowrap"); if (vwrap.children[0] == ge("currenttitle")) { vwrap.appendChild(vwrap.children[0]); } } else { document.body.classList.remove('mobile-view'); - mvbtn.classList.remove('label-success'); + mvbtn.classList.remove('active'); final vwrap = ge("videowrap"); if (vwrap.children[0] != ge("currenttitle")) { vwrap.insertBefore(vwrap.children[1],vwrap.children[0]); diff --git a/src/client/Player.hx b/src/client/Player.hx index efdb71a..19e0c84 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -66,6 +66,7 @@ class Player { } player.innerHTML = ""; player.appendChild(video); + ge("#currenttitle").innerHTML = item.title; } public function addVideoItem(item:VideoItem):Void { @@ -108,6 +109,7 @@ class Player { public function removeVideo():Void { player.removeChild(video); video = null; + ge("#currenttitle").innerHTML = Lang.get("nothingPlaying"); } public function removeItem(url:String):Void { diff --git a/src/client/Split.hx b/src/client/Split.hx new file mode 100644 index 0000000..5d9d249 --- /dev/null +++ b/src/client/Split.hx @@ -0,0 +1,7 @@ +package client; + +@:native("Split") +extern class Split { + function new(divs:Array<String>, opts:Dynamic):Void; + function setSizes(sizes:Array<Int>):Void; +} diff --git a/src/server/Main.hx b/src/server/Main.hx index bc52718..45be2af 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -21,6 +21,7 @@ class Main { final wss:WSServer; final config:Config; final clients:Array<Client> = []; + final freeIds:Array<Int> = []; final videoList:Array<VideoItem> = []; final videoTimer = new VideoTimer(); final messages:Array<Message> = []; @@ -72,8 +73,12 @@ class Main { function onConnect(ws:WebSocket, req):Void { final ip = req.connection.remoteAddress; - trace('Client connected ($ip)'); - final client = new Client(ws, "Unknown", false); + final id = freeIds.length > 0 ? freeIds.shift() : clients.length; + final name = 'Guest ${id + 1}'; + trace('$name connected ($ip)'); + final isAdmin = req.connection.localAddress == ip; + final client = new Client(ws, id, name, 0); + if (isAdmin) client.group.set(Admin); clients.push(client); if (clients.length == 1) if (videoTimer.isPaused()) videoTimer.play(); @@ -98,6 +103,7 @@ class Main { }); ws.on("close", err -> { trace('Client ${client.name} disconnected'); + sortedPush(freeIds, client.id); clients.remove(client); sendClientList(); if (client.isLeader) { @@ -107,6 +113,17 @@ class Main { }); } + function sortedPush(ids:Array<Int>, id:Int):Void { + for (i in 0...ids.length) { + final n = ids[i]; + if (id < n) { + ids.insert(i, id); + return; + } + } + ids.push(id); + } + function onMessage(client:Client, data:WsEvent):Void { switch (data.type) { case Connected: @@ -131,11 +148,13 @@ class Main { case LoginError: case Logout: final oldName = client.name; - client.name = "Unknown"; + final id = clients.indexOf(client) + 1; + client.name = 'Guest $id'; send(client, { type: data.type, logout: { - clientName: oldName, + oldClientName: oldName, + clientName: client.name, clients: clientList() } }); @@ -150,7 +169,7 @@ class Main { data.message.clientName = client.name; final time = "[" + new Date().toTimeString().split(" ")[0] + "] "; messages.push({text: text, name: client.name, time: time}); - if (messages.length > config.serverChatHistory) messages.pop(); + if (messages.length > config.serverChatHistory) messages.shift(); broadcast(data); case AddVideo: videoList.push(data.addVideo.item); @@ -202,7 +221,11 @@ class Main { broadcastExcept(client, data); case SetLeader: clients.setLeader(data.setLeader.clientName); - sendClientList(); + broadcast({ + type: SetLeader, setLeader: { + clientName: data.setLeader.clientName + } + }); if (videoList.length == 0) return; if (!clients.hasLeader()) { if (videoTimer.isPaused()) videoTimer.play(); @@ -212,6 +235,8 @@ class Main { } }); } + case ClearChat: + if (client.isAdmin) broadcast(data); } } |
