diff --git a/index.html b/index.html index bd0029d8..92c1ad98 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,8 @@ - + + @@ -18,7 +19,6 @@ - diff --git a/static/addons/panel.html b/static/addons/panel.html deleted file mode 100644 index b8841b5c..00000000 --- a/static/addons/panel.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
- Music Controls -
-
- -
-
-
-
-
-
-
20%
-
-
diff --git a/static/config.json b/static/config.json index 06762e80..b9aa3cbe 100644 --- a/static/config.json +++ b/static/config.json @@ -18,7 +18,7 @@ "redirectRootLogin": "/main/friends", "redirectRootNoLogin": "/about", "showFeaturesPanel": true, - "showInstanceSpecificPanel": true, + "showInstanceSpecificPanel": false, "sidebarRight": false, "subjectLineBehavior": "email", "theme": "paw-catppuccin", diff --git a/static/addons/rot.css b/static/rot/rot.css similarity index 97% rename from static/addons/rot.css rename to static/rot/rot.css index 2b5a8060..c9c75ee0 100644 --- a/static/addons/rot.css +++ b/static/rot/rot.css @@ -1,7 +1,3 @@ -#music-controls { - padding: 15px; -} - .music-controls-title { text-align: left; font-size: 18px; @@ -92,15 +88,13 @@ input:checked ~ .mutecheck { } .audioControl { - border: 1.5px solid var(--icon); border-radius: var(--panelRadius); padding: 6px; - margin-top: 13px; display: flex; align-items: center; gap: 2px; } -#user-audio-percentage { +.volume-percentage { margin-right: 10px; } diff --git a/static/rot/rot.js b/static/rot/rot.js new file mode 100644 index 00000000..da89b267 --- /dev/null +++ b/static/rot/rot.js @@ -0,0 +1,386 @@ +// Rot.js +// one rotten apple spoils the bunch + +(() => { + //Helper functions + function getPromiseFromEvent(item, event) { + return new Promise((resolve) => { + const listener = () => { + item.removeEventListener(event, listener); + resolve(); + } + item.addEventListener(event, listener); + }); + } + + function sex(sex) { + return sex // Sex + } + + function rand(list) { + return list ? list[list.length * Math.random() | 0] : null; + } + + //Music functions + function setMusic(url) { + if (audio.src == url || (!url && audio.src === window.location.toString())) + return; + + audio.pause(); + + if (url) { + console.log("Setting music: " + url); + audio.src = url; + playMusic(); + } else { + // This line will cause a lot of errors. + // Setting src to "" will cause any pending .play()s to fail. + audio.src = ""; + } + } + + function playMusic() { + //Starts playing the music if it isn't muted and isn't already playing + if (audio.src && audio.src != "" && audio.paused && !audio.muted) + audio.play().catch(() => getPromiseFromEvent(window, 'click').then(audio.play)); + } + + function volumeSet(number) { + audio.volume = number; //Set + localStorage.audiovolume = number; //Save + updateVolumeLabel(); + } + function volumeAdd(number) { + volumeSet(Math.min(1, Math.max(0, audio.volume + number))); + } + function updateVolumeLabel() { + const percentage = `${Math.round(audio.volume * 100)}%`; + const txtElm = document.querySelector('.volume-percentage'); + if (txtElm) + txtElm.innerHTML = percentage; + + const slider = document.querySelector('.volume-slider'); + if (slider) { + const thumb = slider.querySelector('.volume-thumb'); + const fill = slider.querySelector('.volume-fill'); + if (thumb) thumb.style.left = percentage; + if (fill) fill.style.width = percentage; + } + } + + //Registers a mutation observer for an element. Upon triggering and the callback + //returning true, observation ceases. All observers are disconnected and the array is cleared. + const observers = []; + function waitUntilSpecial(selector, callback) { + const newObserver = new MutationObserver((mutationRecords, observer) => { + // Query for elements matching the specified selector + Array.from(document.querySelectorAll(selector)).forEach((element) => { + //Callback + if (!callback(element)) + return; + + //Clean up + console.log("Cleaning up"); + for (const o of observers) + o.disconnect(); + observers.length = 0; + }); + }); + observers.push(newObserver); + newObserver.observe(document.documentElement, { + childList: true, + subtree: true + }); + } + + //Theme application + function applyMainTheme() { + console.log("Applying main theme"); + setMusic(null); + + // + waitUntilSpecial('meta[name="pageMusic"]', (pageMusic) => { + setMusic(pageMusic.content); + playMusic(); + return true; + }); + + // + waitUntilSpecial("#pageMusic", (pageMusic) => { + setMusic(pageMusic.getAttribute("href")); + playMusic(); + return true; + }); + } + + function applyUserTheme() { + console.log("Applying user theme"); + setMusic(null); + + //Configure by post + waitUntilSpecial(".pin", (pinnedPost) => { + let p = pinnedPost.nextElementSibling; + if (!p) + return false; + + let statusBody = p.querySelector(".StatusBody"); + if (!statusBody) + return false; + + let txtElm = statusBody.querySelector(".text"); + if (!txtElm) + return false; + + if (txtElm.innerHTML.replace(/(<([^>]+)>)/ig, '').search(/profile them/ig) == -1) + return false; + + const ptp = pinnedPost.nextElementSibling.querySelector(".StatusBody"); + if (!ptp) + return false; + + const musicContainer = ptp.querySelector(".audio-container"); + if (musicContainer) { + setMusic(rand(musicContainer.children).src); + return true; + } else { + const placeholderContainer = ptp.querySelector(".placeholder-container"); + if (placeholderContainer && placeholderContainer.href.endsWith(".m4a")) { + setMusic(placeholderContainer.href); + return true; + } else { + setMusic(null); + return false; + } + } + }); + + //Configure by fields + waitUntilSpecial(".user-profile-field-name", () => { + const fields = [...document.getElementsByClassName("user-profile-field-name")] + if (fields.length == 0) + return false; + + const musicFields = fields.filter((x) => x.title.toLowerCase().replace(/\s+/g, '') == "music") + if (musicFields.length > 0) { + setMusic(rand(musicFields).nextElementSibling.title); + return true; + } else { + setMusic(null); + return false; + } + }); + } + + //Switch-based monkey patching router bullshit + let lastPath = null; + function updateRot() { + try { + let newPath = window.location.pathname; + if (lastPath == newPath) + return; + lastPath = newPath; + + let pathSpl = newPath.split("/"); + switch (pathSpl.length) { + case 1: + applyMainTheme(); //Root + break; + + case 2: + switch (pathSpl[1]) { + case "about": + case "announcements": + case "lists": + case "bookmarks": + applyMainTheme(); + break; + + default: + applyUserTheme(); + break; + } + break; + + case 3: + switch (pathSpl[1]) { + case "main": + applyMainTheme(); //Main timelines + break; + + case "users": + applyUserTheme(); + break; + + case "notice": + applyMainTheme(); //Statuses + break; + + default: + applyMainTheme(); + break; + } + break; + + default: + applyMainTheme(); + break; + } + } catch (e) { + console.error(e); + } + } + + //Rot music player + const audio = document.createElement("audio"); + audio.loop = true; + audio.id = "user-music"; + audio.style = "display:none;"; + if (localStorage.audiovolume && localStorage.audiovolume >= 0 && localStorage.audiovolume <= 1) + audio.volume = localStorage.audiovolume; //Load volume + else + audio.volume = 0.2; //Default volume + + //Monkey patches and event listeners + const oldPushState = history.pushState; + history.pushState = function pushState() { + const ret = oldPushState.apply(this, arguments); + updateRot(); + return ret; + }; + + const oldReplaceState = history.replaceState; + history.replaceState = function replaceState() { + const ret = oldReplaceState.apply(this, arguments); + updateRot(); + return ret; + }; + + addEventListener('locationchange', updateRot); + addEventListener('popstate', updateRot); + + function createAudioControls() { + let NavPanel = document.querySelector('.NavPanel'); + let sideDrawer = document.querySelector('.side-drawer'); + if (!NavPanel && !sideDrawer) + return; + + if (document.querySelector('.audioControl')) + return; + + //Initialize audio controls and event listeners + console.log("Adding music controls"); + const panel = document.createElement("div"); + panel.className = "panel panel-default"; + + const panelHeading = document.createElement("div"); + panelHeading.className = "panel-heading"; + panel.appendChild(panelHeading); + + const title = document.createElement("div"); + title.className = "title"; + title.innerText = "Music Controls"; + panelHeading.appendChild(title); + + const panelBody = document.createElement("div"); + panelBody.className = "panel-body"; + panel.appendChild(panelBody); + + const audioControl = document.createElement("div"); + audioControl.className = "audioControl"; + panelBody.appendChild(audioControl); + + const mutebutton = document.createElement("label"); + mutebutton.className = "mutebutton"; + audioControl.appendChild(mutebutton); + + const musicmute = document.createElement("input"); + musicmute.className = "music-mute"; + musicmute.setAttribute("type", "checkbox"); + musicmute.checked = true; + mutebutton.appendChild(musicmute); + + const mutecheck = document.createElement("span"); + mutecheck.className = "mutecheck"; + mutecheck.title = "Mute music"; + mutebutton.appendChild(mutecheck); + + const volumeSlider = document.createElement("div"); + volumeSlider.className = "volume-slider"; + audioControl.appendChild(volumeSlider); + + const volumeTrack = document.createElement("div"); + volumeTrack.className = "volume-track"; + volumeSlider.appendChild(volumeTrack); + + const volumeFill = document.createElement("div"); + volumeFill.className = "volume-fill"; + volumeTrack.appendChild(volumeFill); + + const volumeThumb = document.createElement("div"); + volumeThumb.className = "volume-thumb"; + volumeSlider.appendChild(volumeThumb); + + const volumePercentage = document.createElement("div"); + volumePercentage.className = "volume-percentage"; + audioControl.appendChild(volumePercentage); + + if (NavPanel) + NavPanel.insertAdjacentElement('afterend', panel); + else if (sideDrawer) + sideDrawer.insertAdjacentElement('beforeend', audioControl); + + { + let isDragging = false; + + function updateVolume(e) { + const rect = volumeSlider.getBoundingClientRect(); + let x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); + let percentage = (x / rect.width) * 100; + + // Update actual volume + volumeSet(percentage / 100); + } + + function onMouseDown(e) { + isDragging = true; + updateVolume(e); + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + } + + function onMouseMove(e) { + if (isDragging) { + updateVolume(e); + } + } + + function onMouseUp() { + isDragging = false; + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + } + + volumeSlider.addEventListener('mousedown', onMouseDown); + } + + audio.muted = musicmute.checked = localStorage.audiomuted === "true"; + musicmute.addEventListener('click', () => { + localStorage.audiomuted = audio.muted = musicmute.checked; + playMusic(); + }) + + updateVolumeLabel(); + } + + createAudioControls(); + window.addEventListener('resize', createAudioControls); + + new MutationObserver((mutationRecords, observer) => { + createAudioControls(); + }).observe(document.body, { + childList: true, + subtree: true + }); +})(); + +console.log("rot.js loaded");