| 1 |
( function () { |
| 2 |
var el = wp.element.createElement; |
| 3 |
var useState = wp.element.useState; |
| 4 |
var Fragment = wp.element.Fragment; |
| 5 |
var registerBlockType = wp.blocks.registerBlockType; |
| 6 |
var __ = wp.i18n.__; |
| 7 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 8 |
var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 9 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 10 |
var PanelBody = wp.components.PanelBody; |
| 11 |
var RangeControl = wp.components.RangeControl; |
| 12 |
var SelectControl = wp.components.SelectControl; |
| 13 |
var TextControl = wp.components.TextControl; |
| 14 |
var ToggleControl = wp.components.ToggleControl; |
| 15 |
var Button = wp.components.Button; |
| 16 |
|
| 17 |
var DEFAULT_SUBJECTS = [ |
| 18 |
{id:1, name:'Math', score:'90', max:'100', weight:'30'}, |
| 19 |
{id:2, name:'English', score:'85', max:'100', weight:'30'}, |
| 20 |
{id:3, name:'Science', score:'78', max:'100', weight:'20'}, |
| 21 |
{id:4, name:'History', score:'92', max:'100', weight:'20'} |
| 22 |
]; |
| 23 |
|
| 24 |
var GRADE_SCALE = [ |
| 25 |
{letter:'A+',min:97,color:'gradeAColor',dflt:'#059669'}, |
| 26 |
{letter:'A', min:93,color:'gradeAColor',dflt:'#10b981'}, |
| 27 |
{letter:'A−',min:90,color:'gradeAColor',dflt:'#34d399'}, |
| 28 |
{letter:'B+',min:87,color:'gradeBColor',dflt:'#2563eb'}, |
| 29 |
{letter:'B', min:83,color:'gradeBColor',dflt:'#3b82f6'}, |
| 30 |
{letter:'B−',min:80,color:'gradeBColor',dflt:'#60a5fa'}, |
| 31 |
{letter:'C+',min:77,color:'gradeCColor',dflt:'#d97706'}, |
| 32 |
{letter:'C', min:73,color:'gradeCColor',dflt:'#f59e0b'}, |
| 33 |
{letter:'C−',min:70,color:'gradeCColor',dflt:'#fbbf24'}, |
| 34 |
{letter:'D+',min:67,color:'gradeDColor',dflt:'#dc2626'}, |
| 35 |
{letter:'D', min:63,color:'gradeDColor',dflt:'#ef4444'}, |
| 36 |
{letter:'D−',min:60,color:'gradeDColor',dflt:'#f87171'}, |
| 37 |
{letter:'F', min:0, color:'gradeFColor',dflt:'#6b7280'} |
| 38 |
]; |
| 39 |
|
| 40 |
function getLetter(pct, a) { |
| 41 |
for (var i=0; i<GRADE_SCALE.length; i++) { |
| 42 |
if (pct >= GRADE_SCALE[i].min) { |
| 43 |
return {letter: GRADE_SCALE[i].letter, color: a[GRADE_SCALE[i].color] || GRADE_SCALE[i].dflt}; |
| 44 |
} |
| 45 |
} |
| 46 |
return {letter:'F', color: a.gradeFColor||'#6b7280'}; |
| 47 |
} |
| 48 |
|
| 49 |
function calcWeighted(subjects) { |
| 50 |
var totalWeight = 0, weightedSum = 0; |
| 51 |
subjects.forEach(function(s) { |
| 52 |
var sc = parseFloat(s.score)||0, mx = parseFloat(s.max)||100, wt = parseFloat(s.weight)||0; |
| 53 |
var pct = mx > 0 ? (sc/mx)*100 : 0; |
| 54 |
weightedSum += pct * wt; |
| 55 |
totalWeight += wt; |
| 56 |
}); |
| 57 |
return totalWeight > 0 ? weightedSum / totalWeight : 0; |
| 58 |
} |
| 59 |
|
| 60 |
function calcSimple(subjects) { |
| 61 |
if (!subjects.length) return 0; |
| 62 |
var sum = 0, total = 0; |
| 63 |
subjects.forEach(function(s){ |
| 64 |
var sc=parseFloat(s.score)||0, mx=parseFloat(s.max)||100; |
| 65 |
sum+=sc; total+=mx; |
| 66 |
}); |
| 67 |
return total > 0 ? (sum/total)*100 : 0; |
| 68 |
} |
| 69 |
|
| 70 |
var nextId = 10; |
| 71 |
|
| 72 |
/* ── Typography lazy getters ── */ |
| 73 |
function _tc() { return window.bkbgTypographyControl || null; } |
| 74 |
function _tv() { return window.bkbgTypoCssVars || function () { return {}; }; } |
| 75 |
|
| 76 |
function GCPreview(props) { |
| 77 |
var a = props.attributes; |
| 78 |
var accent = a.accentColor || '#6c3fb5'; |
| 79 |
var _subs = useState(DEFAULT_SUBJECTS.map(function(s){return Object.assign({},s);})); |
| 80 |
var subs = _subs[0]; var setSubs = _subs[1]; |
| 81 |
|
| 82 |
var pct = a.gradeMode === 'simple' ? calcSimple(subs) : calcWeighted(subs); |
| 83 |
var gInfo = getLetter(pct, a); |
| 84 |
|
| 85 |
var inpStyle = {padding:'6px 8px',border:'1.5px solid '+(a.inputBorder||'#e5e7eb'),borderRadius:(a.inputRadius||8)+'px',fontSize:'14px',fontFamily:'inherit',outline:'none',width:'100%',boxSizing:'border-box'}; |
| 86 |
var lblStyle = {fontSize:'12px',fontWeight:600,color:a.labelColor||'#374151',display:'block',marginBottom:'3px'}; |
| 87 |
|
| 88 |
function updateSub(id, field, val) { |
| 89 |
setSubs(subs.map(function(s){ return s.id===id ? Object.assign({},s,{[field]:val}) : s; })); |
| 90 |
} |
| 91 |
|
| 92 |
var wrapS = Object.assign({paddingTop:(a.paddingTop||60)+'px',paddingBottom:(a.paddingBottom||60)+'px',background:a.sectionBg||undefined},_tv()(a.typoTitle,'--bkbg-grc-tt-'),_tv()(a.typoResult,'--bkbg-grc-rs-'),_tv()(a.typoLetter,'--bkbg-grc-lt-')); |
| 93 |
return el('div',{style:wrapS}, |
| 94 |
el('div',{style:{background:a.cardBg||'#fff',borderRadius:(a.cardRadius||16)+'px',padding:'32px',maxWidth:(a.maxWidth||640)+'px',margin:'0 auto',boxShadow:'0 4px 24px rgba(0,0,0,.09)'}}, |
| 95 |
|
| 96 |
(a.showTitle||a.showSubtitle) && el('div',{style:{marginBottom:'22px'}}, |
| 97 |
a.showTitle && el('div',{className:'bkbg-grc-title',style:{color:a.titleColor,marginBottom:'6px'}},a.title), |
| 98 |
a.showSubtitle && el('div',{style:{fontSize:'15px',color:a.subtitleColor,opacity:.75}},a.subtitle) |
| 99 |
), |
| 100 |
|
| 101 |
// Result card |
| 102 |
el('div',{style:{background:a.resultBg||accent,borderRadius:'12px',padding:'20px',textAlign:'center',color:a.resultColor||'#fff',marginBottom:'20px',display:'flex',alignItems:'center',justifyContent:'center',gap:'24px'}}, |
| 103 |
el('div',null, |
| 104 |
el('div',{className:'bkbg-grc-result-pct'},pct.toFixed(1)+'%'), |
| 105 |
el('div',{style:{fontSize:'14px',opacity:.8,marginTop:'4px'}},a.gradeMode==='simple'?'Simple Average':'Weighted Average') |
| 106 |
), |
| 107 |
a.showLetterGrade && el(Fragment,null, |
| 108 |
el('div',{style:{width:'1px',height:'60px',background:'rgba(255,255,255,.25)'}}), |
| 109 |
el('div',{style:{textAlign:'center'}}, |
| 110 |
el('div',{className:'bkbg-grc-result-letter',style:{background:'rgba(255,255,255,.15)',borderRadius:'12px',padding:'0 16px'}},gInfo.letter), |
| 111 |
el('div',{style:{fontSize:'13px',opacity:.8,marginTop:'4px'}},'Letter Grade') |
| 112 |
) |
| 113 |
) |
| 114 |
), |
| 115 |
|
| 116 |
// Subjects table header |
| 117 |
a.showBreakdown && el('div',null, |
| 118 |
el('div',{style:{display:'grid',gridTemplateColumns:'2fr 1fr 1fr 1fr 80px',gap:'6px',padding:'6px 10px',marginBottom:'4px'}}, |
| 119 |
el('div',{style:lblStyle},'Subject'), |
| 120 |
el('div',{style:lblStyle},'Score'), |
| 121 |
el('div',{style:lblStyle},'Max'), |
| 122 |
el('div',{style:{...lblStyle}},'Weight %'), |
| 123 |
el('div',{style:lblStyle},'') |
| 124 |
), |
| 125 |
|
| 126 |
subs.map(function(s) { |
| 127 |
var spct = parseFloat(s.max)>0 ? (parseFloat(s.score)||0)/(parseFloat(s.max)||100)*100 : 0; |
| 128 |
var sg = getLetter(spct, a); |
| 129 |
return el('div',{key:s.id,style:{display:'grid',gridTemplateColumns:'2fr 1fr 1fr 1fr 80px',gap:'6px',alignItems:'center',background:a.rowBg||'#f9fafb',border:'1px solid '+(a.rowBorder||'#e5e7eb'),borderRadius:'8px',padding:'8px 10px',marginBottom:'6px'}}, |
| 130 |
el('input',{type:'text',value:s.name,placeholder:'Subject',style:inpStyle,onChange:function(e){updateSub(s.id,'name',e.target.value);}}), |
| 131 |
el('input',{type:'number',value:s.score,min:0,style:inpStyle,onChange:function(e){updateSub(s.id,'score',e.target.value);}}), |
| 132 |
el('input',{type:'number',value:s.max,min:1,style:inpStyle,onChange:function(e){updateSub(s.id,'max',e.target.value);}}), |
| 133 |
el('input',{type:'number',value:s.weight,min:0,max:100,style:inpStyle,onChange:function(e){updateSub(s.id,'weight',e.target.value);}}), |
| 134 |
el('div',{style:{display:'flex',gap:'6px',alignItems:'center'}}, |
| 135 |
el('span',{style:{fontWeight:700,fontSize:'13px',color:sg.color}},sg.letter), |
| 136 |
el('button',{onClick:function(){setSubs(subs.filter(function(x){return x.id!==s.id;}));},style:{border:'none',background:'#fee2e2',color:'#ef4444',borderRadius:'6px',cursor:'pointer',padding:'3px 8px',fontWeight:700,fontSize:'13px',fontFamily:'inherit'}},'×') |
| 137 |
) |
| 138 |
); |
| 139 |
}), |
| 140 |
|
| 141 |
el('button',{onClick:function(){nextId++;setSubs(subs.concat({id:nextId,name:'',score:'',max:'100',weight:'0'}));},style:{marginTop:'8px',padding:'8px 16px',background:accent,color:'#fff',border:'none',borderRadius:'8px',fontWeight:700,cursor:'pointer',fontSize:'14px',fontFamily:'inherit'}}, '+ Add Subject') |
| 142 |
), |
| 143 |
|
| 144 |
// Grade scale |
| 145 |
a.showGradeScale && el('div',{style:{marginTop:'20px',background:a.scaleBg||'#f3f4f6',border:'1px solid '+(a.scaleBorder||'#e5e7eb'),borderRadius:'10px',padding:'14px 16px'}}, |
| 146 |
el('div',{style:{fontSize:'12px',fontWeight:700,color:a.labelColor||'#374151',textTransform:'uppercase',letterSpacing:'.05em',marginBottom:'10px'}},'Grade Scale'), |
| 147 |
el('div',{style:{display:'flex',flexWrap:'wrap',gap:'6px'}}, |
| 148 |
GRADE_SCALE.map(function(gs){ |
| 149 |
return el('div',{key:gs.letter,style:{background:'#fff',border:'2px solid '+(a[gs.color]||gs.dflt),borderRadius:'6px',padding:'4px 10px',textAlign:'center',minWidth:'50px'}}, |
| 150 |
el('div',{style:{fontWeight:800,fontSize:'14px',color:a[gs.color]||gs.dflt}},gs.letter), |
| 151 |
el('div',{style:{fontSize:'11px',color:'#6b7280'}},gs.min+'%+') |
| 152 |
); |
| 153 |
}) |
| 154 |
) |
| 155 |
) |
| 156 |
) |
| 157 |
); |
| 158 |
} |
| 159 |
|
| 160 |
registerBlockType('blockenberg/grade-calculator', { |
| 161 |
edit: function(props) { |
| 162 |
var a = props.attributes; var set = props.setAttributes; |
| 163 |
var blockProps = useBlockProps(); |
| 164 |
var colorSettings = [ |
| 165 |
{value:a.accentColor, onChange:function(v){set({accentColor:v});}, label:'Accent Color'}, |
| 166 |
{value:a.cardBg, onChange:function(v){set({cardBg:v});}, label:'Card Background'}, |
| 167 |
{value:a.resultBg, onChange:function(v){set({resultBg:v});}, label:'Result Card Background'}, |
| 168 |
{value:a.resultColor, onChange:function(v){set({resultColor:v});}, label:'Result Card Text'}, |
| 169 |
{value:a.gradeAColor, onChange:function(v){set({gradeAColor:v});}, label:'Grade A Color'}, |
| 170 |
{value:a.gradeBColor, onChange:function(v){set({gradeBColor:v});}, label:'Grade B Color'}, |
| 171 |
{value:a.gradeCColor, onChange:function(v){set({gradeCColor:v});}, label:'Grade C Color'}, |
| 172 |
{value:a.gradeDColor, onChange:function(v){set({gradeDColor:v});}, label:'Grade D Color'}, |
| 173 |
{value:a.gradeFColor, onChange:function(v){set({gradeFColor:v});}, label:'Grade F Color'}, |
| 174 |
{value:a.rowBg, onChange:function(v){set({rowBg:v});}, label:'Table Row Background'}, |
| 175 |
{value:a.rowBorder, onChange:function(v){set({rowBorder:v});}, label:'Table Row Border'}, |
| 176 |
{value:a.scaleBg, onChange:function(v){set({scaleBg:v});}, label:'Scale Background'}, |
| 177 |
{value:a.scaleBorder, onChange:function(v){set({scaleBorder:v});}, label:'Scale Border'}, |
| 178 |
{value:a.inputBorder, onChange:function(v){set({inputBorder:v});}, label:'Input Border'}, |
| 179 |
{value:a.labelColor, onChange:function(v){set({labelColor:v});}, label:'Label Color'}, |
| 180 |
{value:a.titleColor, onChange:function(v){set({titleColor:v});}, label:'Title Color'}, |
| 181 |
{value:a.subtitleColor,onChange:function(v){set({subtitleColor:v});}, label:'Subtitle Color'}, |
| 182 |
{value:a.sectionBg, onChange:function(v){set({sectionBg:v});}, label:'Section Background'} |
| 183 |
]; |
| 184 |
return el(Fragment, null, |
| 185 |
el(InspectorControls, null, |
| 186 |
el(PanelBody,{title:'Header',initialOpen:false}, |
| 187 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Title', checked:a.showTitle, onChange:function(v){set({showTitle:v});}}), |
| 188 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Subtitle',checked:a.showSubtitle,onChange:function(v){set({showSubtitle:v});}}), |
| 189 |
el(TextControl,{label:'Title', value:a.title, onChange:function(v){set({title:v});}}), |
| 190 |
el(TextControl,{label:'Subtitle',value:a.subtitle,onChange:function(v){set({subtitle:v});}}) |
| 191 |
), |
| 192 |
el(PanelBody,{title:'Calculator Settings',initialOpen:true}, |
| 193 |
el(SelectControl,{label:'Grade Mode',value:a.gradeMode,options:[{label:'Weighted Average',value:'weighted'},{label:'Simple Average',value:'simple'}],onChange:function(v){set({gradeMode:v});}}), |
| 194 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Letter Grade', checked:a.showLetterGrade,onChange:function(v){set({showLetterGrade:v});}}), |
| 195 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Grade Scale', checked:a.showGradeScale, onChange:function(v){set({showGradeScale:v});}}), |
| 196 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Subject Breakdown',checked:a.showBreakdown,onChange:function(v){set({showBreakdown:v});}}) |
| 197 |
), |
| 198 |
|
| 199 |
el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false }, |
| 200 |
_tc() && el(_tc(), { label:'Title', value:a.typoTitle, onChange:function(v){ set({typoTitle:v}); } }), |
| 201 |
_tc() && el(_tc(), { label:'Result %', value:a.typoResult, onChange:function(v){ set({typoResult:v}); } }), |
| 202 |
_tc() && el(_tc(), { label:'Letter Grade', value:a.typoLetter, onChange:function(v){ set({typoLetter:v}); } }) |
| 203 |
), |
| 204 |
el(PanelColorSettings,{title:'Colors',initialOpen:false,colorSettings:colorSettings}), |
| 205 |
el(PanelBody,{title:'Sizing & Layout',initialOpen:false}, |
| 206 |
el(RangeControl,{label:'Letter Grade Size', value:a.letterSize, min:24,max:80,step:2, onChange:function(v){set({letterSize:v});}}), |
| 207 |
el(RangeControl,{label:'Card Border Radius',value:a.cardRadius, min:0, max:40,step:1, onChange:function(v){set({cardRadius:v});}}), |
| 208 |
el(RangeControl,{label:'Input Border Radius',value:a.inputRadius,min:0,max:20,step:1, onChange:function(v){set({inputRadius:v});}}), |
| 209 |
el(RangeControl,{label:'Max Width (px)', value:a.maxWidth, min:360,max:1100,step:10,onChange:function(v){set({maxWidth:v});}}), |
| 210 |
el(RangeControl,{label:'Padding Top', value:a.paddingTop, min:0, max:160,step:4, onChange:function(v){set({paddingTop:v});}}), |
| 211 |
el(RangeControl,{label:'Padding Bottom', value:a.paddingBottom,min:0,max:160,step:4,onChange:function(v){set({paddingBottom:v});}}) |
| 212 |
) |
| 213 |
), |
| 214 |
el('div', blockProps, el(GCPreview, {attributes:a})) |
| 215 |
); |
| 216 |
}, |
| 217 |
save: function(props) { |
| 218 |
var a = props.attributes; |
| 219 |
return el('div', wp.blockEditor.useBlockProps.save(), |
| 220 |
el('div',{className:'bkbg-grc-app','data-opts':JSON.stringify(a)})); |
| 221 |
} |
| 222 |
}); |
| 223 |
}() ); |
| 224 |
|