@@ -65,7 +91,7 @@
-
Open streams in player
+
Open streams in player
-
-
diff --git a/src/css/auth.css b/src/css/auth.css
index 23e8e1a..2a54109 100644
--- a/src/css/auth.css
+++ b/src/css/auth.css
@@ -26,7 +26,7 @@
.auth-button {
margin: 10px;
- border-radius: 8px;
+ border-radius: 5px;
font-size: 1rem;
padding: 10px;
background-color: #9146ff;
diff --git a/src/css/main.css b/src/css/main.css
index d6489ea..cc788f0 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -36,13 +36,13 @@ body {
}
.search-container {
- width: 50%;
+ width: auto;
display: inline-flex;
height: 100%;
}
.search-container input[type="text"] {
- width: 100%;
+ width: 150px;
background-color: #242429;
color: #fff;
border: 2px solid #242429;
@@ -87,14 +87,14 @@ body {
color: #fff;
font-weight: bold;
background-color: #38383f;
- padding: 7px 7px 5.5px 7px;
+ padding: 6.5px 7px;
border: 0;
border-radius: 5px;
text-decoration: none;
- margin: 10px;
+ margin: 10px 10px 10px 0;
display: inline;
align-content: center;
- font-size: 0.9rem;
+ font-size: 1rem;
cursor: pointer;
user-select: none;
}
@@ -107,13 +107,6 @@ body {
background-color: #772ce8;
}
-.navbar-icon-link {
- padding: 6.5px 7.5px;
- margin-left: 0;
- margin-right: 10px;
- font-size: 1rem;
-}
-
.refresh-button {
margin-left: 0px;
border-radius: 0 5px 5px 0;
@@ -123,6 +116,74 @@ body {
+/* FILTER DROPDOWN */
+.dropdown {
+ display: none;
+ align-items: center;
+}
+
+.dropdown-content {
+ position: absolute;
+ top: 100%;
+ right: 26%;
+ background-color: #242429;
+ padding: 0;
+ width: 176px;
+ height: auto;
+ border: 1px solid #38383f;
+ border-radius: 5px;
+ display: flex;
+ box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 0 10px 6px rgba(0, 0, 0, 0.3);
+}
+
+.dropdown-body {
+ color: #fff;
+ width: 100%;
+ padding: 5px 0;
+}
+
+.filter-option {
+ height: 30px;
+ font-size: 1rem;
+ padding: 0 5px;
+}
+
+.filter-button {
+ display: inline-flex;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ padding: 0 6px;
+ background-color: #242429;
+ border: 0;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.filter-button:hover {
+ background-color: #38383f;
+}
+
+.filter-button:active {
+ background-color: #54545f;
+}
+
+.active {
+ background-color: #9146ff;
+}
+
+.active:hover {
+ background-color: #772ce8;
+}
+
+.active:active {
+ background-color: #5c16c5;
+}
+
+.filter-icon {
+ margin-right: 8px;
+}
+
/* MAIN ELEMENT CLASSES */
.streams {
@@ -179,11 +240,11 @@ body {
position: absolute;
right: 10.1%;
bottom: 15.5%;
- background: rgba(0, 0, 0, 0.8);
+ background: rgba(0, 0, 0, 0.6);
color: rgba(255, 255, 255, 0.8);
font-size: 0.78rem;
font-weight: 600;
- padding: 2px 2px 1px 3px;;
+ padding: 2px 2px 1px 3px;
border-radius: 4px 0;
text-shadow: 0 0 4px #000;
z-index: 900;
@@ -231,6 +292,6 @@ body {
color: #fff;
text-align: center;
font-weight: bold;
- font-size: 1rem;
- padding: 10px;
+ font-size: 1.1rem;
+ padding: 42px 0 0 0;
}
diff --git a/src/css/settings.css b/src/css/settings.css
index 43fa4e3..276cb5b 100644
--- a/src/css/settings.css
+++ b/src/css/settings.css
@@ -229,11 +229,11 @@ input:checked+.slider:before {
}
.settings-link:hover {
- background-color: #9146ff;
+ background-color: #54545f;
}
.settings-link:active {
- background-color: #772ce8;
+ background-color: #9146ff;
}
/* LOGOUNT BUTTON */
diff --git a/src/css/tooltips.css b/src/css/tooltips.css
index 179f5cf..78c418a 100644
--- a/src/css/tooltips.css
+++ b/src/css/tooltips.css
@@ -1,26 +1,26 @@
/* SETTINGS TOOLTIP */
-[aria-label] {
+[settings-label] {
position: relative;
}
-[aria-label]:after,
-[aria-label]:before {
+[settings-label]:after,
+[settings-label]:before {
content: "";
position: absolute;
z-index: 500;
pointer-events: none;
}
-[aria-label]:after {
- content: attr(aria-label);
+[settings-label]:after {
+ content: attr(settings-label);
display: block;
position: absolute;
top: 133%;
right: 0%;
z-index: 500;
pointer-events: none;
- padding: 6px 5px;
+ padding: 5px;
line-height: 15px;
white-space: nowrap;
text-decoration: none;
@@ -33,7 +33,7 @@
opacity: 0;
}
-[aria-label]:before {
+[settings-label]:before {
content: "";
border-style: solid;
border-width: 0 8px 10px 8px;
@@ -44,45 +44,45 @@
opacity: 0;
}
-[aria-label]:hover:after {
+[settings-label]:hover:after {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label]:hover:before {
+[settings-label]:hover:before {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label]:not(:hover):after,
-[aria-label]:not(:hover):before {
+[settings-label]:not(:hover):after,
+[settings-label]:not(:hover):before {
transition-delay: 0s;
}
/* TWITCH LIVE FOLLOWING TOOLTIP */
-[aria-label-2] {
+[twitch-label] {
position: relative;
}
-[aria-label-2]:after,
-[aria-label-2]:before {
+[twitch-label]:after,
+[twitch-label]:before {
content: "";
position: absolute;
z-index: 500;
pointer-events: none;
}
-[aria-label-2]:after {
- content: attr(aria-label-2);
+[twitch-label]:after {
+ content: attr(twitch-label);
display: block;
position: absolute;
top: 133%;
right: -132%;
z-index: 500;
pointer-events: none;
- padding: 6px 5px;
+ padding: 5px;
line-height: 15px;
white-space: nowrap;
text-decoration: none;
@@ -95,7 +95,7 @@
opacity: 0;
}
-[aria-label-2]:before {
+[twitch-label]:before {
content: "";
border-style: solid;
border-width: 0 8px 10px 8px;
@@ -106,45 +106,45 @@
opacity: 0;
}
-[aria-label-2]:hover:after {
+[twitch-label]:hover:after {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label-2]:hover:before {
+[twitch-label]:hover:before {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label-2]:not(:hover):after,
-[aria-label-2]:not(:hover):before {
+[twitch-label]:not(:hover):after,
+[twitch-label]:not(:hover):before {
transition-delay: 0s;
}
/* REFRESH BUTTON TOOLTIP */
-[aria-label-3] {
+[refresh-label] {
position: relative;
}
-[aria-label-3]:after,
-[aria-label-3]:before {
+[refresh-label]:after,
+[refresh-label]:before {
content: "";
position: absolute;
z-index: 500;
pointer-events: none;
}
-[aria-label-3]:after {
- content: attr(aria-label-3);
+[refresh-label]:after {
+ content: attr(refresh-label);
display: block;
position: absolute;
top: 133%;
left: -125%;
z-index: 500;
pointer-events: none;
- padding: 6px 5px;
+ padding: 5px;
line-height: 15px;
white-space: nowrap;
text-decoration: none;
@@ -157,7 +157,7 @@
opacity: 0;
}
-[aria-label-3]:before {
+[refresh-label]:before {
content: "";
border-style: solid;
border-width: 0 8px 10px 8px;
@@ -168,45 +168,45 @@
opacity: 0;
}
-[aria-label-3]:hover:after {
+[refresh-label]:hover:after {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label-3]:hover:before {
+[refresh-label]:hover:before {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label-3]:not(:hover):after,
-[aria-label-3]:not(:hover):before {
+[refresh-label]:not(:hover):after,
+[refresh-label]:not(:hover):before {
transition-delay: 0s;
}
/* "Open streams in Player" SETTINGS OPTION INFO LINK TOOLTIP */
-[aria-label-4] {
+[info-label] {
position: relative;
}
-[aria-label-4]:after,
-[aria-label-4]:before {
+[info-label]:after,
+[info-label]:before {
content: "";
position: absolute;
z-index: 500;
pointer-events: none;
}
-[aria-label-4]:after {
- content: attr(aria-label-4);
+[info-label]:after {
+ content: attr(info-label);
display: block;
position: absolute;
top: 174%;
- left: -420%;
+ left: -450%;
z-index: 500;
pointer-events: none;
- padding: 6px 5px;
+ padding: 5px;
line-height: 15px;
white-space: nowrap;
text-decoration: none;
@@ -219,7 +219,7 @@
opacity: 0;
}
-[aria-label-4]:before {
+[info-label]:before {
content: "";
border-style: solid;
border-width: 0 8px 10px 8px;
@@ -230,17 +230,79 @@
opacity: 0;
}
-[aria-label-4]:hover:after {
+[info-label]:hover:after {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label-4]:hover:before {
+[info-label]:hover:before {
opacity: 1;
transition-delay: 0.3s;
}
-[aria-label-4]:not(:hover):after,
-[aria-label-4]:not(:hover):before {
+[info-label]:not(:hover):after,
+[info-label]:not(:hover):before {
transition-delay: 0s;
}
+
+
+/* FILTER BUTTON TOOLTIP */
+
+[filter-label] {
+ position: relative;
+}
+
+[filter-label]:after,
+[filter-label]:before {
+ content: "";
+ position: absolute;
+ z-index: 500;
+ pointer-events: none;
+}
+
+[filter-label]:after {
+ content: attr(filter-label);
+ display: block;
+ position: absolute;
+ top: 133%;
+ left: -99.5%;
+ z-index: 500;
+ pointer-events: none;
+ padding: 5px;
+ line-height: 15px;
+ white-space: nowrap;
+ text-decoration: none;
+ text-indent: 0;
+ overflow: visible;
+ font-size: .8em;
+ color: #000;
+ background-color: #fff;
+ border-radius: 5px;
+ opacity: 0;
+}
+
+[filter-label]:before {
+ content: "";
+ border-style: solid;
+ border-width: 0 8px 10px 8px;
+ border-color: transparent transparent #fff transparent;
+ top: 35px;
+ right: 50%;
+ transform: translateX(50%);
+ opacity: 0;
+}
+
+[filter-label]:hover:after {
+ opacity: 1;
+ transition-delay: 0.3s;
+}
+
+[filter-label]:hover:before {
+ opacity: 1;
+ transition-delay: 0.3s;
+}
+
+[filter-label]:not(:hover):after,
+[filter-label]:not(:hover):before {
+ transition-delay: 0s;
+}
\ No newline at end of file
diff --git a/src/js/background.js b/src/js/background.js
index 92039c6..a66732c 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -188,6 +188,7 @@ const getLiveTwitchStreams = async () => {
channelName: stream["user_name"],
viewerCount: stream["viewer_count"],
liveTime: getTimePassed(stream["started_at"]),
+ startedAt: getStartedAtTime(stream["started_at"]),
})),
});
diff --git a/src/js/main.js b/src/js/main.js
index 6c860a1..83e6c74 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -84,8 +84,56 @@ const loadTwitchContent = async () => {
}
if (res.twitchStreams) {
+ let filteredStreams = [...res.twitchStreams]; // Copy the array to avoid modifying the original
+
+ // Filter/Sort options
+ const selectedFilter = getSelectedFilterOption();
+ switch (selectedFilter) {
+ case "Broadcaster":
+ filteredStreams.sort((a, b) => (a.channelName > b.channelName ? 1 : -1));
+ break;
+ case "Category":
+ filteredStreams.sort((a, b) => (a.gameName > b.gameName ? 1 : -1));
+ break;
+ case "Uptime":
+ filteredStreams.sort((a, b) => {
+ const aUptime = new Date(a.startedAt).getTime();
+ const bUptime = new Date(b.startedAt).getTime();
+ return aUptime - bUptime;
+ });
+ break;
+ case "Viewers (High to Low)":
+ filteredStreams.sort((a, b) => b.viewerCount - a.viewerCount);
+ break;
+ case "Viewers (Low to High)":
+ filteredStreams.sort((a, b) => a.viewerCount - b.viewerCount);
+ break;
+ case "Recently Started":
+ filteredStreams.sort((a, b) => {
+ const aStarted = new Date(a.startedAt).getTime();
+ const bStarted = new Date(b.startedAt).getTime();
+ return bStarted - aStarted;
+ });
+ break;
+ case "Longest Running":
+ filteredStreams.sort((a, b) => {
+ const aStarted = new Date(a.startedAt).getTime();
+ const bStarted = new Date(b.startedAt).getTime();
+ const aRunning = a.runningAt ? new Date(a.runningAt).getTime() : Date.now();
+ const bRunning = b.runningAt ? new Date(b.runningAt).getTime() : Date.now();
+ const aDuration = aRunning - aStarted;
+ const bDuration = bRunning - bStarted;
+
+ // console.log("aDuration:", aDuration, "bDuration:", bDuration); // debugging
+ return bDuration - aDuration;
+ });
+ break;
+ default:
+ break;
+ }
+
const query = filterInput.value.toLowerCase();
- const filteredStreams = res.twitchStreams.filter(
+ filteredStreams = filteredStreams.filter(
(stream) =>
stream.channelName.toLowerCase().includes(query) ||
stream.title.toLowerCase().includes(query) ||
@@ -105,6 +153,7 @@ const loadTwitchContent = async () => {
const uptime = document.createElement("div");
uptime.setAttribute("class", "stream-uptime");
uptime.innerHTML = `${stream.liveTime}`;
+ uptime.setAttribute("title", `${stream.startedAt}`);
streamThumbnail.appendChild(uptime);
const thumbnail = document.createElement("img");
@@ -125,6 +174,7 @@ const loadTwitchContent = async () => {
const categoryAndViewCount = document.createElement("span");
categoryAndViewCount.setAttribute("class", "stream-game-and-viewers");
categoryAndViewCount.innerHTML = `${stream.gameName} - ${formatViewerCount(stream.viewerCount)} viewers`;
+ categoryAndViewCount.setAttribute("title", `${stream.gameName} - ${formatViewerCount(stream.viewerCount)} viewers`);
streamDetails.appendChild(categoryAndViewCount);
const title = document.createElement("span");
@@ -159,6 +209,55 @@ const handleRefreshButtonClick = () => {
loadTwitchContent();
};
+// Function to get the selected filter option
+const getSelectedFilterOption = () => {
+ const broadcasterButton = document.getElementById("broadcasterButton");
+ const categoryButton = document.getElementById("categoryButton");
+ const viewersHighToLowButton = document.getElementById("viewersHighToLowButton");
+ const viewersLowToHighButton = document.getElementById("viewersLowToHighButton");
+ const startedButton = document.getElementById("startedButton");
+ const runningButton = document.getElementById("runningButton");
+
+ if (broadcasterButton && broadcasterButton.classList.contains("active")) {
+ return "Broadcaster";
+ } else if (categoryButton && categoryButton.classList.contains("active")) {
+ return "Category";
+ } else if (viewersHighToLowButton && viewersHighToLowButton.classList.contains("active")) {
+ return "Viewers (High to Low)";
+ } else if (viewersLowToHighButton && viewersLowToHighButton.classList.contains("active")) {
+ return "Viewers (Low to High)";
+ } else if (startedButton && startedButton.classList.contains("active")) {
+ return "Recently Started";
+ } else if (runningButton && runningButton.classList.contains("active")) {
+ return "Longest Running";
+ }
+
+ return "viewersHighToLowButton"; // Default filter option
+};
+
+// Function to set the selected filter option
+const setSelectedFilterOption = (buttonId) => {
+ // Remove the 'active' class from all filter buttons
+ const filterButtons = document.querySelectorAll('.filter-button');
+ filterButtons.forEach(button => button.classList.remove('active'));
+
+ // Add the 'active' class to the clicked filter button
+ const selectedButton = document.getElementById(buttonId);
+ if (selectedButton) {
+ selectedButton.classList.add('active');
+ }
+};
+
+// Event listeners for the filter buttons
+const filterButtons = document.querySelectorAll('.filter-button');
+filterButtons.forEach(button => {
+ button.addEventListener('click', function() {
+ const buttonId = this.id;
+ setSelectedFilterOption(buttonId);
+ loadTwitchContent(); // Reload content based on the selected filter
+ });
+});
+
// Event listeners for the search input and refresh button
filterInput.addEventListener("input", loadTwitchContent);
refreshButton.addEventListener("click", handleRefreshButtonClick);
diff --git a/src/js/settings.js b/src/js/settings.js
index 3daecd5..565d2c4 100644
--- a/src/js/settings.js
+++ b/src/js/settings.js
@@ -41,38 +41,35 @@ const colorInput = document.getElementById("colorInput");
console.log("Toggle Switch Status - Open in New Window:", openInNewWindowToggle.checked);*/
// Custom color badge input
-colorInput.addEventListener("keydown", function (event) {
- if (event.key === "Enter") {
- const color = this.value.trim();
+colorInput.addEventListener("focus", function () {
+ this.addEventListener("input", handleColorInput);
+});
+
+colorInput.addEventListener("blur", function () {
+ this.removeEventListener("input", handleColorInput);
+});
+
+function handleColorInput() {
+ const color = this.value.trim();
+
+ // Check if the entered color is a valid HEX format and starts with "#"
+ if (/^#[0-9A-Fa-f]{6}$/.test(color) && color.startsWith("#") && color.length >= 7) {
+ // Clear any existing timeout
+ clearTimeout(this.timeoutId);
- // Check if the entered color is a valid HEX format and starts with "#"
- if (/^#[0-9A-Fa-f]{6}$/.test(color) && color.startsWith("#")) {
+ // Schedule the update after a delay (e.g., 100 milliseconds)
+ this.timeoutId = setTimeout(() => {
chrome.storage.local.set({ customBadgeColor: color }, () => {
// Update the badge color
chrome.action.setBadgeBackgroundColor({ color });
});
- } else {
- // If the input is invalid or empty, set to default color and clear the input
- chrome.storage.local.set({ customBadgeColor: "" }, () => {
- this.value = "";
- chrome.action.setBadgeBackgroundColor({ color: "#666666" });
- });
- }
-
- // Prevent the default form submission behavior
- event.preventDefault();
+ }, 100);
+ } else if (color.length === 0) {
+ // If the input is empty, set to default color
+ chrome.storage.local.set({ customBadgeColor: "" }, () => {
+ chrome.action.setBadgeBackgroundColor({ color: "#666666" });
+ });
}
-});
-
-// Settings Modal
-var modal = document.getElementById("settingsModal");
-var btn = document.getElementById("settingsBtn");
-var span = document.getElementsByClassName("settings-close-btn")[0];
-btn.onclick = function() {
- modal.style.display = "flex";
-}
-span.onclick = function() {
- modal.style.display = "none";
}
// Function to handle the logout button click
@@ -95,3 +92,28 @@ const logoutButton = document.getElementById("logoutBtn");
if (logoutButton) {
logoutButton.addEventListener("click", handleLogoutButtonClick);
}
+
+// Filter dropdown
+var dropdown = document.getElementById("filterDropdown");
+var filterBtn = document.getElementById("filterButton");
+filterBtn.onclick = function(event) {
+ dropdown.style.display = dropdown.style.display === "flex" ? "none" : "flex";
+ event.stopPropagation();
+};
+
+window.onclick = function(event) {
+ if (!event.target.matches('.dropdown-content') && dropdown.style.display === "flex") {
+ dropdown.style.display = "none";
+ }
+}
+
+// Settings Modal
+var modal = document.getElementById("settingsModal");
+var btn = document.getElementById("settingsBtn");
+var span = document.getElementsByClassName("settings-close-btn")[0];
+btn.onclick = function() {
+ modal.style.display = "flex";
+}
+span.onclick = function() {
+ modal.style.display = "none";
+}
diff --git a/src/js/util.js b/src/js/util.js
index 1b455c3..1442d44 100644
--- a/src/js/util.js
+++ b/src/js/util.js
@@ -1,3 +1,21 @@
+const getStartedAtTime = (startTime) => {
+ const options = {
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ hour12: true,
+ };
+
+ const formattedTime = new Intl.DateTimeFormat('en-US', options)
+ .format(new Date(startTime))
+ .replace(',', '');
+
+ return formattedTime;
+};
+
const getTimePassed = (startTime) => {
let elapsedTime = Date.now() - Date.parse(startTime);
const hours = Math.floor(elapsedTime / 3600000);