Preserve PNG transparency for static images and show a checkerboard preview.
Static uploads were forced to JPEG with a white fill, which turned white icons into solid squares; PNG sources now stay PNG and CMS previews get a visible background. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
6d85a217e1
commit
95390933a7
@@ -65,6 +65,18 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Checkerboard so white/transparent PNG icons stay visible in the CMS. */
|
||||||
|
.imageButtonTransparent {
|
||||||
|
background-color: #64748b;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(45deg, #475569 25%, transparent 25%),
|
||||||
|
linear-gradient(-45deg, #475569 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, transparent 75%, #475569 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, #475569 75%);
|
||||||
|
background-size: 16px 16px;
|
||||||
|
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useLocale } from '@meshkee/dashboard-ui'
|
|||||||
import { useT } from '../i18n/useT'
|
import { useT } from '../i18n/useT'
|
||||||
import type { WebsiteStaticImageItem } from '../types/websiteStaticImage'
|
import type { WebsiteStaticImageItem } from '../types/websiteStaticImage'
|
||||||
import { cssAspectRatio, slotPreviewSize } from '../utils/aspectRatio'
|
import { cssAspectRatio, slotPreviewSize } from '../utils/aspectRatio'
|
||||||
|
import { isPngImageUrl } from '../utils/imageUpload'
|
||||||
import { Tooltip } from './Tooltip'
|
import { Tooltip } from './Tooltip'
|
||||||
import controlStyles from './CategoryRow.module.css'
|
import controlStyles from './CategoryRow.module.css'
|
||||||
import styles from './WebsiteStaticImageGallery.module.css'
|
import styles from './WebsiteStaticImageGallery.module.css'
|
||||||
@@ -106,7 +107,7 @@ export function WebsiteStaticImageGallery({
|
|||||||
<article className={styles.card}>
|
<article className={styles.card}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.imageButton}
|
className={`${styles.imageButton} ${isPngImageUrl(image.url) ? styles.imageButtonTransparent : ''}`}
|
||||||
onClick={() => onAddAt(index)}
|
onClick={() => onAddAt(index)}
|
||||||
aria-label={replaceLabel}
|
aria-label={replaceLabel}
|
||||||
style={{ aspectRatio: cssAspect }}
|
style={{ aspectRatio: cssAspect }}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { BusinessMessageKey } from '../i18n/messages'
|
|||||||
import { ApiError } from '../lib/api'
|
import { ApiError } from '../lib/api'
|
||||||
import { getBusinessDomain } from '../lib/config'
|
import { getBusinessDomain } from '../lib/config'
|
||||||
import { resolveDataUrlToMediaId } from '../services/mediaService'
|
import { resolveDataUrlToMediaId } from '../services/mediaService'
|
||||||
|
import { uploadFilenameForImageDataUrl } from '../utils/imageUpload'
|
||||||
import {
|
import {
|
||||||
deleteWebsiteStaticImageSlot,
|
deleteWebsiteStaticImageSlot,
|
||||||
listWebsiteStaticImageSlots,
|
listWebsiteStaticImageSlots,
|
||||||
@@ -230,7 +231,7 @@ export function WebsiteStaticImagesPage() {
|
|||||||
const occupying = occupyingImage(slot, index)
|
const occupying = occupyingImage(slot, index)
|
||||||
const imageMediaId = await resolveDataUrlToMediaId(
|
const imageMediaId = await resolveDataUrlToMediaId(
|
||||||
data.image,
|
data.image,
|
||||||
'static-image.jpg',
|
uploadFilenameForImageDataUrl(data.image ?? '', 'static-image'),
|
||||||
occupying?.imageMediaId,
|
occupying?.imageMediaId,
|
||||||
)
|
)
|
||||||
if (!imageMediaId) {
|
if (!imageMediaId) {
|
||||||
|
|||||||
@@ -98,3 +98,21 @@ export async function ensureJpegUploadFile(
|
|||||||
: `${filename.replace(/\.[^.]+$/, '') || 'image'}.jpg`,
|
: `${filename.replace(/\.[^.]+$/, '') || 'image'}.jpg`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pick .png vs .jpg from a data URL so PNG transparency is not flattened to JPEG. */
|
||||||
|
export function uploadFilenameForImageDataUrl(
|
||||||
|
dataUrl: string,
|
||||||
|
baseName: string,
|
||||||
|
): string {
|
||||||
|
const safe = baseName.replace(/\.[^.]+$/, '') || 'image'
|
||||||
|
return /^data:image\/png/i.test(dataUrl) ? `${safe}.png` : `${safe}.jpg`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPngImageUrl(url: string): boolean {
|
||||||
|
if (/^data:image\/png/i.test(url)) return true
|
||||||
|
try {
|
||||||
|
return new URL(url, 'https://local').pathname.toLowerCase().endsWith('.png')
|
||||||
|
} catch {
|
||||||
|
return /\.png(\?|#|$)/i.test(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user