| 1 |
( function () { |
| 2 |
var el = React.createElement; |
| 3 |
var registerBlockType = wp.blocks.registerBlockType; |
| 4 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 5 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 6 |
var PanelBody = wp.components.PanelBody; |
| 7 |
var RangeControl = wp.components.RangeControl; |
| 8 |
var SelectControl = wp.components.SelectControl; |
| 9 |
var TextControl = wp.components.TextControl; |
| 10 |
var ToggleControl = wp.components.ToggleControl; |
| 11 |
var Button = wp.components.Button; |
| 12 |
var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 13 |
|
| 14 |
/* ── lazy typography helpers ── */ |
| 15 |
function _tc() { return window.bkbgTypographyControl || null; } |
| 16 |
Object.defineProperty(window, '__bkSR_tvf', { get: function () { delete window.__bkSR_tvf; return (window.__bkSR_tvf = window.bkbgTypoCssVars || function () { return {}; }); } }); |
| 17 |
function getTypoCssVars(a) { |
| 18 |
var o = {}; |
| 19 |
Object.assign(o, window.__bkSR_tvf(a.pctTypo || {}, '--bksr-pc-')); |
| 20 |
Object.assign(o, window.__bkSR_tvf(a.labelTypo || {}, '--bksr-lb-')); |
| 21 |
return o; |
| 22 |
} |
| 23 |
|
| 24 |
var LAYOUT_OPTS = [ |
| 25 |
{ value: 'row', label: 'Row (individual rings)' }, |
| 26 |
{ value: 'concentric', label: 'Concentric (nested rings)' } |
| 27 |
]; |
| 28 |
var EASING_OPTS = [ |
| 29 |
{ value: 'ease-out', label: 'Ease Out' }, |
| 30 |
{ value: 'ease-in', label: 'Ease In' }, |
| 31 |
{ value: 'linear', label: 'Linear' } |
| 32 |
]; |
| 33 |
var LINECAP_OPTS = [ |
| 34 |
{ value: 'round', label: 'Round' }, |
| 35 |
{ value: 'butt', label: 'Flat' }, |
| 36 |
{ value: 'square', label: 'Square' } |
| 37 |
]; |
| 38 |
var LABEL_POS_OPTS = [ |
| 39 |
{ value: 'below', label: 'Below ring' }, |
| 40 |
{ value: 'inside',label: 'Inside ring' } |
| 41 |
]; |
| 42 |
|
| 43 |
/** Draw a single ring preview as inline SVG */ |
| 44 |
function RingPreview(props) { |
| 45 |
var skill = props.skill; |
| 46 |
var a = props.attr; |
| 47 |
var size = a.ringSize || 140; |
| 48 |
var sw = a.strokeWidth || 10; |
| 49 |
var r = (size - sw) / 2; |
| 50 |
var circ = 2 * Math.PI * r; |
| 51 |
var cx = size / 2, cy = size / 2; |
| 52 |
var dashArray = circ; |
| 53 |
var dashOffset = circ * (1 - (skill.pct || 0) / 100); |
| 54 |
|
| 55 |
return el('div', { style: { textAlign:'center', display:'flex', flexDirection:'column', alignItems:'center' } }, |
| 56 |
el('svg', { width:size, height:size, viewBox:'0 0 '+size+' '+size, style:{display:'block'} }, |
| 57 |
/* Track */ |
| 58 |
el('circle', { cx:cx, cy:cy, r:r, fill:'none', stroke:a.trackColor||'#1f2937', strokeWidth:sw }), |
| 59 |
/* Progress */ |
| 60 |
el('circle', { |
| 61 |
cx:cx, cy:cy, r:r, fill:'none', |
| 62 |
stroke: skill.color || '#7c3aed', |
| 63 |
strokeWidth:sw, |
| 64 |
strokeDasharray: dashArray, |
| 65 |
strokeDashoffset: dashOffset, |
| 66 |
strokeLinecap: a.lineCap||'round', |
| 67 |
transform:'rotate(-90 '+cx+' '+cy+')' |
| 68 |
}), |
| 69 |
/* Center percentage */ |
| 70 |
a.showPercentage && a.labelPosition === 'inside' && el('text', { |
| 71 |
x:cx, y:cy, dominantBaseline:'middle', textAnchor:'middle', |
| 72 |
fill: a.pctColor||'#fff', fontSize:(a.pctSize||28), fontWeight:a.pctFontWeight||700 |
| 73 |
}, (skill.pct||0)+'%'), |
| 74 |
/* Small skill name inside */ |
| 75 |
a.showLabel && a.labelPosition === 'inside' && el('text', { |
| 76 |
x:cx, y:cy+(a.pctSize||28)*0.65, dominantBaseline:'middle', textAnchor:'middle', |
| 77 |
fill:a.labelColor||'#fff', fontSize:a.labelSize||14, opacity:0.7 |
| 78 |
}, skill.name) |
| 79 |
), |
| 80 |
/* Outside labels */ |
| 81 |
(a.showPercentage && a.labelPosition !== 'inside') && el('div', { className: 'bkbg-sr-pct-below', style: { color:a.pctColor||'#fff' } }, (skill.pct||0)+'%'), |
| 82 |
(a.showLabel && a.labelPosition !== 'inside') && el('div', { className: 'bkbg-sr-name-below', style: { color:a.labelColor||'#fff' } }, skill.name) |
| 83 |
); |
| 84 |
} |
| 85 |
|
| 86 |
/** Concentric SVG preview */ |
| 87 |
function ConcentricPreview(props) { |
| 88 |
var a = props.attr; |
| 89 |
var skills = a.skills || []; |
| 90 |
var ringGap = a.ringGap || 14; |
| 91 |
var sw = a.strokeWidth || 10; |
| 92 |
var totalSize = (a.ringSize || 140) + (skills.length - 1) * (sw + ringGap); |
| 93 |
var cx = totalSize / 2, cy = totalSize / 2; |
| 94 |
|
| 95 |
var rings = skills.map(function(skill, i) { |
| 96 |
var r = (totalSize / 2) - sw/2 - i*(sw + ringGap); |
| 97 |
if (r <= 0) return null; |
| 98 |
var circ = 2 * Math.PI * r; |
| 99 |
return el('g', { key: i }, |
| 100 |
el('circle', {cx:cx,cy:cy,r:r,fill:'none',stroke:a.trackColor||'#1f2937',strokeWidth:sw}), |
| 101 |
el('circle', { |
| 102 |
cx:cx,cy:cy,r:r,fill:'none', |
| 103 |
stroke:skill.color||'#7c3aed',strokeWidth:sw, |
| 104 |
strokeDasharray:circ,strokeDashoffset:circ*(1-(skill.pct||0)/100), |
| 105 |
strokeLinecap:a.lineCap||'round',transform:'rotate(-90 '+cx+' '+cy+')' |
| 106 |
}) |
| 107 |
); |
| 108 |
}).filter(Boolean); |
| 109 |
|
| 110 |
return el('div', { style: { display:'flex', flexDirection:'column', alignItems:'center', gap:16 } }, |
| 111 |
el('svg', { width:totalSize, height:totalSize, viewBox:'0 0 '+totalSize+' '+totalSize, style:{display:'block',maxWidth:'100%'} }, |
| 112 |
rings, |
| 113 |
a.centerLabel && el('text', {x:cx,y:cy,dominantBaseline:'middle',textAnchor:'middle',fill:a.centerLabelColor||'#fff',fontSize:a.centerLabelSize||18,fontWeight:700},a.centerLabel) |
| 114 |
), |
| 115 |
/* Legend */ |
| 116 |
el('div', { style: { display:'flex', flexDirection:'column', gap:6, minWidth:160 } }, |
| 117 |
skills.map(function(s,i){ |
| 118 |
return el('div',{key:i,style:{display:'flex',alignItems:'center',gap:8}}, |
| 119 |
el('div',{style:{width:10,height:10,borderRadius:'50%',background:s.color||'#7c3aed',flexShrink:0}}), |
| 120 |
el('span',{className:'bkbg-sr-legend-name',style:{color:a.labelColor||'#fff',flex:1}},s.name), |
| 121 |
el('span',{className:'bkbg-sr-legend-pct',style:{color:s.color||'#7c3aed'}},(s.pct||0)+'%') |
| 122 |
); |
| 123 |
}) |
| 124 |
) |
| 125 |
); |
| 126 |
} |
| 127 |
|
| 128 |
registerBlockType('blockenberg/skill-rings', { |
| 129 |
title: 'Skill Rings', |
| 130 |
icon: 'chart-pie', |
| 131 |
category: 'bkbg-effects', |
| 132 |
|
| 133 |
edit: function (props) { |
| 134 |
var attr = props.attributes; var setAttr = props.setAttributes; |
| 135 |
|
| 136 |
function updateSkill(i, key, val) { |
| 137 |
var s = (attr.skills||[]).map(function(x){return Object.assign({},x);}); s[i][key]=val; setAttr({skills:s}); |
| 138 |
} |
| 139 |
function addSkill() { setAttr({skills:(attr.skills||[]).concat([{name:'New Skill',pct:70,color:'#7c3aed'}])}); } |
| 140 |
function removeSkill(i) { setAttr({skills:(attr.skills||[]).filter(function(_,j){return j!==i;})}); } |
| 141 |
|
| 142 |
var skillPanels = (attr.skills||[]).map(function(s,i){ |
| 143 |
return el(PanelBody,{key:i,title:(i+1)+'. '+s.name,initialOpen:false}, |
| 144 |
el(TextControl,{__nextHasNoMarginBottom:true,label:'Skill Name',value:s.name,onChange:function(v){updateSkill(i,'name',v);}}), |
| 145 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Percentage',value:s.pct,min:0,max:100,onChange:function(v){updateSkill(i,'pct',v);}}), |
| 146 |
el('div',{style:{marginBottom:8}}, |
| 147 |
el('label',{style:{display:'block',fontSize:12,marginBottom:4}},'Color'), |
| 148 |
el('input',{type:'color',value:s.color||'#7c3aed',style:{width:40,height:30,cursor:'pointer',border:'1px solid #ccc',borderRadius:4},onChange:function(e){updateSkill(i,'color',e.target.value);}}) |
| 149 |
), |
| 150 |
el(Button,{variant:'tertiary',isDestructive:true,isSmall:true,onClick:function(){removeSkill(i);}},'Remove') |
| 151 |
); |
| 152 |
}); |
| 153 |
|
| 154 |
var preview = attr.layout === 'concentric' |
| 155 |
? el(ConcentricPreview, {attr:attr}) |
| 156 |
: el('div', { style: { display:'flex', flexWrap:'wrap', gap:32, justifyContent:'center' } }, |
| 157 |
(attr.skills||[]).map(function(s,i){ return el(RingPreview,{key:i,skill:s,attr:attr}); }) |
| 158 |
); |
| 159 |
|
| 160 |
return el(React.Fragment, null, |
| 161 |
el(InspectorControls, null, |
| 162 |
el(PanelBody,{title:'Layout & Animation',initialOpen:true}, |
| 163 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Layout',value:attr.layout,options:LAYOUT_OPTS,onChange:function(v){setAttr({layout:v});}}), |
| 164 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:attr.layout==='concentric'?'Outer Ring Size (px)':'Ring Size (px)',value:attr.ringSize,min:80,max:400,onChange:function(v){setAttr({ringSize:v});}}), |
| 165 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Stroke Width (px)',value:attr.strokeWidth,min:2,max:40,onChange:function(v){setAttr({strokeWidth:v});}}), |
| 166 |
attr.layout==='concentric' && el(RangeControl,{__nextHasNoMarginBottom:true,label:'Gap Between Rings (px)',value:attr.ringGap,min:2,max:40,onChange:function(v){setAttr({ringGap:v});}}), |
| 167 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Line Cap',value:attr.lineCap,options:LINECAP_OPTS,onChange:function(v){setAttr({lineCap:v});}}) |
| 168 |
), |
| 169 |
el(PanelBody,{title:'Animation',initialOpen:false}, |
| 170 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Duration (ms)',value:attr.animDuration,min:200,max:4000,step:100,onChange:function(v){setAttr({animDuration:v});}}), |
| 171 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Easing',value:attr.animEasing,options:EASING_OPTS,onChange:function(v){setAttr({animEasing:v});}}), |
| 172 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Stagger Delay (ms)',value:attr.animDelay,min:0,max:500,onChange:function(v){setAttr({animDelay:v});}}) |
| 173 |
), |
| 174 |
el(PanelBody,{title:'Labels',initialOpen:false}, |
| 175 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Percentage',checked:attr.showPercentage,onChange:function(v){setAttr({showPercentage:v});}}), |
| 176 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Skill Name',checked:attr.showLabel,onChange:function(v){setAttr({showLabel:v});}}), |
| 177 |
attr.layout === 'row' && el(SelectControl,{__nextHasNoMarginBottom:true,label:'Label Position',value:attr.labelPosition,options:LABEL_POS_OPTS,onChange:function(v){setAttr({labelPosition:v});}}), |
| 178 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Percentage Size (px)',value:attr.pctSize,min:12,max:60,onChange:function(v){setAttr({pctSize:v});}}), |
| 179 |
attr.layout === 'concentric' && el(TextControl,{__nextHasNoMarginBottom:true,label:'Center Label',value:attr.centerLabel,onChange:function(v){setAttr({centerLabel:v});}}) |
| 180 |
), |
| 181 |
el(PanelBody,{title:'Spacing',initialOpen:false}, |
| 182 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Padding Top (px)',value:attr.paddingTop,min:0,max:200,onChange:function(v){setAttr({paddingTop:v});}}), |
| 183 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Padding Bottom (px)',value:attr.paddingBottom,min:0,max:200,onChange:function(v){setAttr({paddingBottom:v});}}) |
| 184 |
), |
| 185 |
|
| 186 |
el( PanelBody, { title: 'Typography', initialOpen: false }, |
| 187 |
_tc() && el(_tc(), { label: 'Percentage', value: attr.pctTypo || {}, onChange: function (v) { setAttr({ pctTypo: v }); } }), |
| 188 |
_tc() && el(_tc(), { label: 'Label', value: attr.labelTypo || {}, onChange: function (v) { setAttr({ labelTypo: v }); } }) |
| 189 |
), |
| 190 |
el(PanelColorSettings,{ |
| 191 |
title:'Colors',initialOpen:false, |
| 192 |
colorSettings:[ |
| 193 |
{value:attr.bgColor, onChange:function(v){setAttr({bgColor: v||''});},label:'Background'}, |
| 194 |
{value:attr.trackColor, onChange:function(v){setAttr({trackColor: v||'#1f2937'});},label:'Track (empty arc)'}, |
| 195 |
{value:attr.pctColor, onChange:function(v){setAttr({pctColor: v||'#ffffff'});},label:'Percentage Text'}, |
| 196 |
{value:attr.labelColor, onChange:function(v){setAttr({labelColor: v||'#ffffff'});},label:'Label Text'}, |
| 197 |
attr.layout==='concentric' && {value:attr.centerLabelColor,onChange:function(v){setAttr({centerLabelColor:v||'#fff'});},label:'Center Label'} |
| 198 |
].filter(Boolean) |
| 199 |
}), |
| 200 |
el(PanelBody,{title:'Skills',initialOpen:false}, |
| 201 |
skillPanels, |
| 202 |
el(Button,{variant:'secondary',isSmall:true,style:{marginTop:8},onClick:addSkill},'+ Add Skill') |
| 203 |
) |
| 204 |
), |
| 205 |
el('div', useBlockProps(), |
| 206 |
el('div', { |
| 207 |
style: Object.assign({ background:attr.bgColor||'transparent', padding:(attr.paddingTop||60)+'px clamp(20px,5vw,60px) '+(attr.paddingBottom||60)+'px' }, getTypoCssVars(attr)) |
| 208 |
}, preview) |
| 209 |
) |
| 210 |
); |
| 211 |
}, |
| 212 |
|
| 213 |
save: function (props) { |
| 214 |
var sv = getTypoCssVars(props.attributes); |
| 215 |
return el('div', useBlockProps.save(), |
| 216 |
el('div', { className:'bkbg-sr-app', 'data-opts':JSON.stringify(props.attributes), style: sv }) |
| 217 |
); |
| 218 |
} |
| 219 |
}); |
| 220 |
}() ); |
| 221 |
|