| 1 |
( function () { |
| 2 |
const el = window.wp.element.createElement; |
| 3 |
const { registerBlockType } = window.wp.blocks; |
| 4 |
const { InspectorControls, useBlockProps, PanelColorSettings } = window.wp.blockEditor; |
| 5 |
const { PanelBody, RangeControl, ToggleControl, TextControl, SelectControl, Button } = window.wp.components; |
| 6 |
const { __ } = window.wp.i18n; |
| 7 |
|
| 8 |
var _bkbgCSgetTC, _bkbgCSgetTV; |
| 9 |
function getTC() { return _bkbgCSgetTC || (_bkbgCSgetTC = window.bkbgTypographyControl); } |
| 10 |
function getTV() { return _bkbgCSgetTV || (_bkbgCSgetTV = window.bkbgTypoCssVars); } |
| 11 |
function tv(obj, prefix) { var fn = getTV(); return fn ? fn(obj, prefix) : {}; } |
| 12 |
|
| 13 |
// ── Helpers ─────────────────────────────────────────────────────────────── |
| 14 |
function overallScore( criteria ) { |
| 15 |
if ( ! criteria || ! criteria.length ) return 0; |
| 16 |
const total = criteria.reduce( ( s, c ) => s + ( ( c.score || 0 ) / ( c.max || 100 ) ) * 100, 0 ); |
| 17 |
return Math.round( total / criteria.length ); |
| 18 |
} |
| 19 |
|
| 20 |
function scoreGrade( n ) { |
| 21 |
if ( n >= 90 ) return 'A'; |
| 22 |
if ( n >= 80 ) return 'B'; |
| 23 |
if ( n >= 70 ) return 'C'; |
| 24 |
if ( n >= 60 ) return 'D'; |
| 25 |
return 'F'; |
| 26 |
} |
| 27 |
|
| 28 |
function scoreColor( n, a ) { |
| 29 |
if ( n >= 75 ) return a.passColor; |
| 30 |
if ( n >= 50 ) return a.warnColor; |
| 31 |
return a.failColor; |
| 32 |
} |
| 33 |
|
| 34 |
// ── Circular gauge ──────────────────────────────────────────────────────── |
| 35 |
function renderGauge( a, score ) { |
| 36 |
const S = a.gaugeSize; |
| 37 |
const SW = a.strokeWidth; |
| 38 |
const r = ( S - SW * 2 ) / 2; |
| 39 |
const cx = S / 2; |
| 40 |
const cy = S / 2; |
| 41 |
const circ = 2 * Math.PI * r; |
| 42 |
// Arc from -90° (top), goes clockwise |
| 43 |
const offset = circ - ( score / 100 ) * circ; |
| 44 |
const clr = scoreColor( score, a ); |
| 45 |
const grade = a.showGrade ? scoreGrade( score ) : ''; |
| 46 |
|
| 47 |
return el( 'div', { className: 'bkbg-cs-gauge', style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, flexShrink: 0 } }, |
| 48 |
a.label && el( 'div', { className: 'bkbg-cs-gauge-label', style: { color: a.mutedColor, textAlign: 'center' } }, a.label ), |
| 49 |
el( 'svg', { width: S, height: S, viewBox: `0 0 ${ S } ${ S }`, style: { display: 'block' } }, |
| 50 |
// Track |
| 51 |
el( 'circle', { cx, cy, r, fill: 'none', stroke: a.trackColor, strokeWidth: SW } ), |
| 52 |
// Progress arc |
| 53 |
el( 'circle', { |
| 54 |
cx, cy, r, |
| 55 |
fill: 'none', |
| 56 |
stroke: clr, |
| 57 |
strokeWidth: SW, |
| 58 |
strokeDasharray: circ, |
| 59 |
strokeDashoffset: offset, |
| 60 |
strokeLinecap: 'round', |
| 61 |
transform: `rotate(-90 ${ cx } ${ cy })`, |
| 62 |
style: { transition: 'stroke-dashoffset 0.5s' } |
| 63 |
} ), |
| 64 |
// Score number |
| 65 |
el( 'text', { x: cx, y: cy - ( grade ? 10 : 0 ), textAnchor: 'middle', dominantBaseline: 'middle', fontSize: S * 0.22, fontWeight: 800, fill: clr }, score ), |
| 66 |
// Grade |
| 67 |
grade && el( 'text', { x: cx, y: cy + S * 0.14, textAnchor: 'middle', dominantBaseline: 'middle', fontSize: S * 0.12, fontWeight: 700, fill: a.mutedColor }, `Grade ${ grade }` ), |
| 68 |
), |
| 69 |
); |
| 70 |
} |
| 71 |
|
| 72 |
// ── Criteria list ───────────────────────────────────────────────────────── |
| 73 |
function renderCriteria( a ) { |
| 74 |
const criteria = a.criteria || []; |
| 75 |
return el( 'div', { className: 'bkbg-cs-criteria', style: { display: 'flex', flexDirection: 'column', gap: 10, flex: 1 } }, |
| 76 |
criteria.map( ( c, ci ) => { |
| 77 |
const pct = Math.min( 100, Math.round( ( ( c.score || 0 ) / ( c.max || 100 ) ) * 100 ) ); |
| 78 |
const clr = scoreColor( pct, a ); |
| 79 |
return el( 'div', { key: ci, className: 'bkbg-cs-criterion' }, |
| 80 |
el( 'div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 } }, |
| 81 |
el( 'span', { className: 'bkbg-cs-crit-label', style: { color: a.textColor } }, c.label ), |
| 82 |
el( 'span', { className: 'bkbg-cs-crit-score', style: { color: clr, minWidth: 36, textAlign: 'right' } }, `${ c.score }/${ c.max }` ), |
| 83 |
), |
| 84 |
el( 'div', { style: { height: 6, background: a.trackColor, borderRadius: 99, overflow: 'hidden', marginBottom: c.note ? 3 : 0 } }, |
| 85 |
el( 'div', { style: { height: '100%', width: pct + '%', background: clr, borderRadius: 99 } } ) |
| 86 |
), |
| 87 |
c.note && el( 'span', { className: 'bkbg-cs-crit-note', style: { color: a.mutedColor } }, c.note ), |
| 88 |
); |
| 89 |
} ) |
| 90 |
); |
| 91 |
} |
| 92 |
|
| 93 |
// ── Combined render ─────────────────────────────────────────────────────── |
| 94 |
function renderScore( a ) { |
| 95 |
const score = overallScore( a.criteria ); |
| 96 |
const isStack = a.layout !== 'side'; |
| 97 |
const containerStyle = { |
| 98 |
display: 'flex', |
| 99 |
flexDirection: isStack ? 'column' : 'row', |
| 100 |
gap: 28, |
| 101 |
alignItems: isStack ? 'center' : 'flex-start', |
| 102 |
}; |
| 103 |
return el( 'div', { className: 'bkbg-cs-inner', style: containerStyle }, |
| 104 |
a.showGauge && renderGauge( a, score ), |
| 105 |
a.showCriteria && renderCriteria( a ), |
| 106 |
); |
| 107 |
} |
| 108 |
|
| 109 |
function updCriterion( setAttributes, criteria, ci, field, val ) { |
| 110 |
setAttributes( { criteria: criteria.map( ( c, i ) => i === ci ? { ...c, [field]: val } : c ) } ); |
| 111 |
} |
| 112 |
|
| 113 |
// ── Block ───────────────────────────────────────────────────────────────── |
| 114 |
registerBlockType( 'blockenberg/content-score', { |
| 115 |
edit: function ( props ) { |
| 116 |
const { attributes: a, setAttributes } = props; |
| 117 |
const blockProps = useBlockProps( { className: 'bkbg-cs-wrap', style: Object.assign({ background: a.bgColor, padding: '20px', borderRadius: a.borderRadius + 'px', boxSizing: 'border-box', '--bkcs-label-sz': (a.labelSize || 13) + 'px' }, tv(a.typoTitle, '--bkcs-title-'), tv(a.typoBody, '--bkcs-body-')) } ); |
| 118 |
|
| 119 |
return el( 'div', blockProps, |
| 120 |
el( InspectorControls, {}, |
| 121 |
|
| 122 |
el( PanelBody, { title: __( 'Settings', 'blockenberg' ), initialOpen: true }, |
| 123 |
el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ), |
| 124 |
el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ), |
| 125 |
el( TextControl, { label: __( 'Gauge Label', 'blockenberg' ), value: a.label, onChange: v => setAttributes( { label: v } ) } ), |
| 126 |
el( ToggleControl, { label: __( 'Show Gauge', 'blockenberg' ), checked: a.showGauge, onChange: v => setAttributes( { showGauge: v } ) } ), |
| 127 |
el( ToggleControl, { label: __( 'Show Grade', 'blockenberg' ), checked: a.showGrade, onChange: v => setAttributes( { showGrade: v } ) } ), |
| 128 |
el( ToggleControl, { label: __( 'Show Criteria', 'blockenberg' ), checked: a.showCriteria, onChange: v => setAttributes( { showCriteria: v } ) } ), |
| 129 |
el( SelectControl, { |
| 130 |
label: __( 'Layout', 'blockenberg' ), |
| 131 |
value: a.layout, |
| 132 |
options: [ { label: __( 'Side by Side', 'blockenberg' ), value: 'side' }, { label: __( 'Stacked', 'blockenberg' ), value: 'stack' } ], |
| 133 |
onChange: v => setAttributes( { layout: v } ), |
| 134 |
} ), |
| 135 |
), |
| 136 |
|
| 137 |
el( PanelBody, { title: __( 'Style', 'blockenberg' ), initialOpen: false }, |
| 138 |
el( RangeControl, { label: __( 'Gauge Size', 'blockenberg' ), value: a.gaugeSize, onChange: v => setAttributes( { gaugeSize: v } ), min: 80, max: 260, step: 10 } ), |
| 139 |
el( RangeControl, { label: __( 'Ring Width', 'blockenberg' ), value: a.strokeWidth, onChange: v => setAttributes( { strokeWidth: v } ), min: 5, max: 30 } ), |
| 140 |
el( RangeControl, { label: __( 'Border Radius', 'blockenberg' ),value: a.borderRadius,onChange: v => setAttributes( { borderRadius:v } ), min: 0, max: 28 } ), |
| 141 |
), |
| 142 |
|
| 143 |
|
| 144 |
el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false }, |
| 145 |
getTC() && el( getTC(), { label: __( 'Title', 'blockenberg' ), value: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } }), |
| 146 |
getTC() && el( getTC(), { label: __( 'Body Text', 'blockenberg' ), value: a.typoBody, onChange: function (v) { setAttributes({ typoBody: v }); } }), |
| 147 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: __('Label Size (px)', 'blockenberg'), value: a.labelSize, min: 10, max: 24, onChange: function (v) { setAttributes({ labelSize: v }); } }) |
| 148 |
), |
| 149 |
el( PanelColorSettings, { |
| 150 |
title: __( 'Colors', 'blockenberg' ), initialOpen: false, |
| 151 |
colorSettings: [ |
| 152 |
{ label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) }, |
| 153 |
{ label: __( 'Card BG', 'blockenberg' ), value: a.cardBg, onChange: v => setAttributes( { cardBg: v || '#f9fafb' } ) }, |
| 154 |
{ label: __( 'Title', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) }, |
| 155 |
{ label: __( 'Text', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#374151' } ) }, |
| 156 |
{ label: __( 'Muted', 'blockenberg' ), value: a.mutedColor, onChange: v => setAttributes( { mutedColor: v || '#6b7280' } ) }, |
| 157 |
{ label: __( 'Track', 'blockenberg' ), value: a.trackColor, onChange: v => setAttributes( { trackColor: v || '#e5e7eb' } ) }, |
| 158 |
{ label: __( 'Pass (≥75%)', 'blockenberg' ),value: a.passColor, onChange: v => setAttributes( { passColor: v || '#10b981' } ) }, |
| 159 |
{ label: __( 'Warn (50–74%)', 'blockenberg' ),value: a.warnColor,onChange: v => setAttributes( { warnColor: v || '#f59e0b' } ) }, |
| 160 |
{ label: __( 'Fail (<50%)', 'blockenberg' ), value: a.failColor, onChange: v => setAttributes( { failColor: v || '#ef4444' } ) }, |
| 161 |
] |
| 162 |
} ), |
| 163 |
|
| 164 |
el( PanelBody, { title: __( 'Criteria', 'blockenberg' ), initialOpen: false }, |
| 165 |
el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { criteria: [ ...( a.criteria || [] ), { label: 'New Criterion', score: 70, max: 100, note: '' } ] } ) }, __( '+ Add Criterion', 'blockenberg' ) ), |
| 166 |
a.criteria.map( ( c, ci ) => |
| 167 |
el( PanelBody, { key: ci, title: `${ c.label } (${ c.score }/${ c.max })`, initialOpen: false }, |
| 168 |
el( TextControl, { label: __( 'Label', 'blockenberg' ), value: c.label, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'label', v ) } ), |
| 169 |
el( RangeControl, { label: __( 'Score', 'blockenberg' ), value: c.score, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'score', v ), min: 0, max: c.max || 100 } ), |
| 170 |
el( RangeControl, { label: __( 'Max', 'blockenberg' ), value: c.max, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'max', v ), min: 1, max: 200 } ), |
| 171 |
el( TextControl, { label: __( 'Note', 'blockenberg' ), value: c.note, onChange: v => updCriterion( setAttributes, a.criteria, ci, 'note', v ) } ), |
| 172 |
el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { criteria: a.criteria.filter( ( _, x ) => x !== ci ) } ) }, __( 'Remove', 'blockenberg' ) ), |
| 173 |
) |
| 174 |
), |
| 175 |
), |
| 176 |
), |
| 177 |
|
| 178 |
a.showTitle && a.title && el( 'h3', { className: 'bkbg-cs-title', style: { color: a.titleColor } }, a.title ), |
| 179 |
renderScore( a ), |
| 180 |
); |
| 181 |
}, |
| 182 |
|
| 183 |
save: function ( { attributes: a } ) { |
| 184 |
const blockProps = useBlockProps.save( { className: 'bkbg-cs-wrap', style: Object.assign({ background: a.bgColor, padding: '20px', borderRadius: a.borderRadius + 'px', boxSizing: 'border-box', '--bkcs-label-sz': (a.labelSize || 13) + 'px' }, tv(a.typoTitle, '--bkcs-title-'), tv(a.typoBody, '--bkcs-body-')) } ); |
| 185 |
return el( 'div', blockProps, |
| 186 |
a.showTitle && a.title ? el( 'h3', { className: 'bkbg-cs-title', style: { color: a.titleColor } }, a.title ) : null, |
| 187 |
renderScore( a ), |
| 188 |
); |
| 189 |
}, |
| 190 |
} ); |
| 191 |
}() ); |
| 192 |
|