feat: work on windows resizing
This commit is contained in:
@ -1,23 +1,51 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { useStore } from "../store";
|
||||
import { SectionItem, QuestItem, CombatType } from "../types";
|
||||
import QuestDetailPanel from "./QuestDetailPanel";
|
||||
import { TextWithCoords } from "./TextWithCoords";
|
||||
|
||||
function useWindowWidth() {
|
||||
const [width, setWidth] = useState(window.innerWidth);
|
||||
useEffect(() => {
|
||||
const handler = () => setWidth(window.innerWidth);
|
||||
window.addEventListener("resize", handler);
|
||||
return () => window.removeEventListener("resize", handler);
|
||||
}, []);
|
||||
return width;
|
||||
}
|
||||
|
||||
function combatIcon(name: string): string {
|
||||
const l = name.toLowerCase();
|
||||
if (l.includes("solo")) return "⚔️";
|
||||
if (l.includes("group") || l.includes("groupe")) return "👥";
|
||||
if (l.includes("boss")) return "💀";
|
||||
if (l.includes("arène") || l.includes("arene")) return "🏟️";
|
||||
return "⚔️";
|
||||
if (l.includes("solo") || l.includes("seul")) return "🗡️";
|
||||
if (l.includes("group") || l.includes("groupe")) return "⚔️";
|
||||
if (l.includes("donjon") || l.includes("boss")) return "💀";
|
||||
return "🗡️";
|
||||
}
|
||||
|
||||
export default function GuideView() {
|
||||
const { activeGuideData, completedQuests, toggleQuest } = useStore();
|
||||
const [resourcesCollapsed, setResourcesCollapsed] = useState(false);
|
||||
const { activeGuideData, completedQuests, toggleQuest, activeProfileId, resourcesPanelCollapsed, setResourcesPanelCollapsed, resourceInventory, setResourceQuantity } = useStore();
|
||||
const resourcesCollapsed = resourcesPanelCollapsed;
|
||||
const setResourcesCollapsed = setResourcesPanelCollapsed;
|
||||
const [selectedQuest, setSelectedQuest] = useState<{ name: string; url: string | null } | null>(null);
|
||||
const windowWidth = useWindowWidth();
|
||||
const resourcesIsOverlay = resourcesCollapsed || windowWidth < 500;
|
||||
|
||||
if (!activeGuideData) return null;
|
||||
|
||||
if (selectedQuest && activeProfileId) {
|
||||
return (
|
||||
<div style={{ flex: 1, display: "flex", overflow: "hidden", minHeight: 0 }}>
|
||||
<QuestDetailPanel
|
||||
questName={selectedQuest.name}
|
||||
questUrl={selectedQuest.url}
|
||||
profileId={activeProfileId}
|
||||
onClose={() => setSelectedQuest(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { name, effect, recommended_level, resources, sections, combat_legend } = activeGuideData;
|
||||
|
||||
const allQuests = collectAllQuests(sections);
|
||||
@ -26,7 +54,7 @@ export default function GuideView() {
|
||||
const isDone = pct === 100;
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1, display: "flex", overflow: "hidden", minHeight: 0 }}>
|
||||
<div style={{ flex: 1, display: "flex", overflow: "hidden", minHeight: 0, position: "relative" }}>
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: "20px 24px" }}>
|
||||
|
||||
{/* Header */}
|
||||
@ -87,7 +115,7 @@ export default function GuideView() {
|
||||
{section.name}
|
||||
</h2>
|
||||
{section.items.map((item, ii) => (
|
||||
<SectionItemView key={ii} item={item} completedQuests={completedQuests} onToggle={toggleQuest} />
|
||||
<SectionItemView key={ii} item={item} completedQuests={completedQuests} onToggle={toggleQuest} onSelect={setSelectedQuest} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
@ -96,25 +124,41 @@ export default function GuideView() {
|
||||
{/* Resources panel */}
|
||||
{resources.length > 0 && (
|
||||
<div style={{
|
||||
width: resourcesCollapsed ? "32px" : "190px",
|
||||
flexShrink: 0, background: "#161b22",
|
||||
borderLeft: "1px solid #2d3748",
|
||||
display: "flex", flexDirection: "column",
|
||||
position: resourcesIsOverlay ? "absolute" : "relative",
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex: 10,
|
||||
width: resourcesCollapsed ? "36px" : "190px",
|
||||
flexShrink: 0,
|
||||
background: resourcesCollapsed ? "transparent" : "#161b22",
|
||||
borderLeft: resourcesCollapsed ? "none" : "1px solid #2d3748",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
transition: "width 0.2s ease",
|
||||
transition: "width 0.2s ease, background 0.2s ease",
|
||||
}}>
|
||||
{/* Toggle */}
|
||||
<button
|
||||
onClick={() => setResourcesCollapsed(c => !c)}
|
||||
onClick={() => setResourcesCollapsed(!resourcesCollapsed)}
|
||||
title={resourcesCollapsed ? "Afficher les ressources" : "Masquer les ressources"}
|
||||
style={{
|
||||
width: "100%", height: "36px", flexShrink: 0,
|
||||
background: "transparent", border: "none",
|
||||
width: "100%",
|
||||
height: "36px",
|
||||
flexShrink: 0,
|
||||
background: resourcesCollapsed ? "rgba(22,27,34,0.9)" : "transparent",
|
||||
border: resourcesCollapsed ? "1px solid #2d3748" : "none",
|
||||
borderRight: "none",
|
||||
borderRadius: resourcesCollapsed ? "6px 0 0 6px" : "0",
|
||||
borderBottom: "1px solid #2d3748",
|
||||
color: "#4a5568", cursor: "pointer",
|
||||
display: "flex", alignItems: "center",
|
||||
color: "#4a5568",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: resourcesCollapsed ? "center" : "flex-start",
|
||||
padding: "0 10px", gap: "6px",
|
||||
padding: "0 10px",
|
||||
gap: "6px",
|
||||
marginTop: resourcesCollapsed ? "8px" : "0",
|
||||
transition: "all 0.15s",
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = "#f0c040")}
|
||||
@ -138,15 +182,49 @@ export default function GuideView() {
|
||||
{/* List */}
|
||||
{!resourcesCollapsed && (
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: "10px 14px", scrollbarWidth: "none" }}>
|
||||
{resources.map((r, i) => (
|
||||
<div key={i} style={{
|
||||
display: "flex", justifyContent: "space-between", alignItems: "center",
|
||||
padding: "5px 0", borderBottom: "1px solid #1f2937", fontSize: "12px",
|
||||
}}>
|
||||
<span style={{ color: "#94a3b8", flex: 1, marginRight: "8px" }}>{r.name}</span>
|
||||
<span style={{ color: "#f0c040", fontWeight: 700, flexShrink: 0 }}>×{r.quantity}</span>
|
||||
</div>
|
||||
))}
|
||||
{resources.map((r, i) => {
|
||||
const owned = resourceInventory[r.name] ?? 0;
|
||||
const done = owned >= r.quantity;
|
||||
return (
|
||||
<div key={i} style={{
|
||||
padding: "6px 0", borderBottom: "1px solid #1f2937", fontSize: "12px",
|
||||
}}>
|
||||
<span style={{
|
||||
color: done ? "#4ade80" : "#94a3b8",
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
wordBreak: "break-word",
|
||||
marginBottom: "3px",
|
||||
} as React.CSSProperties}>
|
||||
{r.name}
|
||||
</span>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={owned === 0 ? "" : owned}
|
||||
placeholder="0"
|
||||
onChange={e => {
|
||||
const v = parseInt(e.target.value);
|
||||
setResourceQuantity(r.name, isNaN(v) ? 0 : Math.max(0, v));
|
||||
}}
|
||||
style={{
|
||||
width: "42px", background: "#0d1117",
|
||||
border: `1px solid ${done ? "rgba(74,222,128,0.4)" : "#2d3748"}`,
|
||||
borderRadius: "4px", padding: "2px 4px",
|
||||
color: done ? "#4ade80" : "#e2e8f0",
|
||||
fontSize: "11px", outline: "none", textAlign: "right",
|
||||
}}
|
||||
/>
|
||||
<span style={{ color: done ? "#4ade80" : "#f0c040", fontWeight: 700, flexShrink: 0 }}>
|
||||
/ ×{r.quantity}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -179,10 +257,11 @@ function Legend({ legend }: { legend: CombatType[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SectionItemView({ item, completedQuests, onToggle }: {
|
||||
function SectionItemView({ item, completedQuests, onToggle, onSelect }: {
|
||||
item: SectionItem;
|
||||
completedQuests: Set<string>;
|
||||
onToggle: (name: string) => void;
|
||||
onSelect: (quest: { name: string; url: string | null }) => void;
|
||||
}) {
|
||||
if (item.type === "Instruction") {
|
||||
if (item.text.startsWith("__ZONE__:")) {
|
||||
@ -209,7 +288,7 @@ function SectionItemView({ item, completedQuests, onToggle }: {
|
||||
<span style={{ color: "#4a9eff", fontSize: "10px", fontWeight: 600, display: "block", marginBottom: "2px", textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||
Rappel
|
||||
</span>
|
||||
{item.text}
|
||||
<TextWithCoords text={item.text} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -226,32 +305,32 @@ function SectionItemView({ item, completedQuests, onToggle }: {
|
||||
</div>
|
||||
)}
|
||||
{item.quests.map((q, i) => (
|
||||
<QuestRow key={i} quest={q} indent completed={completedQuests.has(q.name)} onToggle={onToggle} />
|
||||
<QuestRow key={i} quest={q} indent completed={completedQuests.has(q.name)} onToggle={onToggle} onSelect={onSelect} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === "Quest") {
|
||||
return <QuestRow quest={item} completed={completedQuests.has(item.name)} onToggle={onToggle} />;
|
||||
return <QuestRow quest={item} completed={completedQuests.has(item.name)} onToggle={onToggle} onSelect={onSelect} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function QuestRow({ quest, completed, onToggle, indent }: {
|
||||
function QuestRow({ quest, completed, onToggle, onSelect, indent }: {
|
||||
quest: QuestItem;
|
||||
completed: boolean;
|
||||
onToggle: (name: string) => void;
|
||||
onSelect: (quest: { name: string; url: string | null }) => void;
|
||||
indent?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onToggle(quest.name)}
|
||||
style={{
|
||||
display: "flex", alignItems: "flex-start", gap: "8px",
|
||||
padding: indent ? "3px 0" : "4px 6px",
|
||||
borderRadius: "5px", cursor: "pointer",
|
||||
borderRadius: "5px",
|
||||
marginBottom: indent ? "1px" : "2px",
|
||||
opacity: completed ? 0.5 : 1,
|
||||
transition: "all 0.12s",
|
||||
@ -263,21 +342,19 @@ function QuestRow({ quest, completed, onToggle, indent }: {
|
||||
type="checkbox"
|
||||
checked={completed}
|
||||
onChange={() => onToggle(quest.name)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
style={{ marginTop: "2px", flexShrink: 0 }}
|
||||
style={{ marginTop: "2px", flexShrink: 0, cursor: "pointer" }}
|
||||
/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "baseline", flexWrap: "wrap", gap: "2px 8px" }}>
|
||||
<span style={{
|
||||
fontSize: "12px", lineHeight: 1.4,
|
||||
color: completed ? "#4a5568" : quest.url ? "#93c5fd" : "#e2e8f0",
|
||||
textDecoration: completed ? "line-through" : quest.url ? "underline" : "none",
|
||||
textDecorationColor: "rgba(147,197,253,0.4)",
|
||||
cursor: quest.url ? "pointer" : "default",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
onClick={e => {
|
||||
if (quest.url) { e.stopPropagation(); openUrl(quest.url); }
|
||||
<span
|
||||
onClick={() => onSelect({ name: quest.name, url: quest.url })}
|
||||
style={{
|
||||
fontSize: "12px", lineHeight: 1.4,
|
||||
color: completed ? "#4a5568" : "#93c5fd",
|
||||
textDecoration: completed ? "line-through" : "underline",
|
||||
textDecorationColor: "rgba(147,197,253,0.3)",
|
||||
cursor: "pointer",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{quest.name}
|
||||
@ -290,7 +367,7 @@ function QuestRow({ quest, completed, onToggle, indent }: {
|
||||
</div>
|
||||
{quest.note && (
|
||||
<div style={{ fontSize: "11px", color: "#4a5568", marginTop: "2px", fontStyle: "italic" }}>
|
||||
→ {quest.note}
|
||||
→ <TextWithCoords text={quest.note} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user