diff options
| author | RblSb <msrblsb@gmail.com> | 2023-01-07 08:20:41 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2023-01-07 08:20:41 +0300 |
| commit | b3d825acb9d12208cf44f2d4b385163a86c38695 (patch) | |
| tree | 09fdb4c54753c30f46f0c2824b550bbfb1217572 | |
| parent | c677e281d3d74d5925e19eb0479c27f46a3c7857 (diff) | |
Hints, Open in App button, ui animations
| -rw-r--r-- | res/client.js | 262 | ||||
| -rw-r--r-- | res/css/des.css | 99 | ||||
| -rw-r--r-- | res/index.html | 114 | ||||
| -rw-r--r-- | res/langs/en.json | 11 | ||||
| -rw-r--r-- | res/langs/ru.json | 11 | ||||
| -rw-r--r-- | src/client/Buttons.hx | 85 | ||||
| -rw-r--r-- | src/client/ClientSettings.hx | 3 | ||||
| -rw-r--r-- | src/client/Main.hx | 172 | ||||
| -rw-r--r-- | src/client/Player.hx | 3 | ||||
| -rw-r--r-- | src/client/Utils.hx | 5 | ||||
| -rw-r--r-- | src/client/players/RawSubs.hx | 2 | ||||
| -rw-r--r-- | src/client/players/Youtube.hx | 2 |
12 files changed, 562 insertions, 207 deletions
diff --git a/res/client.js b/res/client.js index b5be1ad..92ad1c7 100644 --- a/res/client.js +++ b/res/client.js @@ -547,20 +547,26 @@ client_Buttons.init = function(main) { }; var smilesBtn = window.document.querySelector("#smilesbtn"); smilesBtn.onclick = function(e) { - var smilesWrap = window.document.querySelector("#smileswrap"); - if(smilesWrap.children.length == 0) { + var wrap = window.document.querySelector("#smiles-wrap"); + var list = window.document.querySelector("#smiles-list"); + if(list.children.length == 0) { return; } if(smilesBtn.classList.toggle("active")) { - smilesWrap.style.display = "grid"; + wrap.style.display = "block"; + var tmp = client_Buttons.outerHeight(list); + wrap.style.height = tmp + "px"; } else { - smilesWrap.style.display = "none"; + wrap.style.height = "0"; + wrap.addEventListener("transitionend",function(e) { + return wrap.style.display = "none"; + },{ once : true}); } - if(smilesWrap.firstElementChild.dataset.src == null) { + if(list.firstElementChild.dataset.src == null) { return; } var _g = 0; - var _g1 = smilesWrap.children; + var _g1 = list.children; while(_g < _g1.length) { var child = _g1[_g]; ++_g; @@ -595,13 +601,19 @@ client_Buttons.init = function(main) { userlistToggle.onclick = function(e) { var icon = userlistToggle.firstElementChild; var isHidden = icon.getAttribute("name") == "chevron-forward"; + var wrap = window.document.querySelector("#userlist-wrap"); var style = window.document.querySelector("#userlist").style; if(isHidden) { - style.display = "block"; icon.setAttribute("name","chevron-down"); + style.display = "block"; + var tmp = client_Buttons.outerHeight(wrap.firstElementChild); + wrap.style.height = tmp + "px"; + wrap.style.marginBottom = "1rem"; } else { - style.display = "none"; icon.setAttribute("name","chevron-forward"); + style.display = "none"; + wrap.style.height = "0"; + wrap.style.marginBottom = "0rem"; } client_Buttons.settings.isUserListHidden = !isHidden; client_Settings.write(client_Buttons.settings); @@ -609,7 +621,14 @@ client_Buttons.init = function(main) { window.document.querySelector("#usercount").onclick = userlistToggle.onclick; if(client_Buttons.settings.isUserListHidden) { userlistToggle.onclick(); + } else { + var wrap = window.document.querySelector("#userlist-wrap"); + var tmp = client_Buttons.outerHeight(wrap.firstElementChild); + wrap.style.height = tmp + "px"; } + haxe_Timer.delay(function() { + window.document.querySelector("#userlist-wrap").style.transition = "200ms"; + },0); var toggleSynch = window.document.querySelector("#togglesynch"); toggleSynch.onclick = function(e) { var icon = toggleSynch.firstElementChild; @@ -672,11 +691,21 @@ client_Buttons.init = function(main) { }; var showMediaUrl = window.document.querySelector("#showmediaurl"); showMediaUrl.onclick = function(e) { - client_Buttons.showPlayerGroup(showMediaUrl); + if(client_Buttons.showPlayerGroup(showMediaUrl)) { + haxe_Timer.delay(function() { + window.document.querySelector("#addfromurl").scrollIntoView(); + window.document.querySelector("#mediaurl").focus(); + },100); + } }; var showCustomEmbed = window.document.querySelector("#showcustomembed"); showCustomEmbed.onclick = function(e) { - client_Buttons.showPlayerGroup(showCustomEmbed); + if(client_Buttons.showPlayerGroup(showCustomEmbed)) { + haxe_Timer.delay(function() { + window.document.querySelector("#customembed").scrollIntoView(); + window.document.querySelector("#customembed-title").focus(); + },100); + } }; var mediaUrl = window.document.querySelector("#mediaurl"); mediaUrl.oninput = function() { @@ -692,7 +721,15 @@ client_Buttons.init = function(main) { }; var showOptions = window.document.querySelector("#showoptions"); showOptions.onclick = function(e) { - return client_Buttons.toggleGroup(showOptions); + var isActive = client_Buttons.toggleGroup(showOptions); + var tmp = isActive ? "1" : "0"; + window.document.querySelector("#optionsPanel").style.opacity = tmp; + return haxe_Timer.delay(function() { + if(showOptions.classList.contains("active") != isActive) { + return; + } + window.document.querySelector("#optionsPanel").classList.toggle("collapse",!isActive); + },isActive ? 0 : 200); }; window.document.querySelector("#exitBtn").onclick = function(e) { if((main.personal.group & 2) != 0) { @@ -721,14 +758,27 @@ client_Buttons.showPlayerGroup = function(el) { } client_Buttons.toggleGroup(group); } - client_Buttons.toggleGroup(el); + return client_Buttons.toggleGroup(el); }; client_Buttons.toggleGroup = function(el) { el.classList.toggle("collapsed"); var id = el.dataset.target; - window.document.querySelector(id).classList.toggle("collapse"); + var target = window.document.querySelector(id); + if(target.classList.toggle("collapse")) { + target.style.height = "0"; + } else { + if(target.style.height == "") { + target.style.height = "0"; + } + var tmp = client_Buttons.outerHeight(target.firstElementChild); + target.style.height = tmp + "px"; + } return el.classList.toggle("active"); }; +client_Buttons.outerHeight = function(el) { + var style = window.getComputedStyle(el); + return el.getBoundingClientRect().height + parseFloat(style.marginTop) + parseFloat(style.marginBottom); +}; client_Buttons.swapPlayerAndChat = function() { client_Buttons.settings.isSwapped = window.document.querySelector("body").classList.toggle("swap"); var sizes = window.document.body.style.gridTemplateColumns.split(" "); @@ -1157,7 +1207,7 @@ var client_Main = function() { if(this.host == "") { this.host = "localhost"; } - client_Settings.init({ version : 3, name : "", hash : "", isExtendedPlayer : false, playerSize : 1, chatSize : 300, synchThreshold : 2, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true},$bind(this,this.settingsPatcher)); + client_Settings.init({ version : 4, name : "", hash : "", isExtendedPlayer : false, playerSize : 1, chatSize : 300, synchThreshold : 2, isSwapped : false, isUserListHidden : true, latestLinks : [], latestSubs : [], hotkeysEnabled : true, showHintList : true},$bind(this,this.settingsPatcher)); this.settings = client_Settings.read(); this.initListeners(); this.onTimeGet = new haxe_Timer(this.settings.synchThreshold * 1000); @@ -1180,38 +1230,40 @@ client_Main.__name__ = true; client_Main.main = function() { new client_Main(); }; -client_Main.serverMessage = function(type,text,isText) { +client_Main.chatMessageConnected = function() { + var div = window.document.createElement("div"); + div.className = "server-msg-reconnect"; + div.textContent = Lang.get("msgConnected"); + var msgBuf = window.document.querySelector("#messagebuffer"); + msgBuf.appendChild(div); + msgBuf.scrollTop = msgBuf.scrollHeight; +}; +client_Main.chatMessageDisconnected = function() { + var div = window.document.createElement("div"); + div.className = "server-msg-disconnect"; + div.textContent = Lang.get("msgDisconnected"); + var msgBuf = window.document.querySelector("#messagebuffer"); + msgBuf.appendChild(div); + msgBuf.scrollTop = msgBuf.scrollHeight; +}; +client_Main.serverMessage = function(text,isText,withTimestamp) { + if(withTimestamp == null) { + withTimestamp = true; + } if(isText == null) { isText = true; } - var msgBuf = window.document.querySelector("#messagebuffer"); var div = window.document.createElement("div"); var time = HxOverrides.dateStr(new Date()).split(" ")[1]; - switch(type) { - case 1: - div.className = "server-msg-reconnect"; - div.textContent = Lang.get("msgConnected"); - break; - case 2: - div.className = "server-msg-disconnect"; - div.textContent = Lang.get("msgDisconnected"); - break; - case 3: - div.className = "server-whisper"; - div.textContent = time + text + " " + Lang.get("entered"); - break; - case 4: - div.className = "server-whisper"; - div.innerHTML = "<div class=\"head\">\n\t\t\t\t\t<div class=\"server-whisper\"></div>\n\t\t\t\t\t<span class=\"timestamp\">" + time + "</span>\n\t\t\t\t</div>"; - var textDiv = div.querySelector(".server-whisper"); - if(isText) { - textDiv.textContent = text; - } else { - textDiv.innerHTML = text; - } - break; - default: + div.className = "server-whisper"; + div.innerHTML = "<div class=\"head\">\n\t\t\t<div class=\"server-whisper\"></div>\n\t\t\t<span class=\"timestamp\">" + (withTimestamp ? time : "") + "</span>\n\t\t</div>"; + var textDiv = div.querySelector(".server-whisper"); + if(isText) { + textDiv.textContent = text; + } else { + textDiv.innerHTML = text; } + var msgBuf = window.document.querySelector("#messagebuffer"); msgBuf.appendChild(div); msgBuf.scrollTop = msgBuf.scrollHeight; }; @@ -1225,6 +1277,9 @@ client_Main.prototype = { data.latestSubs = []; break; case 3: + data.showHintList = true; + break; + case 4: throw haxe_Exception.thrown("skipped version " + version); default: throw haxe_Exception.thrown("skipped version " + version); @@ -1252,12 +1307,12 @@ client_Main.prototype = { this.ws = new WebSocket("" + protocol + "//" + this.host + colonPort + path); this.ws.onmessage = $bind(this,this.onMessage); this.ws.onopen = function() { - client_Main.serverMessage(1); + client_Main.chatMessageConnected(); return _gthis.isConnected = true; }; this.ws.onclose = function() { if(_gthis.isConnected) { - client_Main.serverMessage(2); + client_Main.chatMessageDisconnected(); } _gthis.isConnected = false; _gthis.player.pause(); @@ -1414,7 +1469,7 @@ client_Main.prototype = { } this.player.getVideoData({ url : url, atEnd : atEnd},function(data) { if(data.duration == 0) { - client_Main.serverMessage(4,Lang.get("addVideoError")); + client_Main.serverMessage(Lang.get("addVideoError")); return; } if(data.title == null) { @@ -1443,7 +1498,7 @@ client_Main.prototype = { var isTemp = window.document.querySelector("#customembed").querySelector(".add-temp").checked; this.player.getIframeData({ url : iframe, atEnd : atEnd},function(data) { if(data.duration == 0) { - client_Main.serverMessage(4,Lang.get("addVideoError")); + client_Main.serverMessage(Lang.get("addVideoError")); return; } if(title.length > 0) { @@ -1492,7 +1547,7 @@ client_Main.prototype = { var data = JSON.parse(e.data); if(this.config != null && this.config.isVerbose) { var t = data.type; - haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 390, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 394, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]}); } client_JsApi.fireOnceEvent(data); switch(data.type) { @@ -1627,7 +1682,7 @@ client_Main.prototype = { break; case "ServerMessage": var id = data.serverMessage.textId; - client_Main.serverMessage(4,id == "usernameError" ? StringTools.replace(Lang.get(id),"$MAX","" + this.config.maxLoginLength) : Lang.get(id)); + client_Main.serverMessage(id == "usernameError" ? StringTools.replace(Lang.get(id),"$MAX","" + this.config.maxLoginLength) : Lang.get(id)); break; case "SetLeader": ClientTools.setLeader(this.clients,data.setLeader.clientName); @@ -1713,7 +1768,7 @@ client_Main.prototype = { this.setLeaderButton((this.personal.group & 4) != 0); this.setPlaylistLock(connected.isPlaylistOpen); this.clearChat(); - client_Main.serverMessage(1); + client_Main.chatMessageConnected(); var _g = 0; var _g1 = connected.history; while(_g < _g1.length) { @@ -1723,6 +1778,80 @@ client_Main.prototype = { } this.player.setItems(connected.videoList,connected.itemPos); this.onUserGroupChanged(); + if(this.settings.showHintList) { + this.showChatHintList(); + } + } + ,showChatHintList: function() { + var _gthis = this; + var text = Lang.get("hintListStart"); + var addVideos = "<button id=\"addVideosHintButton\">" + Lang.get("addVideos") + "</button>"; + text += "</br>" + StringTools.replace(Lang.get("hintListAddVideo"),"$addVideos",addVideos); + var requestLeader = "<button id=\"requestLeaderHintButton\">" + Lang.get("requestLeader") + "</button>"; + text += "</br>" + StringTools.replace(Lang.get("hintListRequestLeader"),"$requestLeader",requestLeader); + if(client_Utils.isTouch()) { + text += " " + Lang.get("hintListRequestLeaderTouch"); + } else { + text += " " + Lang.get("hintListRequestLeaderMouse"); + } + if(client_Utils.isAndroid()) { + var openInAppLink = "<button id=\"openInApp\">" + Lang.get("openInApp") + "</button>"; + text += "</br>" + StringTools.replace(Lang.get("hintListOpenInApp"),"$openInApp",openInAppLink); + } + var hideThisMessage = "<button id=\"hideHintList\">" + Lang.get("hideThisMessage") + "</button>"; + text += "</br>" + StringTools.replace(Lang.get("hintListHide"),"$hideThisMessage",hideThisMessage); + client_Main.serverMessage(text,false,false); + window.document.querySelector("#addVideosHintButton").onclick = function(e) { + var addBtn = window.document.querySelector("#showmediaurl"); + addBtn.scrollIntoView(); + return haxe_Timer.delay(function() { + if(!window.document.querySelector("#addfromurl").classList.contains("collapse")) { + window.document.querySelector("#mediaurl").focus(); + return; + } + addBtn.onclick(); + },300); + }; + window.document.querySelector("#requestLeaderHintButton").onclick = function(e) { + window.scrollTo(0,0); + if(client_Utils.isTouch()) { + window.document.querySelector("#leader_btn").classList.add("hint"); + haxe_Timer.delay(function() { + window.document.querySelector("#leader_btn").classList.remove("hint"); + },1000); + } + }; + window.document.querySelector("#requestLeaderHintButton").onpointerenter = function(e) { + if(client_Utils.isTouch()) { + return; + } + window.document.querySelector("#leader_btn").classList.add("hint"); + }; + window.document.querySelector("#requestLeaderHintButton").onpointerleave = function(e) { + window.document.querySelector("#leader_btn").classList.remove("hint"); + }; + if(client_Utils.isAndroid()) { + window.document.querySelector("#openInApp").onclick = function(e) { + var isRedirected = false; + window.addEventListener("blur",function(e) { + isRedirected = true; + return isRedirected; + },{ once : true}); + window.setTimeout(function() { + if(isRedirected || window.document.hidden) { + return; + } + window.location.href = "https://github.com/RblSb/SyncTubeApp#readme"; + },500); + window.location.href = "synctube://" + $global.location.href; + return false; + }; + } + window.document.querySelector("#hideHintList").onclick = function(e) { + window.document.querySelector("#hideHintList").parentElement.remove(); + _gthis.settings.showHintList = false; + client_Settings.write(_gthis.settings); + }; } ,onUserGroupChanged: function() { var button = window.document.querySelector("#queue_next"); @@ -1789,18 +1918,18 @@ client_Main.prototype = { this.filters.push({ regex : new EReg("(^| )" + this.escapeRegExp(emote.name) + "(?!\\S)","g"), replace : "$1<" + tag + " class=\"channel-emote\" src=\"" + emote.image + "\" title=\"" + emote.name + "\"/>"}); } window.document.querySelector("#smilesbtn").classList.remove("active"); - var smilesWrap = window.document.querySelector("#smileswrap"); - smilesWrap.style.display = "none"; - smilesWrap.onclick = function(e) { + window.document.querySelector("#smiles-wrap").style.display = "none"; + var smilesList = window.document.querySelector("#smiles-list"); + smilesList.onclick = function(e) { var el = e.target; - if(el == smilesWrap) { + if(el == smilesList) { return; } var form = window.document.querySelector("#chatline"); form.value += " " + el.title; form.focus(); }; - smilesWrap.textContent = ""; + smilesList.textContent = ""; var _g = 0; var _g1 = config.emotes; while(_g < _g1.length) { @@ -1811,7 +1940,7 @@ client_Main.prototype = { el.className = "smile-preview"; el.dataset.src = emote.image; el.title = emote.name; - smilesWrap.appendChild(el); + smilesList.appendChild(el); } } ,onLogin: function(data,clientName) { @@ -1992,6 +2121,9 @@ client_Main.prototype = { case "fb":case "flashback": this.send({ type : "Flashback"}); return false; + case "help": + this.showChatHintList(); + return true; case "kick": this.mergeRedundantArgs(args,0,1); this.send({ type : "KickClient", kickClient : { name : args[0]}}); @@ -2071,12 +2203,7 @@ client_Main.prototype = { this.onBlinkTab.run(); } ,setLeaderButton: function(flag) { - var leaderBtn = window.document.querySelector("#leader_btn"); - if(flag) { - leaderBtn.classList.add("success-bg"); - } else { - leaderBtn.classList.remove("success-bg"); - } + window.document.querySelector("#leader_btn").classList.toggle("success-bg",flag); } ,setPlaylistLock: function(isOpen) { this.isPlaylistOpen = isOpen; @@ -2084,13 +2211,11 @@ client_Main.prototype = { var icon = lockPlaylist.firstElementChild; if(isOpen) { lockPlaylist.title = Lang.get("playlistOpen"); - lockPlaylist.classList.add("btn-success"); lockPlaylist.classList.add("success"); lockPlaylist.classList.remove("danger"); icon.setAttribute("name","lock-open"); } else { lockPlaylist.title = Lang.get("playlistLocked"); - lockPlaylist.classList.add("btn-danger"); lockPlaylist.classList.add("danger"); lockPlaylist.classList.remove("success"); icon.setAttribute("name","lock-closed"); @@ -2339,11 +2464,7 @@ client_Player.prototype = { var btn = item.querySelector(".qbtn-tmp"); btn.title = isTemp ? Lang.get("makePermanent") : Lang.get("makeTemporary"); btn.firstElementChild.setAttribute("name",isTemp ? "lock-open" : "lock-closed"); - if(isTemp) { - item.classList.add("queue_temp"); - } else { - item.classList.remove("queue_temp"); - } + item.classList.toggle("queue_temp",isTemp); } ,removeItem: function(url) { this.removeElementItem(url); @@ -2615,7 +2736,7 @@ client_Player.prototype = { } }; http.onError = function(msg) { - haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 477, className : "client.Player", methodName : "skipAd"}); + haxe_Log.trace(msg,{ fileName : "src/client/Player.hx", lineNumber : 476, className : "client.Player", methodName : "skipAd"}); }; http.request(); } @@ -2679,6 +2800,9 @@ client_Utils.isIOS = function() { return true; } }; +client_Utils.isAndroid = function() { + return $global.navigator.userAgent.toLowerCase().indexOf("android") > -1; +}; client_Utils.nodeFromString = function(div) { var wrapper = window.document.createElement("div"); wrapper.innerHTML = div; @@ -3208,7 +3332,7 @@ client_players_RawSubs.convertAssTime = function(time) { }; client_players_RawSubs.isProxyError = function(text) { if(StringTools.startsWith(text,"Proxy error:")) { - client_Main.serverMessage(4,"Failed to add subs: proxy error"); + client_Main.serverMessage("Failed to add subs: proxy error"); haxe_Log.trace("Failed to add subs: " + text,{ fileName : "src/client/players/RawSubs.hx", lineNumber : 191, className : "client.players.RawSubs", methodName : "isProxyError"}); return true; } @@ -3378,7 +3502,7 @@ client_players_Youtube.prototype = { loadJson(dataUrl); } ,youtubeApiError: function(error) { - client_Main.serverMessage(4,"Error " + error.code + ": " + error.message,false); + client_Main.serverMessage("Error " + error.code + ": " + error.message,false); } ,getRemoteDataFallback: function(url,callback) { var _gthis = this; diff --git a/res/css/des.css b/res/css/des.css index aa02177..968ad39 100644 --- a/res/css/des.css +++ b/res/css/des.css @@ -11,6 +11,7 @@ --foreground: #bbb; --accent: #0055ff; --success: #009632; + --leader-hint: #00963288; --warning: #ffb800; --error: #ff0800; --border: #333; @@ -19,6 +20,8 @@ html { box-sizing: border-box; + scroll-behavior: smooth; + -webkit-tap-highlight-color: transparent; } *, @@ -122,6 +125,10 @@ button { button:not(:first-child) { margin-left: .5rem; } +.server-whisper button { + margin-left: 0; + font-style: italic; +} button:hover, button.active { @@ -129,6 +136,16 @@ button.active { color: var(--foreground); } +/* Disable hover on touch devices */ +@media (hover: none) { + button:hover:not(.active) { + background-color: var(--background-chat); + } + .info header button:hover:not(.active) { + background-color: transparent; + } +} + button:hover ion-icon, button.active ion-icon, button:focus, @@ -159,6 +176,18 @@ button span { pointer-events: none; } +#leader_btn { + border: .125rem solid; + border-color: transparent; + transition: border-color ease-in-out 800ms; +} +#leader_btn.hint { + border-radius: .5rem; + border: .125rem solid; + color: var(--foreground); + border-color: var(--leader-hint); +} + /* Input */ label { @@ -227,14 +256,19 @@ button.danger-bg:focus { text-align: center; } -.collapse { - display: none; - visibility: hidden; +.collapsible { + overflow: hidden; + transition: height 200ms; } -.collapse.in { - display: block; - visibility: visible; +.collapse-list { + padding: 1rem; + margin: 0 auto; + max-width: 32rem; +} + +.collapse { + height: 0; } .display-flex { @@ -374,15 +408,8 @@ header h4 { flex-grow: 1; } -#addfromurl, -#customembed { - padding: 1rem; - margin: 0 auto; - max-width: 32rem; -} - -#addfromurl>*, -#customembed>* { +#addfromurl>*>*, +#customembed>*>* { margin-bottom: 1rem; } @@ -396,8 +423,8 @@ header h4 { flex-grow: 2; } -#customembed>input, -#customembed>textarea { +#customembed>*>input, +#customembed>*>textarea { display: block; width: 100%; } @@ -481,16 +508,23 @@ footer#footer { /* Users online */ -#userlist { +#userlist-wrap { overflow-y: auto; background-color: var(--background-video); border-right: 0; - padding: 1rem; border-radius: 1rem; - height: 12rem; + flex-shrink: 0; + + transition: 0ms; + height: 0; margin-bottom: 1rem; } +#userlist { + padding: 1rem; + height: 11rem; +} + .userlist_item { display: flex; align-items: center; @@ -544,19 +578,21 @@ footer#footer { left: 1rem; bottom: 1rem; right: 1rem; + transition: opacity 200ms; + opacity: 0; } -#optionsPanel div { +#optionsList div { margin-bottom: .5rem; } -#optionsPanel div:not(:first-child) { +#optionsList div:not(:first-child) { border-top: .063rem solid; border-color: var(--border); padding-top: 1rem; } -#optionsPanel li button { +#optionsList li button { padding: 1rem 0; display: flex; align-items: stretch; @@ -565,7 +601,7 @@ footer#footer { text-align: left; } -#optionsPanel li button:hover { +#optionsList li button:hover { background-color: var(--background-chat); } @@ -631,15 +667,20 @@ footer#footer { /* Emotes */ -#smileswrap { +#smiles-wrap { display: none; - background: rgba(0, 0, 0, 0.7); + height: 0; width: 100%; - height: 12rem; - padding: 1rem; + background: rgba(0, 0, 0, 0.7); border-radius: 1rem; overflow-y: scroll; text-align: center; +} + +#smiles-list { + display: grid; + height: 12rem; + padding: 1rem; grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr)); grid-gap: .5rem; gap: .5rem; @@ -807,6 +848,6 @@ html { } .mobile-view #optionsPanel { - top: 2rem; + top: 2.2rem; bottom: 0; } diff --git a/res/index.html b/res/index.html index 3892a41..20df11c 100644 --- a/res/index.html +++ b/res/index.html @@ -82,37 +82,41 @@ </span> </div> <!-- Add video --> - <div class="collapse" id="addfromurl" aria-expanded="false"> - <div class="display-flex"> - <button id="insert_template" title="${addTemplateUrl}"> - <div>></div> - </button> - <input id="mediaurl" type="text" placeholder="${addVideoFromUrl}"> - </div> - <div id="mediatitleblock" class="display-flex" style="display: none;"> - <input id="mediatitle" type="text" placeholder="${optionalTitle}"> - </div> - <div id="subsurlblock" class="display-flex" style="display: none;"> - <input id="subsurl" type="text" placeholder="${subtitlesUrlOptional}"> - </div> - <div> - <label> - <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} - </label> - <button id="queue_next">${queueNext}</button> - <button id="queue_end">${queueLast}</button> + <div id="addfromurl" class="collapsible collapse" aria-expanded="false"> + <div class="collapse-list"> + <div class="display-flex"> + <button id="insert_template" title="${addTemplateUrl}"> + <div>></div> + </button> + <input id="mediaurl" type="text" placeholder="${addVideoFromUrl}"> + </div> + <div id="mediatitleblock" class="display-flex" style="display: none;"> + <input id="mediatitle" type="text" placeholder="${optionalTitle}"> + </div> + <div id="subsurlblock" class="display-flex" style="display: none;"> + <input id="subsurl" type="text" placeholder="${subtitlesUrlOptional}"> + </div> + <div> + <label> + <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} + </label> + <button id="queue_next">${queueNext}</button> + <button id="queue_end">${queueLast}</button> + </div> </div> </div> - <div class="collapse" id="customembed" aria-expanded="false"> - <input id="customembed-title" type="text" placeholder="${optionalTitle}"> - <textarea id="customembed-content" rows="5" - placeholder="${pasteEmbedCodeAndClick} '${queueNext}' ${or} '${queueLast}'. ${acceptableEmbedCodesAre} <iframe> ${or} <object>. ${customEmbedsCannotBeSynchronized}."></textarea> - <div> - <label> - <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} - </label> - <button id="ce_queue_next">${queueNext}</button> - <button id="ce_queue_end">${queueLast}</button> + <div class="collapsible collapse" id="customembed" aria-expanded="false"> + <div class="collapse-list"> + <input id="customembed-title" type="text" placeholder="${optionalTitle}"> + <textarea id="customembed-content" rows="5" + placeholder="${pasteEmbedCodeAndClick} '${queueNext}' ${or} '${queueLast}'. ${acceptableEmbedCodesAre} <iframe> ${or} <object>. ${customEmbedsCannotBeSynchronized}."></textarea> + <div> + <label> + <input class="add-temp" type="checkbox" checked="checked">${addAsTemporary} + </label> + <button id="ce_queue_next">${queueNext}</button> + <button id="ce_queue_end">${queueLast}</button> + </div> </div> </div> <!-- Queue --> @@ -142,35 +146,41 @@ <span> <button id="leader_btn" class="unselectable" title="${leaderDesc}">${leader}</button> <!-- Settings button --> - <button id="showoptions" class="collapsed" data-toggle="collapse" data-target="#optionsPanel" + <button id="showoptions" class="collapsed" data-toggle="collapse" data-target="#optionsList" aria-expanded="false"> <ion-icon name="settings-sharp"></ion-icon> </button> </span> </div> <!-- User list --> - <div id="userlist"></div> + <div id="userlist-wrap" class="collapsible"> + <div id="userlist"></div> + </div> <!-- Settings list --> <ul id="optionsPanel" class="collapse" aria-expanded="false"> - <div> - <h4>${account}</h4> - <li><button id="exitBtn">${login}</button></li> - </div> - <div> - <h4>${general}</h4> - <li><button id="hotkeysBtn"><span>${hotkeys}</span></button></li> - <li><button id="swapLayoutBtn" title="${swapLayout}"><span>${swapLayout}</span></button></li> - </div> - <div> - <h4>${video}</h4> - <li><button id="synchThresholdBtn"><span>${synchThreshold}</span></button></li> - <li><button id="setVideoUrlBtn"><span>${setVideoUrl}</span></button></li> - <li><button id="selectLocalVideoBtn"><span>${selectLocalVideo}</span></button></li> - <li><button id="removeVideoBtn"><span>${removeVideo}</span></button></li> - </div> - <div id="adminMenu" style="display: none;"> - <h4>${chat}</h4> - <li><button id="clearchatbtn"><span>${clearChat}</span></button></li> + <div id="optionsList" class="collapse"> + <div> + <div> + <h4>${account}</h4> + <li><button id="exitBtn">${login}</button></li> + </div> + <div> + <h4>${general}</h4> + <li><button id="hotkeysBtn"><span>${hotkeys}</span></button></li> + <li><button id="swapLayoutBtn" title="${swapLayout}"><span>${swapLayout}</span></button></li> + </div> + <div> + <h4>${video}</h4> + <li><button id="synchThresholdBtn"><span>${synchThreshold}</span></button></li> + <li><button id="setVideoUrlBtn"><span>${setVideoUrl}</span></button></li> + <li><button id="selectLocalVideoBtn"><span>${selectLocalVideo}</span></button></li> + <li><button id="removeVideoBtn"><span>${removeVideo}</span></button></li> + </div> + <div id="adminMenu" style="display: none;"> + <h4>${chat}</h4> + <li><button id="clearchatbtn"><span>${clearChat}</span></button></li> + </div> + </div> </div> </ul> <!-- Messages --> @@ -182,7 +192,9 @@ <ion-icon name="happy"></ion-icon> </button> </div> - <div id="smileswrap"></div> + <div id="smiles-wrap" class="collapsible"> + <div id="smiles-list"></div> + </div> <!-- Guest login --> <div id="guestlogin" style="display: none;"> <label>${enterAsGuest}</label> diff --git a/res/langs/en.json b/res/langs/en.json index e7ea8f4..90a77aa 100644 --- a/res/langs/en.json +++ b/res/langs/en.json @@ -5,6 +5,17 @@ "joined": "joined", "online": "online", "nothingPlaying": "Nothing Playing", + "hintListStart": "Welcome to SyncTube! Here you can:", + "hintListAddVideo": "$addVideos to watch together", + "hintListRequestLeader": "$requestLeader to pause and rewind videos for everyone", + "hintListRequestLeaderMouse": "(also use right mouse button for quick pause)", + "hintListRequestLeaderTouch": "(also use long tap for quick pause)", + "hintListOpenInApp": "$openInApp this server for better Android experience", + "hintListHide": "$hideThisMessage and send <b>/help</b> in chat to see it again", + "addVideos": "Add Videos", + "requestLeader": "Request Leader", + "openInApp": "Open in App", + "hideThisMessage": "Hide this message", "usernameError": "Username length must be from 1 to $MAX characters and don't repeat another's. Characters &^<>'\" are not allowed.", "passwordMatchError": "Wrong password.", "accessError": "Access Error.", diff --git a/res/langs/ru.json b/res/langs/ru.json index 637e883..7615efd 100644 --- a/res/langs/ru.json +++ b/res/langs/ru.json @@ -5,6 +5,17 @@ "joined": "вошел", "online": "онлайн", "nothingPlaying": "Ничего не играет", + "hintListStart": "Добро пожаловать на SyncTube! Здесь вы можете:", + "hintListAddVideo": "$addVideos для совместного просмотра", + "hintListRequestLeader": "$requestLeader для всеобщей паузы и перемотки видео", + "hintListRequestLeaderMouse": "(кстати, правая кнопка мыши сразу сделает паузу)", + "hintListRequestLeaderTouch": "(кстати, удерживайте кнопку для быстрой паузы)", + "hintListOpenInApp": "$openInApp этот сервер для лучшего опыта на Android", + "hintListHide": "$hideThisMessage и отправлять <b>`/help`</b> в чат чтобы прочесть его снова", + "addVideos": "Добавлять видео", + "requestLeader": "Запрашивать лидера", + "openInApp": "Открыть в приложении", + "hideThisMessage": "Скрыть это сообщение", "usernameError": "Ник должен быть от 1 до $MAX символов и не повторять чужие. Символы &^<>'\" запрещены.", "passwordMatchError": "Неправильный пароль.", "accessError": "Ошибка доступа.", diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx index b023ab7..071db16 100644 --- a/src/client/Buttons.hx +++ b/src/client/Buttons.hx @@ -40,13 +40,21 @@ class Buttons { final smilesBtn = ge("#smilesbtn"); smilesBtn.onclick = e -> { - final smilesWrap = ge("#smileswrap"); - if (smilesWrap.children.length == 0) return; + final wrap = ge("#smiles-wrap"); + final list = ge("#smiles-list"); + if (list.children.length == 0) return; final isActive = smilesBtn.classList.toggle("active"); - if (isActive) smilesWrap.style.display = "grid"; - else smilesWrap.style.display = "none"; - if (smilesWrap.firstElementChild.dataset.src == null) return; - for (child in smilesWrap.children) { + if (isActive) { + wrap.style.display = "block"; + wrap.style.height = outerHeight(list) + "px"; + } else { + wrap.style.height = "0"; + wrap.addEventListener("transitionend", e -> { + wrap.style.display = "none"; + }, {once: true}); + } + if (list.firstElementChild.dataset.src == null) return; + for (child in list.children) { (cast child : ImageElement).src = child.dataset.src; child.removeAttribute("data-src"); } @@ -79,19 +87,34 @@ class Buttons { userlistToggle.onclick = e -> { final icon = userlistToggle.firstElementChild; final isHidden = icon.getAttribute("name") == "chevron-forward"; + final wrap = ge("#userlist-wrap"); final style = ge("#userlist").style; if (isHidden) { - style.display = "block"; icon.setAttribute("name", "chevron-down"); + style.display = "block"; + final list = wrap.firstElementChild; + wrap.style.height = outerHeight(list) + "px"; + wrap.style.marginBottom = "1rem"; } else { - style.display = "none"; icon.setAttribute("name", "chevron-forward"); + style.display = "none"; + wrap.style.height = "0"; + wrap.style.marginBottom = "0rem"; } settings.isUserListHidden = !isHidden; Settings.write(settings); } ge("#usercount").onclick = userlistToggle.onclick; if (settings.isUserListHidden) userlistToggle.onclick(); + else { + final wrap = ge("#userlist-wrap"); + final list = wrap.firstElementChild; + wrap.style.height = outerHeight(list) + "px"; + } + // enable animation after page loads + Timer.delay(() -> { + ge("#userlist-wrap").style.transition = "200ms"; + }, 0); final toggleSynch = ge("#togglesynch"); toggleSynch.onclick = e -> { @@ -154,10 +177,22 @@ class Buttons { } final showMediaUrl = ge("#showmediaurl"); - showMediaUrl.onclick = e -> showPlayerGroup(showMediaUrl); + showMediaUrl.onclick = e -> { + final isOpen = showPlayerGroup(showMediaUrl); + if (isOpen) Timer.delay(() -> { + ge("#addfromurl").scrollIntoView(); + ge("#mediaurl").focus(); + }, 100); + } final showCustomEmbed = ge("#showcustomembed"); - showCustomEmbed.onclick = e -> showPlayerGroup(showCustomEmbed); + showCustomEmbed.onclick = e -> { + final isOpen = showPlayerGroup(showCustomEmbed); + if (isOpen) Timer.delay(() -> { + ge("#customembed").scrollIntoView(); + ge("#customembed-title").focus(); + }, 100); + } final mediaUrl:InputElement = cast ge("#mediaurl"); mediaUrl.oninput = () -> { @@ -175,7 +210,14 @@ class Buttons { } final showOptions = ge("#showoptions"); - showOptions.onclick = e -> toggleGroup(showOptions); + showOptions.onclick = e -> { + final isActive = toggleGroup(showOptions); + ge("#optionsPanel").style.opacity = isActive ? "1" : "0"; + Timer.delay(() -> { + if (showOptions.classList.contains("active") != isActive) return; + ge("#optionsPanel").classList.toggle("collapse", !isActive); + }, isActive ? 0 : 200); + } final exitBtn = ge("#exitBtn"); exitBtn.onclick = e -> { @@ -191,22 +233,37 @@ class Buttons { } } - static function showPlayerGroup(el:Element):Void { + static function showPlayerGroup(el:Element):Bool { final groups:Array<Element> = cast document.querySelectorAll('[data-target]'); for (group in groups) { if (el == group) continue; if (group.classList.contains("collapsed")) continue; toggleGroup(group); } - toggleGroup(el); + return toggleGroup(el); } static function toggleGroup(el:Element):Bool { el.classList.toggle("collapsed"); - ge(el.dataset.target).classList.toggle("collapse"); + final target = ge(el.dataset.target); + final isClosed = target.classList.toggle("collapse"); + if (isClosed) { + target.style.height = "0"; + } else { + final list = target.firstElementChild; + if (target.style.height == "") target.style.height = "0"; + target.style.height = outerHeight(list) + "px"; + } return el.classList.toggle("active"); } + static function outerHeight(el:Element):Float { + final style = window.getComputedStyle(el); + return (el.getBoundingClientRect().height + + Std.parseFloat(style.marginTop) + + Std.parseFloat(style.marginBottom)); + } + static function swapPlayerAndChat():Void { settings.isSwapped = ge("body").classList.toggle("swap"); final sizes = document.body.style.gridTemplateColumns.split(" "); diff --git a/src/client/ClientSettings.hx b/src/client/ClientSettings.hx index c787824..8403ff3 100644 --- a/src/client/ClientSettings.hx +++ b/src/client/ClientSettings.hx @@ -12,5 +12,6 @@ typedef ClientSettings = { isUserListHidden:Bool, latestLinks:Array<String>, latestSubs:Array<String>, - hotkeysEnabled:Bool + hotkeysEnabled:Bool, + showHintList:Bool } diff --git a/src/client/Main.hx b/src/client/Main.hx index e1cb1ef..c8688b4 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -25,7 +25,7 @@ using ClientTools; using StringTools; class Main { - static inline var SETTINGS_VERSION = 3; + static inline var SETTINGS_VERSION = 4; public final settings:ClientSettings; public var isSyncActive = true; @@ -67,7 +67,8 @@ class Main { isUserListHidden: true, latestLinks: [], latestSubs: [], - hotkeysEnabled: true + hotkeysEnabled: true, + showHintList: true } Settings.init(defaults, settingsPatcher); settings = Settings.read(); @@ -98,6 +99,9 @@ class Main { case 2: final data:ClientSettings = data; data.latestSubs = []; + case 3: + final data:ClientSettings = data; + data.showHintList = true; case SETTINGS_VERSION, _: throw 'skipped version $version'; } @@ -119,13 +123,13 @@ class Main { ws = new WebSocket('$protocol//$host$colonPort$path'); ws.onmessage = onMessage; ws.onopen = () -> { - serverMessage(1); + chatMessageConnected(); isConnected = true; } ws.onclose = () -> { // if initial connection refused // or server/client offline - if (isConnected) serverMessage(2); + if (isConnected) chatMessageDisconnected(); isConnected = false; player.pause(); if (disabledReconnection) return; @@ -282,7 +286,7 @@ class Main { }; player.getVideoData(obj, (data:VideoData) -> { if (data.duration == 0) { - serverMessage(4, Lang.get("addVideoError")); + serverMessage(Lang.get("addVideoError")); return; } if (data.title == null) data.title = Lang.get("rawVideo"); @@ -322,7 +326,7 @@ class Main { }; player.getIframeData(obj, (data:VideoData) -> { if (data.duration == 0) { - serverMessage(4, Lang.get("addVideoError")); + serverMessage(Lang.get("addVideoError")); return; } if (title.length > 0) data.title = title; @@ -438,7 +442,7 @@ class Main { default: Lang.get(id); } - serverMessage(4, text); + serverMessage(text); case AddVideo: player.addVideoItem(data.addVideo.item, data.addVideo.atEnd); @@ -573,12 +577,82 @@ class Main { setLeaderButton(isLeader()); setPlaylistLock(connected.isPlaylistOpen); clearChat(); - serverMessage(1); + chatMessageConnected(); for (message in connected.history) { addMessage(message.name, message.text, message.time); } player.setItems(connected.videoList, connected.itemPos); onUserGroupChanged(); + if (settings.showHintList) showChatHintList(); + } + + function showChatHintList():Void { + var text = Lang.get("hintListStart"); + + final addVideos = '<button id="addVideosHintButton">${Lang.get("addVideos")}</button>'; + text += "</br>" + Lang.get("hintListAddVideo").replace("$addVideos", addVideos); + + final requestLeader = '<button id="requestLeaderHintButton">${Lang.get("requestLeader")}</button>'; + text += "</br>" + + Lang.get("hintListRequestLeader").replace("$requestLeader", requestLeader); + + if (Utils.isTouch()) text += " " + Lang.get("hintListRequestLeaderTouch"); + else text += " " + Lang.get("hintListRequestLeaderMouse"); + + if (Utils.isAndroid()) { + final openInAppLink = '<button id="openInApp">${Lang.get("openInApp")}</button>'; + text += "</br>" + + Lang.get("hintListOpenInApp").replace("$openInApp", openInAppLink); + } + + final hideThisMessage = '<button id="hideHintList">${Lang.get("hideThisMessage")}</button>'; + text += "</br>" + + Lang.get("hintListHide").replace("$hideThisMessage", hideThisMessage); + + serverMessage(text, false, false); + + ge("#addVideosHintButton").onclick = e -> { + final addBtn = ge("#showmediaurl"); + addBtn.scrollIntoView(); + Timer.delay(() -> { + if (!ge("#addfromurl").classList.contains("collapse")) { + ge("#mediaurl").focus(); + return; + } + addBtn.onclick(); + }, 300); + } + ge("#requestLeaderHintButton").onclick = (e:MouseEvent) -> { + window.scrollTo(0, 0); + if (Utils.isTouch()) { + ge("#leader_btn").classList.add("hint"); + Timer.delay(() -> ge("#leader_btn").classList.remove("hint"), 1000); + } + } + ge("#requestLeaderHintButton").onpointerenter = e -> { + if (Utils.isTouch()) return; + ge("#leader_btn").classList.add("hint"); + } + ge("#requestLeaderHintButton").onpointerleave = e -> { + ge("#leader_btn").classList.remove("hint"); + } + if (Utils.isAndroid()) { + ge("#openInApp").onclick = e -> { + var isRedirected = false; + window.addEventListener("blur", e -> isRedirected = true, {once: true}); + window.setTimeout(function() { + if (isRedirected || document.hidden) return; + window.location.href = "https://github.com/RblSb/SyncTubeApp#readme"; + }, 500); + window.location.href = 'synctube://${Browser.location.href}'; + return false; + } + } + ge("#hideHintList").onclick = e -> { + ge("#hideHintList").parentElement.remove(); + settings.showHintList = false; + Settings.write(settings); + } } function onUserGroupChanged():Void { @@ -649,23 +723,24 @@ class Main { }); } ge("#smilesbtn").classList.remove("active"); - final smilesWrap = ge("#smileswrap"); + final smilesWrap = ge("#smiles-wrap"); smilesWrap.style.display = "none"; - smilesWrap.onclick = (e:MouseEvent) -> { + final smilesList = ge("#smiles-list"); + smilesList.onclick = (e:MouseEvent) -> { final el:Element = cast e.target; - if (el == smilesWrap) return; + if (el == smilesList) return; final form:InputElement = cast ge("#chatline"); form.value += ' ${el.title}'; form.focus(); } - smilesWrap.textContent = ""; + smilesList.textContent = ""; for (emote in config.emotes) { final tag = emote.image.endsWith("mp4") ? "video" : "img"; final el = document.createElement(tag); el.className = "smile-preview"; el.dataset.src = emote.image; el.title = emote.name; - smilesWrap.appendChild(el); + smilesList.appendChild(el); } } @@ -713,31 +788,50 @@ class Main { ws.send(Json.stringify(data)); } - public static function serverMessage(type:Int, ?text:String, isText = true):Void { + static function chatMessageConnected():Void { + final div = document.createDivElement(); + div.className = "server-msg-reconnect"; + div.textContent = Lang.get("msgConnected"); final msgBuf = ge("#messagebuffer"); + msgBuf.appendChild(div); + msgBuf.scrollTop = msgBuf.scrollHeight; + } + + static function chatMessageDisconnected():Void { + final div = document.createDivElement(); + div.className = "server-msg-disconnect"; + div.textContent = Lang.get("msgDisconnected"); + final msgBuf = ge("#messagebuffer"); + msgBuf.appendChild(div); + msgBuf.scrollTop = msgBuf.scrollHeight; + } + + public static function serverMessage(text:String, isText = true, withTimestamp = true):Void { final div = document.createDivElement(); final time = Date.now().toString().split(" ")[1]; - switch (type) { - case 1: - div.className = "server-msg-reconnect"; - div.textContent = Lang.get("msgConnected"); - case 2: - div.className = "server-msg-disconnect"; - div.textContent = Lang.get("msgDisconnected"); - case 3: - div.className = "server-whisper"; - div.textContent = time + text + " " + Lang.get("entered"); - case 4: - div.className = "server-whisper"; - div.innerHTML = '<div class="head"> - <div class="server-whisper"></div> - <span class="timestamp">$time</span> - </div>'; - final textDiv = div.querySelector(".server-whisper"); - if (isText) textDiv.textContent = text; - else textDiv.innerHTML = text; - default: - } + div.className = "server-whisper"; + div.innerHTML = '<div class="head"> + <div class="server-whisper"></div> + <span class="timestamp">${withTimestamp ? time : ""}</span> + </div>'; + final textDiv = div.querySelector(".server-whisper"); + if (isText) textDiv.textContent = text; + else textDiv.innerHTML = text; + final msgBuf = ge("#messagebuffer"); + msgBuf.appendChild(div); + msgBuf.scrollTop = msgBuf.scrollHeight; + } + + public static function serverHtmlMessage(el:Element):Void { + final div = document.createDivElement(); + final time = Date.now().toString().split(" ")[1]; + div.className = "server-whisper"; + div.innerHTML = '<div class="head"> + <div class="server-whisper"></div> + <span class="timestamp">$time</span> + </div>'; + div.querySelector(".server-whisper").appendChild(el); + final msgBuf = ge("#messagebuffer"); msgBuf.appendChild(div); msgBuf.scrollTop = msgBuf.scrollHeight; } @@ -865,6 +959,9 @@ class Main { if (command.length == 0) return false; switch (command) { + case "help": + showChatHintList(); + return true; case "ban": mergeRedundantArgs(args, 0, 2); final name = args[0]; @@ -977,8 +1074,7 @@ class Main { function setLeaderButton(flag:Bool):Void { final leaderBtn = ge("#leader_btn"); - if (flag) leaderBtn.classList.add("success-bg"); - else leaderBtn.classList.remove("success-bg"); + leaderBtn.classList.toggle("success-bg", flag); } function setPlaylistLock(isOpen:Bool):Void { @@ -987,13 +1083,11 @@ class Main { final icon = lockPlaylist.firstElementChild; if (isOpen) { lockPlaylist.title = Lang.get("playlistOpen"); - lockPlaylist.classList.add("btn-success"); lockPlaylist.classList.add("success"); lockPlaylist.classList.remove("danger"); icon.setAttribute("name", "lock-open"); } else { lockPlaylist.title = Lang.get("playlistLocked"); - lockPlaylist.classList.add("btn-danger"); lockPlaylist.classList.add("danger"); lockPlaylist.classList.remove("success"); icon.setAttribute("name", "lock-closed"); diff --git a/src/client/Player.hx b/src/client/Player.hx index 0227f05..207f93a 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -266,8 +266,7 @@ class Player { btn.title = isTemp ? Lang.get("makePermanent") : Lang.get("makeTemporary"); final iconType = isTemp ? "lock-open" : "lock-closed"; btn.firstElementChild.setAttribute("name", iconType); - if (isTemp) item.classList.add("queue_temp"); - else item.classList.remove("queue_temp"); + item.classList.toggle("queue_temp", isTemp); } public function removeItem(url:String):Void { diff --git a/src/client/Utils.hx b/src/client/Utils.hx index 2c887ba..cdf9f21 100644 --- a/src/client/Utils.hx +++ b/src/client/Utils.hx @@ -17,6 +17,11 @@ class Utils { || (~/^Mac/.match(navigator.platform) && navigator.maxTouchPoints > 4); } + public static function isAndroid():Bool { + final ua = navigator.userAgent.toLowerCase(); + return ua.indexOf("android") > -1; + } + public static function nodeFromString(div:String):Element { final wrapper = document.createDivElement(); wrapper.innerHTML = div; diff --git a/src/client/players/RawSubs.hx b/src/client/players/RawSubs.hx index eede788..09b21d1 100644 --- a/src/client/players/RawSubs.hx +++ b/src/client/players/RawSubs.hx @@ -187,7 +187,7 @@ class RawSubs { static function isProxyError(text:String):Bool { if (text.startsWith("Proxy error:")) { - Main.serverMessage(4, 'Failed to add subs: proxy error'); + Main.serverMessage("Failed to add subs: proxy error"); trace('Failed to add subs: $text'); return true; } diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index 9ed5630..b370f24 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -175,7 +175,7 @@ class Youtube implements IPlayer { function youtubeApiError(error:Dynamic):Void { final code:Int = error.code; final msg:String = error.message; - Main.serverMessage(4, 'Error $code: $msg', false); + Main.serverMessage('Error $code: $msg', false); } function getRemoteDataFallback(url:String, callback:(data:VideoData) -> Void):Void { |
