4sp-dv-latest 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/4simpleproblems_v5_latest.html +142 -183
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>4SP - VERSION 5 CLIENT</title>
|
|
7
7
|
<link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/npm/4sp-dv@latest/images/logo.png">
|
|
8
8
|
|
|
9
|
-
<base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.
|
|
9
|
+
<base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.19/logged-in/">
|
|
10
10
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
11
11
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap" rel="stylesheet">
|
|
12
12
|
|
|
@@ -162,15 +162,14 @@
|
|
|
162
162
|
}
|
|
163
163
|
.icon-btn:hover { background-color: #374151; color: white; }
|
|
164
164
|
|
|
165
|
-
.auth-menu
|
|
165
|
+
.auth-menu {
|
|
166
166
|
position: absolute; right: 0; top: 50px; width: 14rem;
|
|
167
167
|
background: var(--menu-bg, #000);
|
|
168
168
|
border: 1px solid var(--menu-border, #374151);
|
|
169
169
|
border-radius: 0.75rem;
|
|
170
170
|
padding: 0.5rem; display: none; flex-direction: column; gap: 0.25rem; z-index: 50;
|
|
171
171
|
}
|
|
172
|
-
.
|
|
173
|
-
.auth-menu.open, .pin-context-menu.open { display: flex; }
|
|
172
|
+
.auth-menu.open { display: flex; }
|
|
174
173
|
|
|
175
174
|
.auth-menu-item {
|
|
176
175
|
display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem;
|
|
@@ -261,6 +260,18 @@
|
|
|
261
260
|
color: #6366f1;
|
|
262
261
|
}
|
|
263
262
|
|
|
263
|
+
.btn-toolbar-style.btn-primary-override-danger {
|
|
264
|
+
justify-content: center;
|
|
265
|
+
background-color: rgba(220, 38, 38, 0.1);
|
|
266
|
+
border: 1px solid #dc2626;
|
|
267
|
+
color: #dc2626;
|
|
268
|
+
}
|
|
269
|
+
.btn-toolbar-style.btn-primary-override-danger:hover {
|
|
270
|
+
background-color: rgba(220, 38, 38, 0.15);
|
|
271
|
+
border-color: #ef4444;
|
|
272
|
+
color: #ef4444;
|
|
273
|
+
}
|
|
274
|
+
|
|
264
275
|
.settings-tab {
|
|
265
276
|
width: 100%;
|
|
266
277
|
justify-content: flex-start;
|
|
@@ -410,23 +421,6 @@
|
|
|
410
421
|
</div>
|
|
411
422
|
|
|
412
423
|
<div class="auth-controls-wrapper">
|
|
413
|
-
<div class="relative" id="pin-wrapper">
|
|
414
|
-
<button id="pin-btn" class="icon-btn" title="Pin this page">
|
|
415
|
-
<i class="fa-solid fa-thumbtack"></i>
|
|
416
|
-
</button>
|
|
417
|
-
<div id="pin-menu" class="pin-context-menu">
|
|
418
|
-
<div class="auth-menu-item" id="repin-btn">
|
|
419
|
-
<i class="fa-solid fa-thumbtack w-4"></i> Repin
|
|
420
|
-
</div>
|
|
421
|
-
<div class="auth-menu-item text-red-400 hover:text-red-300" id="remove-pin-btn">
|
|
422
|
-
<i class="fa-solid fa-xmark w-4"></i> Remove Pin
|
|
423
|
-
</div>
|
|
424
|
-
<div class="auth-menu-item text-red-400 hover:text-red-300" id="hide-pin-btn">
|
|
425
|
-
<i class="fa-solid fa-eye-slash w-4"></i> Hide Button
|
|
426
|
-
</div>
|
|
427
|
-
</div>
|
|
428
|
-
</div>
|
|
429
|
-
|
|
430
424
|
<div class="relative">
|
|
431
425
|
<button id="auth-btn" class="icon-btn">
|
|
432
426
|
<i class="fa-solid fa-user"></i>
|
|
@@ -439,9 +433,6 @@
|
|
|
439
433
|
<a href="#" class="auth-menu-item" onclick="loadPage('settings.html'); return false;">
|
|
440
434
|
<i class="fa-solid fa-gear w-4"></i> Settings
|
|
441
435
|
</a>
|
|
442
|
-
<div class="auth-menu-item hidden" id="show-pin-menu-item">
|
|
443
|
-
<i class="fa-solid fa-eye w-4"></i> Show Pin Button
|
|
444
|
-
</div>
|
|
445
436
|
<div class="auth-menu-item text-red-400 hover:text-red-300" id="logout-btn">
|
|
446
437
|
<i class="fa-solid fa-right-from-bracket w-4"></i> Disconnect
|
|
447
438
|
</div>
|
|
@@ -474,6 +465,7 @@
|
|
|
474
465
|
<div id="settings-main-view">
|
|
475
466
|
<div id="tab-general" class="settings-section">
|
|
476
467
|
<h3 class="text-3xl font-bold text-white mb-6">General Settings</h3>
|
|
468
|
+
|
|
477
469
|
<div class="settings-box">
|
|
478
470
|
<h3 class="text-xl font-bold text-white mb-2">Account Username</h3>
|
|
479
471
|
<label class="block text-gray-400 text-sm mb-2 font-light">New Username</label>
|
|
@@ -483,6 +475,28 @@
|
|
|
483
475
|
</div>
|
|
484
476
|
<p id="username-msg" class="general-message-area text-sm mt-2"></p>
|
|
485
477
|
</div>
|
|
478
|
+
|
|
479
|
+
<h3 class="text-xl font-bold text-white mb-2 mt-6">Organization</h3>
|
|
480
|
+
<div class="settings-box bg-cyan-500/10 border-cyan-500/50 p-4">
|
|
481
|
+
<p class="text-sm font-light text-cyan-300 mb-3">
|
|
482
|
+
Visit the official 4SP Organization website for updates and more tools.
|
|
483
|
+
</p>
|
|
484
|
+
<a href="https://4sp-organization.github.io/" target="_blank" class="btn-toolbar-style w-full justify-center bg-cyan-500/10 border-cyan-500 text-cyan-500 hover:bg-cyan-500/20 hover:text-cyan-400">
|
|
485
|
+
<i class="fa-solid fa-globe mr-2"></i> Visit Website
|
|
486
|
+
</a>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<h3 class="text-xl font-bold text-white mb-2 mt-6">Disconnect Client</h3>
|
|
490
|
+
<div class="settings-box bg-red-900/10 border-red-700/50 p-4">
|
|
491
|
+
<p class="text-sm font-light text-red-300 mb-3">
|
|
492
|
+
<i class="fa-solid fa-triangle-exclamation mr-1"></i>
|
|
493
|
+
Disconnect this client from your account. You will need to generate a new code to reconnect.
|
|
494
|
+
</p>
|
|
495
|
+
<button id="disconnect-account-btn" class="btn-toolbar-style btn-primary-override-danger w-full justify-center">
|
|
496
|
+
<i class="fa-solid fa-power-off mr-2"></i> Disconnect Account
|
|
497
|
+
</button>
|
|
498
|
+
</div>
|
|
499
|
+
|
|
486
500
|
</div>
|
|
487
501
|
|
|
488
502
|
<div id="tab-personalization" class="settings-section hidden">
|
|
@@ -569,18 +583,19 @@
|
|
|
569
583
|
<h3 class="text-3xl font-bold text-white mb-6">About 4SP</h3>
|
|
570
584
|
<div class="settings-box p-6 flex flex-col items-center text-center">
|
|
571
585
|
<img src="https://cdn.jsdelivr.net/npm/4sp-asset-library@latest/logo.png" class="h-24 mb-4" alt="Logo">
|
|
572
|
-
<h1 class="text-3xl font-bold text-white mb-2">
|
|
573
|
-
<p class="text-gray-400 mb-6 max-w-lg font-light">
|
|
586
|
+
<h1 class="text-3xl font-bold text-white mb-2">(4SP) 4simpleproblems</h1>
|
|
587
|
+
<p class="text-gray-400 mb-6 max-w-lg font-light">From a soundboard, to a full downloadable client of a platform.</p>
|
|
574
588
|
|
|
575
|
-
<div class="inline-block bg-[#0a0a0a] border border-[#333] px-4 py-2 rounded-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
589
|
+
<div class="inline-block bg-[#0a0a0a] border border-[#333] px-4 py-2 rounded-[14px] mb-8">
|
|
590
|
+
<span class="text-gray-500 text-sm">Version:</span>
|
|
591
|
+
<span class="text-indigo-400 font-mono font-bold ml-2">5.0.0 (DV)</span>
|
|
592
|
+
</div>
|
|
593
|
+
|
|
579
594
|
|
|
580
595
|
<div class="flex gap-4">
|
|
581
|
-
<a href="
|
|
582
|
-
<a href="
|
|
583
|
-
<a href="
|
|
596
|
+
<a href="https://github.com/4simpleproblems-v5" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-github fa-xl"></i></a>
|
|
597
|
+
<a href="https://youtube.com/4simpleproblems" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-youtube fa-xl"></i></a>
|
|
598
|
+
<a href="https://x.com/@4simpleproblems" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-discord fa-xl"></i></a>
|
|
584
599
|
</div>
|
|
585
600
|
</div>
|
|
586
601
|
</div>
|
|
@@ -1806,15 +1821,6 @@
|
|
|
1806
1821
|
const glideLeft = document.getElementById('glide-left');
|
|
1807
1822
|
const glideRight = document.getElementById('glide-right');
|
|
1808
1823
|
|
|
1809
|
-
// Pin Elements
|
|
1810
|
-
const pinWrapper = document.getElementById('pin-wrapper');
|
|
1811
|
-
const pinBtn = document.getElementById('pin-btn');
|
|
1812
|
-
const pinMenu = document.getElementById('pin-menu');
|
|
1813
|
-
const repinBtn = document.getElementById('repin-btn');
|
|
1814
|
-
const removePinBtn = document.getElementById('remove-pin-btn');
|
|
1815
|
-
const hidePinBtn = document.getElementById('hide-pin-btn');
|
|
1816
|
-
const showPinMenuItem = document.getElementById('show-pin-menu-item');
|
|
1817
|
-
|
|
1818
1824
|
// Auth Elements
|
|
1819
1825
|
const authBtn = document.getElementById('auth-btn');
|
|
1820
1826
|
const authMenu = document.getElementById('auth-menu');
|
|
@@ -1822,10 +1828,8 @@
|
|
|
1822
1828
|
const displayUsername = document.getElementById('display-username');
|
|
1823
1829
|
const displayEmail = document.getElementById('display-email');
|
|
1824
1830
|
|
|
1825
|
-
const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.
|
|
1831
|
+
const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.19/logged-in/';
|
|
1826
1832
|
const STORAGE_KEY = 'local_access_code';
|
|
1827
|
-
const PINNED_PAGE_KEY = 'local_pinned_page';
|
|
1828
|
-
const PIN_HIDDEN_KEY = 'local_pin_hidden';
|
|
1829
1833
|
const USER_DATA_KEY = 'local_user_data';
|
|
1830
1834
|
const LAST_PAGE_KEY = 'local_last_page';
|
|
1831
1835
|
const OWNER_EMAIL = "4simpleproblems@gmail.com"; // For admin checks
|
|
@@ -1844,8 +1848,9 @@
|
|
|
1844
1848
|
};
|
|
1845
1849
|
|
|
1846
1850
|
let currentUser = null;
|
|
1851
|
+
let userDataUnsubscribe = null;
|
|
1847
1852
|
|
|
1848
|
-
// --- ADMIN KEYBINDS LOGIC (
|
|
1853
|
+
// --- ADMIN KEYBINDS LOGIC (Updated to Match admin_keybinds.js exactly) ---
|
|
1849
1854
|
function initAdminKeybinds(db, auth, user) {
|
|
1850
1855
|
console.log("[Admin Keybinds] Initializing for", user.uid);
|
|
1851
1856
|
let explicitEnabled = true;
|
|
@@ -1860,7 +1865,7 @@
|
|
|
1860
1865
|
const toast = document.createElement("div");
|
|
1861
1866
|
toast.id = "admin-keybind-toast";
|
|
1862
1867
|
|
|
1863
|
-
// Styles
|
|
1868
|
+
// Styles matching admin_keybinds.js exactly
|
|
1864
1869
|
Object.assign(toast.style, {
|
|
1865
1870
|
position: "fixed",
|
|
1866
1871
|
bottom: "24px",
|
|
@@ -1868,12 +1873,12 @@
|
|
|
1868
1873
|
display: "flex",
|
|
1869
1874
|
alignItems: "center",
|
|
1870
1875
|
gap: "12px",
|
|
1871
|
-
backgroundColor: "rgba(13, 13, 13, 0.95)",
|
|
1876
|
+
backgroundColor: "rgba(13, 13, 13, 0.95)", // Dark glass effect
|
|
1872
1877
|
backdropFilter: "blur(5px)",
|
|
1873
|
-
color: "#c0c0c0",
|
|
1878
|
+
color: "#c0c0c0", // Light gray text
|
|
1874
1879
|
padding: "14px 20px",
|
|
1875
1880
|
borderRadius: "12px",
|
|
1876
|
-
border: "1px solid #333",
|
|
1881
|
+
border: "1px solid #333", // Dark border
|
|
1877
1882
|
fontFamily: "'Geist', 'Roboto', sans-serif",
|
|
1878
1883
|
fontSize: "14px",
|
|
1879
1884
|
fontWeight: "500",
|
|
@@ -1919,8 +1924,10 @@
|
|
|
1919
1924
|
const data = docSnap.data();
|
|
1920
1925
|
if (data.explicitEnabled !== undefined) {
|
|
1921
1926
|
explicitEnabled = data.explicitEnabled;
|
|
1927
|
+
console.log(`[Admin Keybinds] Explicit Enabled: ${explicitEnabled}`);
|
|
1922
1928
|
}
|
|
1923
1929
|
} else {
|
|
1930
|
+
console.log("[Admin Keybinds] Config doc missing. Defaulting to TRUE.");
|
|
1924
1931
|
explicitEnabled = true;
|
|
1925
1932
|
}
|
|
1926
1933
|
}, (error) => console.error("Config Listen Error:", error));
|
|
@@ -1931,11 +1938,14 @@
|
|
|
1931
1938
|
|
|
1932
1939
|
// Key Listener
|
|
1933
1940
|
document.addEventListener('keydown', async (e) => {
|
|
1934
|
-
|
|
1941
|
+
// CHANGED: Trigger is Ctrl + Shift + E as requested
|
|
1942
|
+
if (e.ctrlKey && e.shiftKey && (e.key.toLowerCase() === 'e' || e.code === 'KeyE')) {
|
|
1935
1943
|
e.preventDefault();
|
|
1936
|
-
console.log("[Admin Keybinds] Ctrl+
|
|
1944
|
+
console.log("[Admin Keybinds] Ctrl+Shift+E detected.");
|
|
1937
1945
|
|
|
1938
1946
|
if (explicitEnabled) {
|
|
1947
|
+
// Logic for disabling (Immediate)
|
|
1948
|
+
console.log("[Admin Keybinds] Disabling explicit sounds...");
|
|
1939
1949
|
try {
|
|
1940
1950
|
await setDoc(doc(db, 'config', 'soundboard'), { explicitEnabled: false }, { merge: true });
|
|
1941
1951
|
showAdminToast("Explicit Sounds: DISABLED", "red");
|
|
@@ -1945,8 +1955,10 @@
|
|
|
1945
1955
|
showAdminToast("Error: Check Console", "red");
|
|
1946
1956
|
}
|
|
1947
1957
|
} else {
|
|
1958
|
+
// Logic for enabling (Double Press Safety)
|
|
1948
1959
|
const now = Date.now();
|
|
1949
1960
|
if (now - lastEnablePressTime < 1500) {
|
|
1961
|
+
console.log("[Admin Keybinds] Enabling explicit sounds...");
|
|
1950
1962
|
try {
|
|
1951
1963
|
await setDoc(doc(db, 'config', 'soundboard'), { explicitEnabled: true }, { merge: true });
|
|
1952
1964
|
showAdminToast("Explicit Sounds: ENABLED", "green");
|
|
@@ -1956,7 +1968,8 @@
|
|
|
1956
1968
|
showAdminToast("Error: Check Console", "red");
|
|
1957
1969
|
}
|
|
1958
1970
|
} else {
|
|
1959
|
-
|
|
1971
|
+
console.log("[Admin Keybinds] Waiting for confirm...");
|
|
1972
|
+
showAdminToast("Press Ctrl+Shift+E again to ENABLE", "blue");
|
|
1960
1973
|
lastEnablePressTime = now;
|
|
1961
1974
|
}
|
|
1962
1975
|
}
|
|
@@ -2038,9 +2051,6 @@
|
|
|
2038
2051
|
// --- Navbar Logic ---
|
|
2039
2052
|
function renderNavbar(activePageUrl) {
|
|
2040
2053
|
const tabsContainerEl = document.getElementById('tabs-container');
|
|
2041
|
-
const pinBtnEl = document.getElementById('pin-btn');
|
|
2042
|
-
const pinWrapperEl = document.getElementById('pin-wrapper');
|
|
2043
|
-
const showPinMenuItemEl = document.getElementById('show-pin-menu-item');
|
|
2044
2054
|
|
|
2045
2055
|
// Glide Elements
|
|
2046
2056
|
const glideLeft = document.getElementById('glide-left');
|
|
@@ -2105,86 +2115,6 @@
|
|
|
2105
2115
|
|
|
2106
2116
|
// Initial check
|
|
2107
2117
|
setTimeout(updateScrollGilders, 50);
|
|
2108
|
-
|
|
2109
|
-
// Update Pin Button State
|
|
2110
|
-
const pinned = localStorage.getItem(PINNED_PAGE_KEY);
|
|
2111
|
-
if (pinBtnEl) {
|
|
2112
|
-
if (pinned && pinned === currentPath) {
|
|
2113
|
-
pinBtnEl.style.color = '#4f46e5';
|
|
2114
|
-
} else {
|
|
2115
|
-
pinBtnEl.style.color = '#d1d5db';
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
// Update Pin Visibility
|
|
2120
|
-
const isHidden = localStorage.getItem(PIN_HIDDEN_KEY) === 'true';
|
|
2121
|
-
if (isHidden) {
|
|
2122
|
-
if (pinWrapperEl) pinWrapperEl.classList.add('hidden');
|
|
2123
|
-
if (showPinMenuItemEl) {
|
|
2124
|
-
showPinMenuItemEl.classList.remove('hidden');
|
|
2125
|
-
showPinMenuItemEl.onclick = () => {
|
|
2126
|
-
localStorage.setItem(PIN_HIDDEN_KEY, 'false');
|
|
2127
|
-
renderNavbar(activePageUrl);
|
|
2128
|
-
};
|
|
2129
|
-
}
|
|
2130
|
-
} else {
|
|
2131
|
-
if (pinWrapperEl) pinWrapperEl.classList.remove('hidden');
|
|
2132
|
-
if (showPinMenuItemEl) showPinMenuItemEl.classList.add('hidden');
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
// --- Pin Logic ---
|
|
2137
|
-
function getCurrentPage() {
|
|
2138
|
-
const currentFrameSrc = appFrame.contentWindow.location.href;
|
|
2139
|
-
let pageName = currentFrameSrc.replace(BASE_URL, '');
|
|
2140
|
-
if (!pageName || pageName === 'about:blank') pageName = window.lastLoadedPage || 'dashboard.html';
|
|
2141
|
-
return pageName;
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
// Pin Button
|
|
2145
|
-
if (pinBtn) {
|
|
2146
|
-
pinBtn.addEventListener('click', () => {
|
|
2147
|
-
const pageName = getCurrentPage();
|
|
2148
|
-
const currentPinned = localStorage.getItem(PINNED_PAGE_KEY);
|
|
2149
|
-
|
|
2150
|
-
if (currentPinned === pageName) {
|
|
2151
|
-
localStorage.removeItem(PINNED_PAGE_KEY);
|
|
2152
|
-
} else {
|
|
2153
|
-
localStorage.setItem(PINNED_PAGE_KEY, pageName);
|
|
2154
|
-
}
|
|
2155
|
-
renderNavbar(pageName);
|
|
2156
|
-
});
|
|
2157
|
-
|
|
2158
|
-
// Context Menu
|
|
2159
|
-
pinBtn.addEventListener('contextmenu', (e) => {
|
|
2160
|
-
e.preventDefault();
|
|
2161
|
-
if (pinMenu) pinMenu.classList.toggle('open');
|
|
2162
|
-
if (authMenu) authMenu.classList.remove('open');
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
if (repinBtn) {
|
|
2167
|
-
repinBtn.addEventListener('click', () => {
|
|
2168
|
-
localStorage.setItem(PINNED_PAGE_KEY, getCurrentPage());
|
|
2169
|
-
renderNavbar(getCurrentPage());
|
|
2170
|
-
if (pinMenu) pinMenu.classList.remove('open');
|
|
2171
|
-
});
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
if (removePinBtn) {
|
|
2175
|
-
removePinBtn.addEventListener('click', () => {
|
|
2176
|
-
localStorage.removeItem(PINNED_PAGE_KEY);
|
|
2177
|
-
renderNavbar(getCurrentPage());
|
|
2178
|
-
if (pinMenu) pinMenu.classList.remove('open');
|
|
2179
|
-
});
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
if (hidePinBtn) {
|
|
2183
|
-
hidePinBtn.addEventListener('click', () => {
|
|
2184
|
-
localStorage.setItem(PIN_HIDDEN_KEY, 'true');
|
|
2185
|
-
renderNavbar(getCurrentPage());
|
|
2186
|
-
if (pinMenu) pinMenu.classList.remove('open');
|
|
2187
|
-
});
|
|
2188
2118
|
}
|
|
2189
2119
|
|
|
2190
2120
|
// Auth Menu
|
|
@@ -2192,7 +2122,6 @@
|
|
|
2192
2122
|
authBtn.addEventListener('click', (e) => {
|
|
2193
2123
|
e.stopPropagation();
|
|
2194
2124
|
if (authMenu) authMenu.classList.toggle('open');
|
|
2195
|
-
if (pinMenu) pinMenu.classList.remove('open');
|
|
2196
2125
|
});
|
|
2197
2126
|
}
|
|
2198
2127
|
|
|
@@ -2200,42 +2129,56 @@
|
|
|
2200
2129
|
if (authMenu && authBtn && !authMenu.contains(e.target) && !authBtn.contains(e.target)) {
|
|
2201
2130
|
authMenu.classList.remove('open');
|
|
2202
2131
|
}
|
|
2203
|
-
if (pinMenu && pinBtn && !pinMenu.contains(e.target) && !pinBtn.contains(e.target)) {
|
|
2204
|
-
pinMenu.classList.remove('open');
|
|
2205
|
-
}
|
|
2206
2132
|
});
|
|
2207
2133
|
|
|
2208
2134
|
if (logoutBtn) {
|
|
2209
2135
|
logoutBtn.addEventListener('click', () => {
|
|
2210
2136
|
localStorage.removeItem(STORAGE_KEY);
|
|
2211
2137
|
localStorage.removeItem(USER_DATA_KEY);
|
|
2138
|
+
if (userDataUnsubscribe) userDataUnsubscribe();
|
|
2212
2139
|
location.reload();
|
|
2213
2140
|
});
|
|
2214
2141
|
}
|
|
2215
2142
|
|
|
2216
2143
|
async function fetchUserData(ownerUid) {
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
const [userDoc, adminDoc] = await Promise.all([
|
|
2220
|
-
getDoc(doc(db, "users", ownerUid)),
|
|
2221
|
-
getDoc(doc(db, "admins", ownerUid))
|
|
2222
|
-
]);
|
|
2223
|
-
|
|
2224
|
-
if (userDoc.exists()) {
|
|
2225
|
-
const data = userDoc.data();
|
|
2226
|
-
const userData = {
|
|
2227
|
-
uid: ownerUid,
|
|
2228
|
-
username: data.username || "User",
|
|
2229
|
-
email: data.email || "No email"
|
|
2230
|
-
};
|
|
2231
|
-
localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData));
|
|
2232
|
-
updateUIWithUser(userData);
|
|
2233
|
-
currentUser = userData; // Set global
|
|
2234
|
-
}
|
|
2144
|
+
// Setup Real-time Listener for User Data (PFP Sync)
|
|
2145
|
+
if (userDataUnsubscribe) userDataUnsubscribe();
|
|
2235
2146
|
|
|
2236
|
-
|
|
2147
|
+
try {
|
|
2148
|
+
// Initial fetch to check admin status (less frequent change)
|
|
2149
|
+
const adminDoc = await getDoc(doc(db, "admins", ownerUid));
|
|
2150
|
+
|
|
2151
|
+
userDataUnsubscribe = onSnapshot(doc(db, "users", ownerUid), (docSnap) => {
|
|
2152
|
+
if (docSnap.exists()) {
|
|
2153
|
+
const data = docSnap.data();
|
|
2154
|
+
// Merge with existing local data to preserve properties not in basic user doc if any
|
|
2155
|
+
const newUserData = {
|
|
2156
|
+
...currentUser,
|
|
2157
|
+
uid: ownerUid,
|
|
2158
|
+
username: data.username || "User",
|
|
2159
|
+
email: data.email || "No email",
|
|
2160
|
+
pfpType: data.pfpType,
|
|
2161
|
+
customPfp: data.customPfp,
|
|
2162
|
+
pfpLetterBg: data.pfpLetterBg,
|
|
2163
|
+
navbarTheme: data.navbarTheme // Sync theme if updated remotely
|
|
2164
|
+
};
|
|
2165
|
+
|
|
2166
|
+
localStorage.setItem(USER_DATA_KEY, JSON.stringify(newUserData));
|
|
2167
|
+
updateUIWithUser(newUserData);
|
|
2168
|
+
currentUser = newUserData; // Update global state
|
|
2169
|
+
|
|
2170
|
+
// If settings overlay is open, we might need to update preview there too
|
|
2171
|
+
if (!document.getElementById('settings-overlay').classList.contains('hidden')) {
|
|
2172
|
+
// Dispatch event or manually update if elements exist
|
|
2173
|
+
const preview = document.getElementById('pfp-preview');
|
|
2174
|
+
if (preview) updateSettingsPfpPreview(newUserData, preview);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
// Check Admin Status (kept separate as it's static usually)
|
|
2237
2180
|
let isAdmin = false;
|
|
2238
|
-
if (
|
|
2181
|
+
if (currentUser && currentUser.email === OWNER_EMAIL) isAdmin = true;
|
|
2239
2182
|
if (adminDoc.exists()) isAdmin = true;
|
|
2240
2183
|
|
|
2241
2184
|
if (isAdmin) {
|
|
@@ -2250,11 +2193,27 @@
|
|
|
2250
2193
|
initAnalytics(db, ownerUid);
|
|
2251
2194
|
|
|
2252
2195
|
} catch (e) {
|
|
2253
|
-
console.error("Error
|
|
2254
|
-
// Even on error, try init analytics as anon/fallback
|
|
2196
|
+
console.error("Error setting up user listener:", e);
|
|
2255
2197
|
initAnalytics(db, ownerUid);
|
|
2256
2198
|
}
|
|
2257
2199
|
}
|
|
2200
|
+
|
|
2201
|
+
// Helper to update the settings panel preview if open
|
|
2202
|
+
function updateSettingsPfpPreview(user, previewEl) {
|
|
2203
|
+
const pfpType = user.pfpType || 'letter';
|
|
2204
|
+
if (pfpType === 'custom' && user.customPfp) {
|
|
2205
|
+
previewEl.style.backgroundImage = `url('${user.customPfp}')`;
|
|
2206
|
+
previewEl.style.backgroundColor = "transparent";
|
|
2207
|
+
previewEl.style.backgroundSize = "cover";
|
|
2208
|
+
previewEl.innerText = "";
|
|
2209
|
+
} else {
|
|
2210
|
+
const letter = (user.username || 'U').charAt(0).toUpperCase();
|
|
2211
|
+
const bgColor = user.pfpLetterBg || '#3B82F6';
|
|
2212
|
+
previewEl.style.backgroundImage = 'none';
|
|
2213
|
+
previewEl.style.backgroundColor = bgColor;
|
|
2214
|
+
previewEl.innerText = letter;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2258
2217
|
|
|
2259
2218
|
function updateUIWithUser(userData) {
|
|
2260
2219
|
if (!userData) return;
|
|
@@ -2397,15 +2356,11 @@
|
|
|
2397
2356
|
// Determine start page
|
|
2398
2357
|
let startPage = 'dashboard.html';
|
|
2399
2358
|
const lastPage = localStorage.getItem(LAST_PAGE_KEY);
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
// Priority: Last Page > Pinned > Default
|
|
2359
|
+
|
|
2360
|
+
// Priority: Last Page > > Default
|
|
2403
2361
|
if (lastPage) {
|
|
2404
2362
|
const isValid = Object.values(PAGE_DATA).some(p => p.url === lastPage);
|
|
2405
2363
|
if (isValid) startPage = lastPage;
|
|
2406
|
-
} else if (pinned) {
|
|
2407
|
-
const isValid = Object.values(PAGE_DATA).some(p => p.url === pinned);
|
|
2408
|
-
if (isValid) startPage = pinned;
|
|
2409
2364
|
}
|
|
2410
2365
|
|
|
2411
2366
|
loadPage(startPage);
|
|
@@ -2637,11 +2592,20 @@
|
|
|
2637
2592
|
|
|
2638
2593
|
// --- Settings Data & Actions ---
|
|
2639
2594
|
|
|
2640
|
-
// --- 1. General: Username ---
|
|
2595
|
+
// --- 1. General: Username & New Actions ---
|
|
2641
2596
|
const settingsUsernameInput = document.getElementById('settings-username-input');
|
|
2642
2597
|
const saveUsernameBtn = document.getElementById('save-username-btn');
|
|
2643
2598
|
const usernameMsg = document.getElementById('username-msg');
|
|
2644
2599
|
|
|
2600
|
+
// Add Disconnect Listener
|
|
2601
|
+
document.getElementById('disconnect-account-btn').addEventListener('click', () => {
|
|
2602
|
+
// Re-use existing logout logic
|
|
2603
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
2604
|
+
localStorage.removeItem(USER_DATA_KEY);
|
|
2605
|
+
if (userDataUnsubscribe) userDataUnsubscribe();
|
|
2606
|
+
location.reload();
|
|
2607
|
+
});
|
|
2608
|
+
|
|
2645
2609
|
async function checkProfanity(text) {
|
|
2646
2610
|
try {
|
|
2647
2611
|
const res = await fetch(`https://www.purgomalum.com/service/containsprofanity?text=${encodeURIComponent(text)}`);
|
|
@@ -2679,9 +2643,7 @@
|
|
|
2679
2643
|
|
|
2680
2644
|
// Save
|
|
2681
2645
|
await updateDoc(doc(db, "users", currentUser.uid), { username: newName });
|
|
2682
|
-
|
|
2683
|
-
localStorage.setItem(USER_DATA_KEY, JSON.stringify(currentUser));
|
|
2684
|
-
updateUIWithUser(currentUser);
|
|
2646
|
+
// Local update happens via onSnapshot listener now
|
|
2685
2647
|
usernameMsg.innerText = "Saved!";
|
|
2686
2648
|
usernameMsg.className = "text-green-400 text-xs mt-2 min-h-[20px]";
|
|
2687
2649
|
|
|
@@ -2754,10 +2716,7 @@
|
|
|
2754
2716
|
pfpLetterBg: pfpState.letterColor,
|
|
2755
2717
|
letterAvatarColor: pfpState.letterColor // Legacy support
|
|
2756
2718
|
});
|
|
2757
|
-
//
|
|
2758
|
-
currentUser.pfpType = 'letter';
|
|
2759
|
-
currentUser.pfpLetterBg = pfpState.letterColor;
|
|
2760
|
-
localStorage.setItem(USER_DATA_KEY, JSON.stringify(currentUser));
|
|
2719
|
+
// Local update handled by snapshot
|
|
2761
2720
|
saveLetterPfpBtn.innerText = "Saved!";
|
|
2762
2721
|
setTimeout(() => saveLetterPfpBtn.innerText = "Set Letter Avatar", 2000);
|
|
2763
2722
|
} catch(e) {
|
|
@@ -2923,21 +2882,21 @@
|
|
|
2923
2882
|
try {
|
|
2924
2883
|
submitCropBtn.disabled = true;
|
|
2925
2884
|
submitCropBtn.textContent = "Saving...";
|
|
2885
|
+
|
|
2886
|
+
// 1. Update Firestore
|
|
2926
2887
|
await updateDoc(doc(db, "users", currentUser.uid), {
|
|
2927
2888
|
customPfp: base64,
|
|
2928
2889
|
pfpType: 'custom'
|
|
2929
2890
|
});
|
|
2930
2891
|
|
|
2892
|
+
// 2. Update Local State Immediately
|
|
2931
2893
|
currentUser.customPfp = base64;
|
|
2932
2894
|
currentUser.pfpType = 'custom';
|
|
2933
2895
|
localStorage.setItem(USER_DATA_KEY, JSON.stringify(currentUser));
|
|
2896
|
+
updateUIWithUser(currentUser);
|
|
2897
|
+
updateSettingsPfpPreview(currentUser, pfpPreview);
|
|
2934
2898
|
|
|
2935
|
-
// Update UI
|
|
2936
|
-
pfpPreview.innerText = "";
|
|
2937
|
-
pfpPreview.style.backgroundImage = `url('${base64}')`;
|
|
2938
|
-
pfpPreview.style.backgroundColor = "transparent";
|
|
2939
|
-
pfpPreview.style.backgroundSize = "cover";
|
|
2940
|
-
|
|
2899
|
+
// Update UI Feedback
|
|
2941
2900
|
cropperModal.style.display = 'none';
|
|
2942
2901
|
saveUploadPfpBtn.innerText = "Saved!"; // Feedback on the main btn too
|
|
2943
2902
|
} catch (e) {
|
|
@@ -3151,7 +3110,7 @@
|
|
|
3151
3110
|
// Global store for active keys (in-memory)
|
|
3152
3111
|
window.activePanicKeys = [];
|
|
3153
3112
|
|
|
3154
|
-
|
|
3113
|
+
async function loadPanicKeys() {
|
|
3155
3114
|
try {
|
|
3156
3115
|
const settingsMap = await getPanicKeySettings();
|
|
3157
3116
|
window.activePanicKeys = []; // Reset
|