feat: Introduce main application UI with family tree, map, and timeline views, alongside updated authentication and documentation.
This commit is contained in:
parent
bde8f2b693
commit
6e2ed796c0
5 changed files with 149 additions and 30 deletions
27
README.md
27
README.md
|
|
@ -1 +1,26 @@
|
|||
https://funnyeivske.github.io/Roesstad/
|
||||
# Røsstad Familietre
|
||||
|
||||
**Nettsiden finner du her:** [https://funnyeivske.github.io/Roesstad/](https://funnyeivske.github.io/Roesstad/)
|
||||
|
||||
## Om Nettsiden
|
||||
Dette er et digitalt samlingspunkt for Røsstad-familietreet. Nettsiden er designet for å bevare og visualisere familiens historie på en moderne og interaktiv måte.
|
||||
|
||||
## Hvordan den fungerer
|
||||
|
||||
### 1. Familietre (Hovedvisning)
|
||||
Her ser du selve slektstreet.
|
||||
- **Navigering:** Du kan dra i bakgrunnen for å flytte deg rundt, og zoome ut/inn.
|
||||
- **Personkort:** Klikk på en person for å sette dem i fokus.
|
||||
- **Legg til:** Bruk den store grønne knappen nede til høyre for å legge til nye familiemedlemmer.
|
||||
|
||||
### 2. Kart
|
||||
Se hvor familien har sine røtter.
|
||||
- Når du legger til en person, kan du skrive inn et sted (f.eks. "Grimstad").
|
||||
- Personen vil da dukke opp som en markør på kartet.
|
||||
|
||||
### 3. Tidslinje
|
||||
En kronologisk oversikt over familiens historie.
|
||||
- Her listes alle fødsler og dødsfall opp automatisk basert på informasjonen i treet.
|
||||
|
||||
---
|
||||
*Laget med ❤️ for Røsstad-familien.*
|
||||
|
|
|
|||
101
Script.js
101
Script.js
|
|
@ -21,13 +21,26 @@ const state = {
|
|||
map: null
|
||||
};
|
||||
|
||||
// --- IMMEDIATE SETUP (UI Handlers) ---
|
||||
// This ensures buttons work even if Auth is slow
|
||||
setupTabs();
|
||||
setupZoom();
|
||||
setupModal();
|
||||
setupHelp();
|
||||
setupFAB();
|
||||
|
||||
// --- AUTHENTICATION ---
|
||||
onAuthStateChanged(auth, (user) => {
|
||||
if (user) {
|
||||
state.user = user;
|
||||
initApp();
|
||||
startDataListener();
|
||||
} else {
|
||||
window.location.href = "index.html";
|
||||
// Redirect to login if not authenticated
|
||||
// Note: In local development with file://, this might trigger unexpectedly.
|
||||
// For now, we assume index.html is the login page.
|
||||
if (!window.location.pathname.endsWith('index.html') && !window.location.pathname.endsWith('/')) {
|
||||
window.location.href = "index.html";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -35,9 +48,8 @@ document.getElementById('btn-logout')?.addEventListener('click', () => {
|
|||
signOut(auth).then(() => window.location.href = "index.html");
|
||||
});
|
||||
|
||||
// --- APP LOGIC ---
|
||||
function initApp() {
|
||||
// Start Data Listener
|
||||
// --- DATA LISTENER ---
|
||||
function startDataListener() {
|
||||
const q = query(collection(db, 'familyMembers'));
|
||||
onSnapshot(q, (snapshot) => {
|
||||
state.people = snapshot.docs.map(d => ({ id: d.id, ...d.data() }));
|
||||
|
|
@ -50,11 +62,6 @@ function initApp() {
|
|||
|
||||
renderAll();
|
||||
});
|
||||
|
||||
// Setup Navigation
|
||||
setupTabs();
|
||||
setupZoom();
|
||||
setupModal();
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
|
|
@ -74,7 +81,8 @@ function setupTabs() {
|
|||
// Show View
|
||||
document.querySelectorAll('.view-section').forEach(v => v.classList.add('hidden'));
|
||||
const viewId = `view-${btn.dataset.view}`;
|
||||
document.getElementById(viewId).classList.remove('hidden');
|
||||
const viewEl = document.getElementById(viewId);
|
||||
if (viewEl) viewEl.classList.remove('hidden');
|
||||
|
||||
// Special handling for Map resize
|
||||
if (btn.dataset.view === 'map' && state.map) {
|
||||
|
|
@ -97,7 +105,10 @@ function renderTree() {
|
|||
childrenRow.innerHTML = '';
|
||||
|
||||
if (state.people.length === 0) {
|
||||
focusRow.innerHTML = `<button class="btn-primary" onclick="window.openModal('create-first')">Start familietreet</button>`;
|
||||
focusRow.innerHTML = `<div style="text-align:center; color:var(--color-text-muted);">
|
||||
<p>Ingen personer i treet ennå.</p>
|
||||
<p>Klikk på <i class="ph-bold ph-plus-circle" style="color:var(--color-primary);"></i> nede til høyre for å starte!</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -203,6 +214,9 @@ function createSlot(person, label, action) {
|
|||
// --- MAP RENDERER ---
|
||||
function renderMap() {
|
||||
if (!state.map) {
|
||||
const mapContainer = document.getElementById('map-container');
|
||||
if (!mapContainer) return;
|
||||
|
||||
state.map = L.map('map-container').setView([65.0, 13.0], 5);
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© OpenStreetMap © CARTO',
|
||||
|
|
@ -274,18 +288,21 @@ function renderTimeline() {
|
|||
function setupModal() {
|
||||
window.openModal = (action) => {
|
||||
state.modal.action = action;
|
||||
document.getElementById('modal-overlay').classList.remove('hidden');
|
||||
const overlay = document.getElementById('modal-overlay');
|
||||
if (overlay) overlay.classList.remove('hidden');
|
||||
|
||||
// Reset Form
|
||||
document.querySelectorAll('.input-field').forEach(i => i.value = '');
|
||||
|
||||
// Set Title
|
||||
const title = document.getElementById('modal-title');
|
||||
if (action === 'add-parent-m') title.textContent = "Legg til Far";
|
||||
else if (action === 'add-parent-f') title.textContent = "Legg til Mor";
|
||||
else if (action === 'add-child') title.textContent = "Legg til Barn";
|
||||
else if (action === 'add-partner') title.textContent = "Legg til Partner";
|
||||
else title.textContent = "Start Tre";
|
||||
if (title) {
|
||||
if (action === 'add-parent-m') title.textContent = "Legg til Far";
|
||||
else if (action === 'add-parent-f') title.textContent = "Legg til Mor";
|
||||
else if (action === 'add-child') title.textContent = "Legg til Barn";
|
||||
else if (action === 'add-partner') title.textContent = "Legg til Partner";
|
||||
else title.textContent = "Legg til Person";
|
||||
}
|
||||
|
||||
// Default Mode
|
||||
window.setModalMode('new');
|
||||
|
|
@ -300,11 +317,13 @@ function setupModal() {
|
|||
document.getElementById('form-existing').classList.toggle('hidden', mode !== 'existing');
|
||||
};
|
||||
|
||||
document.getElementById('btn-close-modal').onclick = () => {
|
||||
const closeBtn = document.getElementById('btn-close-modal');
|
||||
if (closeBtn) closeBtn.onclick = () => {
|
||||
document.getElementById('modal-overlay').classList.add('hidden');
|
||||
};
|
||||
|
||||
document.getElementById('btn-save').onclick = async () => {
|
||||
const saveBtn = document.getElementById('btn-save');
|
||||
if (saveBtn) saveBtn.onclick = async () => {
|
||||
const focusP = state.people.find(p => p.id === state.focusId);
|
||||
let targetId = null;
|
||||
|
||||
|
|
@ -335,14 +354,18 @@ function setupModal() {
|
|||
|
||||
const docRef = await addDoc(collection(db, 'familyMembers'), newPerson);
|
||||
targetId = docRef.id;
|
||||
if (state.modal.action === 'create-first') state.focusId = targetId;
|
||||
|
||||
// If creating first person or using generic add
|
||||
if (state.modal.action === 'create-first' || !state.focusId) {
|
||||
state.focusId = targetId;
|
||||
}
|
||||
|
||||
} else {
|
||||
targetId = document.getElementById('selected-existing-id').value;
|
||||
}
|
||||
|
||||
// Link Relations
|
||||
if (focusP && targetId) {
|
||||
if (focusP && targetId && state.modal.action !== 'create-first' && state.modal.action !== 'add-generic') {
|
||||
const col = collection(db, 'familyMembers');
|
||||
const updates = [];
|
||||
|
||||
|
|
@ -365,6 +388,7 @@ function setupModal() {
|
|||
|
||||
function renderExistingList() {
|
||||
const list = document.getElementById('existing-list');
|
||||
if (!list) return;
|
||||
list.innerHTML = '';
|
||||
state.people.filter(p => p.id !== state.focusId).forEach(p => {
|
||||
const div = document.createElement('div');
|
||||
|
|
@ -385,17 +409,42 @@ function setupZoom() {
|
|||
const grid = document.getElementById('tree-grid');
|
||||
const display = document.getElementById('zoom-display');
|
||||
|
||||
document.getElementById('btn-zoom-in').onclick = () => {
|
||||
const btnIn = document.getElementById('btn-zoom-in');
|
||||
if (btnIn) btnIn.onclick = () => {
|
||||
state.scale += 0.1;
|
||||
updateZoom();
|
||||
};
|
||||
document.getElementById('btn-zoom-out').onclick = () => {
|
||||
|
||||
const btnOut = document.getElementById('btn-zoom-out');
|
||||
if (btnOut) btnOut.onclick = () => {
|
||||
if (state.scale > 0.4) state.scale -= 0.1;
|
||||
updateZoom();
|
||||
};
|
||||
|
||||
function updateZoom() {
|
||||
grid.style.transform = `scale(${state.scale})`;
|
||||
display.textContent = Math.round(state.scale * 100) + '%';
|
||||
if (grid) grid.style.transform = `scale(${state.scale})`;
|
||||
if (display) display.textContent = Math.round(state.scale * 100) + '%';
|
||||
}
|
||||
}
|
||||
|
||||
// --- HELP ---
|
||||
function setupHelp() {
|
||||
const btn = document.getElementById('btn-help');
|
||||
const modal = document.getElementById('modal-help');
|
||||
const close = document.getElementById('btn-close-help');
|
||||
|
||||
if (btn && modal) {
|
||||
btn.onclick = () => modal.classList.remove('hidden');
|
||||
}
|
||||
if (close && modal) {
|
||||
close.onclick = () => modal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// --- FAB ---
|
||||
function setupFAB() {
|
||||
const fab = document.getElementById('fab-add');
|
||||
if (fab) {
|
||||
fab.onclick = () => window.openModal('add-generic');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@
|
|||
|
||||
<!-- CONTROLS -->
|
||||
<div class="header-controls">
|
||||
<button id="btn-help" class="icon-btn" title="Hjelp"><i class="ph ph-question"></i></button>
|
||||
<div style="width:1px; height:24px; background:#cbd5e1; margin:0 0.5rem;"></div>
|
||||
|
||||
<button id="btn-zoom-out" class="icon-btn" title="Zoom ut"><i class="ph ph-minus-circle"></i></button>
|
||||
<span id="zoom-display"
|
||||
style="font-size:0.9rem; font-weight:600; width:40px; text-align:center;">100%</span>
|
||||
|
|
@ -73,10 +76,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FLOATING ACTION BUTTON (FAB) -->
|
||||
<button id="fab-add" class="fab-btn" title="Legg til person">
|
||||
<i class="ph-bold ph-plus"></i>
|
||||
</button>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- MODAL -->
|
||||
<!-- MODAL: ADD PERSON -->
|
||||
<div id="modal-overlay" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
|
@ -142,6 +150,29 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODAL: HELP -->
|
||||
<div id="modal-help" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Hjelp & Instruksjoner</h3>
|
||||
<button id="btn-close-help" class="icon-btn"><i class="ph ph-x"></i></button>
|
||||
</div>
|
||||
<div style="line-height:1.6; color:var(--color-text-main);">
|
||||
<p><strong>Velkommen til Røsstad Familietre!</strong></p>
|
||||
<ul style="padding-left:1.2rem; margin-bottom:1.5rem;">
|
||||
<li><strong>Legg til person:</strong> Klikk på den store grønne pluss-knappen nede til høyre, eller
|
||||
bruk de små knappene på kortene.</li>
|
||||
<li><strong>Navigasjon:</strong> Bruk fanene øverst for å bytte mellom Tre, Kart og Tidslinje.</li>
|
||||
<li><strong>Zoom:</strong> Bruk pluss/minus knappene oppe til høyre for å zoome i treet.</li>
|
||||
<li><strong>Flytt treet:</strong> Du kan dra og slippe treet for å bevege deg rundt.</li>
|
||||
</ul>
|
||||
<p><em>Tips: Legg til et sted (f.eks. "Grimstad") for å se personen på kartet!</em></p>
|
||||
</div>
|
||||
<button class="btn-primary"
|
||||
onclick="document.getElementById('modal-help').classList.add('hidden')">Forstått</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
2
login.js
2
login.js
|
|
@ -31,7 +31,7 @@ if (form) {
|
|||
try {
|
||||
await signInWithEmailAndPassword(auth, email, password);
|
||||
// Redirect on success
|
||||
window.location.href = "Røsstad.html";
|
||||
window.location.href = "app.html";
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
handleError(error);
|
||||
|
|
|
|||
14
style.css
14
style.css
|
|
@ -567,3 +567,17 @@ body.login-page {
|
|||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
/* --- FAB (Floating Action Button) --- */
|
||||
.fab-btn {
|
||||
position: fixed; bottom: 2rem; right: 2rem;
|
||||
width: 64px; height: 64px; border-radius: 50%;
|
||||
background: var(--color-primary); color: white;
|
||||
border: none; box-shadow: var(--shadow-lg);
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
font-size: 2rem; cursor: pointer; z-index: 500;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
}
|
||||
.fab-btn:hover {
|
||||
transform: scale(1.1) rotate(90deg);
|
||||
background: var(--color-primary-dark);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue