mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-10-02 07:34:35 +08:00
fix(dsh): keep extreme screenshot thumbnails usable
This commit is contained in:
@@ -4,6 +4,17 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.status,
|
||||
.retry {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.thumbnail,
|
||||
.retry,
|
||||
.preview button {
|
||||
@@ -27,10 +38,12 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.retry,
|
||||
.retry {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.preview button {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import css from "./ScreenshotImage.module.css";
|
||||
|
||||
type ImageState = { status: "loading" } | { status: "error" } | { status: "ready"; url: string };
|
||||
|
||||
const MIN_EDGE = 60;
|
||||
const MAX_EDGE = 240;
|
||||
|
||||
/** Plugin-owned presentation: DSH's attachment client no longer exports image components. */
|
||||
export function ScreenshotImage({
|
||||
attachment,
|
||||
@@ -16,6 +19,13 @@ export function ScreenshotImage({
|
||||
const [attempt, setAttempt] = useState(0);
|
||||
const [open, setOpen] = useState(false);
|
||||
const label = attachment.name ?? "screenshot";
|
||||
const naturalRatio = attachment.width / attachment.height;
|
||||
const ratio = Math.min(4, Math.max(0.25, naturalRatio));
|
||||
// Keep both axes usable for loading/retry, even for tiny element captures.
|
||||
const width = Math.max(
|
||||
MIN_EDGE * Math.max(1, ratio),
|
||||
Math.min(MAX_EDGE, MAX_EDGE * ratio, attachment.width, attachment.height * ratio),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
@@ -45,19 +55,23 @@ export function ScreenshotImage({
|
||||
<div
|
||||
className={css.frame}
|
||||
style={{
|
||||
width: Math.min(attachment.width, 240, (240 * attachment.width) / attachment.height),
|
||||
aspectRatio: `${attachment.width} / ${attachment.height}`,
|
||||
width,
|
||||
aspectRatio: String(ratio),
|
||||
}}
|
||||
>
|
||||
{image.status === "loading" ? (
|
||||
<span role="status">Loading…</span>
|
||||
<span role="status" className={css.status}>
|
||||
Loading…
|
||||
</span>
|
||||
) : image.status === "error" ? (
|
||||
<button
|
||||
type="button"
|
||||
className={css.retry}
|
||||
aria-label="Load failed — retry"
|
||||
title="Load failed — retry"
|
||||
onClick={() => setAttempt((value) => value + 1)}
|
||||
>
|
||||
Load failed — retry
|
||||
Retry
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
@@ -72,6 +86,11 @@ export function ScreenshotImage({
|
||||
alt={label}
|
||||
width={attachment.width}
|
||||
height={attachment.height}
|
||||
style={{
|
||||
objectFit: naturalRatio === ratio ? "scale-down" : "cover",
|
||||
objectPosition:
|
||||
naturalRatio < 0.25 ? "center top" : naturalRatio > 4 ? "left center" : "center",
|
||||
}}
|
||||
onError={() => setImage({ status: "error" })}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -19,6 +19,39 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ width: 1280, height: 40, boxWidth: 240, ratio: 4, fit: "cover", position: "left center" },
|
||||
{ width: 300, height: 3000, boxWidth: 60, ratio: 0.25, fit: "cover", position: "center top" },
|
||||
{ width: 1, height: 10000, boxWidth: 60, ratio: 0.25, fit: "cover", position: "center top" },
|
||||
{ width: 16, height: 16, boxWidth: 60, ratio: 1, fit: "scale-down", position: "center" },
|
||||
{ width: 128, height: 96, boxWidth: 128, ratio: 4 / 3, fit: "scale-down", position: "center" },
|
||||
])("keeps a $width×$height capture usable without cropping its preview", async (size) => {
|
||||
const imageAttachment = { ...attachment, width: size.width, height: size.height };
|
||||
let finish!: (url: string) => void;
|
||||
const load = vi.fn(
|
||||
() =>
|
||||
new Promise<string>((resolve) => {
|
||||
finish = resolve;
|
||||
}),
|
||||
);
|
||||
render(<ScreenshotImage attachment={imageAttachment} load={load} />);
|
||||
const frame = screen.getByRole("status").parentElement!;
|
||||
expect(frame.style.width).toBe(`${size.boxWidth}px`);
|
||||
const [horizontal, vertical = 1] = frame.style.aspectRatio.split("/").map(Number);
|
||||
expect(horizontal / vertical).toBe(size.ratio);
|
||||
await act(async () => finish("blob:thumbnail"));
|
||||
const thumbnail = screen.getByRole("img");
|
||||
expect(thumbnail.parentElement?.parentElement).toBe(frame);
|
||||
expect(thumbnail.style.objectFit).toBe(size.fit);
|
||||
expect(thumbnail.style.objectPosition).toBe(size.position);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Open screenshot screenshot.png" }));
|
||||
const preview = screen.getByRole("dialog").querySelector("img")!;
|
||||
expect(preview.getAttribute("src")).toBe("blob:thumbnail");
|
||||
expect(preview.style.objectFit).toBe("");
|
||||
expect(preview.style.objectPosition).toBe("");
|
||||
});
|
||||
|
||||
it("retries an attachment read failure and releases the loaded URL on unmount", async () => {
|
||||
const load = vi.fn().mockRejectedValueOnce(new Error("offline")).mockResolvedValue("blob:retry");
|
||||
const view = render(<ScreenshotImage attachment={attachment} load={load} />);
|
||||
|
||||
Reference in New Issue
Block a user