refactor(builder): remove dead component settings UI
Each component defined a .craft.related.settings panel that was never rendered -- the right panel renders only GuidedStyles (per-type *StylePanel components), never .related.settings. Removed all dead settings components across every component, their settings-only helpers (including the dead uploadToWhp/showBrowser/handleBrowse asset-browse blocks in Logo/Navbar/VideoBlock/FeaturesGrid, and CtasEditor in _cta-helpers), and dropped the now-empty related keys. Render output, .craft props/rules, and toHtml statics are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { CSSProperties, useCallback, useRef, useState } from 'react';
|
||||
import React, { CSSProperties } from 'react';
|
||||
import { useNode, Element, UserComponent } from '@craftjs/core';
|
||||
import { Container } from '../layout/Container';
|
||||
import { cssPropsToString } from '../../utils/style-helpers';
|
||||
@@ -61,28 +61,6 @@ function buildEmbedParams(
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/* ---------- Upload helper ---------- */
|
||||
|
||||
async function uploadToWhp(file: File): Promise<string | null> {
|
||||
const cfg = (window as any).WHP_CONFIG;
|
||||
if (!cfg) return URL.createObjectURL(file);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
try {
|
||||
const resp = await fetch(`${cfg.apiUrl}?action=upload_asset&site_id=${cfg.siteId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': cfg.csrfToken },
|
||||
body: formData,
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.success && data.url) return data.url;
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Placeholder ---------- */
|
||||
|
||||
const VIDEO_PLACEHOLDER = (
|
||||
@@ -297,342 +275,6 @@ export const VideoBlock: UserComponent<VideoBlockProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
/* ========================================================================
|
||||
Settings Panel
|
||||
======================================================================== */
|
||||
|
||||
const VideoBlockSettings: React.FC = () => {
|
||||
const {
|
||||
actions: { setProp },
|
||||
props,
|
||||
} = useNode((node) => ({
|
||||
props: node.data.props as VideoBlockProps,
|
||||
}));
|
||||
|
||||
const [urlInput, setUrlInput] = useState(props.videoUrl || '');
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const detected = props.videoUrl ? detectVideoType(props.videoUrl) : { type: 'none' as VideoType, embedUrl: '' };
|
||||
|
||||
const applyUrl = useCallback(
|
||||
(url: string) => {
|
||||
const info = detectVideoType(url);
|
||||
setProp((p: VideoBlockProps) => {
|
||||
p.videoUrl = url;
|
||||
p.videoType = info.type;
|
||||
p.embedUrl = info.embedUrl;
|
||||
});
|
||||
},
|
||||
[setProp]
|
||||
);
|
||||
|
||||
const handleUpload = useCallback(
|
||||
async (file: File) => {
|
||||
const url = await uploadToWhp(file);
|
||||
if (url) {
|
||||
setUrlInput(url);
|
||||
applyUrl(url);
|
||||
}
|
||||
},
|
||||
[applyUrl]
|
||||
);
|
||||
|
||||
const handleBrowse = useCallback(async () => {
|
||||
const cfg = (window as any).WHP_CONFIG;
|
||||
if (!cfg) return;
|
||||
try {
|
||||
const resp = await fetch(`${cfg.apiUrl}?action=list_assets&site_id=${cfg.siteId}`);
|
||||
const data = await resp.json();
|
||||
if (data.success && Array.isArray(data.assets)) {
|
||||
const videos = data.assets.filter(
|
||||
(a: any) => (a.type || '').startsWith('video') || (a.name || '').match(/\.(mp4|webm|ogg|mov)$/i)
|
||||
);
|
||||
if (videos.length === 0) {
|
||||
alert('No video assets uploaded yet. Use the Upload button to add one.');
|
||||
return;
|
||||
}
|
||||
const names = videos.map((a: any, i: number) => `${i + 1}. ${a.name}`).join('\n');
|
||||
const choice = prompt(`Select a video (enter number):\n\n${names}`);
|
||||
if (choice) {
|
||||
const idx = parseInt(choice, 10) - 1;
|
||||
if (videos[idx]) {
|
||||
setUrlInput(videos[idx].url);
|
||||
applyUrl(videos[idx].url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Browse failed:', e);
|
||||
}
|
||||
}, [applyUrl]);
|
||||
|
||||
const typeBadge = (label: string, color: string) => (
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 4,
|
||||
background: color,
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
fontWeight: 600,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
|
||||
const overlayPresets = ['#000000', '#1e293b', '#0f172a', '#312e81', '#064e3b', '#7f1d1d'];
|
||||
|
||||
const labelStyle: CSSProperties = { fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 4 };
|
||||
const inputStyle: CSSProperties = {
|
||||
width: '100%',
|
||||
padding: '4px 8px',
|
||||
background: '#27272a',
|
||||
color: '#e4e4e7',
|
||||
border: '1px solid #3f3f46',
|
||||
borderRadius: 4,
|
||||
fontSize: 12,
|
||||
};
|
||||
const checkboxRowStyle: CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
fontSize: 12,
|
||||
color: '#e4e4e7',
|
||||
cursor: 'pointer',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{/* Video URL */}
|
||||
<div>
|
||||
<label style={labelStyle}>Video URL</label>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<input
|
||||
type="text"
|
||||
value={urlInput}
|
||||
onChange={(e) => setUrlInput(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') applyUrl(urlInput);
|
||||
}}
|
||||
placeholder="YouTube, Vimeo, or direct video URL..."
|
||||
style={{ ...inputStyle, flex: 1 }}
|
||||
/>
|
||||
<button
|
||||
onClick={() => applyUrl(urlInput)}
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
fontSize: 11,
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #3f3f46',
|
||||
background: '#3b82f6',
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Detected type badge */}
|
||||
{detected.type !== 'none' && (
|
||||
<div style={{ marginTop: 6 }}>
|
||||
{detected.type === 'youtube' && typeBadge('YouTube', '#dc2626')}
|
||||
{detected.type === 'vimeo' && typeBadge('Vimeo', '#1ab7ea')}
|
||||
{detected.type === 'file' && typeBadge('Video File', '#16a34a')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Upload / Browse */}
|
||||
<div>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '8px 10px',
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #3f3f46',
|
||||
background: '#3b82f6',
|
||||
color: '#fff',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-upload" style={{ marginRight: 4 }} /> Upload
|
||||
</button>
|
||||
<button
|
||||
onClick={handleBrowse}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '8px 10px',
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #3f3f46',
|
||||
background: '#27272a',
|
||||
color: '#e4e4e7',
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-folder-open" style={{ marginRight: 4 }} /> Browse
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="video/*"
|
||||
style={{ display: 'none' }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleUpload(file);
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Playback options */}
|
||||
<div>
|
||||
<label style={labelStyle}>Playback</label>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={checkboxRowStyle}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.autoplay ?? false}
|
||||
onChange={(e) => setProp((p: VideoBlockProps) => { p.autoplay = e.target.checked; })}
|
||||
/>
|
||||
Autoplay
|
||||
</label>
|
||||
<label style={checkboxRowStyle}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.muted ?? true}
|
||||
onChange={(e) => setProp((p: VideoBlockProps) => { p.muted = e.target.checked; })}
|
||||
/>
|
||||
Muted
|
||||
</label>
|
||||
<label style={checkboxRowStyle}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.loop ?? false}
|
||||
onChange={(e) => setProp((p: VideoBlockProps) => { p.loop = e.target.checked; })}
|
||||
/>
|
||||
Loop
|
||||
</label>
|
||||
<label style={checkboxRowStyle}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.controls ?? true}
|
||||
onChange={(e) => setProp((p: VideoBlockProps) => { p.controls = e.target.checked; })}
|
||||
/>
|
||||
Show Controls
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mode toggle */}
|
||||
<div>
|
||||
<label style={labelStyle}>Display Mode</label>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<button
|
||||
onClick={() => setProp((p: VideoBlockProps) => { p.isBackground = false; })}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '6px 10px',
|
||||
fontSize: 11,
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #3f3f46',
|
||||
background: !props.isBackground ? '#3b82f6' : '#27272a',
|
||||
color: !props.isBackground ? '#fff' : '#a1a1aa',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
Normal
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setProp((p: VideoBlockProps) => { p.isBackground = true; })}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '6px 10px',
|
||||
fontSize: 11,
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #3f3f46',
|
||||
background: props.isBackground ? '#3b82f6' : '#27272a',
|
||||
color: props.isBackground ? '#fff' : '#a1a1aa',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
Background
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background mode options */}
|
||||
{props.isBackground && (
|
||||
<>
|
||||
<div>
|
||||
<label style={labelStyle}>Overlay Color</label>
|
||||
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
|
||||
{overlayPresets.map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
onClick={() => setProp((p: VideoBlockProps) => { p.overlayColor = c; })}
|
||||
style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 4,
|
||||
border: '1px solid #3f3f46',
|
||||
backgroundColor: c,
|
||||
cursor: 'pointer',
|
||||
outline: props.overlayColor === c ? '2px solid #3b82f6' : 'none',
|
||||
outlineOffset: 1,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={labelStyle}>
|
||||
Overlay Opacity: {props.overlayOpacity ?? 50}%
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
value={props.overlayOpacity ?? 50}
|
||||
onChange={(e) =>
|
||||
setProp((p: VideoBlockProps) => {
|
||||
p.overlayOpacity = parseInt(e.target.value, 10);
|
||||
})
|
||||
}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={labelStyle}>Inner Max Width</label>
|
||||
<input
|
||||
type="text"
|
||||
value={props.innerMaxWidth || '1200px'}
|
||||
onChange={(e) => setProp((p: VideoBlockProps) => { p.innerMaxWidth = e.target.value; })}
|
||||
style={inputStyle}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ========================================================================
|
||||
Craft Config
|
||||
======================================================================== */
|
||||
@@ -658,9 +300,6 @@ VideoBlock.craft = {
|
||||
canMoveIn: () => true,
|
||||
canMoveOut: () => true,
|
||||
},
|
||||
related: {
|
||||
settings: VideoBlockSettings,
|
||||
},
|
||||
};
|
||||
|
||||
/* ========================================================================
|
||||
|
||||
Reference in New Issue
Block a user