Merge pull request 'Make the hero Moon image configurable (src/alt), backwards-compatible' (#20) from feature/configurable-moon-image into main
Reviewed-on: UOM/website#20 Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
This commit is contained in:
@@ -107,12 +107,15 @@ function content(element) {
|
|||||||
case 'buttons':
|
case 'buttons':
|
||||||
return <Buttons props={element.props || {}} />
|
return <Buttons props={element.props || {}} />
|
||||||
case 'moon': {
|
case 'moon': {
|
||||||
const size = element.props?.size || 96
|
const props = element.props || {}
|
||||||
const glow = element.props?.glow ?? 0.45
|
const size = props.size || 96
|
||||||
|
const glow = props.glow ?? 0.45
|
||||||
|
// Image source is configurable; old layouts with no src fall back to the
|
||||||
|
// default hero moon so they render exactly as before. Size/glow unchanged.
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={MOON_IMAGE}
|
src={props.src || MOON_IMAGE}
|
||||||
alt=""
|
alt={props.alt || ''}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
style={{
|
style={{
|
||||||
width: size,
|
width: size,
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ const round2 = (v) => Math.round(v * 100) / 100
|
|||||||
const genId = () => (crypto.randomUUID ? crypto.randomUUID() : `el-${Date.now()}-${Math.random()}`)
|
const genId = () => (crypto.randomUUID ? crypto.randomUUID() : `el-${Date.now()}-${Math.random()}`)
|
||||||
const hexOf = (v) => (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(v || '') ? v : '#ffffff')
|
const hexOf = (v) => (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(v || '') ? v : '#ffffff')
|
||||||
|
|
||||||
|
// Soft page-weight warning shown before uploading a large hero image. This is
|
||||||
|
// only a nudge (hero images render on the public landing page); the server hard-
|
||||||
|
// limits uploads at 8 MB. Returns true when the caller should abort the upload.
|
||||||
|
const WARN_UPLOAD_MB = 5
|
||||||
|
function tooLargeToUpload(size) {
|
||||||
|
return (
|
||||||
|
size > WARN_UPLOAD_MB * 1024 * 1024 &&
|
||||||
|
!confirm(`This image is over ${WARN_UPLOAD_MB} MB and may slow the page. Upload anyway?`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function newElement(type, z) {
|
function newElement(type, z) {
|
||||||
const base = { id: genId(), type, x: 50, y: 50, z, anchor: 'center' }
|
const base = { id: genId(), type, x: 50, y: 50, z, anchor: 'center' }
|
||||||
if (type === 'text_block') {
|
if (type === 'text_block') {
|
||||||
@@ -206,7 +217,7 @@ export default function HeroEditor() {
|
|||||||
const file = e.target.files?.[0]
|
const file = e.target.files?.[0]
|
||||||
e.target.value = ''
|
e.target.value = ''
|
||||||
if (!file) return
|
if (!file) return
|
||||||
if (file.size > 1024 * 1024 && !confirm('This image is over 1 MB and may slow the page. Upload anyway?')) return
|
if (tooLargeToUpload(file.size)) return
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
try {
|
try {
|
||||||
const { url } = await api.admin.upload(file)
|
const { url } = await api.admin.upload(file)
|
||||||
@@ -499,9 +510,45 @@ const swatch = { width: '100%', height: 38, padding: 2, border: '1px solid var(-
|
|||||||
|
|
||||||
function MoonPanel({ element, onProps }) {
|
function MoonPanel({ element, onProps }) {
|
||||||
const p = element.props || {}
|
const p = element.props || {}
|
||||||
|
const [up, setUp] = useState(false)
|
||||||
|
// Reuses the shared admin upload endpoint (same as the image/background panels);
|
||||||
|
// a successful upload just points props.src at the returned URL.
|
||||||
|
async function onFile(e) {
|
||||||
|
const f = e.target.files?.[0]
|
||||||
|
e.target.value = ''
|
||||||
|
if (!f) return
|
||||||
|
if (tooLargeToUpload(f.size)) return
|
||||||
|
setUp(true)
|
||||||
|
try {
|
||||||
|
const { url } = await api.admin.upload(f)
|
||||||
|
onProps({ src: url })
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
} finally {
|
||||||
|
setUp(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
<p className="sans dim" style={{ margin: 0, fontSize: '0.8rem' }}>Uses the moon from the hero artwork.</p>
|
<div>
|
||||||
|
<span className="field-label">Moon image</span>
|
||||||
|
{p.src ? (
|
||||||
|
<img src={p.src} alt="" style={{ width: '100%', maxHeight: 90, objectFit: 'contain', borderRadius: 6, border: '1px solid var(--line)', marginBottom: 8 }} />
|
||||||
|
) : (
|
||||||
|
<p className="sans dim" style={{ margin: '0 0 8px', fontSize: '0.8rem' }}>Using the default moon from the hero artwork.</p>
|
||||||
|
)}
|
||||||
|
<label className="btn btn-ghost btn-sq" style={{ display: 'inline-block', cursor: 'pointer' }}>
|
||||||
|
{up ? 'Uploading…' : p.src ? 'Replace' : 'Upload'}
|
||||||
|
<input type="file" accept="image/*" onChange={onFile} hidden disabled={up} />
|
||||||
|
</label>
|
||||||
|
{p.src && (
|
||||||
|
<span className="link-accent" style={{ fontSize: '0.8rem', marginLeft: 10 }} onClick={() => onProps({ src: '' })}>Use default</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
<span className="field-label">Alt text</span>
|
||||||
|
<input className="input" value={p.alt || ''} onChange={(e) => onProps({ alt: e.target.value })} />
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span className="field-label">Size — {p.size || 96}px</span>
|
<span className="field-label">Size — {p.size || 96}px</span>
|
||||||
<input type="range" min="24" max="320" step="1" value={p.size || 96} onChange={(e) => onProps({ size: Number(e.target.value) })} style={{ width: '100%' }} />
|
<input type="range" min="24" max="320" step="1" value={p.size || 96} onChange={(e) => onProps({ size: Number(e.target.value) })} style={{ width: '100%' }} />
|
||||||
@@ -547,7 +594,7 @@ function ImagePanel({ element, onProps }) {
|
|||||||
const f = e.target.files?.[0]
|
const f = e.target.files?.[0]
|
||||||
e.target.value = ''
|
e.target.value = ''
|
||||||
if (!f) return
|
if (!f) return
|
||||||
if (f.size > 1024 * 1024 && !confirm('This image is over 1 MB and may slow the page. Upload anyway?')) return
|
if (tooLargeToUpload(f.size)) return
|
||||||
setUp(true)
|
setUp(true)
|
||||||
try {
|
try {
|
||||||
const { url } = await api.admin.upload(f)
|
const { url } = await api.admin.upload(f)
|
||||||
|
|||||||
Reference in New Issue
Block a user