| 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, Button, ColorPicker, Popover } = window.wp.components; |
| 6 |
const { __ } = window.wp.i18n; |
| 7 |
const { useState } = window.wp.element; |
| 8 |
|
| 9 |
var _tc, _tv; |
| 10 |
function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); } |
| 11 |
function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); } |
| 12 |
|
| 13 |
// ── Helpers ────────────────────────────────────────────────────────────── |
| 14 |
function updBranch( setAttributes, branches, idx, field, val ) { |
| 15 |
const next = branches.map( ( b, i ) => i === idx ? { ...b, [field]: val } : b ); |
| 16 |
setAttributes( { branches: next } ); |
| 17 |
} |
| 18 |
|
| 19 |
function updItem( setAttributes, branches, bIdx, iIdx, val ) { |
| 20 |
const next = branches.map( ( b, i ) => { |
| 21 |
if ( i !== bIdx ) return b; |
| 22 |
const items = b.items.map( ( it, j ) => j === iIdx ? val : it ); |
| 23 |
return { ...b, items }; |
| 24 |
} ); |
| 25 |
setAttributes( { branches: next } ); |
| 26 |
} |
| 27 |
|
| 28 |
// ── SVG renderer ───────────────────────────────────────────────────────── |
| 29 |
function renderMindMap( a, opts ) { |
| 30 |
opts = opts || {}; |
| 31 |
var it = opts.inlineTypo; |
| 32 |
const W = a.svgWidth, H = a.svgHeight; |
| 33 |
const cx = W / 2, cy = H / 2; |
| 34 |
const count = a.branches.length || 1; |
| 35 |
const CR = a.centralRadius; |
| 36 |
const BR = a.branchRadius; |
| 37 |
const dist = Math.min( W, H ) * 0.31; |
| 38 |
const nodes = []; |
| 39 |
|
| 40 |
// Effective sizes (read from typo if available, else old attr) |
| 41 |
var cfSize = (!it && a.centralTypo && a.centralTypo.sizeDesktop) || a.centralFontSize; |
| 42 |
var bfSize = (!it && a.branchTypo && a.branchTypo.sizeDesktop) || a.branchFontSize; |
| 43 |
var ifSize = (!it && a.itemTypo && a.itemTypo.sizeDesktop) || a.itemFontSize; |
| 44 |
|
| 45 |
// Text element props: inline attrs (old) or CSS classes (new) |
| 46 |
var ctProps = it ? { fontSize: a.centralFontSize, fontWeight: (a.fontWeight || 600), fontFamily: 'inherit' } : { className: 'bkbg-mmap-central' }; |
| 47 |
var brProps = it ? { fontSize: a.branchFontSize, fontWeight: (a.fontWeight || 600), fontFamily: 'inherit' } : { className: 'bkbg-mmap-branch' }; |
| 48 |
var itProps = it ? { fontSize: a.itemFontSize, fontFamily: 'inherit' } : { className: 'bkbg-mmap-item' }; |
| 49 |
|
| 50 |
// Background |
| 51 |
nodes.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: H, fill: a.bgColor || '#f9fafb', rx: 10 } ) ); |
| 52 |
|
| 53 |
// Render branches |
| 54 |
a.branches.forEach( ( branch, i ) => { |
| 55 |
const angle = ( 2 * Math.PI * i / count ) - Math.PI / 2; |
| 56 |
const bx = cx + dist * Math.cos( angle ); |
| 57 |
const by = cy + dist * Math.sin( angle ); |
| 58 |
const color = branch.color || '#4f46e5'; |
| 59 |
|
| 60 |
// Connector line: center edge → branch edge |
| 61 |
if ( a.showLines ) { |
| 62 |
nodes.push( el( 'line', { |
| 63 |
key: 'ln' + i, |
| 64 |
x1: cx + CR * Math.cos( angle ), |
| 65 |
y1: cy + CR * Math.sin( angle ), |
| 66 |
x2: bx - BR * Math.cos( angle ), |
| 67 |
y2: by - BR * Math.sin( angle ), |
| 68 |
stroke: color, strokeWidth: 2.5, strokeOpacity: 0.55, |
| 69 |
strokeDasharray: a.dashedLines ? '7 5' : null, |
| 70 |
} ) ); |
| 71 |
} |
| 72 |
|
| 73 |
// Branch circle fill + stroke |
| 74 |
nodes.push( el( 'circle', { key: 'bf' + i, cx: bx, cy: by, r: BR, fill: color, opacity: 0.14 } ) ); |
| 75 |
nodes.push( el( 'circle', { key: 'bs' + i, cx: bx, cy: by, r: BR, fill: 'none', stroke: color, strokeWidth: 2 } ) ); |
| 76 |
|
| 77 |
// Branch label |
| 78 |
nodes.push( el( 'text', Object.assign({ |
| 79 |
key: 'bl' + i, x: bx, y: by, |
| 80 |
textAnchor: 'middle', dominantBaseline: 'middle', |
| 81 |
fill: color |
| 82 |
}, brProps), branch.label || __( 'Branch', 'blockenberg' ) ) ); |
| 83 |
|
| 84 |
// Sub-items |
| 85 |
if ( branch.items && branch.items.length > 0 ) { |
| 86 |
const subDist = BR + 20 + ifSize * 3.5; |
| 87 |
const spread = Math.min( 1.1, ( branch.items.length - 1 ) * 0.35 ); |
| 88 |
branch.items.forEach( ( item, j ) => { |
| 89 |
if ( !item ) return; |
| 90 |
const off = branch.items.length > 1 ? -spread / 2 + spread * j / ( branch.items.length - 1 ) : 0; |
| 91 |
const sa = angle + off; |
| 92 |
const sx = bx + subDist * Math.cos( sa ); |
| 93 |
const sy = by + subDist * Math.sin( sa ); |
| 94 |
const anchor = Math.cos( sa ) >= 0 ? 'start' : 'end'; |
| 95 |
|
| 96 |
// Sub-connector |
| 97 |
nodes.push( el( 'line', { |
| 98 |
key: 'sl' + i + '_' + j, |
| 99 |
x1: bx + BR * Math.cos( sa ), y1: by + BR * Math.sin( sa ), |
| 100 |
x2: sx - 6 * Math.cos( sa ), y2: sy - 6 * Math.sin( sa ), |
| 101 |
stroke: color, strokeWidth: 1.5, strokeOpacity: 0.45 |
| 102 |
} ) ); |
| 103 |
|
| 104 |
// Dot |
| 105 |
nodes.push( el( 'circle', { key: 'sd' + i + '_' + j, cx: sx, cy: sy, r: 5, fill: color, opacity: 0.7 } ) ); |
| 106 |
|
| 107 |
// Label |
| 108 |
nodes.push( el( 'text', Object.assign({ |
| 109 |
key: 'st' + i + '_' + j, |
| 110 |
x: sx + ( Math.cos( sa ) >= 0 ? 9 : -9 ), y: sy, |
| 111 |
textAnchor: anchor, dominantBaseline: 'middle', |
| 112 |
fill: a.textColor || '#374151' |
| 113 |
}, itProps), item ) ); |
| 114 |
} ); |
| 115 |
} |
| 116 |
} ); |
| 117 |
|
| 118 |
// Central circle (on top of lines) |
| 119 |
nodes.push( el( 'circle', { key: 'cc', cx, cy, r: CR, fill: a.centralColor || '#4f46e5' } ) ); |
| 120 |
|
| 121 |
// Central topic text — split on spaces for multi-word wrapping |
| 122 |
const words = ( a.centralTopic || 'Main Topic' ).split( ' ' ); |
| 123 |
const lineH = cfSize * ( ((!it && a.centralTypo && a.centralTypo.lineHeightDesktop) || a.lineHeight || 1.3) ); |
| 124 |
const startY = cy - ( ( words.length - 1 ) / 2 ) * lineH; |
| 125 |
words.forEach( ( w, i ) => { |
| 126 |
nodes.push( el( 'text', Object.assign({ |
| 127 |
key: 'ct' + i, x: cx, y: startY + i * lineH, |
| 128 |
textAnchor: 'middle', dominantBaseline: 'middle', |
| 129 |
fill: '#ffffff' |
| 130 |
}, ctProps), w ) ); |
| 131 |
} ); |
| 132 |
|
| 133 |
return el( 'svg', { |
| 134 |
viewBox: `0 0 ${ W } ${ H }`, width: '100%', |
| 135 |
style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } |
| 136 |
}, ...nodes ); |
| 137 |
} |
| 138 |
|
| 139 |
// ── Block registration ──────────────────────────────────────────────────── /* ── colour-swatch + popover ── */ |
| 140 |
function BkbgColorSwatch(p) { |
| 141 |
var st = useState(false), open = st[0], setOpen = st[1]; |
| 142 |
return el('div', { style:{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 0', gap:'8px' } }, |
| 143 |
el('span', { style:{ fontSize:'12px', color:'#1e1e1e', flex:1, lineHeight:1.4 } }, p.label), |
| 144 |
el('div', { style:{ position:'relative', flexShrink:0 } }, |
| 145 |
el('button', { type:'button', title: p.value||'none', onClick: function(){ setOpen(!open); }, |
| 146 |
style:{ width:'28px', height:'28px', borderRadius:'4px', border: open ? '2px solid #007cba' : '2px solid #ddd', cursor:'pointer', padding:0, display:'block', background: p.value||'#ffffff', flexShrink:0 } }), |
| 147 |
open && el(Popover, { position:'bottom left', onClose: function(){ setOpen(false); } }, |
| 148 |
el('div', { style:{ padding:'8px' }, onMouseDown: function(e){ e.stopPropagation(); } }, |
| 149 |
el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'6px' } }, |
| 150 |
el('strong', { style:{ fontSize:'12px' } }, p.label), |
| 151 |
el(Button, { icon:'no-alt', isSmall:true, onClick: function(){ setOpen(false); } }) |
| 152 |
), |
| 153 |
el(ColorPicker, { color: p.value, enableAlpha:true, onChange: p.onChange }) |
| 154 |
) |
| 155 |
) |
| 156 |
) |
| 157 |
); |
| 158 |
} |
| 159 |
registerBlockType( 'blockenberg/mind-map', { |
| 160 |
deprecated: [{ |
| 161 |
save: function ( { attributes: a } ) { |
| 162 |
var blockProps = useBlockProps.save( { className: 'bkbg-mind-map-wrap' } ); |
| 163 |
return el( 'div', blockProps, |
| 164 |
el( 'div', { className: 'bkbg-mind-map-svg' }, renderMindMap( a, { inlineTypo: true } ) ), |
| 165 |
); |
| 166 |
}, |
| 167 |
}], |
| 168 |
edit: function ( props ) { |
| 169 |
const { attributes: a, setAttributes } = props; |
| 170 |
var blockProps = useBlockProps((function () { |
| 171 |
var _tvf = getTypoCssVars(); |
| 172 |
var s = {}; |
| 173 |
Object.assign(s, _tvf(a.centralTypo, '--bkbg-mmap-ct-')); |
| 174 |
Object.assign(s, _tvf(a.branchTypo, '--bkbg-mmap-br-')); |
| 175 |
Object.assign(s, _tvf(a.itemTypo, '--bkbg-mmap-it-')); |
| 176 |
return { className: 'bkbg-mind-map-wrap', style: s }; |
| 177 |
})()); |
| 178 |
|
| 179 |
return el( 'div', blockProps, |
| 180 |
el( InspectorControls, {}, |
| 181 |
|
| 182 |
el( PanelBody, { title: __( 'Central Topic', 'blockenberg' ), initialOpen: true }, |
| 183 |
el( TextControl, { label: __( 'Topic Text', 'blockenberg' ), value: a.centralTopic, onChange: v => setAttributes( { centralTopic: v } ) } ), |
| 184 |
el( RangeControl, { label: __( 'Central Radius', 'blockenberg' ), value: a.centralRadius, onChange: v => setAttributes( { centralRadius: v } ), min: 30, max: 100 } ), |
| 185 |
), |
| 186 |
|
| 187 |
|
| 188 |
el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false }, |
| 189 |
el( getTypoControl(), { label: __( 'Central Topic', 'blockenberg' ), value: a.centralTypo, onChange: function (v) { setAttributes({ centralTypo: v }); } }), |
| 190 |
el( getTypoControl(), { label: __( 'Branch Label', 'blockenberg' ), value: a.branchTypo, onChange: function (v) { setAttributes({ branchTypo: v }); } }), |
| 191 |
el( getTypoControl(), { label: __( 'Item Label', 'blockenberg' ), value: a.itemTypo, onChange: function (v) { setAttributes({ itemTypo: v }); } }) |
| 192 |
), |
| 193 |
el( PanelColorSettings, { |
| 194 |
title: __( 'Colors', 'blockenberg' ), |
| 195 |
initialOpen: false, |
| 196 |
colorSettings: [ |
| 197 |
{ label: __( 'Central Color', 'blockenberg' ), value: a.centralColor, onChange: v => setAttributes( { centralColor: v || '#4f46e5' } ) }, |
| 198 |
{ label: __( 'Item Text Color', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#374151' } ) }, |
| 199 |
{ label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#f9fafb' } ) }, |
| 200 |
] |
| 201 |
} ), |
| 202 |
|
| 203 |
el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false }, |
| 204 |
el( RangeControl, { label: __( 'Canvas Width', 'blockenberg' ), value: a.svgWidth, onChange: v => setAttributes( { svgWidth: v } ), min: 400, max: 1200, step: 50 } ), |
| 205 |
el( RangeControl, { label: __( 'Canvas Height', 'blockenberg' ), value: a.svgHeight, onChange: v => setAttributes( { svgHeight: v } ), min: 300, max: 900, step: 50 } ), |
| 206 |
el( RangeControl, { label: __( 'Branch Radius', 'blockenberg' ), value: a.branchRadius, onChange: v => setAttributes( { branchRadius: v } ), min: 20, max: 80 } ), |
| 207 |
el( ToggleControl, { label: __( 'Show Lines', 'blockenberg' ), checked: a.showLines, onChange: v => setAttributes( { showLines: v } ) } ), |
| 208 |
el( ToggleControl, { label: __( 'Dashed Lines', 'blockenberg' ), checked: a.dashedLines, onChange: v => setAttributes( { dashedLines: v } ) } ), |
| 209 |
), |
| 210 |
|
| 211 |
el( PanelBody, { title: __( 'Branches', 'blockenberg' ), initialOpen: false }, |
| 212 |
el( Button, { |
| 213 |
variant: 'secondary', style: { marginBottom: 12 }, |
| 214 |
onClick: () => setAttributes( { branches: [ ...a.branches, { label: __( 'New Branch', 'blockenberg' ), color: '#6b7280', items: [] } ] } ) |
| 215 |
}, __( '+ Add Branch', 'blockenberg' ) ), |
| 216 |
|
| 217 |
a.branches.map( ( branch, i ) => |
| 218 |
el( PanelBody, { key: i, title: ( branch.label || `Branch ${ i + 1 }` ), initialOpen: false }, |
| 219 |
el( TextControl, { label: __( 'Label', 'blockenberg' ), value: branch.label, onChange: v => updBranch( setAttributes, a.branches, i, 'label', v ) } ), |
| 220 |
el( BkbgColorSwatch, { label: __( 'Color', 'blockenberg' ), value: branch.color, onChange: v => updBranch( setAttributes, a.branches, i, 'color', v ) } ), |
| 221 |
|
| 222 |
el( 'p', { style: { fontWeight: 600, margin: '10px 0 4px' } }, __( 'Sub-items', 'blockenberg' ) ), |
| 223 |
|
| 224 |
branch.items.map( ( item, j ) => |
| 225 |
el( 'div', { key: j, style: { display: 'flex', gap: 4, marginBottom: 4, alignItems: 'center' } }, |
| 226 |
el( TextControl, { value: item, hideLabelFromVision: true, label: `Item ${ j + 1 }`, onChange: v => updItem( setAttributes, a.branches, i, j, v ), style: { flex: 1 } } ), |
| 227 |
el( Button, { isDestructive: true, isSmall: true, onClick: () => { const items = branch.items.filter( ( _, x ) => x !== j ); updBranch( setAttributes, a.branches, i, 'items', items ); } }, '×' ), |
| 228 |
) |
| 229 |
), |
| 230 |
|
| 231 |
el( 'div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 6 } }, |
| 232 |
el( Button, { variant: 'secondary', isSmall: true, onClick: () => updBranch( setAttributes, a.branches, i, 'items', [ ...branch.items, '' ] ) }, __( '+ Sub-item', 'blockenberg' ) ), |
| 233 |
el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { branches: a.branches.filter( ( _, x ) => x !== i ) } ) }, __( 'Remove Branch', 'blockenberg' ) ), |
| 234 |
), |
| 235 |
) |
| 236 |
), |
| 237 |
), |
| 238 |
), |
| 239 |
|
| 240 |
el( 'div', { className: 'bkbg-mind-map-svg' }, renderMindMap( a ) ), |
| 241 |
); |
| 242 |
}, |
| 243 |
|
| 244 |
save: function ( { attributes: a } ) { |
| 245 |
var blockProps = useBlockProps.save((function () { |
| 246 |
var _tvf = getTypoCssVars(); |
| 247 |
var s = {}; |
| 248 |
Object.assign(s, _tvf(a.centralTypo, '--bkbg-mmap-ct-')); |
| 249 |
Object.assign(s, _tvf(a.branchTypo, '--bkbg-mmap-br-')); |
| 250 |
Object.assign(s, _tvf(a.itemTypo, '--bkbg-mmap-it-')); |
| 251 |
return { className: 'bkbg-mind-map-wrap', style: s }; |
| 252 |
})()); |
| 253 |
return el( 'div', blockProps, |
| 254 |
el( 'div', { className: 'bkbg-mind-map-svg' }, renderMindMap( a ) ), |
| 255 |
); |
| 256 |
}, |
| 257 |
} ); |
| 258 |
}() ); |
| 259 |
|