diff options
| -rw-r--r-- | build/client.js | 426 | ||||
| -rw-r--r-- | build/server.js | 139 | ||||
| -rw-r--r-- | res/css/cytube.css | 11 | ||||
| -rw-r--r-- | res/css/des.css | 3 | ||||
| -rw-r--r-- | res/index.html | 8 | ||||
| -rw-r--r-- | res/js/split.min.js | 3 | ||||
| -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 |
15 files changed, 717 insertions, 342 deletions
diff --git a/build/client.js b/build/client.js index 5997420..3c9515c 100644 --- a/build/client.js +++ b/build/client.js @@ -7,17 +7,32 @@ function $extend(from, fields) { if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString; return proto; } -var Client = function(ws,name,isLeader) { - if(isLeader == null) { - isLeader = false; - } - this.ws = ws; +var ClientGroup = $hxEnums["ClientGroup"] = { __ename__ : true, __constructs__ : ["User","Leader","Admin"] + ,User: {_hx_index:0,__enum__:"ClientGroup",toString:$estr} + ,Leader: {_hx_index:1,__enum__:"ClientGroup",toString:$estr} + ,Admin: {_hx_index:2,__enum__:"ClientGroup",toString:$estr} +}; +var Client = function(ws,id,name,group) { this.name = name; - this.isLeader = isLeader; + var i = group; + if(group == null) { + i = 0; + } + this.group = i; }; Client.__name__ = true; Client.fromData = function(data) { - return new Client(null,data.name,data.isLeader); + return new Client(null,null,data.name,data.group); +}; +Client.prototype = { + setGroupFlag: function(type,flag) { + if(flag) { + this.group |= 1 << type._hx_index; + } else { + this.group &= -1 - (1 << type._hx_index); + } + return flag; + } }; var ClientTools = function() { }; ClientTools.__name__ = true; @@ -27,13 +42,13 @@ ClientTools.setLeader = function(clients,name) { var client = clients[_g]; ++_g; if(client.name == name) { - client.isLeader = true; - } else if(client.isLeader) { - client.isLeader = false; + client.setGroupFlag(ClientGroup.Leader,true); + } else if((client.group & 2) != 0) { + client.setGroupFlag(ClientGroup.Leader,false); } } }; -ClientTools.getByName = function(clients,name) { +ClientTools.getByName = function(clients,name,def) { var _g = 0; while(_g < clients.length) { var client = clients[_g]; @@ -42,7 +57,7 @@ ClientTools.getByName = function(clients,name) { return client; } } - return null; + return def; }; var EReg = function(r,opt) { this.r = new RegExp(r,opt.split("u").join("")); @@ -57,6 +72,13 @@ EReg.prototype = { this.r.s = s; return this.r.m != null; } + ,matched: function(n) { + if(this.r.m != null && n >= 0 && n < this.r.m.length) { + return this.r.m[n]; + } else { + throw new js__$Boot_HaxeError("EReg::matched"); + } + } }; var HxOverrides = function() { }; HxOverrides.__name__ = true; @@ -249,16 +271,150 @@ StringTools.startsWith = function(s,start) { StringTools.replace = function(s,sub,by) { return s.split(sub).join(by); }; +var client_Buttons = function() { }; +client_Buttons.__name__ = true; +client_Buttons.init = function(main) { + client_Buttons.initChatInput(main); + var smilesBtn = window.document.querySelector("#smilesbtn"); + smilesBtn.onclick = function(e) { + smilesBtn.classList.toggle("active"); + var smilesWrap = window.document.querySelector("#smileswrap"); + if(smilesBtn.classList.contains("active")) { + return smilesWrap.style.display = "block"; + } else { + return smilesWrap.style.display = "none"; + } + }; + window.document.querySelector("#clearchatbtn").style.display = "inline-block"; + window.document.querySelector("#clearchatbtn").onclick = function(e1) { + if((main.personal.group & 4) != 0) { + main.send({ type : "ClearChat"}); + } + return; + }; + var userList = window.document.querySelector("#userlist"); + userList.onclick = function(e2) { + if((main.personal.group & 4) == 0) { + return; + } + var el = e2.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}}); + return; + }; + client_Buttons.split = new Split(["#chatwrap","#videowrap"],{ sizes : [40,60], onDragEnd : function() { + return window.dispatchEvent(new Event("resize")); + }, minSize : 185, snapOffset : 0}); + var userlistToggle = window.document.querySelector("#userlisttoggle"); + userlistToggle.onclick = function(e3) { + var style = window.document.querySelector("#userlist").style; + if(style.display == "none") { + userlistToggle.classList.add("glyphicon-chevron-down"); + userlistToggle.classList.remove("glyphicon-chevron-right"); + return style.display = "block"; + } else { + userlistToggle.classList.add("glyphicon-chevron-right"); + userlistToggle.classList.remove("glyphicon-chevron-down"); + return style.display = "none"; + } + }; + window.document.querySelector("#usercount").onclick = userlistToggle.onclick; + var extendPlayer = window.document.querySelector("#extendplayer"); + extendPlayer.onclick = function(e4) { + if(extendPlayer.classList.contains("active")) { + client_Buttons.split.setSizes([40,60]); + window.document.querySelector("#userlist").style.width = "90px"; + } else { + client_Buttons.split.setSizes([20,80]); + window.document.querySelector("#userlist").style.width = "80px"; + } + extendPlayer.classList.toggle("active"); + return window.dispatchEvent(new Event("resize")); + }; + window.document.querySelector("#showmediaurl").onclick = function(e5) { + window.document.querySelector("#showmediaurl").classList.toggle("collapsed"); + window.document.querySelector("#showmediaurl").classList.toggle("active"); + return window.document.querySelector("#addfromurl").classList.toggle("collapse"); + }; + window.onresize = client_Buttons.onVideoResize; + window.dispatchEvent(new Event("resize")); +}; +client_Buttons.onVideoResize = function() { + var height = window.document.querySelector("#ytapiplayer").offsetHeight - window.document.querySelector("#chatline").offsetHeight; + window.document.querySelector("#messagebuffer").style.height = "" + height + "px"; + window.document.querySelector("#userlist").style.height = "" + height + "px"; +}; +client_Buttons.initChatInput = function(main) { + var guestName = window.document.querySelector("#guestname"); + guestName.onkeydown = function(e) { + if(guestName.value.length == 0) { + return; + } + if(e.keyCode == 13) { + main.send({ type : "Login", login : { clientName : guestName.value}}); + } + return; + }; + var chatLine = window.document.querySelector("#chatline"); + chatLine.onkeydown = function(e1) { + switch(e1.keyCode) { + case 13: + if(chatLine.value.length == 0) { + return; + } + main.send({ type : "Message", message : { clientName : "", text : chatLine.value}}); + client_Buttons.personalHistory.push(chatLine.value); + if(client_Buttons.personalHistory.length > 50) { + client_Buttons.personalHistory.shift(); + } + client_Buttons.personalHistoryId = -1; + chatLine.value = ""; + break; + case 38: + client_Buttons.personalHistoryId--; + if(client_Buttons.personalHistoryId == -2) { + client_Buttons.personalHistoryId = client_Buttons.personalHistory.length - 1; + if(client_Buttons.personalHistoryId == -1) { + return; + } + } else if(client_Buttons.personalHistoryId == -1) { + client_Buttons.personalHistoryId++; + } + chatLine.value = client_Buttons.personalHistory[client_Buttons.personalHistoryId]; + break; + case 40: + if(client_Buttons.personalHistoryId == -1) { + return; + } + client_Buttons.personalHistoryId++; + if(client_Buttons.personalHistoryId > client_Buttons.personalHistory.length - 1) { + client_Buttons.personalHistoryId = -1; + chatLine.value = ""; + return; + } + chatLine.value = client_Buttons.personalHistory[client_Buttons.personalHistoryId]; + break; + } + }; +}; var client_Main = function(host,port) { if(port == null) { port = 4201; } this.onTimeGet = new haxe_Timer(2000); this.isConnected = false; - this.personalHistoryId = -1; + this.personal = new Client(null,null,"Unknown",0); this.filters = []; this.pageTitle = window.document.title; - this.personalHistory = []; this.clients = []; var _gthis = this; this.player = new client_Player(this); @@ -313,113 +469,41 @@ client_Main.prototype = { } ,initListeners: function() { var _gthis = this; - window.document.querySelector("#smilesbtn").onclick = function(e) { - var smilesWrap = window.document.querySelector("#smileswrap"); - if(smilesWrap.style.display == "") { - return smilesWrap.style.display = "block"; - } else { - return smilesWrap.style.display = ""; - } - }; - var guestName = window.document.querySelector("#guestname"); - guestName.onkeydown = function(e1) { - if(guestName.value.length == 0) { - return; - } - if(e1.keyCode == 13) { - _gthis.send({ type : "Login", login : { clientName : guestName.value}}); - } - return; - }; - var chatLine = window.document.querySelector("#chatline"); - chatLine.onkeydown = function(e2) { - switch(e2.keyCode) { - case 13: - if(chatLine.value.length == 0) { - return; - } - _gthis.send({ type : "Message", message : { clientName : "", text : chatLine.value}}); - _gthis.personalHistory.push(chatLine.value); - if(_gthis.personalHistory.length > 50) { - _gthis.personalHistory.shift(); - } - _gthis.personalHistoryId = -1; - chatLine.value = ""; - break; - case 38: - _gthis.personalHistoryId--; - if(_gthis.personalHistoryId == -2) { - _gthis.personalHistoryId = _gthis.personalHistory.length - 1; - if(_gthis.personalHistoryId == -1) { - return; - } - } else if(_gthis.personalHistoryId == -1) { - _gthis.personalHistoryId++; - } - chatLine.value = _gthis.personalHistory[_gthis.personalHistoryId]; - break; - case 40: - if(_gthis.personalHistoryId == -1) { - return; - } - _gthis.personalHistoryId++; - if(_gthis.personalHistoryId > _gthis.personalHistory.length - 1) { - _gthis.personalHistoryId = -1; - chatLine.value = ""; - return; - } - chatLine.value = _gthis.personalHistory[_gthis.personalHistoryId]; - break; - } - }; + client_Buttons.init(this); client_MobileView.init(); - var leaderBtn = window.document.querySelector("#leader_btn"); - leaderBtn.onclick = function(e3) { - if(_gthis.personal == null) { - return; - } - if(!_gthis.personal.isLeader) { - leaderBtn.classList.add("label-success"); - } else { - leaderBtn.classList.remove("label-success"); - } - _gthis.send({ type : "SetLeader", setLeader : { clientName : _gthis.personal.isLeader ? "" : _gthis.personal.name}}); + window.document.querySelector("#leader_btn").onclick = function(e) { + _gthis.setLeaderButton((_gthis.personal.group & 2) == 0); + _gthis.send({ type : "SetLeader", setLeader : { clientName : (_gthis.personal.group & 2) != 0 ? "" : _gthis.personal.name}}); return; }; - window.document.querySelector("#showmediaurl").onclick = function(e4) { - window.document.querySelector("#showmediaurl").classList.toggle("collapsed"); - window.document.querySelector("#showmediaurl").classList.toggle("active"); - return window.document.querySelector("#addfromurl").classList.toggle("collapse"); - }; - window.document.querySelector("#queue_next").onclick = function(e5) { + window.document.querySelector("#queue_next").onclick = function(e1) { _gthis.addVideoUrl(); return; }; - window.document.querySelector("#queue_end").onclick = function(e6) { + window.document.querySelector("#queue_end").onclick = function(e2) { _gthis.addVideoUrl(); return; }; - window.document.querySelector("#mediaurl").onkeydown = function(e7) { - if(e7.keyCode == 13) { + window.document.querySelector("#mediaurl").onkeydown = function(e3) { + if(e3.keyCode == 13) { _gthis.addVideoUrl(); } }; } - ,isLeader: function() { - if(this.personal != null) { - return this.personal.isLeader; - } else { - return false; - } - } ,addVideoUrl: function() { var _gthis = this; var mediaUrl = window.document.querySelector("#mediaurl"); var url = mediaUrl.value; - var name = this.personal == null ? "Unknown" : this.personal.name; + var pos = url.lastIndexOf("/") + 1; + var name = HxOverrides.substr(url,pos,null); + var matchName = new EReg("^(.+)\\.",""); + if(matchName.match(name)) { + name = matchName.matched(1); + } else { + name = Lang.get("rawVideo"); + } this.getRemoteVideoDuration(mediaUrl.value,function(duration) { - var tmp = Lang.get("rawVideo"); - _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : tmp, author : name, duration : duration}}}); + _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}}}); return; }); mediaUrl.value = ""; @@ -428,8 +512,11 @@ client_Main.prototype = { var player = window.document.querySelector("#ytapiplayer"); var video = window.document.createElement("video"); video.src = src; + video.onerror = function(e) { + callback(0); + return; + }; video.onloadedmetadata = function() { - haxe_Log.trace(video.duration,{ fileName : "src/client/Main.hx", lineNumber : 185, className : "client.Main", methodName : "getRemoteVideoDuration"}); player.removeChild(video); callback(video.duration); return; @@ -447,7 +534,7 @@ client_Main.prototype = { var data = JSON.parse(e.data); var t = data.type; var t1 = t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null); - haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 201, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 149, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); switch(data.type) { case "AddVideo": if(this.player.isListEmpty()) { @@ -455,43 +542,16 @@ client_Main.prototype = { } this.player.addVideoItem(data.addVideo.item); break; + case "ClearChat": + window.document.querySelector("#messagebuffer").innerHTML = ""; + break; case "Connected": - this.setConfig(data.connected.config); - if(data.connected.isUnknownClient) { - this.updateClients(data.connected.clients); - window.document.querySelector("#guestlogin").style.display = "block"; - window.document.querySelector("#chatline").style.display = "none"; - } else { - this.onLogin(data.connected.clients,data.connected.clientName); - } - var guestName = window.document.querySelector("#guestname"); - if(guestName.value.length > 0) { - this.send({ type : "Login", login : { clientName : guestName.value}}); - } - var _g = 0; - var _g1 = data.connected.history; - while(_g < _g1.length) { - var message = _g1[_g]; - ++_g; - this.addMessage(message.name,message.text,message.time); - } - var list = data.connected.videoList; - if(list.length == 0) { - return; - } - this.player.setVideo(list[0]); - var _g2 = 0; - var _g3 = data.connected.videoList; - while(_g2 < _g3.length) { - var video = _g3[_g2]; - ++_g2; - this.player.addVideoItem(video); - } + this.onConnected(data); break; case "GetTime": var newTime = data.getTime.time; var time = this.player.getTime(); - if(this.isLeader()) { + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { if(Math.abs(time - newTime) < 2) { return; } @@ -517,22 +577,21 @@ client_Main.prototype = { break; case "Logout": this.updateClients(data.logout.clients); - this.personal = null; - window.document.querySelector("#guestlogin").style.display = "block"; - window.document.querySelector("#chatline").style.display = "none"; + this.personal = new Client(null,null,data.logout.clientName,0); + this.showGuestLoginPanel(); break; case "Message": this.addMessage(data.message.clientName,data.message.text); break; case "Pause": - if(this.isLeader()) { + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { return; } this.player.pause(); this.player.setTime(data.pause.time); break; case "Play": - if(this.isLeader()) { + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { return; } this.player.setTime(data.play.time); @@ -547,13 +606,8 @@ client_Main.prototype = { case "SetLeader": ClientTools.setLeader(this.clients,data.setLeader.clientName); this.updateUserList(); - var leaderBtn = window.document.querySelector("#leader_btn"); - if(this.isLeader()) { - leaderBtn.classList.add("label-success"); - } else { - leaderBtn.classList.remove("label-success"); - } - if(this.isLeader()) { + this.setLeaderButton((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0); + if((this.personal.group & 1 << ClientGroup.Leader._hx_index) != 0) { this.player.setTime(this.player.getTime(),false); } break; @@ -567,9 +621,7 @@ client_Main.prototype = { break; case "UpdateClients": this.updateClients(data.updateClients.clients); - if(this.personal != null) { - this.personal = ClientTools.getByName(this.clients,this.personal.name); - } + this.personal = ClientTools.getByName(this.clients,this.personal.name,this.personal); break; case "VideoLoaded": this.player.setTime(0); @@ -577,6 +629,36 @@ client_Main.prototype = { break; } } + ,onConnected: function(data) { + var connected = data.connected; + this.setConfig(connected.config); + if(connected.isUnknownClient) { + this.updateClients(connected.clients); + this.personal = ClientTools.getByName(this.clients,connected.clientName,this.personal); + this.showGuestLoginPanel(); + } else { + this.onLogin(connected.clients,connected.clientName); + } + var guestName = window.document.querySelector("#guestname"); + if(guestName.value.length > 0) { + this.send({ type : "Login", login : { clientName : guestName.value}}); + } + var _g = 0; + var _g1 = connected.history; + while(_g < _g1.length) { + var message = _g1[_g]; + ++_g; + this.addMessage(message.name,message.text,message.time); + } + var list = connected.videoList; + if(list.length == 0) { + return; + } + this.player.setVideo(list[0]); + var _g2 = 0; + var _g3 = connected.videoList; + while(_g2 < _g3.length) this.player.addVideoItem(_g3[_g2++]); + } ,setConfig: function(config) { this.config = config; this.pageTitle = config.channelName; @@ -620,10 +702,18 @@ client_Main.prototype = { } ,onLogin: function(data,clientName) { this.updateClients(data); - this.personal = ClientTools.getByName(this.clients,clientName); - if(this.personal == null) { + var newPersonal = ClientTools.getByName(this.clients,clientName); + if(newPersonal == null) { return; } + this.personal = newPersonal; + this.hideGuestLoginPanel(); + } + ,showGuestLoginPanel: function() { + window.document.querySelector("#guestlogin").style.display = "block"; + window.document.querySelector("#chatline").style.display = "none"; + } + ,hideGuestLoginPanel: function() { window.document.querySelector("#guestlogin").style.display = "none"; window.document.querySelector("#chatline").style.display = "block"; } @@ -674,10 +764,11 @@ client_Main.prototype = { while(_g < _g1.length) { var client1 = _g1[_g]; ++_g; - if(client1.isLeader) { + list_b += "<div class=\"userlist_item\">"; + if((client1.group & 2) != 0) { list_b += "<span class=\"glyphicon glyphicon-star-empty\"></span>"; } - list_b += Std.string("<span class=\"" + "userlist_item" + "\">" + client1.name + "</span></br>"); + list_b += Std.string("<span class=\"" + ((client1.group & 4) != 0 ? "userlist_owner" : "") + "\">" + client1.name + "</span></div>"); } window.document.querySelector("#userlist").innerHTML = list_b; } @@ -716,7 +807,7 @@ client_Main.prototype = { while(msgBuf.children.length > 200) msgBuf.removeChild(msgBuf.firstChild); msgBuf.scrollTop = msgBuf.scrollHeight; } - if(this.personal != null && this.personal.name == name) { + if(this.personal.name == name) { msgBuf.scrollTop = msgBuf.scrollHeight; } if(window.document.hidden && this.onBlinkTab == null) { @@ -731,6 +822,14 @@ client_Main.prototype = { this.onBlinkTab.run(); } } + ,setLeaderButton: function(flag) { + var leaderBtn = window.document.querySelector("#leader_btn"); + if((this.personal.group & 2) != 0) { + leaderBtn.classList.add("label-success"); + } else { + leaderBtn.classList.remove("label-success"); + } + } ,escapeRegExp: function(regex) { var _this_r = new RegExp("([.*+?^${}()|[\\]\\\\])","g".split("u").join("")); return regex.replace(_this_r,"\\$1"); @@ -743,14 +842,14 @@ client_MobileView.init = function() { mvbtn.onclick = function(e) { if(client_MobileView.toggleFullScreen()) { window.document.body.classList.add("mobile-view"); - mvbtn.classList.add("label-success"); + mvbtn.classList.add("active"); var vwrap = window.document.querySelector("#videowrap"); if(vwrap.children[0] == window.document.querySelector("currenttitle")) { vwrap.appendChild(vwrap.children[0]); } } else { window.document.body.classList.remove("mobile-view"); - mvbtn.classList.remove("label-success"); + mvbtn.classList.remove("active"); var vwrap1 = window.document.querySelector("videowrap"); if(vwrap1.children[0] != window.document.querySelector("currenttitle")) { vwrap1.insertBefore(vwrap1.children[1],vwrap1.children[0]); @@ -812,21 +911,21 @@ client_Player.prototype = { _gthis.skipSetTime = false; return; } - if(!_gthis.main.isLeader()) { + if((_gthis.main.personal.group & 2) == 0) { return; } _gthis.main.send({ type : "SetTime", setTime : { time : _gthis.video.currentTime}}); return; }; this.video.onpause = function(e2) { - if(!_gthis.main.isLeader()) { + if((_gthis.main.personal.group & 2) == 0) { return; } _gthis.main.send({ type : "Pause", pause : { time : _gthis.video.currentTime}}); return; }; this.video.onplay = function(e3) { - if(!_gthis.main.isLeader()) { + if((_gthis.main.personal.group & 2) == 0) { return; } _gthis.main.send({ type : "Play", play : { time : _gthis.video.currentTime}}); @@ -834,6 +933,7 @@ client_Player.prototype = { }; this.player.innerHTML = ""; this.player.appendChild(this.video); + window.document.querySelector("#currenttitle").innerHTML = item.title; } ,addVideoItem: function(item) { var _gthis = this; @@ -1377,5 +1477,7 @@ js_Boot.__toStr = ({ }).toString; Lang.ids = ["en","ru"]; Lang.langs = new haxe_ds_StringMap(); Lang.lang = HxOverrides.substr(window.navigator.language,0,2).toLowerCase(); +client_Buttons.personalHistory = []; +client_Buttons.personalHistoryId = -1; client_Main.main(); })(typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this); diff --git a/build/server.js b/build/server.js index 14869f0..6c35793 100644 --- a/build/server.js +++ b/build/server.js @@ -1,23 +1,39 @@ // Generated by Haxe 4.0.5 (function ($global) { "use strict"; +var $estr = function() { return js_Boot.__string_rec(this,''); },$hxEnums = $hxEnums || {},$_; function $extend(from, fields) { var proto = Object.create(from); for (var name in fields) proto[name] = fields[name]; if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString; return proto; } -var Client = function(ws,name,isLeader) { - if(isLeader == null) { - isLeader = false; - } +var ClientGroup = $hxEnums["ClientGroup"] = { __ename__ : true, __constructs__ : ["User","Leader","Admin"] + ,User: {_hx_index:0,__enum__:"ClientGroup",toString:$estr} + ,Leader: {_hx_index:1,__enum__:"ClientGroup",toString:$estr} + ,Admin: {_hx_index:2,__enum__:"ClientGroup",toString:$estr} +}; +var Client = function(ws,id,name,group) { this.ws = ws; + this.id = id; this.name = name; - this.isLeader = isLeader; + var i = group; + if(group == null) { + i = 0; + } + this.group = i; }; Client.__name__ = true; Client.prototype = { - getData: function() { - return { name : this.name, isLeader : this.isLeader}; + setGroupFlag: function(type,flag) { + if(flag) { + this.group |= 1 << type._hx_index; + } else { + this.group &= -1 - (1 << type._hx_index); + } + return flag; + } + ,getData: function() { + return { name : this.name, group : this.group}; } }; var ClientTools = function() { }; @@ -28,20 +44,20 @@ ClientTools.setLeader = function(clients,name) { var client = clients[_g]; ++_g; if(client.name == name) { - client.isLeader = true; - } else if(client.isLeader) { - client.isLeader = false; + client.setGroupFlag(ClientGroup.Leader,true); + } else if((client.group & 2) != 0) { + client.setGroupFlag(ClientGroup.Leader,false); } } }; ClientTools.hasLeader = function(clients) { var _g = 0; - while(_g < clients.length) if(clients[_g++].isLeader) { + while(_g < clients.length) if((clients[_g++].group & 2) != 0) { return true; } return false; }; -ClientTools.getByName = function(clients,name) { +ClientTools.getByName = function(clients,name,def) { var _g = 0; while(_g < clients.length) { var client = clients[_g]; @@ -50,7 +66,7 @@ ClientTools.getByName = function(clients,name) { return client; } } - return null; + return def; }; var EReg = function(r,opt) { this.r = new RegExp(r,opt.split("u").join("")); @@ -406,6 +422,34 @@ js_Boot.__string_rec = function(o,s) { case "function": return "<function>"; case "object": + if(o.__enum__) { + var e = $hxEnums[o.__enum__]; + var n = e.__constructs__[o._hx_index]; + var con = e[n]; + if(con.__params__) { + s = s + "\t"; + return n + "(" + ((function($this) { + var $r; + var _g = []; + { + var _g1 = 0; + var _g2 = con.__params__; + while(true) { + if(!(_g1 < _g2.length)) { + break; + } + var p = _g2[_g1]; + _g1 = _g1 + 1; + _g.push(js_Boot.__string_rec(o[p],s)); + } + } + $r = _g; + return $r; + }(this))).join(",") + ")"; + } else { + return n; + } + } if(((o) instanceof Array)) { var str = "["; s += "\t"; @@ -490,7 +534,8 @@ server_HttpServer.serveFiles = function(req,res) { res.end(tmp1); } else { res.statusCode = 500; - res.end("Error getting the file: " + Std.string(err) + "."); + var tmp2 = "Error getting the file: " + Std.string(err) + "."; + res.end(tmp2); } return; } @@ -540,6 +585,7 @@ var server_Main = function(port,wsPort) { this.messages = []; this.videoTimer = new server_VideoTimer(); this.videoList = []; + this.freeIds = []; this.clients = []; this.rootDir = "" + __dirname + "/.."; this.config = this.getUserConfig(); @@ -551,16 +597,16 @@ var server_Main = function(port,wsPort) { process.on("exit",exit); process.on("SIGINT",exit); process.on("uncaughtException",function(log) { - haxe_Log.trace(log,{ fileName : "src/server/Main.hx", lineNumber : 40, className : "server.Main", methodName : "new"}); + haxe_Log.trace(log,{ fileName : "src/server/Main.hx", lineNumber : 41, className : "server.Main", methodName : "new"}); return; }); process.on("unhandledRejection",function(reason,promise) { - haxe_Log.trace("Unhandled Rejection at:",{ fileName : "src/server/Main.hx", lineNumber : 43, className : "server.Main", methodName : "new", customParams : [reason]}); + haxe_Log.trace("Unhandled Rejection at:",{ fileName : "src/server/Main.hx", lineNumber : 44, className : "server.Main", methodName : "new", customParams : [reason]}); return; }); server_Utils.getGlobalIp(function(ip) { - haxe_Log.trace("Local: http://" + server_Utils.getLocalIp() + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 48, className : "server.Main", methodName : "new"}); - haxe_Log.trace("Global: http://" + ip + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 49, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Local: http://" + server_Utils.getLocalIp() + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 49, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Global: http://" + ip + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 50, className : "server.Main", methodName : "new"}); return; }); var dir = "" + this.rootDir + "/res"; @@ -589,7 +635,7 @@ server_Main.prototype = { var field = _g1[_g]; ++_g; if(Reflect.field(config,field) == null) { - haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 67, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 68, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -597,9 +643,20 @@ server_Main.prototype = { } ,onConnect: function(ws,req) { var _gthis = this; - haxe_Log.trace("Client connected (" + req.connection.remoteAddress + ")",{ fileName : "src/server/Main.hx", lineNumber : 75, className : "server.Main", methodName : "onConnect"}); - var client = new Client(ws,"Unknown",false); + var ip = req.connection.remoteAddress; + var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length; + var name = "Guest " + (id + 1); + haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 78, className : "server.Main", methodName : "onConnect"}); + var client = new Client(ws,id,name,0); + if(req.connection.localAddress == ip) { + client.group |= 4; + } this.clients.push(client); + if(this.clients.length == 1) { + if(this.videoTimer.isPaused()) { + this.videoTimer.play(); + } + } var tmp = this.config; var tmp1 = this.messages; var client1 = client.name; @@ -615,17 +672,33 @@ server_Main.prototype = { return; }); ws.on("close",function(err) { - haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 98, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 105, className : "server.Main", methodName : "onConnect"}); + _gthis.sortedPush(_gthis.freeIds,client.id); HxOverrides.remove(_gthis.clients,client); _gthis.sendClientList(); - if(client.isLeader) { + if((client.group & 2) != 0) { if(_gthis.videoTimer.isPaused()) { _gthis.videoTimer.play(); } } + if(_gthis.clients.length == 0) { + _gthis.videoTimer.pause(); + } return; }); } + ,sortedPush: function(ids,id) { + var _g = 0; + var _g1 = ids.length; + while(_g < _g1) { + var i = _g++; + if(id < ids[i]) { + ids.splice(i,0,id); + return; + } + } + ids.push(id); + } ,onMessage: function(client,data) { switch(data.type) { case "AddVideo": @@ -635,6 +708,11 @@ server_Main.prototype = { this.waitVideoStart = haxe_Timer.delay($bind(this,this.startVideoPlayback),3000); } break; + case "ClearChat": + if((client.group & 4) != 0) { + this.broadcast(data); + } + break; case "Connected": break; case "GetTime": @@ -662,8 +740,8 @@ server_Main.prototype = { break; case "Logout": var oldName = client.name; - client.name = "Unknown"; - this.send(client,{ type : data.type, logout : { clientName : oldName, clients : this.clientList()}}); + client.name = "Guest " + (this.clients.indexOf(client) + 1); + this.send(client,{ type : data.type, logout : { oldClientName : oldName, clientName : client.name, clients : this.clientList()}}); this.sendClientList(); break; case "Message": @@ -679,7 +757,7 @@ server_Main.prototype = { var time = "[" + new Date().toTimeString().split(" ")[0] + "] "; this.messages.push({ text : text, name : client.name, time : time}); if(this.messages.length > this.config.serverChatHistory) { - this.messages.pop(); + this.messages.shift(); } this.broadcast(data); break; @@ -687,7 +765,7 @@ server_Main.prototype = { if(this.videoList.length == 0) { return; } - if(!client.isLeader) { + if((client.group & 2) == 0) { return; } this.videoTimer.pause(); @@ -697,7 +775,7 @@ server_Main.prototype = { if(this.videoList.length == 0) { return; } - if(!client.isLeader) { + if((client.group & 2) == 0) { return; } this.videoTimer.play(); @@ -718,7 +796,7 @@ server_Main.prototype = { break; case "SetLeader": ClientTools.setLeader(this.clients,data.setLeader.clientName); - this.sendClientList(); + this.broadcast({ type : "SetLeader", setLeader : { clientName : data.setLeader.clientName}}); if(this.videoList.length == 0) { return; } @@ -733,7 +811,7 @@ server_Main.prototype = { if(this.videoList.length == 0) { return; } - if(!client.isLeader) { + if((client.group & 2) == 0) { return; } this.videoTimer.setTime(data.setTime.time); @@ -886,7 +964,6 @@ sys_FileSystem.exists = function(path) { } }; function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); else return o.iterator(); } -var $_; function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; } $global.$haxeUID |= 0; var __map_reserved = {}; diff --git a/res/css/cytube.css b/res/css/cytube.css index 310e72b..a879de3 100644 --- a/res/css/cytube.css +++ b/res/css/cytube.css @@ -287,20 +287,11 @@ li.ui-sortable-helper, li.ui-sortable-placeholder + li.queue_entry { cursor: pointer; } -.userlist_siteadmin { - color: #cc0000!important; - font-weight: bold!important; -} - .userlist_owner { - color: #0000cc!important; + color: #cc0000!important; font-weight: bold!important; } -.userlist_op { - color: #00aa00!important; -} - .userlist_guest { color: #888888!important; } diff --git a/res/css/des.css b/res/css/des.css index 014d3d6..b85d5f1 100644 --- a/res/css/des.css +++ b/res/css/des.css @@ -176,6 +176,9 @@ src:url('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/fonts/gl .glyphicon-chevron-down:before { content:"\e114" } +.glyphicon-chevron-right::before { + content: "\e080"; +} .glyphicon-retweet:before { content:"\e115" } diff --git a/res/index.html b/res/index.html index 9b5d6f4..9834175 100644 --- a/res/index.html +++ b/res/index.html @@ -63,7 +63,6 @@ <div class="split split-horizontal" id="chatwrap" style="width: calc(40% - 5px);"> <div id="chatheader"><i class="glyphicon glyphicon-chevron-down pull-left pointer" id="userlisttoggle" title="${toggleUserList}"></i> <span class="pointer" id="usercount">${connection}...</span> - <span class="label label-default pull-right pointer" id="mv_btn">${mobileViewBtn}</span> <span class="label label-default pull-right pointer" id="leader_btn">${leader}</span> </div> <div id="userlist" style="height: 389px;"></div> @@ -73,7 +72,7 @@ <input class="form-control" id="guestname" type="text" placeholder="${yourName}"> </div> </div> - <div class="gutter gutter-horizontal" style="width: 10px;"></div> + <!-- <div class="gutter gutter-horizontal" style="width: 10px;"></div> --> <div class="split split-horizontal" id="videowrap" style="width: calc(60% - 5px);"> <p id="currenttitle">${nothingPlaying}</p> <div id="ytapiplayer" class="embed-responsive embed-responsive-16by9"></div> @@ -82,9 +81,9 @@ </div> <div class="row" id="controlsrow"> <div class="col-lg-5 col-md-5" id="leftcontrols"> - <button class="btn btn-sm btn-default" id="clearchatbtn">${clearChat}</button> + <button id="mv_btn" class="btn btn-sm btn-default">${mobileViewBtn}</button> <button class="btn btn-sm btn-default" id="smilesbtn">${emotes}</button> - <button id="image-btn" class="btn btn-sm btn-default">${uploadImage}</button></div> + <button class="btn btn-sm btn-default" id="clearchatbtn" style="display: none;">${clearChat}</button></div> <div class="col-lg-7 col-md-7" id="rightcontrols"> <div class="btn-group" id="plcontrol"> <button class="btn btn-sm btn-default collapsed" id="showsearch" title="${searchVideo}" data-toggle="collapse" data-target="#searchcontrol" aria-expanded="false"><span class="glyphicon glyphicon-search"></span></button> @@ -198,6 +197,7 @@ </footer> <!-- <script src="js/custom.js"></script> --> + <script src="js/split.min.js"></script> <script src="client.js"></script> <!-- <script defer="" src="https://www.youtube.com/iframe_api"></script> <script defer="" src="js/sc.js"></script> diff --git a/res/js/split.min.js b/res/js/split.min.js new file mode 100644 index 0000000..6739874 --- /dev/null +++ b/res/js/split.min.js @@ -0,0 +1,3 @@ +/*! Split.js - v1.5.11 */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var L=window,T=L.document,N="addEventListener",R="removeEventListener",q="getBoundingClientRect",H="horizontal",I=function(){return!1},W=L.attachEvent&&!L[N],i=["","-webkit-","-moz-","-o-"].filter(function(e){var t=T.createElement("div");return t.style.cssText="width:"+e+"calc(9px)",!!t.style.length}).shift()+"calc",s=function(e){return"string"==typeof e||e instanceof String},X=function(e){if(s(e)){var t=T.querySelector(e);if(!t)throw new Error("Selector "+e+" did not match a DOM element");return t}return e},Y=function(e,t,n){var r=e[t];return void 0!==r?r:n},G=function(e,t,n,r){if(t){if("end"===r)return 0;if("center"===r)return e/2}else if(n){if("start"===r)return 0;if("center"===r)return e/2}return e},J=function(e,t){var n=T.createElement("div");return n.className="gutter gutter-"+t,n},K=function(e,t,n){var r={};return s(t)?r[e]=t:r[e]=W?t+"%":i+"("+t+"% - "+n+"px)",r},P=function(e,t){var n;return(n={})[e]=t+"px",n};return function(e,i){void 0===i&&(i={});var u,t,s,o,r,a,l=e;Array.from&&(l=Array.from(l));var c=X(l[0]).parentNode,n=getComputedStyle?getComputedStyle(c):null,f=n?n.flexDirection:null,m=Y(i,"sizes")||l.map(function(){return 100/l.length}),h=Y(i,"minSize",100),d=Array.isArray(h)?h:l.map(function(){return h}),g=Y(i,"expandToMin",!1),v=Y(i,"gutterSize",10),p=Y(i,"gutterAlign","center"),y=Y(i,"snapOffset",30),z=Y(i,"dragInterval",1),S=Y(i,"direction",H),b=Y(i,"cursor",S===H?"col-resize":"row-resize"),_=Y(i,"gutter",J),E=Y(i,"elementStyle",K),w=Y(i,"gutterStyle",P);function k(t,e,n,r){var i=E(u,e,n,r);Object.keys(i).forEach(function(e){t.style[e]=i[e]})}function x(){return a.map(function(e){return e.size})}function M(e){return"touches"in e?e.touches[0][t]:e[t]}function U(e){var t=a[this.a],n=a[this.b],r=t.size+n.size;t.size=e/this.size*r,n.size=r-e/this.size*r,k(t.element,t.size,this._b,t.i),k(n.element,n.size,this._c,n.i)}function O(){var e=a[this.a].element,t=a[this.b].element,n=e[q](),r=t[q]();this.size=n[u]+r[u]+this._b+this._c,this.start=n[s],this.end=n[o]}function C(s){var o=function(e){if(!getComputedStyle)return null;var t=getComputedStyle(e);if(!t)return null;var n=e[r];return 0===n?null:n-=S===H?parseFloat(t.paddingLeft)+parseFloat(t.paddingRight):parseFloat(t.paddingTop)+parseFloat(t.paddingBottom)}(c);if(null===o)return s;if(d.reduce(function(e,t){return e+t},0)>o)return s;var a=0,u=[],e=s.map(function(e,t){var n=o*e/100,r=G(v,0===t,t===s.length-1,p),i=d[t]+r;return n<i?(a+=i-n,u.push(0),i):(u.push(n-i),n)});return 0===a?s:e.map(function(e,t){var n=e;if(0<a&&0<u[t]-a){var r=Math.min(a,u[t]-a);a-=r,n=e-r}return n/o*100})}function D(e){if(!("button"in e&&0!==e.button)){var t=this,n=a[t.a].element,r=a[t.b].element;t.dragging||Y(i,"onDragStart",I)(x()),e.preventDefault(),t.dragging=!0,t.move=function(e){var t,n=a[this.a],r=a[this.b];this.dragging&&(t=M(e)-this.start+(this._b-this.dragOffset),1<z&&(t=Math.round(t/z)*z),t<=n.minSize+y+this._b?t=n.minSize+this._b:t>=this.size-(r.minSize+y+this._c)&&(t=this.size-(r.minSize+this._c)),U.call(this,t),Y(i,"onDrag",I)())}.bind(t),t.stop=function(){var e=this,t=a[e.a].element,n=a[e.b].element;e.dragging&&Y(i,"onDragEnd",I)(x()),e.dragging=!1,L[R]("mouseup",e.stop),L[R]("touchend",e.stop),L[R]("touchcancel",e.stop),L[R]("mousemove",e.move),L[R]("touchmove",e.move),e.stop=null,e.move=null,t[R]("selectstart",I),t[R]("dragstart",I),n[R]("selectstart",I),n[R]("dragstart",I),t.style.userSelect="",t.style.webkitUserSelect="",t.style.MozUserSelect="",t.style.pointerEvents="",n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",e.gutter.style.cursor="",e.parent.style.cursor="",T.body.style.cursor=""}.bind(t),L[N]("mouseup",t.stop),L[N]("touchend",t.stop),L[N]("touchcancel",t.stop),L[N]("mousemove",t.move),L[N]("touchmove",t.move),n[N]("selectstart",I),n[N]("dragstart",I),r[N]("selectstart",I),r[N]("dragstart",I),n.style.userSelect="none",n.style.webkitUserSelect="none",n.style.MozUserSelect="none",n.style.pointerEvents="none",r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",t.gutter.style.cursor=b,t.parent.style.cursor=b,T.body.style.cursor=b,O.call(t),t.dragOffset=M(e)-t.end}}S===H?(u="width",t="clientX",s="left",o="right",r="clientWidth"):"vertical"===S&&(u="height",t="clientY",s="top",o="bottom",r="clientHeight"),m=C(m);var A=[];function j(e){var t=e.i===A.length,n=t?A[e.i-1]:A[e.i];O.call(n);var r=t?n.size-e.minSize-n._c:e.minSize+n._b;U.call(n,r)}function F(e){var s=C(e);s.forEach(function(e,t){if(0<t){var n=A[t-1],r=a[n.a],i=a[n.b];r.size=s[t-1],i.size=e,k(r.element,r.size,n._b,r.i),k(i.element,i.size,n._c,i.i)}})}function B(n,r){A.forEach(function(t){if(!0!==r?t.parent.removeChild(t.gutter):(t.gutter[R]("mousedown",t._a),t.gutter[R]("touchstart",t._a)),!0!==n){var e=E(u,t.a.size,t._b);Object.keys(e).forEach(function(e){a[t.a].element.style[e]="",a[t.b].element.style[e]=""})}})}return(a=l.map(function(e,t){var n,r,i,s={element:X(e),size:m[t],minSize:d[t],i:t};if(0<t&&((n={a:t-1,b:t,dragging:!1,direction:S,parent:c})._b=G(v,t-1==0,!1,p),n._c=G(v,!1,t===l.length-1,p),"row-reverse"===f||"column-reverse"===f)){var o=n.a;n.a=n.b,n.b=o}if(!W&&0<t){var a=_(t,S,s.element);r=a,i=w(u,v,t),Object.keys(i).forEach(function(e){r.style[e]=i[e]}),n._a=D.bind(n),a[N]("mousedown",n._a),a[N]("touchstart",n._a),c.insertBefore(a,s.element),n.gutter=a}return k(s.element,s.size,G(v,0===t,t===l.length-1,p),t),0<t&&A.push(n),s})).forEach(function(e){var t=e.element[q]()[u];t<e.minSize&&(g?j(e):e.minSize=t)}),W?{setSizes:F,destroy:B}:{setSizes:F,getSizes:x,collapse:function(e){j(a[e])},destroy:B,parent:c,pairs:A}}}); +//# sourceMappingURL=split.min.js.map 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); } } |
