Rework backend #1
+53
-50
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import { initializeApp } from './storage';
|
import { initializeApp } from './storage';
|
||||||
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from './storage';
|
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from './storage';
|
||||||
import { getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, getStorage } from './storage';
|
import { getFirestore, doc, setDoc, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, getStorage } from './storage';
|
||||||
import {
|
import {
|
||||||
PlusCircle, Users, Swords, Trash2, Eye, Edit3, Save, XCircle, ChevronsUpDown,
|
PlusCircle, Users, Swords, Trash2, Eye, Edit3, Save, XCircle, ChevronsUpDown,
|
||||||
UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle,
|
UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle,
|
||||||
@@ -95,6 +95,7 @@ const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
|
|||||||
let app;
|
let app;
|
||||||
let db;
|
let db;
|
||||||
let auth;
|
let auth;
|
||||||
|
let storage;
|
||||||
|
|
||||||
// Initialize Firebase
|
// Initialize Firebase
|
||||||
const initializeFirebase = () => {
|
const initializeFirebase = () => {
|
||||||
@@ -110,6 +111,7 @@ const initializeFirebase = () => {
|
|||||||
app = initializeApp(firebaseConfig);
|
app = initializeApp(firebaseConfig);
|
||||||
db = getFirestore(app);
|
db = getFirestore(app);
|
||||||
auth = getAuth(app);
|
auth = getAuth(app);
|
||||||
|
storage = getStorage();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error initializing Firebase:", error);
|
console.error("Error initializing Firebase:", error);
|
||||||
@@ -162,7 +164,7 @@ const logAction = async (message, context = {}, undoData = null) => {
|
|||||||
try {
|
try {
|
||||||
const entry = { timestamp: Date.now(), message, ...context };
|
const entry = { timestamp: Date.now(), message, ...context };
|
||||||
if (undoData) entry.undo = undoData;
|
if (undoData) entry.undo = undoData;
|
||||||
await addDoc(collection(db, getPath.logs()), entry);
|
await storage.addDoc(getPath.logs(), entry);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error writing log:', err);
|
console.error('Error writing log:', err);
|
||||||
}
|
}
|
||||||
@@ -565,7 +567,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, getPath.campaign(campaignId)), {
|
await storage.updateDoc(getPath.campaign(campaignId), {
|
||||||
players: [...campaignCharacters, newCharacter]
|
players: [...campaignCharacters, newCharacter]
|
||||||
});
|
});
|
||||||
setCharacterName('');
|
setCharacterName('');
|
||||||
@@ -601,7 +603,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, getPath.campaign(campaignId)), { players: updatedCharacters });
|
await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
|
||||||
setEditingCharacter(null);
|
setEditingCharacter(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error updating character:", err);
|
console.error("Error updating character:", err);
|
||||||
@@ -620,7 +622,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
|
|||||||
const updatedCharacters = campaignCharacters.filter(c => c.id !== itemToDelete.id);
|
const updatedCharacters = campaignCharacters.filter(c => c.id !== itemToDelete.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, getPath.campaign(campaignId)), { players: updatedCharacters });
|
await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error deleting character:", err);
|
console.error("Error deleting character:", err);
|
||||||
alert("Failed to delete character. Please try again.");
|
alert("Failed to delete character. Please try again.");
|
||||||
@@ -876,7 +878,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
participants: [...participants, newParticipant]
|
participants: [...participants, newParticipant]
|
||||||
});
|
});
|
||||||
logAction(`${nameToAdd} added to encounter (Initiative: ${finalInitiative})`, { encounterName: encounter.name }, {
|
logAction(`${nameToAdd} added to encounter (Initiative: ${finalInitiative})`, { encounterName: encounter.name }, {
|
||||||
@@ -941,7 +943,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
participants: [...participants, ...newParticipants]
|
participants: [...participants, ...newParticipants]
|
||||||
});
|
});
|
||||||
console.log(`Added ${newParticipants.length} characters to the encounter.`);
|
console.log(`Added ${newParticipants.length} characters to the encounter.`);
|
||||||
@@ -959,7 +961,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||||
setEditingParticipant(null);
|
setEditingParticipant(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error updating participant:", err);
|
console.error("Error updating participant:", err);
|
||||||
@@ -988,7 +990,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
participants: updatedParticipants,
|
participants: updatedParticipants,
|
||||||
...computeTurnOrderAfterRemoval(encounter, itemToDelete.id, updatedParticipants)
|
...computeTurnOrderAfterRemoval(encounter, itemToDelete.id, updatedParticipants)
|
||||||
});
|
});
|
||||||
@@ -1018,7 +1020,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
: computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants);
|
: computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
|
await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
|
||||||
logAction(`${participant.name} ${newIsActive ? 'reactivated' : 'deactivated'}`, { encounterName: encounter.name }, {
|
logAction(`${participant.name} ${newIsActive ? 'reactivated' : 'deactivated'}`, { encounterName: encounter.name }, {
|
||||||
encounterPath,
|
encounterPath,
|
||||||
updates: {
|
updates: {
|
||||||
@@ -1102,7 +1104,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
|
await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
|
||||||
setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
|
setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
|
||||||
const hpLine = `${participant.currentHp} → ${newHp} HP`;
|
const hpLine = `${participant.currentHp} → ${newHp} HP`;
|
||||||
const deathSuffix = (isDead && !wasDead) ? (participant.type === 'character' ? ' — Unconscious' : ' — Defeated') : '';
|
const deathSuffix = (isDead && !wasDead) ? (participant.type === 'character' ? ' — Unconscious' : ' — Defeated') : '';
|
||||||
@@ -1133,13 +1135,13 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||||
|
|
||||||
// Wait for animation to complete on player display (2 seconds) then remove participant
|
// Wait for animation to complete on player display (2 seconds) then remove participant
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const finalParticipants = participants.filter(p => p.id !== participantId);
|
const finalParticipants = participants.filter(p => p.id !== participantId);
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: finalParticipants });
|
await storage.updateDoc(encounterPath, { participants: finalParticipants });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error removing dead participant:", err);
|
console.error("Error removing dead participant:", err);
|
||||||
}
|
}
|
||||||
@@ -1154,7 +1156,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error updating death saves:", err);
|
console.error("Error updating death saves:", err);
|
||||||
}
|
}
|
||||||
@@ -1175,7 +1177,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
return { ...p, conditions: next };
|
return { ...p, conditions: next };
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
|
||||||
const cond = CONDITIONS.find(c => c.id === conditionId);
|
const cond = CONDITIONS.find(c => c.id === conditionId);
|
||||||
const condLabel = cond ? `${cond.label} ${cond.emoji}` : conditionId;
|
const condLabel = cond ? `${cond.label} ${cond.emoji}` : conditionId;
|
||||||
logAction(`${participant.name} ${wasActive ? 'lost' : 'gained'} ${condLabel}`, { encounterName: encounter.name }, {
|
logAction(`${participant.name} ${wasActive ? 'lost' : 'gained'} ${condLabel}`, { encounterName: encounter.name }, {
|
||||||
@@ -1228,7 +1230,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
|||||||
currentParticipants.splice(targetIndex, 0, removedItem);
|
currentParticipants.splice(targetIndex, 0, removedItem);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), { participants: currentParticipants });
|
await storage.updateDoc(encounterPath, { participants: currentParticipants });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error reordering participants:", err);
|
console.error("Error reordering participants:", err);
|
||||||
}
|
}
|
||||||
@@ -1600,7 +1602,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
const handleToggleHidePlayerHp = async () => {
|
const handleToggleHidePlayerHp = async () => {
|
||||||
if (!db) return;
|
if (!db) return;
|
||||||
try {
|
try {
|
||||||
await setDoc(doc(db, getPath.activeDisplay()), { hidePlayerHp: !hidePlayerHp }, { merge: true });
|
await storage.setDoc(getPath.activeDisplay(), { hidePlayerHp: !hidePlayerHp }, { merge: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error toggling hidePlayerHp:", err);
|
console.error("Error toggling hidePlayerHp:", err);
|
||||||
}
|
}
|
||||||
@@ -1621,7 +1623,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
const sortedParticipants = sortParticipantsByInitiative(activeParticipants, encounter.participants);
|
const sortedParticipants = sortParticipantsByInitiative(activeParticipants, encounter.participants);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
isStarted: true,
|
isStarted: true,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
round: 1,
|
round: 1,
|
||||||
@@ -1629,7 +1631,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
turnOrderIds: sortedParticipants.map(p => p.id)
|
turnOrderIds: sortedParticipants.map(p => p.id)
|
||||||
});
|
});
|
||||||
|
|
||||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
await storage.setDoc(getPath.activeDisplay(), {
|
||||||
activeCampaignId: campaignId,
|
activeCampaignId: campaignId,
|
||||||
activeEncounterId: encounter.id
|
activeEncounterId: encounter.id
|
||||||
}, { merge: true });
|
}, { merge: true });
|
||||||
@@ -1664,7 +1666,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
isPaused: newPausedState,
|
isPaused: newPausedState,
|
||||||
turnOrderIds: newTurnOrderIds
|
turnOrderIds: newTurnOrderIds
|
||||||
});
|
});
|
||||||
@@ -1690,7 +1692,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
|
|
||||||
if (activePsInOrder.length === 0) {
|
if (activePsInOrder.length === 0) {
|
||||||
alert("No active participants left.");
|
alert("No active participants left.");
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
isStarted: false,
|
isStarted: false,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
currentTurnParticipantId: null,
|
currentTurnParticipantId: null,
|
||||||
@@ -1730,7 +1732,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
if (!nextParticipant) return;
|
if (!nextParticipant) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
currentTurnParticipantId: nextParticipant.id,
|
currentTurnParticipantId: nextParticipant.id,
|
||||||
round: nextRound,
|
round: nextRound,
|
||||||
turnOrderIds: newTurnOrderIds,
|
turnOrderIds: newTurnOrderIds,
|
||||||
@@ -1752,7 +1754,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
if (!db) return;
|
if (!db) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, encounterPath), {
|
await storage.updateDoc(encounterPath, {
|
||||||
isStarted: false,
|
isStarted: false,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
currentTurnParticipantId: null,
|
currentTurnParticipantId: null,
|
||||||
@@ -1760,7 +1762,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
turnOrderIds: []
|
turnOrderIds: []
|
||||||
});
|
});
|
||||||
|
|
||||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
await storage.setDoc(getPath.activeDisplay(), {
|
||||||
activeCampaignId: null,
|
activeCampaignId: null,
|
||||||
activeEncounterId: null
|
activeEncounterId: null
|
||||||
}, { merge: true });
|
}, { merge: true });
|
||||||
@@ -1920,7 +1922,7 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
const newEncounterId = generateId();
|
const newEncounterId = generateId();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setDoc(doc(db, getPath.encounters(campaignId), newEncounterId), {
|
await storage.setDoc(`${getPath.encounters(campaignId)}/${newEncounterId}`, {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
participants: [],
|
participants: [],
|
||||||
@@ -1949,14 +1951,14 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
const encounterId = itemToDelete.id;
|
const encounterId = itemToDelete.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteDoc(doc(db, getPath.encounter(campaignId, encounterId)));
|
await storage.deleteDoc(getPath.encounter(campaignId, encounterId));
|
||||||
|
|
||||||
if (selectedEncounterId === encounterId) {
|
if (selectedEncounterId === encounterId) {
|
||||||
setSelectedEncounterId(null);
|
setSelectedEncounterId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
|
if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
|
||||||
await updateDoc(doc(db, getPath.activeDisplay()), {
|
await storage.updateDoc(getPath.activeDisplay(), {
|
||||||
activeCampaignId: null,
|
activeCampaignId: null,
|
||||||
activeEncounterId: null
|
activeEncounterId: null
|
||||||
});
|
});
|
||||||
@@ -1978,12 +1980,12 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
|
|||||||
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
|
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
|
||||||
|
|
||||||
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
|
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
|
||||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
await storage.setDoc(getPath.activeDisplay(), {
|
||||||
activeCampaignId: null,
|
activeCampaignId: null,
|
||||||
activeEncounterId: null,
|
activeEncounterId: null,
|
||||||
}, { merge: true });
|
}, { merge: true });
|
||||||
} else {
|
} else {
|
||||||
await setDoc(doc(db, getPath.activeDisplay()), {
|
await storage.setDoc(getPath.activeDisplay(), {
|
||||||
activeCampaignId: campaignId,
|
activeCampaignId: campaignId,
|
||||||
activeEncounterId: encounterId,
|
activeEncounterId: encounterId,
|
||||||
}, { merge: true });
|
}, { merge: true });
|
||||||
@@ -2139,8 +2141,8 @@ function AdminView({ userId }) {
|
|||||||
let encounterCount = 0;
|
let encounterCount = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const encountersSnapshot = await getDocs(collection(db, getPath.encounters(campaign.id)));
|
const encounters = await storage.getCollection(getPath.encounters(campaign.id));
|
||||||
encounterCount = encountersSnapshot.size;
|
encounterCount = encounters.length;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to fetch encounters for campaign ${campaign.id}:`, err);
|
console.error(`Failed to fetch encounters for campaign ${campaign.id}:`, err);
|
||||||
}
|
}
|
||||||
@@ -2180,7 +2182,7 @@ function AdminView({ userId }) {
|
|||||||
const newCampaignId = generateId();
|
const newCampaignId = generateId();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setDoc(doc(db, getPath.campaign(newCampaignId)), {
|
await storage.setDoc(getPath.campaign(newCampaignId), {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
playerDisplayBackgroundUrl: backgroundUrl.trim() || '',
|
playerDisplayBackgroundUrl: backgroundUrl.trim() || '',
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
@@ -2208,23 +2210,23 @@ function AdminView({ userId }) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const encountersPath = getPath.encounters(campaignId);
|
const encountersPath = getPath.encounters(campaignId);
|
||||||
const encountersSnapshot = await getDocs(collection(db, encountersPath));
|
const encounters = await storage.getCollection(encountersPath);
|
||||||
const batch = writeBatch(db);
|
const deleteOps = encounters.map(e => {
|
||||||
|
const id = e.id || e.path?.split('/').pop();
|
||||||
|
return { type: 'delete', path: `${encountersPath}/${id}` };
|
||||||
|
});
|
||||||
|
if (deleteOps.length > 0) await storage.batchWrite(deleteOps);
|
||||||
|
|
||||||
encountersSnapshot.docs.forEach(encounterDoc => batch.delete(encounterDoc.ref));
|
await storage.deleteDoc(getPath.campaign(campaignId));
|
||||||
await batch.commit();
|
|
||||||
|
|
||||||
await deleteDoc(doc(db, getPath.campaign(campaignId)));
|
|
||||||
|
|
||||||
if (selectedCampaignId === campaignId) {
|
if (selectedCampaignId === campaignId) {
|
||||||
setSelectedCampaignId(null);
|
setSelectedCampaignId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeDisplayRef = doc(db, getPath.activeDisplay());
|
const activeDisplay = await storage.getDoc(getPath.activeDisplay());
|
||||||
const activeDisplaySnap = await getDoc(activeDisplayRef);
|
|
||||||
|
|
||||||
if (activeDisplaySnap.exists() && activeDisplaySnap.data().activeCampaignId === campaignId) {
|
if (activeDisplay && activeDisplay.activeCampaignId === campaignId) {
|
||||||
await updateDoc(activeDisplayRef, {
|
await storage.updateDoc(getPath.activeDisplay(), {
|
||||||
activeCampaignId: null,
|
activeCampaignId: null,
|
||||||
activeEncounterId: null
|
activeEncounterId: null
|
||||||
});
|
});
|
||||||
@@ -2672,13 +2674,14 @@ function LogsView() {
|
|||||||
const [undoingId, setUndoingId] = useState(null);
|
const [undoingId, setUndoingId] = useState(null);
|
||||||
|
|
||||||
const handleClearLogs = async () => {
|
const handleClearLogs = async () => {
|
||||||
if (!db) return;
|
|
||||||
try {
|
try {
|
||||||
const snapshot = await getDocs(collection(db, getPath.logs()));
|
const logs = await storage.getCollection(getPath.logs());
|
||||||
if (!snapshot.empty) {
|
if (logs.length > 0) {
|
||||||
const batch = writeBatch(db);
|
const ops = logs.map(l => {
|
||||||
snapshot.docs.forEach(d => batch.delete(d.ref));
|
const id = l.id || l.path?.split('/').pop();
|
||||||
await batch.commit();
|
return { type: 'delete', path: `${getPath.logs()}/${id}` };
|
||||||
|
});
|
||||||
|
await storage.batchWrite(ops);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error clearing logs:', err);
|
console.error('Error clearing logs:', err);
|
||||||
@@ -2690,8 +2693,8 @@ function LogsView() {
|
|||||||
if (!db || !entry.undo) return;
|
if (!db || !entry.undo) return;
|
||||||
setUndoingId(entry.id);
|
setUndoingId(entry.id);
|
||||||
try {
|
try {
|
||||||
await updateDoc(doc(db, entry.undo.encounterPath), entry.undo.updates);
|
await storage.updateDoc(entry.undo.encounterPath, entry.undo.updates);
|
||||||
await updateDoc(doc(db, getPath.logs(), entry.id), { undone: true });
|
await storage.updateDoc(`${getPath.logs()}/${entry.id}`, { undone: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error undoing action:', err);
|
console.error('Error undoing action:', err);
|
||||||
alert('Failed to roll back. The encounter may have changed or no longer exists.');
|
alert('Failed to roll back. The encounter may have changed or no longer exists.');
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
import { initializeApp } from 'firebase/app';
|
import { initializeApp } from 'firebase/app';
|
||||||
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from 'firebase/auth';
|
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from 'firebase/auth';
|
||||||
import {
|
import {
|
||||||
getFirestore, doc, setDoc, updateDoc, deleteDoc, addDoc, collection,
|
getFirestore, doc, setDoc, getDoc as getDocReal, getDocs as getDocsReal, addDoc, collection,
|
||||||
onSnapshot, query, orderBy, limit, writeBatch, serverTimestamp,
|
onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, serverTimestamp,
|
||||||
} from 'firebase/firestore';
|
} from 'firebase/firestore';
|
||||||
|
|
||||||
// Path helpers mirror App.js getPath object.
|
// Path helpers mirror App.js getPath object.
|
||||||
@@ -74,7 +74,7 @@ export function createFirebaseStorage() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
async getDoc(path) {
|
async getDoc(path) {
|
||||||
const snap = await import('firebase/firestore').then(({ getDoc: gd, doc: d }) => gd(d(db, path)));
|
const snap = await getDocReal(doc(db, path));
|
||||||
return snap.exists() ? { id: snap.id, ...snap.data() } : null;
|
return snap.exists() ? { id: snap.id, ...snap.data() } : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export function createFirebaseStorage() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async getCollection(collectionPath) {
|
async getCollection(collectionPath) {
|
||||||
const snapshot = await import('firebase/firestore').then(({ getDocs: gd, collection: c }) => gd(c(db, collectionPath)));
|
const snapshot = await getDocsReal(collection(db, collectionPath));
|
||||||
return snapshot.docs.map(d => ({ id: d.id, ...d.data() }));
|
return snapshot.docs.map(d => ({ id: d.id, ...d.data() }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user