feat: upgrade needed ressources and fight for each quest
This commit is contained in:
@ -16,6 +16,19 @@ function useWindowWidth() {
|
||||
|
||||
function combatIcon(name: string): string {
|
||||
const l = name.toLowerCase();
|
||||
if (l === "solo") return "🗡️";
|
||||
if (l === "groupe") return "⚔️";
|
||||
if (l === "donjon") return "💀";
|
||||
if (
|
||||
l === "combat_vagues" ||
|
||||
l === "combat_tactique" ||
|
||||
l === "combat_aleatoire" ||
|
||||
l === "combat_zone" ||
|
||||
l === "combat"
|
||||
) return "🗡️";
|
||||
if (l === "deplacement") return "🗺️";
|
||||
if (l === "item") return "📦";
|
||||
// Fallback — conserve l'ancienne logique pour les données CSV existantes
|
||||
if (l.includes("solo") || l.includes("seul")) return "🗡️";
|
||||
if (l.includes("group") || l.includes("groupe")) return "⚔️";
|
||||
if (l.includes("donjon") || l.includes("boss")) return "💀";
|
||||
@ -324,6 +337,15 @@ function QuestRow({ quest, completed, onToggle, onSelect, indent }: {
|
||||
onSelect: (quest: { name: string; url: string | null }) => void;
|
||||
indent?: boolean;
|
||||
}) {
|
||||
const questPreviews = useStore(s => s.questPreviews);
|
||||
const previewsLoading = useStore(s => s.previewsLoading);
|
||||
const [previewsOpen, setPreviewsOpen] = useState(false);
|
||||
|
||||
const previews = quest.url ? questPreviews[quest.url] : undefined;
|
||||
const showLoadingPlaceholder = previewsLoading && quest.url !== null && previews === undefined;
|
||||
|
||||
const hasPreviewSection = showLoadingPlaceholder || (previews && previews.length > 0);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -364,6 +386,72 @@ function QuestRow({ quest, completed, onToggle, onSelect, indent }: {
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{hasPreviewSection && (
|
||||
<span
|
||||
onClick={() => setPreviewsOpen(o => !o)}
|
||||
style={{
|
||||
display: "inline-block",
|
||||
marginTop: "2px",
|
||||
fontSize: "10px",
|
||||
color: "#4a5568",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.color = "#94a3b8"; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = "#4a5568"; }}
|
||||
>
|
||||
{previewsOpen
|
||||
? `▾ À prévoir`
|
||||
: `▸ À prévoir${previews && previews.length > 0 ? ` (${previews.length})` : ""}`
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{previewsOpen && (
|
||||
<>
|
||||
{showLoadingPlaceholder && (
|
||||
<div style={{
|
||||
marginTop: "3px",
|
||||
width: "60px", height: "14px",
|
||||
borderRadius: "4px",
|
||||
background: "linear-gradient(90deg, #1f2937 25%, #2d3748 50%, #1f2937 75%)",
|
||||
backgroundSize: "200% 100%",
|
||||
animation: "shimmer 1.4s infinite",
|
||||
}} />
|
||||
)}
|
||||
|
||||
{!showLoadingPlaceholder && previews && previews.length > 0 && (
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: "4px", marginTop: "3px" }}>
|
||||
{previews.map((ci, i) => (
|
||||
<span key={i} style={{
|
||||
display: "inline-flex", alignItems: "center", gap: "3px",
|
||||
fontSize: "10px", padding: "1px 6px", borderRadius: "4px",
|
||||
background: "rgba(255,255,255,0.05)", border: "1px solid #2d3748",
|
||||
color: "#4a5568",
|
||||
}}>
|
||||
<span>{combatIcon(ci.combat_type)}</span>
|
||||
<span>×{ci.count}{ci.combat_type !== "item" ? ` ${ci.combat_type}` : ""}</span>
|
||||
{ci.evitable && (
|
||||
<span style={{ color: "#4ade80" }}>(évit.)</span>
|
||||
)}
|
||||
{ci.label && (
|
||||
<span style={{
|
||||
fontStyle: "italic",
|
||||
maxWidth: "80px", overflow: "hidden",
|
||||
textOverflow: "ellipsis", whiteSpace: "nowrap",
|
||||
display: "inline-block",
|
||||
}}>
|
||||
{ci.label}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{quest.note && (
|
||||
<div style={{ fontSize: "11px", color: "#4a5568", marginTop: "2px", fontStyle: "italic" }}>
|
||||
→ <TextWithCoords text={quest.note} />
|
||||
|
||||
@ -110,6 +110,11 @@ input[type="checkbox"] {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
43
src/store.ts
43
src/store.ts
@ -1,6 +1,6 @@
|
||||
import { create } from "zustand";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { Profile, GuideListItem, GuideData, SyncResult } from "./types";
|
||||
import { Profile, GuideListItem, GuideData, SyncResult, CombatIndicator, Section } from "./types";
|
||||
|
||||
interface AppState {
|
||||
profiles: Profile[];
|
||||
@ -15,6 +15,8 @@ interface AppState {
|
||||
sidebarCollapsed: boolean;
|
||||
resourcesPanelCollapsed: boolean;
|
||||
resourceInventory: Record<string, number>;
|
||||
questPreviews: Record<string, CombatIndicator[]>;
|
||||
previewsLoading: boolean;
|
||||
|
||||
setResourcesPanelCollapsed: (v: boolean) => void;
|
||||
loadResourceInventory: () => Promise<void>;
|
||||
@ -28,6 +30,7 @@ interface AppState {
|
||||
openGuide: (gid: string) => Promise<void>;
|
||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||
closeGuide: () => void;
|
||||
loadQuestPreviews: (gid: string) => Promise<void>;
|
||||
|
||||
toggleQuest: (questName: string) => Promise<void>;
|
||||
|
||||
@ -48,6 +51,8 @@ export const useStore = create<AppState>((set, get) => ({
|
||||
sidebarCollapsed: false,
|
||||
resourcesPanelCollapsed: false,
|
||||
resourceInventory: {},
|
||||
questPreviews: {},
|
||||
previewsLoading: false,
|
||||
|
||||
setResourcesPanelCollapsed: (v) => set({ resourcesPanelCollapsed: v }),
|
||||
|
||||
@ -112,13 +117,32 @@ export const useStore = create<AppState>((set, get) => ({
|
||||
const data = await invoke<GuideData>("get_guide", { gid });
|
||||
await invoke("set_setting", { key: "active_guide", value: gid });
|
||||
set({ activeGuideGid: gid, activeGuideData: data, view: "guide" });
|
||||
await get().loadQuestPreviews(gid);
|
||||
},
|
||||
|
||||
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
|
||||
|
||||
closeGuide: () => {
|
||||
invoke("set_setting", { key: "active_guide", value: "" });
|
||||
set({ activeGuideGid: null, activeGuideData: null, view: "home" });
|
||||
set({ activeGuideGid: null, activeGuideData: null, view: "home", questPreviews: {}, previewsLoading: false });
|
||||
},
|
||||
|
||||
loadQuestPreviews: async (gid) => {
|
||||
const { activeGuideData } = get();
|
||||
if (!activeGuideData) return;
|
||||
|
||||
const urls = collectQuestUrls(activeGuideData.sections);
|
||||
if (urls.length === 0) return;
|
||||
|
||||
// Hydratation immédiate depuis le cache DB
|
||||
const cached = await invoke<Record<string, CombatIndicator[]>>("get_cached_previews", { questUrls: urls });
|
||||
set({ questPreviews: cached });
|
||||
|
||||
// Fetch réseau en tâche de fond (fire & forget)
|
||||
set({ previewsLoading: true });
|
||||
invoke<Record<string, CombatIndicator[]>>("fetch_guide_previews", { gid })
|
||||
.then(result => set({ questPreviews: result, previewsLoading: false }))
|
||||
.catch(() => set({ previewsLoading: false }));
|
||||
},
|
||||
|
||||
toggleQuest: async (questName) => {
|
||||
@ -164,3 +188,18 @@ export const useStore = create<AppState>((set, get) => ({
|
||||
await invoke("sync_single_guide", { gid, name });
|
||||
},
|
||||
}));
|
||||
|
||||
function collectQuestUrls(sections: Section[]): string[] {
|
||||
const urls: string[] = [];
|
||||
for (const section of sections) {
|
||||
for (const item of section.items) {
|
||||
if (item.type === "Quest" && item.url) urls.push(item.url);
|
||||
else if (item.type === "Group") {
|
||||
for (const q of item.quests) {
|
||||
if (q.url) urls.push(q.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ export interface Resource {
|
||||
export interface CombatIndicator {
|
||||
combat_type: string;
|
||||
count: string;
|
||||
label?: string;
|
||||
evitable?: boolean;
|
||||
}
|
||||
|
||||
export interface QuestItem {
|
||||
|
||||
Reference in New Issue
Block a user