aboutsummaryrefslogtreecommitdiffstats
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/Buttons.hx85
-rw-r--r--src/client/ClientSettings.hx3
-rw-r--r--src/client/Main.hx172
-rw-r--r--src/client/Player.hx3
-rw-r--r--src/client/Utils.hx5
-rw-r--r--src/client/players/RawSubs.hx2
-rw-r--r--src/client/players/Youtube.hx2
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 {
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage