Generic (non-5e) ruleset mode + UI polish + combat switch fix #7
+52
-2
@@ -1,3 +1,4 @@
|
|||||||
|
//
|
||||||
import React, { useState, useEffect, useRef, useMemo, createContext, useContext, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useMemo, createContext, useContext, useCallback } from 'react';
|
||||||
import * as shared from '@ttrpg/shared';
|
import * as shared from '@ttrpg/shared';
|
||||||
import { initializeApp, getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken, getFirestore, where, orderBy, limit, offset, getStorage, getStorageMode } from './storage';
|
import { initializeApp, getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken, getFirestore, where, orderBy, limit, offset, getStorage, getStorageMode } from './storage';
|
||||||
@@ -2261,7 +2262,10 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
const { data: campaignDoc } = useFirestoreDocument(campaignId ? getPath.campaign(campaignId) : null);
|
const { data: campaignDoc } = useFirestoreDocument(campaignId ? getPath.campaign(campaignId) : null);
|
||||||
|
|
||||||
const [encounters, setEncounters] = useState([]);
|
const [encounters, setEncounters] = useState([]);
|
||||||
const [selectedEncounterId, setSelectedEncounterId] = useState(null);
|
const [selectedEncounterId, setSelectedEncounterId] = useState(() => {
|
||||||
|
try { return localStorage.getItem('ttrpg.selectedEncounter') || null; }
|
||||||
|
catch { return null; }
|
||||||
|
});
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [itemToDelete, setItemToDelete] = useState(null);
|
const [itemToDelete, setItemToDelete] = useState(null);
|
||||||
@@ -2314,6 +2318,10 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedEncounterIdRef.current = selectedEncounterId;
|
selectedEncounterIdRef.current = selectedEncounterId;
|
||||||
|
try {
|
||||||
|
if (selectedEncounterId) localStorage.setItem('ttrpg.selectedEncounter', selectedEncounterId);
|
||||||
|
else localStorage.removeItem('ttrpg.selectedEncounter');
|
||||||
|
} catch {}
|
||||||
}, [selectedEncounterId]);
|
}, [selectedEncounterId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -2636,11 +2644,53 @@ function AdminView({ userId }) {
|
|||||||
|
|
||||||
const [campaignsWithDetails, setCampaignsWithDetails] = useState([]);
|
const [campaignsWithDetails, setCampaignsWithDetails] = useState([]);
|
||||||
const [draggedCampaignId, setDraggedCampaignId] = useState(null);
|
const [draggedCampaignId, setDraggedCampaignId] = useState(null);
|
||||||
const [selectedCampaignId, setSelectedCampaignId] = useState(null);
|
const [selectedCampaignId, setSelectedCampaignId] = useState(() => {
|
||||||
|
try { return localStorage.getItem('ttrpg.selectedCampaign') || null; }
|
||||||
|
catch { return null; }
|
||||||
|
});
|
||||||
const encounterStartedRef = useRef(false);
|
const encounterStartedRef = useRef(false);
|
||||||
const encounterActiveRef = useRef(false);
|
const encounterActiveRef = useRef(false);
|
||||||
const manualSelectRef = useRef(false);
|
const manualSelectRef = useRef(false);
|
||||||
const prevDisplayCampaignRef = useRef(null);
|
const prevDisplayCampaignRef = useRef(null);
|
||||||
|
const scrollRestoredRef = useRef(false);
|
||||||
|
|
||||||
|
// Save scroll on unload + visibility change + interval.
|
||||||
|
useEffect(() => {
|
||||||
|
const onSave = () => {
|
||||||
|
try { localStorage.setItem('ttrpg.scrollY', String(window.scrollY)); } catch {}
|
||||||
|
};
|
||||||
|
window.addEventListener('beforeunload', onSave);
|
||||||
|
window.addEventListener('pagehide', onSave);
|
||||||
|
document.addEventListener('visibilitychange', onSave);
|
||||||
|
const interval = setInterval(onSave, 2000);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeunload', onSave);
|
||||||
|
window.removeEventListener('pagehide', onSave);
|
||||||
|
document.removeEventListener('visibilitychange', onSave);
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Restore scroll once data loaded.
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollRestoredRef.current) return;
|
||||||
|
if (campaignsWithDetails.length === 0) return;
|
||||||
|
try {
|
||||||
|
const y = parseInt(localStorage.getItem('ttrpg.scrollY') || '0', 10);
|
||||||
|
if (y > 0) {
|
||||||
|
scrollRestoredRef.current = true;
|
||||||
|
setTimeout(() => window.scrollTo(0, y), 300);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}, [campaignsWithDetails]);
|
||||||
|
|
||||||
|
// Persist selections across reload.
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
if (selectedCampaignId) localStorage.setItem('ttrpg.selectedCampaign', selectedCampaignId);
|
||||||
|
else localStorage.removeItem('ttrpg.selectedCampaign');
|
||||||
|
} catch {}
|
||||||
|
}, [selectedCampaignId]);
|
||||||
|
|
||||||
// External display change (replay/other DM) = clear manual override, allow follow.
|
// External display change (replay/other DM) = clear manual override, allow follow.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user