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 /src | |
| parent | c677e281d3d74d5925e19eb0479c27f46a3c7857 (diff) | |
Hints, Open in App button, ui animations
Diffstat (limited to 'src')
| -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 |
7 files changed, 214 insertions, 58 deletions
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 { |
