| @@ -11,7 +11,7 @@ function StyledSelect<T extends string>({ | |||||
| }: { | }: { | ||||
| value: T; | value: T; | ||||
| onChange: (v: T) => void; | onChange: (v: T) => void; | ||||
| options: { value: T; label: string }[]; | |||||
| options: { value: T; label: string; style?: React.CSSProperties }[]; | |||||
| }) { | }) { | ||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const ref = useRef<HTMLDivElement>(null); | const ref = useRef<HTMLDivElement>(null); | ||||
| @@ -45,7 +45,7 @@ function StyledSelect<T extends string>({ | |||||
| onClick={() => setOpen(o => !o)} | onClick={() => setOpen(o => !o)} | ||||
| className={`${inputBase} text-left flex items-center justify-between cursor-pointer`} | className={`${inputBase} text-left flex items-center justify-between cursor-pointer`} | ||||
| > | > | ||||
| <span className={displayLabel ? '' : 'text-gray-400'}>{displayLabel || 'Seleziona…'}</span> | |||||
| <span className={displayLabel ? '' : 'text-gray-400'} style={current?.style}>{displayLabel || 'Seleziona…'}</span> | |||||
| <span className={`text-gray-500 transition-transform ${open ? 'rotate-180' : ''}`}>▾</span> | <span className={`text-gray-500 transition-transform ${open ? 'rotate-180' : ''}`}>▾</span> | ||||
| </button> | </button> | ||||
| {open && ( | {open && ( | ||||
| @@ -56,6 +56,7 @@ function StyledSelect<T extends string>({ | |||||
| type="button" | type="button" | ||||
| onClick={() => { onChange(o.value); setOpen(false); }} | onClick={() => { onChange(o.value); setOpen(false); }} | ||||
| className={`w-full text-left px-3 py-2.5 hover:bg-blue-50 transition-colors ${o.value === value ? 'bg-blue-100 font-semibold text-blue-700' : 'text-gray-800'}`} | className={`w-full text-left px-3 py-2.5 hover:bg-blue-50 transition-colors ${o.value === value ? 'bg-blue-100 font-semibold text-blue-700' : 'text-gray-800'}`} | ||||
| style={o.style} | |||||
| > | > | ||||
| {o.label} | {o.label} | ||||
| </button> | </button> | ||||
| @@ -79,6 +80,14 @@ const isVideoFile = (file: File) => | |||||
| const isPlayableVideoFile = (file: File) => | const isPlayableVideoFile = (file: File) => | ||||
| new RegExp(`\\.(${PLAYBACK_SUPPORTED_VIDEO})$`, 'i').test(file.name); | new RegExp(`\\.(${PLAYBACK_SUPPORTED_VIDEO})$`, 'i').test(file.name); | ||||
| const previewFontFamily = (filename: string): string => | |||||
| `PortalPreview-${filename.replace(/[^A-Za-z0-9]/g, '_')}`; | |||||
| const fontFormatFromName = (filename: string): string => { | |||||
| const ext = filename.match(/\.([^.]+)$/)?.[1].toLowerCase() ?? 'woff2'; | |||||
| return ({ woff2: 'woff2', woff: 'woff', ttf: 'truetype', otf: 'opentype' } as Record<string, string>)[ext] ?? 'woff2'; | |||||
| }; | |||||
| const extractFileName = (url: string): string => { | const extractFileName = (url: string): string => { | ||||
| const match = url.match(/[?&]name=([^&]+)/); | const match = url.match(/[?&]name=([^&]+)/); | ||||
| if (match) return decodeURIComponent(match[1]); | if (match) return decodeURIComponent(match[1]); | ||||
| @@ -538,12 +547,22 @@ export default function AdminDashboard() { | |||||
| <div> | <div> | ||||
| <label className="block text-sm font-semibold text-gray-700 mb-1">Font del portale</label> | <label className="block text-sm font-semibold text-gray-700 mb-1">Font del portale</label> | ||||
| <style dangerouslySetInnerHTML={{ __html: availableFonts.map(f => ` | |||||
| @font-face { | |||||
| font-family: '${previewFontFamily(f)}'; | |||||
| src: url('/api/fonts?name=${encodeURIComponent(f)}') format('${fontFormatFromName(f)}'); | |||||
| font-display: swap; | |||||
| }`).join('') }} /> | |||||
| <StyledSelect<string> | <StyledSelect<string> | ||||
| value={portal.fontFamily ?? ''} | value={portal.fontFamily ?? ''} | ||||
| onChange={(v) => setPortal({ ...portal, fontFamily: v })} | onChange={(v) => setPortal({ ...portal, fontFamily: v })} | ||||
| options={[ | options={[ | ||||
| { value: '', label: 'Sistema (Arial)' }, | { value: '', label: 'Sistema (Arial)' }, | ||||
| ...availableFonts.map(f => ({ value: f, label: f.replace(/\.(woff2?|ttf|otf)$/i, '') })), | |||||
| ...availableFonts.map(f => ({ | |||||
| value: f, | |||||
| label: f.replace(/\.(woff2?|ttf|otf)$/i, ''), | |||||
| style: { fontFamily: `'${previewFontFamily(f)}', Arial, Helvetica, sans-serif` }, | |||||
| })), | |||||
| ]} | ]} | ||||
| /> | /> | ||||
| </div> | </div> | ||||