PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / mind-map / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/mind-map/index.js

259 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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