Generic (non-5e) ruleset mode + UI polish + combat switch fix #7
+28
-2
@@ -2330,6 +2330,7 @@ function AdminView({ userId }) {
|
|||||||
const { data: initialActiveInfo } = useFirestoreDocument(getPath.activeDisplay());
|
const { data: initialActiveInfo } = useFirestoreDocument(getPath.activeDisplay());
|
||||||
|
|
||||||
const [campaignsWithDetails, setCampaignsWithDetails] = useState([]);
|
const [campaignsWithDetails, setCampaignsWithDetails] = useState([]);
|
||||||
|
const [draggedCampaignId, setDraggedCampaignId] = useState(null);
|
||||||
const [selectedCampaignId, setSelectedCampaignId] = useState(null);
|
const [selectedCampaignId, setSelectedCampaignId] = useState(null);
|
||||||
const encounterStartedRef = useRef(false);
|
const encounterStartedRef = useRef(false);
|
||||||
const encounterActiveRef = useRef(false);
|
const encounterActiveRef = useRef(false);
|
||||||
@@ -2390,6 +2391,10 @@ function AdminView({ userId }) {
|
|||||||
fetchDetails();
|
fetchDetails();
|
||||||
} else if (campaignsData) {
|
} else if (campaignsData) {
|
||||||
const sorted = [...campaignsData].sort((a, b) => {
|
const sorted = [...campaignsData].sort((a, b) => {
|
||||||
|
const ao = a.order ?? null, bo = b.order ?? null;
|
||||||
|
if (ao !== null && bo !== null) return ao - bo;
|
||||||
|
if (ao !== null) return -1;
|
||||||
|
if (bo !== null) return 1;
|
||||||
const at = a.createdAt || 0;
|
const at = a.createdAt || 0;
|
||||||
const bt = b.createdAt || 0;
|
const bt = b.createdAt || 0;
|
||||||
if (at === bt) return (a.name || '').localeCompare(b.name || '');
|
if (at === bt) return (a.name || '').localeCompare(b.name || '');
|
||||||
@@ -2433,6 +2438,7 @@ function AdminView({ userId }) {
|
|||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
players: [],
|
players: [],
|
||||||
ruleset: ruleset || '5e',
|
ruleset: ruleset || '5e',
|
||||||
|
order: (campaignsWithDetails || []).reduce((max, c) => Math.max(max, c.order ?? -1), -1) + 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
setShowCreateModal(false);
|
setShowCreateModal(false);
|
||||||
@@ -2579,11 +2585,28 @@ function AdminView({ userId }) {
|
|||||||
? { backgroundImage: `url(${campaign.playerDisplayBackgroundUrl})` }
|
? { backgroundImage: `url(${campaign.playerDisplayBackgroundUrl})` }
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const cardClasses = `h-40 flex flex-col justify-between rounded-lg shadow-md cursor-pointer transition-all relative overflow-hidden bg-cover bg-center ${selectedCampaignId === campaign.id ? 'ring-4 ring-amber-500' : ''} ${!campaign.playerDisplayBackgroundUrl ? 'bg-stone-800 hover:bg-stone-700' : 'hover:shadow-xl'}`;
|
const cardClasses = `h-40 flex flex-col justify-between rounded-lg shadow-md cursor-grab transition-all relative overflow-hidden bg-cover bg-center ${selectedCampaignId === campaign.id ? 'ring-4 ring-amber-500' : ''} ${!campaign.playerDisplayBackgroundUrl ? 'bg-stone-800 hover:bg-stone-700' : 'hover:shadow-xl'} ${draggedCampaignId === campaign.id ? 'opacity-50 ring-2 ring-yellow-400' : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={campaign.id}
|
key={campaign.id}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => { setDraggedCampaignId(campaign.id); e.dataTransfer.effectAllowed = 'move'; }}
|
||||||
|
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
|
||||||
|
onDrop={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!db || !draggedCampaignId || draggedCampaignId === campaign.id) { setDraggedCampaignId(null); return; }
|
||||||
|
const reordered = [...campaignsWithDetails];
|
||||||
|
const fromIdx = reordered.findIndex(c => c.id === draggedCampaignId);
|
||||||
|
const toIdx = reordered.findIndex(c => c.id === campaign.id);
|
||||||
|
if (fromIdx === -1 || toIdx === -1) { setDraggedCampaignId(null); return; }
|
||||||
|
const [moved] = reordered.splice(fromIdx, 1);
|
||||||
|
reordered.splice(toIdx, 0, moved);
|
||||||
|
const ops = reordered.map((c, i) => ({ type: 'update', path: getPath.campaign(c.id), data: { order: i } }));
|
||||||
|
try { await storage.batchWrite(ops); } catch (err) {}
|
||||||
|
setDraggedCampaignId(null);
|
||||||
|
}}
|
||||||
|
onDragEnd={() => setDraggedCampaignId(null)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (encounterStartedRef.current) {
|
if (encounterStartedRef.current) {
|
||||||
showToast('End or pause active encounter before switching campaigns.');
|
showToast('End or pause active encounter before switching campaigns.');
|
||||||
@@ -2599,7 +2622,10 @@ function AdminView({ userId }) {
|
|||||||
className={`relative z-10 flex flex-col justify-between h-full ${campaign.playerDisplayBackgroundUrl ? 'bg-black bg-opacity-60 p-3' : 'p-4'}`}
|
className={`relative z-10 flex flex-col justify-between h-full ${campaign.playerDisplayBackgroundUrl ? 'bg-black bg-opacity-60 p-3' : 'p-4'}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold text-white">{campaign.name}</h3>
|
<div className="flex items-center gap-1">
|
||||||
|
<ChevronsUpDown size={14} className="text-white/60 flex-shrink-0" title="Drag to reorder" />
|
||||||
|
<h3 className="text-xl font-semibold text-white">{campaign.name}</h3>
|
||||||
|
</div>
|
||||||
<div className="text-xs text-stone-100 mt-1 space-x-3">
|
<div className="text-xs text-stone-100 mt-1 space-x-3">
|
||||||
<span className="inline-flex items-center">
|
<span className="inline-flex items-center">
|
||||||
<Users size={12} className="mr-1" /> {campaign.characters?.length || 0} Characters
|
<Users size={12} className="mr-1" /> {campaign.characters?.length || 0} Characters
|
||||||
|
|||||||
Reference in New Issue
Block a user