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 / heatmap-calendar / index.js

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

259 lines 14.1 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, SelectControl } = window.wp.components;
6 const { __ } = window.wp.i18n;
7
8 /* ── Typography lazy getters ──────────────────────────────── */
9 var _ttTC, _ttTV;
10 function _tc() { return _ttTC || (_ttTC = window.bkbgTypographyControl); }
11 function _tv() { return _ttTV || (_ttTV = window.bkbgTypoCssVars); }
12
13 const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
14 const DAY_LABELS = ['','Mon','','Wed','','Fri',''];
15
16 // Deterministic pseudo-random from seed
17 function seededRand(seed) {
18 let s = seed;
19 return function () {
20 s = (s * 1664525 + 1013904223) & 0xffffffff;
21 return (s >>> 0) / 0xffffffff;
22 };
23 }
24
25 // Generate 365-day activity data
26 function genData(year, seed, maxVal) {
27 const rand = seededRand(seed);
28 const start = new Date(year, 0, 1);
29 const days = [];
30 let d = new Date(start);
31 while (d.getFullYear() === year) {
32 // cluster activity — busier mid-week
33 const dow = d.getDay(); // 0=Sun
34 const weekBias = (dow >= 1 && dow <= 5) ? 1.4 : 0.5;
35 const r = rand();
36 const val = r < 0.35 ? 0 : Math.round(r * weekBias * maxVal);
37 days.push({ date: new Date(d), value: Math.min(val, maxVal) });
38 d.setDate(d.getDate() + 1);
39 }
40 return days;
41 }
42
43 // Group days into weeks (columns), each starting Sunday
44 function buildWeeks(days) {
45 const weeks = [];
46 // Pad so first day is on its correct DOW
47 const firstDow = days[0].date.getDay(); // 0=Sun
48 let week = Array(firstDow).fill(null);
49 days.forEach(day => {
50 week.push(day);
51 if (week.length === 7) { weeks.push(week); week = []; }
52 });
53 if (week.length) { while (week.length < 7) week.push(null); weeks.push(week); }
54 return weeks;
55 }
56
57 function levelColor(val, maxVal, a) {
58 if (val === 0) return a.color0;
59 if (val <= maxVal * 0.25) return a.color1;
60 if (val <= maxVal * 0.50) return a.color2;
61 if (val <= maxVal * 0.75) return a.color3;
62 return a.color4;
63 }
64
65 // Month label positions (week index where month starts)
66 function monthPositions(weeks) {
67 const pos = [];
68 weeks.forEach((week, wi) => {
69 const firstReal = week.find(d => d);
70 if (!firstReal) return;
71 const d = firstReal.date;
72 if (d.getDate() <= 7) {
73 pos.push({ label: MONTHS[d.getMonth()], week: wi });
74 }
75 });
76 return pos;
77 }
78
79 // ── Render the grid (used in both edit & save) ─────────────────────
80 function renderGrid(a) {
81 const days = genData(a.year, a.seed, a.maxValue);
82 const weeks = buildWeeks(days);
83 const cs = a.cellSize;
84 const gap = a.cellGap;
85 const dayLabelW = a.showDayLabels ? 28 : 0;
86 const monthLabelH = a.showMonths ? 20 : 0;
87 const totalW = dayLabelW + weeks.length * (cs + gap);
88 const totalH = monthLabelH + 7 * (cs + gap);
89 const mPos = monthPositions(weeks);
90
91 return el('div', { className: 'bkbg-hmc-grid-wrap', style: { overflowX: 'auto' } },
92 el('div', {
93 className: 'bkbg-hmc-grid',
94 style: { display: 'inline-flex', flexDirection: 'column', position: 'relative' }
95 },
96 // Month row
97 a.showMonths && el('div', {
98 className: 'bkbg-hmc-months',
99 style: { display: 'flex', marginLeft: dayLabelW + 'px', marginBottom: '4px', height: '16px', position: 'relative' }
100 },
101 mPos.map((m, i) =>
102 el('span', {
103 key: i,
104 style: {
105 position: 'absolute',
106 left: m.week * (cs + gap) + 'px',
107 fontSize: '12px',
108 color: a.labelColor,
109 fontFamily: 'inherit',
110 whiteSpace: 'nowrap',
111 }
112 }, m.label)
113 )
114 ),
115 // Day labels + columns row
116 el('div', { style: { display: 'flex', gap: '0' } },
117 // Day-of-week labels
118 a.showDayLabels && el('div', {
119 style: { display: 'flex', flexDirection: 'column', width: dayLabelW + 'px', marginRight: '0' }
120 },
121 DAY_LABELS.map((lbl, i) =>
122 el('div', {
123 key: i,
124 style: {
125 height: (cs + gap) + 'px', lineHeight: (cs + gap) + 'px',
126 fontSize: '11px', color: a.labelColor,
127 textAlign: 'right', paddingRight: '6px',
128 fontFamily: 'inherit',
129 }
130 }, lbl)
131 )
132 ),
133 // Weeks columns
134 weeks.map((week, wi) =>
135 el('div', {
136 key: wi,
137 style: { display: 'flex', flexDirection: 'column', gap: gap + 'px', marginRight: gap + 'px' }
138 },
139 week.map((day, di) =>
140 el('div', {
141 key: di,
142 title: day ? `${day.date.toDateString()}: ${day.value}` : '',
143 style: {
144 width: cs + 'px', height: cs + 'px',
145 borderRadius: a.cellRadius + 'px',
146 background: day ? levelColor(day.value, a.maxValue, a) : 'transparent',
147 flexShrink: 0,
148 }
149 })
150 )
151 )
152 )
153 )
154 )
155 );
156 }
157
158 // ── Legend ─────────────────────────────────────────────────────────
159 function renderLegend(a) {
160 const cs = a.cellSize;
161 return el('div', { className: 'bkbg-hmc-legend', style: { display: 'flex', alignItems: 'center', gap: '6px', marginTop: '12px' } },
162 el('span', { style: { fontSize: '12px', color: a.labelColor } }, __('Less')),
163 [a.color0, a.color1, a.color2, a.color3, a.color4].map((c, i) =>
164 el('div', {
165 key: i,
166 style: { width: cs + 'px', height: cs + 'px', borderRadius: a.cellRadius + 'px', background: c }
167 })
168 ),
169 el('span', { style: { fontSize: '12px', color: a.labelColor } }, __('More')),
170 );
171 }
172
173 // ── Wrapper style ──────────────────────────────────────────────────
174 function wrapStyle(a) {
175 return Object.assign({
176 background: a.bgColor,
177 borderRadius: a.borderRadius + 'px',
178 paddingTop: a.paddingTop + 'px',
179 paddingBottom: a.paddingBottom + 'px',
180 paddingLeft: '28px',
181 paddingRight: '28px',
182 }, _tv() && _tv()(a.typoTitle, '--bkbg-hmc-tt-'), _tv() && _tv()(a.typoSubtitle, '--bkbg-hmc-st-'));
183 }
184
185 registerBlockType('blockenberg/heatmap-calendar', {
186 edit: function ({ attributes: a, setAttributes }) {
187 const blockProps = useBlockProps({ style: wrapStyle(a) });
188
189 return el('div', blockProps,
190 el(InspectorControls, {},
191 el(PanelBody, { title: __('Content'), initialOpen: true },
192 el(TextControl, { label: __('Title'), value: a.title, onChange: v => setAttributes({ title: v }) }),
193 el(ToggleControl, { label: __('Show title'), checked: a.showTitle, onChange: v => setAttributes({ showTitle: v }) }),
194 el(TextControl, { label: __('Subtitle'), value: a.subtitle, onChange: v => setAttributes({ subtitle: v }) }),
195 el(ToggleControl, { label: __('Show subtitle'), checked: a.showSubtitle, onChange: v => setAttributes({ showSubtitle: v }) }),
196 el(TextControl, { label: __('Legend label'), value: a.legendLabel, onChange: v => setAttributes({ legendLabel: v }) }),
197 el(ToggleControl, { label: __('Show legend'), checked: a.showLegend, onChange: v => setAttributes({ showLegend: v }) }),
198 el(RangeControl, { label: __('Year'), value: a.year, min: 2010, max: 2030, onChange: v => setAttributes({ year: v }) }),
199 el(RangeControl, { label: __('Max value'), value: a.maxValue, min: 5, max: 50, onChange: v => setAttributes({ maxValue: v }) }),
200 el(RangeControl, { label: __('Data seed (changes pattern)'), value: a.seed, min: 1, max: 999, onChange: v => setAttributes({ seed: v }) }),
201 ),
202 el(PanelBody, { title: __('Grid'), initialOpen: false },
203 el(RangeControl, { label: __('Cell size (px)'), value: a.cellSize, min: 8, max: 20, onChange: v => setAttributes({ cellSize: v }) }),
204 el(RangeControl, { label: __('Cell gap (px)'), value: a.cellGap, min: 1, max: 8, onChange: v => setAttributes({ cellGap: v }) }),
205 el(RangeControl, { label: __('Cell radius (px)'), value: a.cellRadius, min: 0, max: 6, onChange: v => setAttributes({ cellRadius: v }) }),
206 el(ToggleControl, { label: __('Show month labels'), checked: a.showMonths, onChange: v => setAttributes({ showMonths: v }) }),
207 el(ToggleControl, { label: __('Show day labels'), checked: a.showDayLabels, onChange: v => setAttributes({ showDayLabels: v }) }),
208 ),
209 el(PanelBody, { title: __('Spacing'), initialOpen: false },
210 el(RangeControl, { label: __('Border radius'), value: a.borderRadius, min: 0, max: 32, onChange: v => setAttributes({ borderRadius: v }) }),
211 el(RangeControl, { label: __('Padding top'), value: a.paddingTop, min: 0, max: 120, onChange: v => setAttributes({ paddingTop: v }) }),
212 el(RangeControl, { label: __('Padding bottom'), value: a.paddingBottom, min: 0, max: 120, onChange: v => setAttributes({ paddingBottom: v }) }),
213 ),
214 el(PanelBody, { title: __('Typography'), initialOpen: false },
215 _tc() && _tc()({ label: __('Title','blockenberg'), value: a.typoTitle, onChange: v => setAttributes({ typoTitle: v }) }),
216 _tc() && _tc()({ label: __('Subtitle','blockenberg'), value: a.typoSubtitle, onChange: v => setAttributes({ typoSubtitle: v }) })
217 ),
218 el(PanelColorSettings, {
219 title: __('Heat Colors'), initialOpen: false,
220 colorSettings: [
221 { label: __('Level 0 (empty)'), value: a.color0, onChange: v => setAttributes({ color0: v || '#ebedf0' }) },
222 { label: __('Level 1 (low)'), value: a.color1, onChange: v => setAttributes({ color1: v || '#9be9a8' }) },
223 { label: __('Level 2'), value: a.color2, onChange: v => setAttributes({ color2: v || '#40c463' }) },
224 { label: __('Level 3'), value: a.color3, onChange: v => setAttributes({ color3: v || '#30a14e' }) },
225 { label: __('Level 4 (high)'), value: a.color4, onChange: v => setAttributes({ color4: v || '#216e39' }) },
226 ]
227 }),
228 el(PanelColorSettings, {
229 title: __('UI Colors'), initialOpen: false,
230 colorSettings: [
231 { label: __('Background'), value: a.bgColor, onChange: v => setAttributes({ bgColor: v || '#ffffff' }) },
232 { label: __('Title'), value: a.titleColor, onChange: v => setAttributes({ titleColor: v || '#0f172a' }) },
233 { label: __('Subtitle'), value: a.subtitleColor, onChange: v => setAttributes({ subtitleColor: v || '#64748b' }) },
234 { label: __('Labels'), value: a.labelColor, onChange: v => setAttributes({ labelColor: v || '#6b7280' }) },
235 ]
236 }),
237 ),
238
239 a.showTitle && el('h3', { className: 'bkbg-hmc-title', style: { color: a.titleColor, margin: '0 0 6px' } }, a.title),
240 a.showSubtitle && el('p', { className: 'bkbg-hmc-subtitle', style: { color: a.subtitleColor, margin: '0 0 20px' } }, a.subtitle),
241 renderGrid(a),
242 a.showLegend && renderLegend(a),
243 );
244 },
245
246 save: function ({ attributes: a }) {
247 return el('div', {
248 className: 'bkbg-hmc-wrap',
249 style: wrapStyle(a),
250 },
251 a.showTitle && el('h3', { className: 'bkbg-hmc-title', style: { color: a.titleColor, margin: '0 0 6px' } }, a.title),
252 a.showSubtitle && el('p', { className: 'bkbg-hmc-subtitle', style: { color: a.subtitleColor, margin: '0 0 20px' } }, a.subtitle),
253 renderGrid(a),
254 a.showLegend && renderLegend(a),
255 );
256 }
257 });
258 }() );
259