fix(media-gallery): harden download filename + zoom/pan from review
- mediaFilename: only skip appending an extension when the body already ends in a plausible short alphanumeric extension, so "Screenshot 2024.01.05" still gets a real extension appended for the saved file. - Lightbox pan: divide the translate by zoom (it runs nested inside scale), so dragging tracks the cursor 1:1 instead of moving `zoom`x too far. - Wheel: ignore deltaY === 0 (pure horizontal scroll no longer zooms out). - Zoom-out button disables at the real reachable minimum (0.2, not dead 0.1). - Zoom-controls group gets role="group" so its aria-label is announced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -135,10 +135,17 @@ function formatBytes(bytes: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A sensible download filename: prefer the event body/filename; if it has no
|
// A sensible download filename: prefer the event body/filename; if it has no
|
||||||
// extension, append one derived from the mimetype so the saved file opens.
|
// plausible extension already, append one derived from the mimetype so the
|
||||||
|
// saved file opens. "Plausible" = a short alphanumeric tail after the last dot,
|
||||||
|
// so "Screenshot 2024.01.05" still gets a real extension appended.
|
||||||
|
function hasFileExtension(name: string): boolean {
|
||||||
|
const dot = name.lastIndexOf('.');
|
||||||
|
if (dot <= 0 || dot === name.length - 1) return false;
|
||||||
|
return /^[a-z0-9]{1,5}$/i.test(name.slice(dot + 1));
|
||||||
|
}
|
||||||
function mediaFilename(body: string, mimeType?: string): string {
|
function mediaFilename(body: string, mimeType?: string): string {
|
||||||
const name = body.trim() || 'media';
|
const name = body.trim() || 'media';
|
||||||
if (name.includes('.')) return name;
|
if (hasFileExtension(name)) return name;
|
||||||
const ext = mimeType ? mimeTypeToExt(mimeType) : '';
|
const ext = mimeType ? mimeTypeToExt(mimeType) : '';
|
||||||
return ext ? `${name}.${ext}` : name;
|
return ext ? `${name}.${ext}` : name;
|
||||||
}
|
}
|
||||||
@@ -254,7 +261,12 @@ function LightboxMedia({
|
|||||||
borderRadius: config.radii.R300,
|
borderRadius: config.radii.R300,
|
||||||
display: 'block',
|
display: 'block',
|
||||||
cursor,
|
cursor,
|
||||||
transform: `scale(${zoom}) translate(${pan.translateX}px, ${pan.translateY}px)`,
|
// translate is nested inside scale(), so it runs in scaled space —
|
||||||
|
// divide by zoom so a dragged pixel moves the image one screen pixel
|
||||||
|
// (1:1 with the cursor) rather than `zoom` pixels.
|
||||||
|
transform: `scale(${zoom}) translate(${pan.translateX / zoom}px, ${
|
||||||
|
pan.translateY / zoom
|
||||||
|
}px)`,
|
||||||
transition: cursor === 'grabbing' ? 'none' : 'transform 120ms ease-out',
|
transition: cursor === 'grabbing' ? 'none' : 'transform 120ms ease-out',
|
||||||
willChange: 'transform',
|
willChange: 'transform',
|
||||||
}}
|
}}
|
||||||
@@ -313,7 +325,7 @@ function Lightbox({
|
|||||||
(e: React.WheelEvent) => {
|
(e: React.WheelEvent) => {
|
||||||
if (!isImage) return;
|
if (!isImage) return;
|
||||||
if (e.deltaY < 0) zoomIn();
|
if (e.deltaY < 0) zoomIn();
|
||||||
else zoomOut();
|
else if (e.deltaY > 0) zoomOut();
|
||||||
},
|
},
|
||||||
[isImage, zoomIn, zoomOut],
|
[isImage, zoomIn, zoomOut],
|
||||||
);
|
);
|
||||||
@@ -372,14 +384,20 @@ function Lightbox({
|
|||||||
{index + 1} / {items.length}
|
{index + 1} / {items.length}
|
||||||
</Text>
|
</Text>
|
||||||
{isImage && (
|
{isImage && (
|
||||||
<Box shrink="No" alignItems="Center" gap="100" aria-label="Zoom controls">
|
<Box
|
||||||
|
shrink="No"
|
||||||
|
alignItems="Center"
|
||||||
|
gap="100"
|
||||||
|
role="group"
|
||||||
|
aria-label="Zoom controls"
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="Surface"
|
variant="Surface"
|
||||||
size="300"
|
size="300"
|
||||||
radii="300"
|
radii="300"
|
||||||
aria-label="Zoom out"
|
aria-label="Zoom out"
|
||||||
onClick={zoomOut}
|
onClick={zoomOut}
|
||||||
disabled={zoom <= 0.1}
|
disabled={zoom <= 0.2}
|
||||||
>
|
>
|
||||||
<Icon size="50" src={Icons.Minus} />
|
<Icon size="50" src={Icons.Minus} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
Reference in New Issue
Block a user