@megaads/wm
Version:
To install the library, use npm:
236 lines (217 loc) • 7.12 kB
JSX
import React, { useMemo } from 'react';
export default function OptionsPanel({ snapshot, service, onSnapshot }) {
if (!snapshot) return null;
const apply = (snap) => onSnapshot(snap);
return (
<div>
<TemplatePicker
groups={snapshot.templateOptions}
onPick={(tpl) => apply(service.selectTemplate(tpl.id))}
/>
<div className="section">
<h3>Layer options ({snapshot.layerOptions.length})</h3>
{snapshot.layerOptions.length === 0 && (
<div style={{ color: '#94a3b8', fontSize: 12, padding: 8, background: '#1e293b', borderRadius: 4 }}>
{snapshot.template
? `Template "${snapshot.template.name}" không có layer interactive nào (không có layer nào có form_label). Đây là template tĩnh — thử chọn template khác.`
: 'Chưa chọn template. Pick một template phía trên để bắt đầu.'}
</div>
)}
{snapshot.layerOptions.map((layer) => (
<LayerControl
key={layer.id}
layer={layer}
service={service}
onSnapshot={apply}
errors={snapshot.validation.errors}
/>
))}
</div>
</div>
);
}
function TemplatePicker({ groups, onPick }) {
if (!groups || groups.length === 0) return null;
return (
<>
{groups.map((group) => (
<div className="section" key={group.artworkId}>
<h3>{group.label}</h3>
<div className="template-grid">
{group.optionValues.map((tpl) => (
<div
key={tpl.id}
className={'template-tile' + (tpl.active ? ' active' : '')}
onClick={() => onPick(tpl)}
>
{tpl.thumbnail && <img src={tpl.thumbnail} alt={tpl.name} />}
<div className="name">{tpl.name}</div>
</div>
))}
</div>
</div>
))}
</>
);
}
function LayerControl({ layer, service, onSnapshot, errors }) {
const error = errors.find((e) => e.type === 'layer' && e.layerId === layer.id);
return (
<div className="layer-control">
<div className="label">
<span>
{layer.form_label || layer.name || layer.id}
{layer.required && <span className="required">*</span>}
</span>
<span className="badge">{layer.input_type}</span>
</div>
{layer.form_visibility && (
<div className="checkbox-row">
<input
id={`vis-${layer.id}`}
type="checkbox"
checked={layer.form_visibility_value !== false}
onChange={(e) =>
onSnapshot(service.changeLayerVisibility(layer, e.target.checked))
}
/>
<label htmlFor={`vis-${layer.id}`} style={{ fontSize: 12 }}>
Show layer
</label>
</div>
)}
<ControlBody layer={layer} service={service} onSnapshot={onSnapshot} />
{error && <div className="error-box">{error.message}</div>}
</div>
);
}
function ControlBody({ layer, service, onSnapshot }) {
switch (layer.input_type) {
case 'text':
return <TextControl layer={layer} service={service} onSnapshot={onSnapshot} />;
case 'photo':
return <PhotoControl layer={layer} service={service} onSnapshot={onSnapshot} />;
case 'option':
case 'clipart':
return <OptionGrid layer={layer} service={service} onSnapshot={onSnapshot} />;
case 'grouped_clipart':
return <GroupedClipartControl layer={layer} service={service} onSnapshot={onSnapshot} />;
default:
return (
<div style={{ fontSize: 12, color: '#64748b' }}>
Unsupported input_type: {layer.input_type}
</div>
);
}
}
function TextControl({ layer, service, onSnapshot }) {
return (
<input
type="text"
value={layer.value || ''}
maxLength={layer.max_length || undefined}
placeholder={layer.text || ''}
onChange={(e) => onSnapshot(service.changeInputValue(layer, e.target.value))}
/>
);
}
function PhotoControl({ layer, service, onSnapshot }) {
const onPick = (e) => {
const file = e.target.files?.[0];
if (!file) return;
const url = URL.createObjectURL(file);
onSnapshot(service.changeUploadValue(layer, url, { file }));
};
return (
<div>
{layer.show_value && (
<img
src={layer.show_value}
alt=""
style={{
maxWidth: '100%',
maxHeight: 100,
border: '1px solid #334155',
borderRadius: 4,
marginBottom: 6,
}}
/>
)}
<input type="file" accept="image/*" onChange={onPick} style={{ fontSize: 12 }} />
</div>
);
}
function OptionGrid({ layer, service, onSnapshot }) {
const items = layer.option_items || [];
if (items.length === 0) {
return <div style={{ fontSize: 12, color: '#64748b' }}>(no items)</div>;
}
return (
<div className="option-grid">
{items.map((item) => (
<div
key={item.id}
className={'option-tile' + (item.active ? ' active' : '')}
onClick={() => onSnapshot(service.changeLayerOptionValue(item, layer))}
title={item.name}
>
{item.url || item.thumbnail ? (
<img src={item.thumbnail || item.url} alt={item.name} />
) : (
<span className="label-text">{item.name}</span>
)}
</div>
))}
</div>
);
}
function GroupedClipartControl({ layer, service, onSnapshot }) {
const groups = layer.option_items || [];
const activeGroup = useMemo(
() => groups.find((g) => g.active) || groups[0] || null,
[groups]
);
if (!groups.length) {
return <div style={{ fontSize: 12, color: '#64748b' }}>(no groups)</div>;
}
return (
<div>
<div className="group-tabs">
{groups.map((group) => (
<div
key={group.id}
className={'group-tab' + (group.active ? ' active' : '')}
onClick={() => onSnapshot(service.changeOptionGroupClipartValue(group, layer))}
title={group.name}
>
{group.thumbnail ? (
<img src={group.thumbnail} alt={group.name} />
) : (
<span className="name">{group.name}</span>
)}
</div>
))}
</div>
{activeGroup && (
<div className="option-grid">
{(activeGroup.options || []).map((item) => (
<div
key={item.id}
className={'option-tile' + (item.active ? ' active' : '')}
onClick={() =>
onSnapshot(service.changeLayerOptionValue(item, activeGroup, layer))
}
title={item.name}
>
{item.url || item.thumbnail ? (
<img src={item.thumbnail || item.url} alt={item.name} />
) : (
<span className="label-text">{item.name}</span>
)}
</div>
))}
</div>
)}
</div>
);
}