Skip to content

Commit a2c84a8

Browse files
committed
Index in JS gesplittet.
1 parent 5f1c290 commit a2c84a8

8 files changed

Lines changed: 9337 additions & 8662 deletions

File tree

index.html

Lines changed: 84 additions & 8662 deletions
Large diffs are not rendered by default.

static/css/styles.css

Lines changed: 1206 additions & 0 deletions
Large diffs are not rendered by default.

static/js/auth-presence.js

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/* =========================================================================
2+
auth-presence.js — Auth, Login-Modal, Presence + generische Modals
3+
=========================================================================
4+
- window.showModal(message, isConfirm?) → Promise<true|false>
5+
- window.showPrompt(message, defaultValue?) → Promise<string|null>
6+
- showLoginModal() → Promise<void>
7+
- setupAuthUI() → Login-State + Online-Counter
8+
9+
Wird in main.js während DOMContentLoaded aufgerufen.
10+
========================================================================= */
11+
12+
import {
13+
db, auth,
14+
DATA_COLLECTION, HISTORY_COLLECTION, USER_PROFILES_COLLECTION,
15+
LOOT_COLLECTION, SNAPSHOTS_COLLECTION,
16+
rosterDocRef, historyCollectionRef, userProfilesCollectionRef,
17+
lootCollectionRef, denylistCollectionRef, aliasDocRef, snapshotsCollectionRef,
18+
doc, setDoc, onSnapshot, collection, deleteDoc, getDoc,
19+
serverTimestamp, query, orderBy, addDoc, updateDoc, where,
20+
getDocs, limit,
21+
onAuthStateChanged, signInWithEmailAndPassword, signOut, signInAnonymously
22+
} from './firebase-init.js';
23+
24+
import { state, offensiveBuffsForAssignment, getCurrentRaidId, debounce, debouncedUpdatePools, debouncedUpdateSummary } from './state.js';
25+
26+
27+
// =============================================================================
28+
// MODAL-FUNKTION (window.showModal)
29+
// =============================================================================
30+
31+
// ============== MODAL-FUNKTION (global verfügbar) ==============
32+
window.showModal = function(message, isConfirm = false) {
33+
34+
// 1. AUFRÄUMEN: Nur dynamische Nachrichten-Fenster löschen!
35+
const allOverlays = document.querySelectorAll('.modal-overlay');
36+
allOverlays.forEach(el => {
37+
// Wir prüfen anhand der ID, ob es ein "wichtiges" statisches Fenster ist
38+
const isStatic = (el.id === 'player-edit-modal' || el.id === 'login-modal-overlay' || el.id === 'image-window.lightbox');
39+
40+
// Nur löschen, wenn es KEIN statisches Fenster ist
41+
if (!isStatic) {
42+
el.remove();
43+
}
44+
});
45+
46+
return new Promise((resolve) => {
47+
const modalOverlay = document.createElement('div');
48+
modalOverlay.className = 'modal-overlay';
49+
// WICHTIG: Keine ID vergeben, damit es als dynamisch erkannt wird
50+
51+
modalOverlay.innerHTML = `
52+
<div class="modal-content">
53+
<h3>Nachricht</h3>
54+
<p>${message}</p>
55+
<div class="modal-buttons">
56+
${isConfirm ? `<button id="modal-cancel-btn" class="cancel-btn">Abbrechen</button>` : ''}
57+
<button id="modal-ok-btn">OK</button>
58+
</div>
59+
</div>
60+
`;
61+
document.body.appendChild(modalOverlay);
62+
63+
const okButton = modalOverlay.querySelector('#modal-ok-btn');
64+
const cancelButton = modalOverlay.querySelector('#modal-cancel-btn');
65+
66+
const close = (value) => {
67+
modalOverlay.remove();
68+
resolve(value);
69+
};
70+
71+
okButton.addEventListener('click', () => close(true));
72+
if (cancelButton) {
73+
cancelButton.addEventListener('click', () => close(false));
74+
}
75+
});
76+
};
77+
78+
79+
// =============================================================================
80+
// PROMPT-FUNKTION (window.showPrompt) — Eingabe-Dialog
81+
// =============================================================================
82+
83+
window.showPrompt = function(message, defaultValue = '') {
84+
return new Promise((resolve) => {
85+
const modalOverlay = document.createElement('div');
86+
modalOverlay.className = 'modal-overlay';
87+
88+
modalOverlay.innerHTML = `
89+
<div class="modal-content">
90+
<h3>Eingabe</h3>
91+
<p>${message}</p>
92+
<input type="text" id="prompt-input"
93+
class="w-full bg-slate-900 border border-slate-600 rounded p-2 text-white focus:border-gold outline-none mb-4"
94+
value="${defaultValue.replace(/"/g, '&quot;')}">
95+
<div class="modal-buttons">
96+
<button id="prompt-cancel-btn" class="cancel-btn">Abbrechen</button>
97+
<button id="prompt-ok-btn">OK</button>
98+
</div>
99+
</div>
100+
`;
101+
document.body.appendChild(modalOverlay);
102+
103+
const input = modalOverlay.querySelector('#prompt-input');
104+
const okBtn = modalOverlay.querySelector('#prompt-ok-btn');
105+
const cancelBtn = modalOverlay.querySelector('#prompt-cancel-btn');
106+
107+
input.focus();
108+
input.select();
109+
110+
const close = (value) => {
111+
modalOverlay.remove();
112+
resolve(value);
113+
};
114+
115+
okBtn.addEventListener('click', () => close(input.value.trim()));
116+
cancelBtn.addEventListener('click', () => close(null));
117+
input.addEventListener('keydown', (e) => {
118+
if (e.key === 'Enter') close(input.value.trim());
119+
if (e.key === 'Escape') close(null);
120+
});
121+
});
122+
};
123+
124+
// =============================================================================
125+
// LOGIN-MODAL
126+
// =============================================================================
127+
128+
// ============== LOGIN MODAL FUNKTION ==============
129+
function showLoginModal() {
130+
return new Promise((resolve) => {
131+
const loginModalOverlay = document.getElementById('login-modal-overlay');
132+
const loginForm = document.getElementById('login-form');
133+
const loginUsernameInput = document.getElementById('login-username');
134+
const loginPasswordInput = document.getElementById('login-password');
135+
const loginCancelBtn = document.getElementById('login-modal-cancel-btn');
136+
137+
loginUsernameInput.value = '';
138+
loginPasswordInput.value = '';
139+
loginModalOverlay.classList.remove('hidden');
140+
loginUsernameInput.focus();
141+
142+
const handleSubmit = async (event) => {
143+
// Verhindert das Neuladen der Seite, was das Standardverhalten eines Formulars ist.
144+
event.preventDefault();
145+
146+
const username = loginUsernameInput.value;
147+
const password = loginPasswordInput.value;
148+
149+
if (!username || !password) {
150+
window.showModal("Bitte Benutzername und Passwort eingeben.");
151+
return;
152+
}
153+
154+
try {
155+
// Die Logik zum Anmelden bei Firebase bleibt hier exakt gleich.
156+
const q = query(userProfilesCollectionRef, where("username", "==", username));
157+
const querySnapshot = await getDocs(q);
158+
159+
if (querySnapshot.empty) {
160+
window.showModal("Benutzername nicht gefunden.");
161+
return;
162+
}
163+
const userProfile = querySnapshot.docs[0].data();
164+
const userCredential = await signInWithEmailAndPassword(auth, userProfile.email, password);
165+
166+
if (userProfile.isManager) {
167+
sessionStorage.setItem('currentManager', username);
168+
location.reload();
169+
} else {
170+
await signOut(auth);
171+
window.showModal("Dieses Konto ist keinem Gildenrat-Status zugeordnet.");
172+
}
173+
} catch (error) {
174+
console.error("Login-Fehler:", error);
175+
window.showModal("Falscher Benutzername oder falsches Passwort.");
176+
}
177+
};
178+
179+
const handleCancel = () => {
180+
cleanupAndResolve(false);
181+
};
182+
183+
// Hilfsfunktion, um alle Event-Listener wieder zu entfernen und das Modal zu schließen
184+
const cleanupAndResolve = (value) => {
185+
loginForm.removeEventListener('submit', handleSubmit);
186+
loginCancelBtn.removeEventListener('click', handleCancel);
187+
loginModalOverlay.classList.add('hidden');
188+
resolve(value);
189+
}
190+
191+
// Wir lauschen jetzt auf das 'submit'-Ereignis des Formulars
192+
loginForm.addEventListener('submit', handleSubmit);
193+
loginCancelBtn.addEventListener('click', handleCancel);
194+
195+
loginModalOverlay.addEventListener('click', function clickOutside(event) {
196+
if (event.target === loginModalOverlay) {
197+
handleCancel();
198+
}
199+
});
200+
});
201+
}
202+
203+
204+
window.showLoginModal = showLoginModal;
205+
206+
// =============================================================================
207+
// AUTH-UI & PRESENCE-INDICATOR
208+
// =============================================================================
209+
210+
async function setupAuthUI() {
211+
const authSection = document.getElementById('auth-section');
212+
const currentManagerUsername = sessionStorage.getItem('currentManager');
213+
214+
if (currentManagerUsername) {
215+
const q = query(userProfilesCollectionRef, where("username", "==", currentManagerUsername));
216+
const querySnapshot = await getDocs(q);
217+
218+
if (!querySnapshot.empty) {
219+
const userProfile = querySnapshot.docs[0].data();
220+
if (userProfile.isManager) {
221+
window.isManager = true;
222+
authSection.innerHTML = `<span class="text-white">Angemeldet als: <strong>${currentManagerUsername}</strong></span><button id="logout-btn" class="ml-2 bg-red-600 hover:bg-red-700 text-white font-bold py-1 px-3 rounded-md text-sm">Logout</button>`;
223+
document.getElementById('logout-btn').addEventListener('click', async () => {
224+
await signOut(auth);
225+
sessionStorage.removeItem('currentManager');
226+
location.hash = '';
227+
location.reload();
228+
});
229+
return;
230+
}
231+
}
232+
sessionStorage.removeItem('currentManager');
233+
}
234+
235+
window.isManager = false;
236+
authSection.innerHTML = `<button id="login-btn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded-md text-sm">Gildenrat-Login</button>`;
237+
document.getElementById('login-btn').addEventListener('click', showLoginModal);
238+
}
239+
240+
onAuthStateChanged(auth, user => {
241+
const presenceIndicator = document.getElementById('presence-indicator');
242+
if (window.heartbeatIntervalId) clearInterval(window.heartbeatIntervalId);
243+
244+
if (!user) {
245+
signInAnonymously(auth).catch(e => console.error("Anonymer Login-Fehler:", e));
246+
presenceIndicator.innerHTML = `<div class="w-3 h-3 bg-gray-500 rounded-full"></div><span>0</span> Online`;
247+
return;
248+
}
249+
250+
const userStatusRef = doc(db, "presence", user.uid);
251+
const updatePresenceTimestamp = () => {
252+
setDoc(userStatusRef, { online: true, last_changed: serverTimestamp() }, { merge: true });
253+
};
254+
updatePresenceTimestamp();
255+
window.heartbeatIntervalId = setInterval(updatePresenceTimestamp, 15 * 60 * 1000);
256+
257+
onSnapshot(collection(db, "presence"), snap => {
258+
const nowInSeconds = Date.now() / 1000;
259+
const onlineUsersCount = snap.docs.filter(doc => {
260+
const data = doc.data();
261+
return data.online && data.last_changed && (nowInSeconds - data.last_changed.seconds < 1000);
262+
}).length;
263+
presenceIndicator.innerHTML = `<div class="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div><span>${onlineUsersCount}</span> Online`;
264+
});
265+
});
266+
267+
window.setupAuthUI = setupAuthUI;

static/js/firebase-init.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* =========================================================================
2+
firebase-init.js — Firebase Setup
3+
=========================================================================
4+
Initialisiert Firebase App, Firestore (mit lokalem Cache), Auth. Exportiert die wichtigsten Refs und Helper-Funktionen, die andere Module brauchen.
5+
=========================================================================
6+
EXPORTIERT (window.*):
7+
- db, auth, app (über Imports)
8+
- rosterDocRef, historyCollectionRef, userProfilesCollectionRef,
9+
- lootCollectionRef, denylistCollectionRef, aliasDocRef, snapshotsCollectionRef
10+
- Konstanten: DATA_COLLECTION, HISTORY_COLLECTION, USER_PROFILES_COLLECTION, LOOT_COLLECTION, SNAPSHOTS_COLLECTION
11+
- Firestore-Funktionen (re-exportiert): doc, setDoc, onSnapshot, collection, etc.
12+
- Auth-Funktionen: onAuthStateChanged, signInWithEmailAndPassword, signOut, signInAnonymously
13+
- window.firebaseTools = { db, doc, getDoc, collection, getDocs, updateDoc }
14+
========================================================================= */
15+
16+
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
17+
import {
18+
getFirestore, initializeFirestore, persistentLocalCache,
19+
doc, setDoc, onSnapshot, collection, deleteDoc, getDoc,
20+
serverTimestamp, query, orderBy, addDoc, updateDoc, where,
21+
getDocs, limit
22+
} from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
23+
import {
24+
getAuth, signInAnonymously, onAuthStateChanged,
25+
signInWithEmailAndPassword, signOut
26+
} from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
27+
28+
// Firestore-Funktionen re-exportieren, damit andere Module nicht selbst importieren müssen
29+
export {
30+
doc, setDoc, onSnapshot, collection, deleteDoc, getDoc,
31+
serverTimestamp, query, orderBy, addDoc, updateDoc, where,
32+
getDocs, limit,
33+
onAuthStateChanged, signInWithEmailAndPassword, signOut, signInAnonymously
34+
};
35+
36+
37+
// =============================================================================
38+
// FIREBASE-CONFIG & INIT
39+
// =============================================================================
40+
41+
const firebaseConfig = {
42+
apiKey: "AIzaSyBmqCCIOKq0OQOTEgJJ7Lj8CYlLihVBVSU",
43+
authDomain: "panik-raid.firebaseapp.com",
44+
projectId: "panik-raid",
45+
storageBucket: "panik-raid.appspot.com",
46+
messagingSenderId: "120578974053",
47+
appId: "1:120578974053:web:927a81dccbb4b33f86c18c"
48+
};
49+
50+
export const app = initializeApp(firebaseConfig);
51+
52+
// Firestore mit persistentem Cache initialisieren
53+
export const db = initializeFirestore(app, {
54+
localCache: persistentLocalCache()
55+
});
56+
57+
export const auth = getAuth(app);
58+
59+
60+
// =============================================================================
61+
// COLLECTION-KONSTANTEN
62+
// =============================================================================
63+
64+
export const DATA_COLLECTION = "raid-tool-data";
65+
export const HISTORY_COLLECTION = "raid-tool-history";
66+
export const USER_PROFILES_COLLECTION = "user_profiles";
67+
export const LOOT_COLLECTION = "raid-tool-loot";
68+
export const SNAPSHOTS_COLLECTION = "raid-tool-snapshots";
69+
70+
71+
// =============================================================================
72+
// FIRESTORE-REFERENZEN
73+
// =============================================================================
74+
75+
export const rosterDocRef = doc(db, DATA_COLLECTION, "currentRoster");
76+
export const historyCollectionRef = collection(db, HISTORY_COLLECTION);
77+
export const userProfilesCollectionRef = collection(db, USER_PROFILES_COLLECTION);
78+
export const lootCollectionRef = collection(db, LOOT_COLLECTION);
79+
export const denylistCollectionRef = collection(db, "snapshot_player_denylist");
80+
export const aliasDocRef = doc(db, DATA_COLLECTION, "nameAliasMap");
81+
export const snapshotsCollectionRef = collection(db, SNAPSHOTS_COLLECTION);
82+
83+
84+
// =============================================================================
85+
// GLOBAL-EXPOSURE (für Legacy-Code)
86+
// =============================================================================
87+
88+
// Damit Inline-Handler und andere Module ohne Import zugreifen können.
89+
// Wir exposen großzügig, damit alte Code-Pfade (slot-system.js, cd-auto-planner.js,
90+
// dynamisch geladene Boss-Seiten) ohne Anpassung funktionieren.
91+
window.firebaseTools = {
92+
db, auth,
93+
doc, setDoc, onSnapshot, collection, deleteDoc, getDoc,
94+
serverTimestamp, query, orderBy, addDoc, updateDoc, where,
95+
getDocs, limit
96+
};

0 commit comments

Comments
 (0)