Added conditions.
This commit is contained in:
+97
-1
@@ -50,6 +50,24 @@ const DEFAULT_INIT_MOD = 0;
|
||||
const MONSTER_DEFAULT_INIT_MOD = 2;
|
||||
const ROLL_DISPLAY_DURATION = 5000;
|
||||
|
||||
const CONDITIONS = [
|
||||
{ id: 'blinded', label: 'Blinded', emoji: '🙈' },
|
||||
{ id: 'charmed', label: 'Charmed', emoji: '💘' },
|
||||
{ id: 'deafened', label: 'Deafened', emoji: '🔇' },
|
||||
{ id: 'exhaustion', label: 'Exhaustion', emoji: '😴' },
|
||||
{ id: 'frightened', label: 'Frightened', emoji: '😱' },
|
||||
{ id: 'grappled', label: 'Grappled', emoji: '🤜' },
|
||||
{ id: 'incapacitated', label: 'Incapacitated', emoji: '💫' },
|
||||
{ id: 'invisible', label: 'Invisible', emoji: '👻' },
|
||||
{ id: 'paralyzed', label: 'Paralyzed', emoji: '⚡' },
|
||||
{ id: 'petrified', label: 'Petrified', emoji: '🗿' },
|
||||
{ id: 'poisoned', label: 'Poisoned', emoji: '🤢' },
|
||||
{ id: 'prone', label: 'Prone', emoji: '⬇️' },
|
||||
{ id: 'restrained', label: 'Restrained', emoji: '🕸️' },
|
||||
{ id: 'stunned', label: 'Stunned', emoji: '💥' },
|
||||
{ id: 'unconscious', label: 'Unconscious', emoji: '💤' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FIREBASE CONFIGURATION
|
||||
// ============================================================================
|
||||
@@ -769,6 +787,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [itemToDelete, setItemToDelete] = useState(null);
|
||||
const [lastRollDetails, setLastRollDetails] = useState(null);
|
||||
const [openConditionsId, setOpenConditionsId] = useState(null);
|
||||
|
||||
const participants = encounter.participants || [];
|
||||
|
||||
@@ -1057,6 +1076,23 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCondition = async (participantId, conditionId) => {
|
||||
if (!db) return;
|
||||
const updatedParticipants = participants.map(p => {
|
||||
if (p.id !== participantId) return p;
|
||||
const current = p.conditions || [];
|
||||
const next = current.includes(conditionId)
|
||||
? current.filter(c => c !== conditionId)
|
||||
: [...current, conditionId];
|
||||
return { ...p, conditions: next };
|
||||
});
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
} catch (err) {
|
||||
console.error("Error updating conditions:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragStart = (e, id) => {
|
||||
setDraggedItemId(id);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
@@ -1329,6 +1365,47 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Active condition badges */}
|
||||
{(p.conditions || []).length > 0 && (
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{(p.conditions || []).map(cId => {
|
||||
const cond = CONDITIONS.find(c => c.id === cId);
|
||||
return cond ? (
|
||||
<span
|
||||
key={cId}
|
||||
className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded-full bg-purple-900 border border-purple-600 text-xs text-purple-200 cursor-pointer hover:bg-purple-800"
|
||||
title={`Remove ${cond.label}`}
|
||||
onClick={() => toggleCondition(p.id, cId)}
|
||||
>
|
||||
{cond.emoji} {cond.label}
|
||||
</span>
|
||||
) : null;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Expandable conditions picker */}
|
||||
{openConditionsId === p.id && (
|
||||
<div className="mt-2 p-2 bg-stone-900 rounded-md border border-stone-600">
|
||||
<p className="text-xs text-stone-400 mb-1 font-medium">Toggle Conditions</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{CONDITIONS.map(cond => {
|
||||
const active = (p.conditions || []).includes(cond.id);
|
||||
return (
|
||||
<button
|
||||
key={cond.id}
|
||||
onClick={() => toggleCondition(p.id, cond.id)}
|
||||
className={`px-2 py-1 rounded text-xs transition-colors ${active ? 'bg-purple-700 border border-purple-400 text-white' : 'bg-stone-700 border border-stone-500 text-stone-300 hover:bg-stone-600'}`}
|
||||
title={cond.label}
|
||||
>
|
||||
{cond.emoji} {cond.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1370,6 +1447,13 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
{p.isActive ? <UserCheck size={18} /> : <UserX size={18} />}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setOpenConditionsId(openConditionsId === p.id ? null : p.id)}
|
||||
className={`p-1 rounded transition-colors bg-stone-700 hover:bg-stone-600 ${openConditionsId === p.id || (p.conditions || []).length > 0 ? 'text-purple-400 hover:text-purple-300' : 'text-stone-400 hover:text-stone-300'}`}
|
||||
title="Conditions"
|
||||
>
|
||||
<span className="text-base leading-none">✨</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingParticipant(p)}
|
||||
className="p-1 rounded transition-colors text-yellow-400 hover:text-yellow-300 bg-stone-700 hover:bg-stone-600"
|
||||
@@ -2308,7 +2392,19 @@ function DisplayView() {
|
||||
</div>
|
||||
|
||||
{p.conditions?.length > 0 && (
|
||||
<p className="text-sm text-yellow-300 mt-2">Conditions: {p.conditions.join(', ')}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{p.conditions.map(cId => {
|
||||
const cond = CONDITIONS.find(c => c.id === cId);
|
||||
return cond ? (
|
||||
<span
|
||||
key={cId}
|
||||
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-purple-900 border border-purple-500 text-xs md:text-sm text-purple-200 font-medium"
|
||||
>
|
||||
{cond.emoji} {cond.label}
|
||||
</span>
|
||||
) : null;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!p.isActive && !isDead && (
|
||||
|
||||
Reference in New Issue
Block a user