feat: add preview of images in the quest
This commit is contained in:
@ -4,8 +4,6 @@ import { openUrl } from "@tauri-apps/plugin-opener";
|
|||||||
import { QuestStep } from "../types";
|
import { QuestStep } from "../types";
|
||||||
import { TextWithCoords } from "./TextWithCoords";
|
import { TextWithCoords } from "./TextWithCoords";
|
||||||
|
|
||||||
const PREVIEW_LENGTH = 280;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
questName: string;
|
questName: string;
|
||||||
questUrl: string | null;
|
questUrl: string | null;
|
||||||
@ -146,40 +144,70 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Quest info header (first step) */}
|
|
||||||
{!loading && headerStep && (
|
{!loading && headerStep && (
|
||||||
<QuestHeader step={headerStep} />
|
<QuestHeader step={headerStep} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action steps */}
|
|
||||||
{!loading && actionSteps.map((step) => {
|
{!loading && actionSteps.map((step) => {
|
||||||
const done = completedSteps.has(step.index);
|
const done = completedSteps.has(step.index);
|
||||||
const expanded = expandedSteps.has(step.index);
|
const expanded = expandedSteps.has(step.index);
|
||||||
const needsTruncate = step.text.length > PREVIEW_LENGTH;
|
|
||||||
|
if (done) {
|
||||||
|
const firstLine = step.text.split('\n').find(l => l.trim().length > 0) ?? step.text;
|
||||||
|
return (
|
||||||
|
<div key={step.index} style={{
|
||||||
|
marginBottom: "4px",
|
||||||
|
padding: "6px 12px",
|
||||||
|
border: "1px solid rgba(74,222,128,0.1)",
|
||||||
|
borderRadius: "7px",
|
||||||
|
background: "rgba(74,222,128,0.03)",
|
||||||
|
opacity: 0.5,
|
||||||
|
}}>
|
||||||
|
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={true}
|
||||||
|
onChange={() => toggleStep(step.index)}
|
||||||
|
style={{ flexShrink: 0, cursor: "pointer" }}
|
||||||
|
/>
|
||||||
|
<span style={{
|
||||||
|
fontSize: "12px", color: "#4a5568",
|
||||||
|
textDecoration: "line-through",
|
||||||
|
overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
|
||||||
|
flex: 1, minWidth: 0,
|
||||||
|
}}>
|
||||||
|
{firstLine}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = step.text.split('\n').filter(l => l.trim().length > 0);
|
||||||
|
const needsTruncate = lines.length > 4;
|
||||||
const displayText = needsTruncate && !expanded
|
const displayText = needsTruncate && !expanded
|
||||||
? step.text.slice(0, PREVIEW_LENGTH).trimEnd() + "…"
|
? lines.slice(0, 4).join('\n')
|
||||||
: step.text;
|
: step.text;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={step.index} style={{
|
<div key={step.index} style={{
|
||||||
marginBottom: "8px",
|
marginBottom: "8px",
|
||||||
background: done ? "rgba(74,222,128,0.04)" : "rgba(255,255,255,0.02)",
|
background: "rgba(255,255,255,0.02)",
|
||||||
border: `1px solid ${done ? "rgba(74,222,128,0.2)" : "#2d3748"}`,
|
border: "1px solid #2d3748",
|
||||||
borderRadius: "7px", padding: "10px 12px",
|
borderRadius: "7px", padding: "10px 12px",
|
||||||
opacity: done ? 0.65 : 1, transition: "all 0.15s",
|
transition: "all 0.15s",
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: "flex", gap: "10px", alignItems: "flex-start" }}>
|
<div style={{ display: "flex", gap: "10px", alignItems: "flex-start" }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={done}
|
checked={false}
|
||||||
onChange={() => toggleStep(step.index)}
|
onChange={() => toggleStep(step.index)}
|
||||||
style={{ marginTop: "2px", flexShrink: 0 }}
|
style={{ marginTop: "2px", flexShrink: 0, cursor: "pointer" }}
|
||||||
/>
|
/>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: "12px", color: done ? "#4a5568" : "#94a3b8",
|
fontSize: "12px", color: "#94a3b8",
|
||||||
lineHeight: 1.6, whiteSpace: "pre-wrap", wordBreak: "break-word",
|
lineHeight: 1.6, whiteSpace: "pre-wrap", wordBreak: "break-word",
|
||||||
textDecoration: done ? "line-through" : "none",
|
|
||||||
}}>
|
}}>
|
||||||
<TextWithCoords text={displayText} />
|
<TextWithCoords text={displayText} />
|
||||||
</div>
|
</div>
|
||||||
@ -198,21 +226,18 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{step.images.length > 0 && (
|
{step.images.length > 0 && (
|
||||||
<div style={{ marginTop: "6px", display: "flex", flexWrap: "wrap", gap: "4px" }}>
|
<div style={{ marginTop: "8px", display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
{step.images.map((src, j) => (
|
{step.images.map((src, j) => (
|
||||||
<button
|
<img
|
||||||
key={j}
|
key={j}
|
||||||
|
src={src}
|
||||||
onClick={() => openUrl(src)}
|
onClick={() => openUrl(src)}
|
||||||
style={{
|
style={{
|
||||||
background: "rgba(74,158,255,0.08)", border: "1px solid rgba(74,158,255,0.25)",
|
maxWidth: "100%", height: "auto",
|
||||||
borderRadius: "4px", color: "#4a9eff", fontSize: "10px",
|
borderRadius: "6px", display: "block",
|
||||||
padding: "2px 8px", cursor: "pointer",
|
border: "1px solid #2d3748", cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={e => (e.currentTarget.style.background = "rgba(74,158,255,0.18)")}
|
/>
|
||||||
onMouseLeave={e => (e.currentTarget.style.background = "rgba(74,158,255,0.08)")}
|
|
||||||
>
|
|
||||||
🖼 Image {j + 1}
|
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -249,21 +274,18 @@ function QuestHeader({ step }: { step: QuestStep }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{step.images.length > 0 && (
|
{step.images.length > 0 && (
|
||||||
<div style={{ padding: "0 14px 10px", display: "flex", flexWrap: "wrap", gap: "4px" }}>
|
<div style={{ padding: "0 14px 10px", display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
{step.images.map((src, j) => (
|
{step.images.map((src, j) => (
|
||||||
<button
|
<img
|
||||||
key={j}
|
key={j}
|
||||||
|
src={src}
|
||||||
onClick={() => openUrl(src)}
|
onClick={() => openUrl(src)}
|
||||||
style={{
|
style={{
|
||||||
background: "rgba(240,192,64,0.08)", border: "1px solid rgba(240,192,64,0.2)",
|
maxWidth: "100%", height: "auto",
|
||||||
borderRadius: "4px", color: "#f0c040", fontSize: "10px",
|
borderRadius: "6px", display: "block",
|
||||||
padding: "2px 8px", cursor: "pointer",
|
border: "1px solid #2d3748", cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={e => (e.currentTarget.style.background = "rgba(240,192,64,0.15)")}
|
/>
|
||||||
onMouseLeave={e => (e.currentTarget.style.background = "rgba(240,192,64,0.08)")}
|
|
||||||
>
|
|
||||||
🖼 Image {j + 1}
|
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user