cleaned up dm eyeball toggle
This commit is contained in:
parent
c7215bb503
commit
eb114910f8
228
src/App.js
228
src/App.js
@ -38,14 +38,15 @@ if (missingKeys.length > 0) {
|
|||||||
const APP_ID = process.env.REACT_APP_TRACKER_APP_ID || 'ttrpg-initiative-tracker-default';
|
const APP_ID = process.env.REACT_APP_TRACKER_APP_ID || 'ttrpg-initiative-tracker-default';
|
||||||
const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
|
const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
|
||||||
const CAMPAIGNS_COLLECTION = `${PUBLIC_DATA_PATH}/campaigns`;
|
const CAMPAIGNS_COLLECTION = `${PUBLIC_DATA_PATH}/campaigns`;
|
||||||
const ACTIVE_DISPLAY_DOC = `${PUBLIC_DATA_PATH}/activeDisplay/status`;
|
const ACTIVE_DISPLAY_DOC = `${PUBLIC_DATA_PATH}/activeDisplay/status`; // Single source for what player display shows
|
||||||
|
|
||||||
// --- Helper Functions ---
|
// --- Helper Functions ---
|
||||||
const generateId = () => crypto.randomUUID();
|
const generateId = () => crypto.randomUUID();
|
||||||
|
|
||||||
function getShareableLinkBase() {
|
// getShareableLinkBase is no longer needed for individual encounter links if we have one player display URL
|
||||||
return window.location.origin + window.location.pathname;
|
// function getShareableLinkBase() {
|
||||||
}
|
// return window.location.origin + window.location.pathname;
|
||||||
|
// }
|
||||||
|
|
||||||
// --- Main App Component ---
|
// --- Main App Component ---
|
||||||
function App() {
|
function App() {
|
||||||
@ -54,7 +55,7 @@ function App() {
|
|||||||
const [viewMode, setViewMode] = useState('admin');
|
const [viewMode, setViewMode] = useState('admin');
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [directDisplayParams, setDirectDisplayParams] = useState(null);
|
// directDisplayParams and hash routing removed, Player Display solely relies on ACTIVE_DISPLAY_DOC
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
@ -63,22 +64,6 @@ function App() {
|
|||||||
setIsAuthReady(false);
|
setIsAuthReady(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const handleHashChange = () => {
|
|
||||||
const hash = window.location.hash;
|
|
||||||
if (hash.startsWith('#/display/')) {
|
|
||||||
const parts = hash.substring('#/display/'.length).split('/');
|
|
||||||
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
||||||
setDirectDisplayParams({ campaignId: parts[0], encounterId: parts[1] });
|
|
||||||
setViewMode('display');
|
|
||||||
} else {
|
|
||||||
setDirectDisplayParams(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setDirectDisplayParams(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener('hashchange', handleHashChange);
|
|
||||||
handleHashChange();
|
|
||||||
|
|
||||||
const initAuth = async () => {
|
const initAuth = async () => {
|
||||||
try {
|
try {
|
||||||
@ -101,7 +86,6 @@ function App() {
|
|||||||
});
|
});
|
||||||
initAuth();
|
initAuth();
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('hashchange', handleHashChange);
|
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -131,13 +115,10 @@ function App() {
|
|||||||
|
|
||||||
const goToAdminView = () => {
|
const goToAdminView = () => {
|
||||||
setViewMode('admin');
|
setViewMode('admin');
|
||||||
setDirectDisplayParams(null);
|
|
||||||
window.location.hash = '';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-800 text-slate-100 font-sans">
|
<div className="min-h-screen bg-slate-800 text-slate-100 font-sans">
|
||||||
{!directDisplayParams && (
|
|
||||||
<header className="bg-slate-900 p-4 shadow-lg">
|
<header className="bg-slate-900 p-4 shadow-lg">
|
||||||
<div className="container mx-auto flex justify-between items-center">
|
<div className="container mx-auto flex justify-between items-center">
|
||||||
<h1
|
<h1
|
||||||
@ -164,21 +145,15 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)}
|
|
||||||
|
|
||||||
<main className={`container mx-auto p-4 md:p-8 ${directDisplayParams ? 'pt-8' : ''}`}>
|
<main className={`container mx-auto p-4 md:p-8`}>
|
||||||
{directDisplayParams && isAuthReady && (
|
{viewMode === 'admin' && isAuthReady && userId && <AdminView userId={userId} />}
|
||||||
<DisplayView campaignIdFromUrl={directDisplayParams.campaignId} encounterIdFromUrl={directDisplayParams.encounterId} />
|
{viewMode === 'display' && isAuthReady && <DisplayView />} {/* DisplayView no longer needs URL params */}
|
||||||
)}
|
|
||||||
{!directDisplayParams && viewMode === 'admin' && isAuthReady && userId && <AdminView userId={userId} />}
|
|
||||||
{!directDisplayParams && viewMode === 'display' && isAuthReady && <DisplayView />}
|
|
||||||
{!isAuthReady && !error && <p>Authenticating...</p>}
|
{!isAuthReady && !error && <p>Authenticating...</p>}
|
||||||
</main>
|
</main>
|
||||||
{!directDisplayParams && (
|
<footer className="bg-slate-900 p-4 text-center text-sm text-slate-400 mt-8">
|
||||||
<footer className="bg-slate-900 p-4 text-center text-sm text-slate-400 mt-8">
|
TTRPG Initiative Tracker v0.1.16
|
||||||
TTRPG Initiative Tracker v0.1.15
|
</footer>
|
||||||
</footer>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -402,7 +377,7 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
const [selectedEncounterId, setSelectedEncounterId] = useState(null);
|
const [selectedEncounterId, setSelectedEncounterId] = useState(null);
|
||||||
const [showCreateEncounterModal, setShowCreateEncounterModal] = useState(false);
|
const [showCreateEncounterModal, setShowCreateEncounterModal] = useState(false);
|
||||||
const [activeDisplayInfo, setActiveDisplayInfo] = useState(null);
|
const [activeDisplayInfo, setActiveDisplayInfo] = useState(null);
|
||||||
const [copiedLinkEncounterId, setCopiedLinkEncounterId] = useState(null);
|
// copiedLinkEncounterId removed as shareable links per encounter are removed
|
||||||
const encountersPath = `${CAMPAIGNS_COLLECTION}/${campaignId}/encounters`;
|
const encountersPath = `${CAMPAIGNS_COLLECTION}/${campaignId}/encounters`;
|
||||||
const selectedEncounterIdRef = useRef(selectedEncounterId);
|
const selectedEncounterIdRef = useRef(selectedEncounterId);
|
||||||
|
|
||||||
@ -464,44 +439,39 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
await deleteDoc(doc(db, encountersPath, encounterId));
|
await deleteDoc(doc(db, encountersPath, encounterId));
|
||||||
if (selectedEncounterId === encounterId) setSelectedEncounterId(null);
|
if (selectedEncounterId === encounterId) setSelectedEncounterId(null);
|
||||||
if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
|
if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
|
||||||
await updateDoc(doc(db, ACTIVE_DISPLAY_DOC), { activeEncounterId: null });
|
await updateDoc(doc(db, ACTIVE_DISPLAY_DOC), { activeCampaignId: null, activeEncounterId: null });
|
||||||
}
|
}
|
||||||
} catch (err) { console.error("Error deleting encounter:", err); }
|
} catch (err) { console.error("Error deleting encounter:", err); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetEncounterAsActiveDisplay = async (encounterId) => {
|
const handleTogglePlayerDisplayForEncounter = async (encounterId) => {
|
||||||
if (!db) return;
|
if (!db) return;
|
||||||
try {
|
try {
|
||||||
const currentActiveCampaign = activeDisplayInfo?.activeCampaignId;
|
const currentActiveCampaign = activeDisplayInfo?.activeCampaignId;
|
||||||
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
|
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
|
||||||
|
|
||||||
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
|
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
|
||||||
// Currently active, so toggle off
|
// Currently active, so toggle off (clear the active display)
|
||||||
await updateDoc(doc(db, ACTIVE_DISPLAY_DOC), {
|
await setDoc(doc(db, ACTIVE_DISPLAY_DOC), {
|
||||||
activeCampaignId: null,
|
activeCampaignId: null,
|
||||||
activeEncounterId: null,
|
activeEncounterId: null,
|
||||||
});
|
}, { merge: true }); // Use set with merge to ensure document exists or is overwritten
|
||||||
console.log("Encounter display toggled OFF for DM.");
|
console.log("Player Display for this encounter turned OFF.");
|
||||||
} else {
|
} else {
|
||||||
// Not active or different encounter, so set this one as active
|
// Not active or different encounter, so set this one as active for players
|
||||||
await setDoc(doc(db, ACTIVE_DISPLAY_DOC), {
|
await setDoc(doc(db, ACTIVE_DISPLAY_DOC), {
|
||||||
activeCampaignId: campaignId,
|
activeCampaignId: campaignId,
|
||||||
activeEncounterId: encounterId,
|
activeEncounterId: encounterId,
|
||||||
}, { merge: true });
|
}, { merge: true });
|
||||||
console.log("Encounter set as active for DM's main display!");
|
console.log("Encounter set as active for Player Display!");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error toggling active display:", err);
|
console.error("Error toggling Player Display for encounter:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyToClipboard = (encounterId) => {
|
// Shareable link per encounter removed
|
||||||
const link = `${getShareableLinkBase()}#/display/${campaignId}/${encounterId}`;
|
// const handleCopyToClipboard = (encounterId) => { ... };
|
||||||
navigator.clipboard.writeText(link).then(() => {
|
|
||||||
setCopiedLinkEncounterId(encounterId);
|
|
||||||
setTimeout(() => setCopiedLinkEncounterId(null), 2000);
|
|
||||||
}).catch(err => console.error('Failed to copy link: ', err));
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedEncounter = encounters.find(e => e.id === selectedEncounterId);
|
const selectedEncounter = encounters.find(e => e.id === selectedEncounterId);
|
||||||
|
|
||||||
@ -514,39 +484,28 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
{encounters.length === 0 && <p className="text-sm text-slate-400">No encounters yet.</p>}
|
{encounters.length === 0 && <p className="text-sm text-slate-400">No encounters yet.</p>}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{encounters.map(encounter => {
|
{encounters.map(encounter => {
|
||||||
const isDmActiveDisplay = activeDisplayInfo && activeDisplayInfo.activeCampaignId === campaignId && activeDisplayInfo.activeEncounterId === encounter.id;
|
const isLiveOnPlayerDisplay = activeDisplayInfo && activeDisplayInfo.activeCampaignId === campaignId && activeDisplayInfo.activeEncounterId === encounter.id;
|
||||||
return (
|
return (
|
||||||
<div key={encounter.id} className={`p-3 rounded-md shadow transition-all ${selectedEncounterId === encounter.id ? 'bg-sky-600 ring-2 ring-sky-400' : 'bg-slate-700 hover:bg-slate-650'} ${isDmActiveDisplay ? 'ring-2 ring-green-500 shadow-md shadow-green-500/30' : ''}`}>
|
<div key={encounter.id} className={`p-3 rounded-md shadow transition-all ${selectedEncounterId === encounter.id ? 'bg-sky-600 ring-2 ring-sky-400' : 'bg-slate-700 hover:bg-slate-650'} ${isLiveOnPlayerDisplay ? 'ring-2 ring-green-500 shadow-md shadow-green-500/30' : ''}`}>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div onClick={() => setSelectedEncounterId(encounter.id)} className="cursor-pointer flex-grow">
|
<div onClick={() => setSelectedEncounterId(encounter.id)} className="cursor-pointer flex-grow">
|
||||||
<h4 className="font-medium text-white">{encounter.name}</h4>
|
<h4 className="font-medium text-white">{encounter.name}</h4>
|
||||||
<p className="text-xs text-slate-300">Participants: {encounter.participants?.length || 0}</p>
|
<p className="text-xs text-slate-300">Participants: {encounter.participants?.length || 0}</p>
|
||||||
{isDmActiveDisplay && <span className="text-xs text-green-400 font-semibold block mt-1">LIVE ON DM DISPLAY</span>}
|
{isLiveOnPlayerDisplay && <span className="text-xs text-green-400 font-semibold block mt-1">LIVE ON PLAYER DISPLAY</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleSetEncounterAsActiveDisplay(encounter.id)}
|
onClick={() => handleTogglePlayerDisplayForEncounter(encounter.id)}
|
||||||
className={`p-1 rounded transition-colors ${isDmActiveDisplay ? 'bg-red-500 hover:bg-red-600 text-white' : 'text-teal-400 hover:text-teal-300 bg-slate-600 hover:bg-slate-500'}`}
|
className={`p-1 rounded transition-colors ${isLiveOnPlayerDisplay ? 'bg-red-500 hover:bg-red-600 text-white' : 'text-teal-400 hover:text-teal-300 bg-slate-600 hover:bg-slate-500'}`}
|
||||||
title={isDmActiveDisplay ? "Deactivate DM Display" : "Set as DM's Active Display"}
|
title={isLiveOnPlayerDisplay ? "Deactivate for Player Display" : "Activate for Player Display"}
|
||||||
>
|
>
|
||||||
{isDmActiveDisplay ? <EyeOff size={18} /> : <Eye size={18} />}
|
{isLiveOnPlayerDisplay ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleCopyToClipboard(encounter.id)} className="p-1 rounded transition-colors text-sky-400 hover:text-sky-300 bg-slate-600 hover:bg-slate-500" title="Copy Share Link for Players"><Share2 size={18} /></button>
|
{/* Share button for individual encounter link removed */}
|
||||||
{copiedLinkEncounterId === encounter.id && <span className="text-xs text-green-400">Copied!</span>}
|
|
||||||
<button onClick={(e) => { e.stopPropagation(); handleDeleteEncounter(encounter.id); }} className="p-1 rounded transition-colors text-red-400 hover:text-red-300 bg-slate-600 hover:bg-slate-500" title="Delete Encounter"><Trash2 size={18} /></button>
|
<button onClick={(e) => { e.stopPropagation(); handleDeleteEncounter(encounter.id); }} className="p-1 rounded transition-colors text-red-400 hover:text-red-300 bg-slate-600 hover:bg-slate-500" title="Delete Encounter"><Trash2 size={18} /></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{selectedEncounterId === encounter.id && (
|
{/* Shareable link display removed */}
|
||||||
<div className="mt-2 pt-2 border-t border-slate-600">
|
|
||||||
<p className="text-xs text-slate-400">Shareable Link for Players:</p>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input type="text" readOnly value={`${getShareableLinkBase()}#/display/${campaignId}/${encounter.id}`} className="text-xs w-full bg-slate-600 px-3 py-2 border border-slate-500 rounded-md shadow-sm focus:outline-none focus:ring-sky-500 focus:border-sky-500 sm:text-sm text-white" />
|
|
||||||
<button onClick={() => handleCopyToClipboard(encounter.id)} className="px-4 py-2 text-xs font-medium text-slate-300 bg-slate-500 hover:bg-slate-400 rounded-md transition-colors p-1">
|
|
||||||
{copiedLinkEncounterId === encounter.id ? 'Copied!' : <CopyIcon size={14}/>}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -872,6 +831,8 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
console.warn("Attempting to end encounter without confirmation");
|
console.warn("Attempting to end encounter without confirmation");
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { isStarted: false, currentTurnParticipantId: null, round: 0, turnOrderIds: [] });
|
await updateDoc(doc(db, encounterPath), { isStarted: false, currentTurnParticipantId: null, round: 0, turnOrderIds: [] });
|
||||||
|
// Optionally, also clear the active display when an encounter ends
|
||||||
|
// await updateDoc(doc(db, ACTIVE_DISPLAY_DOC), { activeCampaignId: null, activeEncounterId: null });
|
||||||
} catch (err) { console.error("Error ending encounter:", err); }
|
} catch (err) { console.error("Error ending encounter:", err); }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -894,72 +855,97 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DisplayView({ campaignIdFromUrl, encounterIdFromUrl }) {
|
function DisplayView() { // Removed campaignIdFromUrl, encounterIdFromUrl props
|
||||||
const [activeEncounterData, setActiveEncounterData] = useState(null);
|
const [activeEncounterData, setActiveEncounterData] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [campaignBackgroundUrl, setCampaignBackgroundUrl] = useState('');
|
const [campaignBackgroundUrl, setCampaignBackgroundUrl] = useState('');
|
||||||
|
const [isPlayerDisplayActive, setIsPlayerDisplayActive] = useState(false); // New state
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!db) {
|
if (!db) {
|
||||||
setError("Firestore not available."); setIsLoading(false); return;
|
setError("Firestore not available."); setIsLoading(false); return;
|
||||||
}
|
}
|
||||||
setIsLoading(true); setError(null); setActiveEncounterData(null); setCampaignBackgroundUrl('');
|
setIsLoading(true); setError(null); setActiveEncounterData(null); setCampaignBackgroundUrl(''); setIsPlayerDisplayActive(false);
|
||||||
|
|
||||||
let unsubscribeEncounter;
|
let unsubscribeEncounter;
|
||||||
let unsubscribeCampaign;
|
let unsubscribeCampaign;
|
||||||
|
|
||||||
const fetchCampaignBackground = async (cId) => {
|
const activeDisplayRef = doc(db, ACTIVE_DISPLAY_DOC);
|
||||||
if (!cId) return;
|
const unsubDisplayConfig = onSnapshot(activeDisplayRef, async (docSnap) => {
|
||||||
const campaignDocRef = doc(db, CAMPAIGNS_COLLECTION, cId);
|
if (docSnap.exists()) {
|
||||||
unsubscribeCampaign = onSnapshot(campaignDocRef, (docSnap) => {
|
const { activeCampaignId, activeEncounterId } = docSnap.data();
|
||||||
if (docSnap.exists()) {
|
if (activeCampaignId && activeEncounterId) {
|
||||||
setCampaignBackgroundUrl(docSnap.data().playerDisplayBackgroundUrl || '');
|
setIsPlayerDisplayActive(true); // An encounter is active for display
|
||||||
}
|
|
||||||
}, (err) => {
|
|
||||||
console.error("Error fetching campaign background:", err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (campaignIdFromUrl && encounterIdFromUrl) {
|
// Fetch campaign background
|
||||||
fetchCampaignBackground(campaignIdFromUrl);
|
const campaignDocRef = doc(db, CAMPAIGNS_COLLECTION, activeCampaignId);
|
||||||
const encounterPath = `${CAMPAIGNS_COLLECTION}/${campaignIdFromUrl}/encounters/${encounterIdFromUrl}`;
|
if (unsubscribeCampaign) unsubscribeCampaign(); // Clean up previous listener
|
||||||
unsubscribeEncounter = onSnapshot(doc(db, encounterPath), (encDocSnap) => {
|
unsubscribeCampaign = onSnapshot(campaignDocRef, (campSnap) => {
|
||||||
if (encDocSnap.exists()) setActiveEncounterData({ id: encDocSnap.id, ...encDocSnap.data() });
|
if (campSnap.exists()) {
|
||||||
else setError("The requested encounter was not found or is not accessible.");
|
setCampaignBackgroundUrl(campSnap.data().playerDisplayBackgroundUrl || '');
|
||||||
|
} else {
|
||||||
|
setCampaignBackgroundUrl(''); // Campaign not found
|
||||||
|
}
|
||||||
|
}, (err) => console.error("Error fetching campaign background for display:", err));
|
||||||
|
|
||||||
|
// Fetch encounter data
|
||||||
|
const encounterPath = `${CAMPAIGNS_COLLECTION}/${activeCampaignId}/encounters/${activeEncounterId}`;
|
||||||
|
if (unsubscribeEncounter) unsubscribeEncounter(); // Clean up previous listener
|
||||||
|
unsubscribeEncounter = onSnapshot(doc(db, encounterPath), (encDocSnap) => {
|
||||||
|
if (encDocSnap.exists()) {
|
||||||
|
setActiveEncounterData({ id: encDocSnap.id, ...encDocSnap.data() });
|
||||||
|
setError(null);
|
||||||
|
} else {
|
||||||
|
setActiveEncounterData(null);
|
||||||
|
setError("Active encounter data not found. The DM might have deleted it or it's misconfigured.");
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
}, (err) => {
|
||||||
|
console.error("Error fetching active encounter details for display:", err);
|
||||||
|
setError("Error loading active encounter data.");
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// No active encounter set by DM
|
||||||
|
setActiveEncounterData(null);
|
||||||
|
setCampaignBackgroundUrl('');
|
||||||
|
setIsPlayerDisplayActive(false); // No encounter is active for display
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ACTIVE_DISPLAY_DOC doesn't exist
|
||||||
|
setActiveEncounterData(null);
|
||||||
|
setCampaignBackgroundUrl('');
|
||||||
|
setIsPlayerDisplayActive(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, (err) => { console.error("Error fetching specific encounter for display:", err); setError("Error loading encounter data from link."); setIsLoading(false); });
|
}
|
||||||
} else {
|
}, (err) => {
|
||||||
const activeDisplayRef = doc(db, ACTIVE_DISPLAY_DOC);
|
console.error("Error fetching active display config:", err);
|
||||||
const unsubDisplayConfig = onSnapshot(activeDisplayRef, async (docSnap) => {
|
setError("Could not load display configuration.");
|
||||||
if (docSnap.exists()) {
|
setIsLoading(false);
|
||||||
const { activeCampaignId, activeEncounterId } = docSnap.data();
|
});
|
||||||
if (activeCampaignId && activeEncounterId) {
|
|
||||||
fetchCampaignBackground(activeCampaignId);
|
|
||||||
const encounterPath = `${CAMPAIGNS_COLLECTION}/${activeCampaignId}/encounters/${activeEncounterId}`;
|
|
||||||
if(unsubscribeEncounter) unsubscribeEncounter();
|
|
||||||
unsubscribeEncounter = onSnapshot(doc(db, encounterPath), (encDocSnap) => {
|
|
||||||
if (encDocSnap.exists()) setActiveEncounterData({ id: encDocSnap.id, ...encDocSnap.data() });
|
|
||||||
else { setActiveEncounterData(null); setError("Active encounter not found. The DM might have deleted it.");}
|
|
||||||
setIsLoading(false);
|
|
||||||
}, (err) => { console.error("Error fetching active encounter details:", err); setError("Error loading active encounter data."); setIsLoading(false);});
|
|
||||||
} else { setActiveEncounterData(null); setIsLoading(false); }
|
|
||||||
} else { setActiveEncounterData(null); setIsLoading(false); }
|
|
||||||
}, (err) => { console.error("Error fetching active display config:", err); setError("Could not load display configuration."); setIsLoading(false); });
|
|
||||||
return () => {
|
|
||||||
unsubDisplayConfig();
|
|
||||||
if (unsubscribeEncounter) unsubscribeEncounter();
|
|
||||||
if (unsubscribeCampaign) unsubscribeCampaign();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return () => {
|
return () => {
|
||||||
if (unsubscribeEncounter) unsubscribeEncounter();
|
unsubDisplayConfig();
|
||||||
if (unsubscribeCampaign) unsubscribeCampaign();
|
if (unsubscribeEncounter) unsubscribeEncounter();
|
||||||
|
if (unsubscribeCampaign) unsubscribeCampaign();
|
||||||
};
|
};
|
||||||
}, [campaignIdFromUrl, encounterIdFromUrl]);
|
}, []); // Removed URL params from dependencies
|
||||||
|
|
||||||
if (isLoading) return <div className="text-center py-10 text-2xl text-slate-300">Loading Player Display...</div>;
|
if (isLoading) return <div className="text-center py-10 text-2xl text-slate-300">Loading Player Display...</div>;
|
||||||
if (error) return <div className="text-center py-10 text-2xl text-red-400">{error}</div>;
|
if (error) return <div className="text-center py-10 text-2xl text-red-400">{error}</div>;
|
||||||
if (!activeEncounterData) return <div className="text-center py-10 text-3xl text-slate-400">No active encounter to display.</div>;
|
|
||||||
|
if (!isPlayerDisplayActive || !activeEncounterData) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black text-slate-400 flex flex-col items-center justify-center p-4 text-center">
|
||||||
|
<EyeOff size={64} className="mb-4 text-slate-500" />
|
||||||
|
<h2 className="text-3xl font-semibold">Game Session Paused</h2>
|
||||||
|
<p className="text-xl mt-2">The Dungeon Master has not activated an encounter for display.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const { name, participants, round, currentTurnParticipantId, isStarted } = activeEncounterData;
|
const { name, participants, round, currentTurnParticipantId, isStarted } = activeEncounterData;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user