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 / pomodoro-timer / index.js

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

263 lines 18.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var useState = wp.element.useState;
4 var useEffect = wp.element.useEffect;
5 var useRef = wp.element.useRef;
6 var Fragment = wp.element.Fragment;
7 var registerBlockType = wp.blocks.registerBlockType;
8 var __ = wp.i18n.__;
9 var InspectorControls = wp.blockEditor.InspectorControls;
10 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
11 var useBlockProps = wp.blockEditor.useBlockProps;
12 var PanelBody = wp.components.PanelBody;
13 var RangeControl = wp.components.RangeControl;
14 var TextControl = wp.components.TextControl;
15 var ToggleControl = wp.components.ToggleControl;
16 var SelectControl = wp.components.SelectControl;
17 var Button = wp.components.Button;
18
19 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20 var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
21
22 /* ── Modes ─────────────────────────────────────────────────────────── */
23 var MODES = { WORK: 'work', SHORT: 'short', LONG: 'long' };
24
25 function pad2(n) { return String(Math.floor(n)).padStart(2, '0'); }
26 function fmtTime(s) { return pad2(Math.floor(s / 60)) + ':' + pad2(s % 60); }
27
28 function PomodoroPreview(props) {
29 var a = props.attributes;
30
31 var workSecs = (a.workMinutes || 25) * 60;
32 var shortSecs = (a.shortBreak || 5) * 60;
33 var longSecs = (a.longBreak || 15) * 60;
34
35 var _mode = useState(MODES.WORK); var mode = _mode[0]; var setMode = _mode[1];
36 var _secs = useState(workSecs); var secs = _secs[0]; var setSecs = _secs[1];
37 var _running = useState(false); var running = _running[0]; var setRunning = _running[1];
38 var _cycles = useState(0); var cycles = _cycles[0]; var setCycles = _cycles[1];
39 var totalRef = useRef(workSecs);
40 var intervalRef = useRef(null);
41
42 var modeColor = mode === MODES.WORK ? (a.accentColor||'#6c3fb5') : mode === MODES.SHORT ? (a.breakColor||'#10b981') : (a.longBreakColor||'#3b82f6');
43
44 function getTotalFor(m) {
45 return m === MODES.WORK ? workSecs : m === MODES.SHORT ? shortSecs : longSecs;
46 }
47
48 function switchMode(m) {
49 if (intervalRef.current) clearInterval(intervalRef.current);
50 setRunning(false);
51 setMode(m);
52 var t = getTotalFor(m);
53 totalRef.current = t;
54 setSecs(t);
55 }
56
57 useEffect(function() {
58 if (running) {
59 intervalRef.current = setInterval(function() {
60 setSecs(function(s) {
61 if (s <= 1) {
62 clearInterval(intervalRef.current);
63 setRunning(false);
64 if (mode === MODES.WORK) {
65 var newCycles = cycles + 1;
66 setCycles(newCycles);
67 if (newCycles % (a.cyclesBeforeLong || 4) === 0) {
68 switchMode(MODES.LONG);
69 } else {
70 switchMode(MODES.SHORT);
71 }
72 } else {
73 switchMode(MODES.WORK);
74 }
75 return 0;
76 }
77 return s - 1;
78 });
79 }, 1000);
80 } else {
81 clearInterval(intervalRef.current);
82 }
83 return function() { clearInterval(intervalRef.current); };
84 }, [running, mode]);
85
86 var progress = 1 - secs / (totalRef.current || workSecs);
87 var size = a.ringSize || 220;
88 var thick = a.ringThickness || 14;
89 var r = (size - thick) / 2;
90 var circ = 2 * Math.PI * r;
91 var offset = circ * (1 - progress);
92
93 var modeLabels = { work: 'Focus Time', short: 'Short Break', long: 'Long Break' };
94
95 return el('div', { style: { paddingTop: a.paddingTop+'px', paddingBottom: a.paddingBottom+'px', background: a.sectionBg||undefined } },
96 el('div', { style: { background: a.cardBg, borderRadius: a.cardRadius+'px', padding: '40px 32px', maxWidth: a.maxWidth+'px', margin: '0 auto', boxShadow: '0 4px 24px rgba(0,0,0,0.09)', textAlign: 'center' } },
97
98 (a.showTitle || a.showSubtitle) && el('div', { style: { marginBottom: '24px' } },
99 a.showTitle && el('div', { className: 'bkbg-pom-title', style: { color: a.titleColor, marginBottom: '6px' } }, a.title),
100 a.showSubtitle && el('div', { className: 'bkbg-pom-subtitle', style: { color: a.subtitleColor } }, a.subtitle)
101 ),
102
103 // Mode tabs
104 el('div', { style: { display: 'flex', gap: '8px', justifyContent: 'center', marginBottom: '28px' } },
105 Object.values(MODES).map(function(m) {
106 var active = m === mode;
107 var mc = m === MODES.WORK ? (a.accentColor||'#6c3fb5') : m === MODES.SHORT ? (a.breakColor||'#10b981') : (a.longBreakColor||'#3b82f6');
108 return el('button', { key: m, onClick: function(){ switchMode(m); }, style: { padding: '7px 16px', borderRadius: '100px', border: '2px solid ' + (active ? mc : '#e5e7eb'), background: active ? mc : 'transparent', color: active ? '#fff' : mc, fontWeight: 700, fontSize: '13px', cursor: 'pointer', fontFamily: 'inherit', transition: 'all .15s' } },
109 modeLabels[m]
110 );
111 })
112 ),
113
114 // SVG ring
115 el('div', { style: { position: 'relative', width: size+'px', height: size+'px', margin: '0 auto 24px' } },
116 el('svg', { width: size, height: size, style: { transform: 'rotate(-90deg)' } },
117 el('circle', { cx: size/2, cy: size/2, r: r, fill: 'none', stroke: a.ringBg||'#e5e7eb', strokeWidth: thick }),
118 el('circle', { cx: size/2, cy: size/2, r: r, fill: 'none', stroke: modeColor, strokeWidth: thick, strokeLinecap: 'round', strokeDasharray: circ, strokeDashoffset: offset, style: { transition: 'stroke-dashoffset .5s linear, stroke .3s' } })
119 ),
120 el('div', { style: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', textAlign: 'center' } },
121 el('div', { style: { fontSize: a.timerSize+'px', fontWeight: 800, color: a.TimerColor||'#1f2937', lineHeight: 1, fontVariantNumeric: 'tabular-nums', fontFamily: 'monospace' } }, fmtTime(secs)),
122 a.showModeLabel && el('div', { style: { fontSize: '13px', fontWeight: 600, color: modeColor, marginTop: '4px' } }, modeLabels[mode])
123 )
124 ),
125
126 // Controls
127 el('div', { style: { display: 'flex', gap: '10px', justifyContent: 'center', marginBottom: '20px' } },
128 el('button', { onClick: function(){ setRunning(function(r){ return !r; }); }, style: { padding: '12px 32px', borderRadius: a.btnRadius+'px', border: 'none', background: a.btnStartBg||'#6c3fb5', color: a.btnStartColor||'#fff', fontWeight: 700, fontSize: '16px', cursor: 'pointer', fontFamily: 'inherit', minWidth: '120px' } },
129 running ? '⏸ Pause' : (secs < getTotalFor(mode) ? '▶ Resume' : '▶ Start')
130 ),
131 el('button', { onClick: function(){ switchMode(mode); }, style: { padding: '12px 20px', borderRadius: a.btnRadius+'px', border: 'none', background: a.btnSecBg||'#f3f4f6', color: a.btnSecColor||'#374151', fontWeight: 600, fontSize: '15px', cursor: 'pointer', fontFamily: 'inherit' } }, '↺ Reset')
132 ),
133
134 // Cycle counter
135 a.showCycleCount && el('div', { style: { display: 'flex', gap: '8px', justifyContent: 'center', alignItems: 'center' } },
136 el('span', { style: { fontSize: '13px', color: a.labelColor, fontWeight: 500, marginRight: '4px' } }, 'Sessions:'),
137 [1,2,3,4].map(function(i) {
138 return el('div', { key: i, style: { width: '12px', height: '12px', borderRadius: '50%', background: i <= (cycles % ((a.cyclesBeforeLong||4))) || (cycles > 0 && cycles % (a.cyclesBeforeLong||4) === 0 && i <= (a.cyclesBeforeLong||4)) ? (a.accentColor||'#6c3fb5') : '#e5e7eb', transition: 'background .2s' } });
139 }),
140 el('span', { style: { fontSize: '12px', color: a.labelColor, marginLeft: '4px' } }, '(' + cycles + ' done)')
141 )
142 )
143 );
144 }
145
146 registerBlockType('blockenberg/pomodoro-timer', {
147 edit: function(props) {
148 var a = props.attributes; var set = props.setAttributes;
149 var blockProps = useBlockProps((function () {
150 var _tv = getTypoCssVars();
151 var s = {};
152 Object.assign(s, _tv(a.titleTypo, '--bkbg-pom-tt-'));
153 Object.assign(s, _tv(a.subtitleTypo, '--bkbg-pom-st-'));
154 return { className: 'bkbg-pom-editor', style: s };
155 })());
156 var colorSettings = [
157 { value: a.accentColor, onChange: function(v){ set({accentColor:v}); }, label: 'Work / Accent Color' },
158 { value: a.breakColor, onChange: function(v){ set({breakColor:v}); }, label: 'Short Break Color' },
159 { value: a.longBreakColor, onChange: function(v){ set({longBreakColor:v}); }, label: 'Long Break Color' },
160 { value: a.ringBg, onChange: function(v){ set({ringBg:v}); }, label: 'Ring Track Background' },
161 { value: a.cardBg, onChange: function(v){ set({cardBg:v}); }, label: 'Card Background' },
162 { value: a.TimerColor, onChange: function(v){ set({TimerColor:v}); }, label: 'Timer Text Color' },
163 { value: a.labelColor, onChange: function(v){ set({labelColor:v}); }, label: 'Label Color' },
164 { value: a.btnStartBg, onChange: function(v){ set({btnStartBg:v}); }, label: 'Start Button Background' },
165 { value: a.btnStartColor, onChange: function(v){ set({btnStartColor:v}); }, label: 'Start Button Text' },
166 { value: a.btnSecBg, onChange: function(v){ set({btnSecBg:v}); }, label: 'Reset Button Background' },
167 { value: a.btnSecColor, onChange: function(v){ set({btnSecColor:v}); }, label: 'Reset Button Text' },
168 { value: a.titleColor, onChange: function(v){ set({titleColor:v}); }, label: 'Title Color' },
169 { value: a.subtitleColor, onChange: function(v){ set({subtitleColor:v}); }, label: 'Subtitle Color' },
170 { value: a.sectionBg, onChange: function(v){ set({sectionBg:v}); }, label: 'Section Background' }
171 ];
172 return el(Fragment, null,
173 el(InspectorControls, null,
174 el(PanelBody, { title: 'Header', initialOpen: false },
175 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Title', checked: a.showTitle, onChange: function(v){ set({showTitle:v}); } }),
176 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Subtitle', checked: a.showSubtitle, onChange: function(v){ set({showSubtitle:v}); } }),
177 el(TextControl, { label: 'Title', value: a.title, onChange: function(v){ set({title:v}); } }),
178 el(TextControl, { label: 'Subtitle', value: a.subtitle, onChange: function(v){ set({subtitle:v}); } })
179 ),
180 el(PanelBody, { title: 'Timer Settings', initialOpen: true },
181 el(RangeControl, { label: 'Work Duration (min)', value: a.workMinutes, min: 1, max: 90, step: 1, onChange: function(v){ set({workMinutes:v}); } }),
182 el(RangeControl, { label: 'Short Break (min)', value: a.shortBreak, min: 1, max: 30, step: 1, onChange: function(v){ set({shortBreak:v}); } }),
183 el(RangeControl, { label: 'Long Break (min)', value: a.longBreak, min: 5, max: 60, step: 1, onChange: function(v){ set({longBreak:v}); } }),
184 el(RangeControl, { label: 'Cycles Before Long Break', value: a.cyclesBeforeLong, min: 2, max: 8, step: 1, onChange: function(v){ set({cyclesBeforeLong:v}); } }),
185 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Mode Label', checked: a.showModeLabel, onChange: function(v){ set({showModeLabel:v}); } }),
186 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Cycle Count', checked: a.showCycleCount, onChange: function(v){ set({showCycleCount:v}); } })
187 ),
188
189 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
190 (function () {
191 var TC = getTypoControl();
192 if (!TC) return el('p', null, 'Loading…');
193 return el(wp.element.Fragment, null,
194 el(TC, { label: 'Title Typography', value: a.titleTypo, onChange: function (v) { set({ titleTypo: v }); } }),
195 el(TC, { label: 'Subtitle Typography', value: a.subtitleTypo, onChange: function (v) { set({ subtitleTypo: v }); } })
196 );
197 })(),
198 el(RangeControl, { label: 'Timer Font Size', value: a.timerSize, min: 28, max: 96, step: 2, onChange: function(v){ set({timerSize:v}); } })
199 ),
200 el(PanelColorSettings, { title: 'Colors', initialOpen: false, colorSettings: colorSettings }),
201 el(PanelBody, { title: 'Sizing & Layout', initialOpen: false },
202 el(RangeControl, { label: 'Ring Size (px)', value: a.ringSize, min: 120,max: 400, step: 10, onChange: function(v){ set({ringSize:v}); } }),
203 el(RangeControl, { label: 'Ring Thickness', value: a.ringThickness, min: 4, max: 40, step: 1, onChange: function(v){ set({ringThickness:v}); } }),
204 el(RangeControl, { label: 'Card Border Radius',value: a.cardRadius, min: 0, max: 48, step: 1, onChange: function(v){ set({cardRadius:v}); } }),
205 el(RangeControl, { label: 'Button Radius', value: a.btnRadius, min: 0, max: 100, step: 2, onChange: function(v){ set({btnRadius:v}); } }),
206 el(RangeControl, { label: 'Max Width (px)', value: a.maxWidth, min: 280,max: 700, step: 10, onChange: function(v){ set({maxWidth:v}); } }),
207 el(RangeControl, { label: 'Padding Top (px)', value: a.paddingTop, min: 0, max: 160, step: 4, onChange: function(v){ set({paddingTop:v}); } }),
208 el(RangeControl, { label: 'Padding Bottom (px)',value:a.paddingBottom, min: 0, max: 160, step: 4, onChange: function(v){ set({paddingBottom:v}); } })
209 )
210 ),
211 el('div', blockProps, el(PomodoroPreview, { attributes: a }))
212 );
213 },
214 save: function(props) {
215 var a = props.attributes;
216 return el('div', wp.blockEditor.useBlockProps.save(), el('div', { className: 'bkbg-pom-app', 'data-opts': JSON.stringify(a) }));
217 },
218 deprecated: [{
219 attributes: {
220 title: { type: 'string', default: 'Pomodoro Timer' },
221 subtitle: { type: 'string', default: 'Stay focused. Work smarter.' },
222 showTitle: { type: 'boolean', default: true },
223 showSubtitle: { type: 'boolean', default: true },
224 workMinutes: { type: 'number', default: 25 },
225 shortBreak: { type: 'number', default: 5 },
226 longBreak: { type: 'number', default: 15 },
227 cyclesBeforeLong: { type: 'number', default: 4 },
228 showCycleCount: { type: 'boolean', default: true },
229 showModeLabel: { type: 'boolean', default: true },
230 autoStartBreak: { type: 'boolean', default: false },
231 accentColor: { type: 'string', default: '#6c3fb5' },
232 breakColor: { type: 'string', default: '#10b981' },
233 longBreakColor: { type: 'string', default: '#3b82f6' },
234 ringBg: { type: 'string', default: '#e5e7eb' },
235 cardBg: { type: 'string', default: '#ffffff' },
236 TimerColor: { type: 'string', default: '#1f2937' },
237 labelColor: { type: 'string', default: '#6b7280' },
238 btnStartBg: { type: 'string', default: '#6c3fb5' },
239 btnStartColor: { type: 'string', default: '#ffffff' },
240 btnSecBg: { type: 'string', default: '#f3f4f6' },
241 btnSecColor: { type: 'string', default: '#374151' },
242 titleColor: { type: 'string', default: '#1f2937' },
243 subtitleColor: { type: 'string', default: '#6b7280' },
244 sectionBg: { type: 'string', default: '' },
245 titleSize: { type: 'number', default: 28 },
246 timerSize: { type: 'number', default: 56 },
247 ringSize: { type: 'number', default: 220 },
248 ringThickness: { type: 'number', default: 14 },
249 cardRadius: { type: 'number', default: 20 },
250 btnRadius: { type: 'number', default: 100 },
251 maxWidth: { type: 'number', default: 440 },
252 paddingTop: { type: 'number', default: 60 },
253 paddingBottom: { type: 'number', default: 60 },
254 titleFontWeight: { type: 'string', default: '700' }
255 },
256 save: function(props) {
257 var a = props.attributes;
258 return wp.element.createElement('div', wp.blockEditor.useBlockProps.save(), wp.element.createElement('div', { className: 'bkbg-pom-app', 'data-opts': JSON.stringify(a) }));
259 }
260 }]
261 });
262 }() );
263