PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 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 / week-schedule / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at blocks/week-schedule/index.js

410 lines 20.7 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 __ = wp.i18n.__;
4 var useState = wp.element.useState;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var useBlockProps = wp.blockEditor.useBlockProps;
8 var PanelBody = wp.components.PanelBody;
9 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
10 var ToggleControl = wp.components.ToggleControl;
11 var RangeControl = wp.components.RangeControl;
12 var SelectControl = wp.components.SelectControl;
13 var TextControl = wp.components.TextControl;
14 var Button = wp.components.Button;
15
16 // Resolve the shared components lazily without defining globals for each block.
17 function _tc(label, key, attrs, setA) {
18 var Control = window.bkbgTypographyControl;
19 return Control ? el(Control, {
20 label: label,
21 value: attrs[key] || {},
22 onChange: function (value) { var update = {}; update[key] = value; setA(update); }
23 }) : null;
24 }
25 function _tvf(vars, key, attrs, prefix) {
26 var cssVars = window.bkbgTypoCssVars;
27 if (cssVars) Object.assign(vars, cssVars(attrs[key] || {}, prefix));
28 return vars;
29 }
30 function getTypoControl(label, key, attrs, setA) { return _tc(label, key, attrs, setA); }
31 function getTypoCssVars(attrs) {
32 var v = {};
33 _tvf(v, 'titleTypo', attrs, '--bkwscd-tt-');
34 _tvf(v, 'eventTypo', attrs, '--bkwscd-ev-');
35 return v;
36 }
37
38 var DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
39 var EVENT_COLORS = ['#6366f1','#ef4444','#0ea5e9','#22c55e','#f59e0b','#8b5cf6','#ec4899','#14b8a6','#f97316','#64748b'];
40
41 function fmt(h, m, format) {
42 m = m || 0;
43 if (format === '24h') {
44 return ('0' + h).slice(-2) + ':' + ('0' + m).slice(-2);
45 }
46 var ampm = h >= 12 ? 'PM' : 'AM';
47 var h12 = h % 12 || 12;
48 return h12 + (m ? ':' + ('0' + m).slice(-2) : '') + ' ' + ampm;
49 }
50
51 function updateEvent(events, idx, field, val) {
52 return events.map(function (e, i) {
53 if (i !== idx) return e;
54 var upd = {}; upd[field] = val;
55 return Object.assign({}, e, upd);
56 });
57 }
58
59 function ScheduleGrid(props) {
60 var a = props.a;
61 var onEventClick = props.onEventClick;
62
63 var numDays = a.showWeekend ? 7 : 5;
64 var totalHours = a.endHour - a.startHour;
65 var gridHeight = totalHours * a.cellHeight;
66
67 var colWidth = 'calc((100% - 48px) / ' + numDays + ')';
68
69 // Time labels column
70 var timeLabels = [];
71 for (var h = a.startHour; h <= a.endHour; h++) {
72 var topPos = (h - a.startHour) * a.cellHeight;
73 timeLabels.push(el('div', {
74 key: h,
75 className: 'bkbg-wscd-time-label',
76 style: { top: topPos + 'px', color: a.timeColor }
77 }, a.showTime ? fmt(h, 0, a.timeFormat) : ''));
78 }
79
80 // Day columns
81 var dayCols = [];
82 for (var d = 0; d < numDays; d++) {
83 var dayD = d;
84 // Hour grid lines
85 var hourLines = [];
86 for (var dh = 0; dh < totalHours; dh++) {
87 hourLines.push(el('div', {
88 key: dh,
89 className: 'bkbg-wscd-hour-line',
90 style: {
91 top: (dh * a.cellHeight) + 'px',
92 borderTopColor: a.gridColor
93 }
94 }));
95 }
96
97 // Events for this day
98 var dayEvents = a.events.filter(function (ev) { return ev.day === dayD; });
99 var eventEls = dayEvents.map(function (ev, ei) {
100 var top = ((ev.startHour + (ev.startMinute || 0) / 60) - a.startHour) * a.cellHeight;
101 var height = (ev.duration || 1) * a.cellHeight - 4;
102 if (top < 0 || top >= gridHeight) return null;
103
104 return el('div', {
105 key: ei,
106 className: 'bkbg-wscd-event',
107 style: {
108 top: top + 'px',
109 height: height + 'px',
110 background: ev.color || '#6366f1',
111 borderRadius: a.eventRadius + 'px'
112 },
113 onClick: function () { if (onEventClick) onEventClick(ev); }
114 },
115 el('div', { className: 'bkbg-wscd-event-title' }, ev.title),
116 a.showInstructor && ev.instructor && el('div', { className: 'bkbg-wscd-event-sub' }, ev.instructor),
117 a.showLocation && ev.location && el('div', { className: 'bkbg-wscd-event-sub' }, '📍 ' + ev.location)
118 );
119 }).filter(Boolean);
120
121 dayCols.push(el('div', {
122 key: d,
123 className: 'bkbg-wscd-day-col',
124 style: {
125 height: gridHeight + 'px',
126 background: a.bgColor,
127 borderColor: a.borderColor
128 }
129 }, hourLines, eventEls));
130 }
131
132 // Day headers
133 var dayHeaders = [];
134 for (var dh2 = 0; dh2 < numDays; dh2++) {
135 dayHeaders.push(el('div', {
136 key: dh2,
137 className: 'bkbg-wscd-day-header',
138 style: { background: a.headerBg, color: a.headerColor, borderColor: a.borderColor }
139 }, a.dayLabels[dh2] || DAY_NAMES[dh2]));
140 }
141
142 return el('div', {
143 className: 'bkbg-wscd-wrap',
144 style: {
145 '--bkbg-wscd-cols': numDays,
146 '--bkbg-wscd-cell-h': a.cellHeight + 'px',
147 '--bkbg-wscd-radius': a.borderRadius + 'px',
148 '--bkbg-wscd-border': a.borderColor,
149 background: a.bgColor
150 }
151 },
152 a.showTitle && a.title && el('div', { className: 'bkbg-wscd-title', style: { color: a.headerColor } }, a.title),
153 el('div', { className: 'bkbg-wscd-grid' },
154 el('div', { className: 'bkbg-wscd-header-row' },
155 el('div', { className: 'bkbg-wscd-time-corner', style: { background: a.headerBg, borderColor: a.borderColor } }),
156 dayHeaders
157 ),
158 el('div', { className: 'bkbg-wscd-body-row' },
159 el('div', { className: 'bkbg-wscd-time-col', style: { background: a.timeBg, borderColor: a.borderColor, height: gridHeight + 'px' } }, timeLabels),
160 el('div', { className: 'bkbg-wscd-days-area', style: { height: gridHeight + 'px' } }, dayCols)
161 )
162 )
163 );
164 }
165
166 registerBlockType('blockenberg/week-schedule', {
167 title: __('Weekly Schedule', 'blockenberg'),
168 icon: 'calendar',
169 category: 'bkbg-blog',
170 description: __('Visual weekly schedule grid with colored time-slot events.', 'blockenberg'),
171
172 edit: function (props) {
173 var a = props.attributes;
174 var setAttributes = props.setAttributes;
175
176 var openState = useState(null);
177 var openIdx = openState[0]; var setOpenIdx = openState[1];
178
179 var hourOpts = [];
180 for (var h = 0; h <= 23; h++) {
181 hourOpts.push({ label: fmt(h, 0, '12h'), value: h });
182 }
183 var minOpts = [{ label: ':00', value: 0 }, { label: ':15', value: 15 }, { label: ':30', value: 30 }, { label: ':45', value: 45 }];
184 var durationOpts = [
185 { label: '30 min', value: 0.5 },
186 { label: '45 min', value: 0.75 },
187 { label: '1 hr', value: 1 },
188 { label: '1.5 hr', value: 1.5 },
189 { label: '2 hr', value: 2 },
190 { label: '2.5 hr', value: 2.5 },
191 { label: '3 hr', value: 3 }
192 ];
193 var dayOpts = DAY_NAMES.map(function (d, i) { return { label: d, value: i }; });
194 var timeFormatOpts = [{ label: '12-hour', value: '12h' }, { label: '24-hour', value: '24h' }];
195
196 function addEvent() {
197 var colorIdx = a.events.length % EVENT_COLORS.length;
198 setAttributes({ events: a.events.concat([{
199 day: 0, startHour: 9, startMinute: 0, duration: 1,
200 title: 'New Class', instructor: '', location: '', color: EVENT_COLORS[colorIdx]
201 }])});
202 }
203 function removeEvent(idx) {
204 setAttributes({ events: a.events.filter(function (_, i) { return i !== idx; }) });
205 }
206
207 var inspector = el(InspectorControls, {},
208 el(PanelBody, { title: __('Schedule Settings', 'blockenberg'), initialOpen: true },
209 el(TextControl, {
210 label: __('Title', 'blockenberg'),
211 value: a.title,
212 __nextHasNoMarginBottom: true,
213 onChange: function (v) { setAttributes({ title: v }); }
214 }),
215 el(ToggleControl, {
216 label: __('Show Title', 'blockenberg'),
217 checked: a.showTitle,
218 __nextHasNoMarginBottom: true,
219 onChange: function (v) { setAttributes({ showTitle: v }); }
220 }),
221 el(SelectControl, {
222 label: __('Start Hour', 'blockenberg'),
223 value: a.startHour,
224 options: hourOpts,
225 __nextHasNoMarginBottom: true,
226 onChange: function (v) { setAttributes({ startHour: parseInt(v, 10) }); }
227 }),
228 el(SelectControl, {
229 label: __('End Hour', 'blockenberg'),
230 value: a.endHour,
231 options: hourOpts,
232 __nextHasNoMarginBottom: true,
233 onChange: function (v) { setAttributes({ endHour: parseInt(v, 10) }); }
234 }),
235 el(RangeControl, {
236 label: __('Cell Height (px/hr)', 'blockenberg'),
237 value: a.cellHeight,
238 min: 40, max: 120,
239 __nextHasNoMarginBottom: true,
240 onChange: function (v) { setAttributes({ cellHeight: v }); }
241 }),
242 el(SelectControl, {
243 label: __('Time Format', 'blockenberg'),
244 value: a.timeFormat,
245 options: timeFormatOpts,
246 __nextHasNoMarginBottom: true,
247 onChange: function (v) { setAttributes({ timeFormat: v }); }
248 }),
249 el(ToggleControl, {
250 label: __('Show Weekend (Sat/Sun)', 'blockenberg'),
251 checked: a.showWeekend,
252 __nextHasNoMarginBottom: true,
253 onChange: function (v) { setAttributes({ showWeekend: v }); }
254 }),
255 el(ToggleControl, {
256 label: __('Show Instructor', 'blockenberg'),
257 checked: a.showInstructor,
258 __nextHasNoMarginBottom: true,
259 onChange: function (v) { setAttributes({ showInstructor: v }); }
260 }),
261 el(ToggleControl, {
262 label: __('Show Location', 'blockenberg'),
263 checked: a.showLocation,
264 __nextHasNoMarginBottom: true,
265 onChange: function (v) { setAttributes({ showLocation: v }); }
266 }),
267 el(RangeControl, {
268 label: __('Border Radius', 'blockenberg'),
269 value: a.borderRadius,
270 min: 0, max: 20,
271 __nextHasNoMarginBottom: true,
272 onChange: function (v) { setAttributes({ borderRadius: v }); }
273 }),
274 el(RangeControl, {
275 label: __('Event Radius', 'blockenberg'),
276 value: a.eventRadius,
277 min: 0, max: 12,
278 __nextHasNoMarginBottom: true,
279 onChange: function (v) { setAttributes({ eventRadius: v }); }
280 })
281 ),
282 el(PanelBody, { title: __('Events (' + a.events.length + ')', 'blockenberg'), initialOpen: false },
283 a.events.map(function (ev, i) {
284 var isOpen = openIdx === i;
285 return el('div', { key: i, className: 'bkbg-wscd-event-editor' },
286 el('div', {
287 className: 'bkbg-wscd-event-head',
288 style: { borderLeft: '4px solid ' + (ev.color || '#6366f1') },
289 onClick: function () { setOpenIdx(isOpen ? null : i); }
290 },
291 el('span', {}, ev.title || 'Event ' + (i + 1)),
292 el('span', { className: 'bkbg-wscd-ev-day' }, DAY_NAMES[ev.day] || ''),
293 el('div', { style: { marginLeft: 'auto', display: 'flex', gap: '4px' } },
294 el('span', {}, isOpen ? '▲' : '▼'),
295 el(Button, { isSmall: true, isDestructive: true, onClick: function (e) { e.stopPropagation(); removeEvent(i); } }, '✕')
296 )
297 ),
298 isOpen && el('div', { className: 'bkbg-wscd-event-fields' },
299 el(TextControl, {
300 label: __('Title', 'blockenberg'),
301 value: ev.title,
302 __nextHasNoMarginBottom: true,
303 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'title', v) }); }
304 }),
305 el(SelectControl, {
306 label: __('Day', 'blockenberg'),
307 value: ev.day,
308 options: dayOpts,
309 __nextHasNoMarginBottom: true,
310 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'day', parseInt(v, 10)) }); }
311 }),
312 el(SelectControl, {
313 label: __('Start Hour', 'blockenberg'),
314 value: ev.startHour,
315 options: hourOpts,
316 __nextHasNoMarginBottom: true,
317 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'startHour', parseInt(v, 10)) }); }
318 }),
319 el(SelectControl, {
320 label: __('Start Minute', 'blockenberg'),
321 value: ev.startMinute || 0,
322 options: minOpts,
323 __nextHasNoMarginBottom: true,
324 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'startMinute', parseInt(v, 10)) }); }
325 }),
326 el(SelectControl, {
327 label: __('Duration', 'blockenberg'),
328 value: ev.duration,
329 options: durationOpts,
330 __nextHasNoMarginBottom: true,
331 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'duration', parseFloat(v)) }); }
332 }),
333 el(TextControl, {
334 label: __('Instructor', 'blockenberg'),
335 value: ev.instructor,
336 __nextHasNoMarginBottom: true,
337 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'instructor', v) }); }
338 }),
339 el(TextControl, {
340 label: __('Location', 'blockenberg'),
341 value: ev.location,
342 __nextHasNoMarginBottom: true,
343 onChange: function (v) { setAttributes({ events: updateEvent(a.events, i, 'location', v) }); }
344 }),
345 el('div', { style: { marginBottom: '8px' } },
346 el('label', { style: { display: 'block', fontSize: '11px', fontWeight: '600', marginBottom: '4px', textTransform: 'uppercase', color: '#1e1e1e' } }, __('Color', 'blockenberg')),
347 el('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px' } },
348 EVENT_COLORS.map(function (c) {
349 return el('button', {
350 key: c,
351 style: {
352 width: '20px', height: '20px',
353 background: c,
354 border: ev.color === c ? '2px solid #000' : '2px solid transparent',
355 borderRadius: '4px',
356 cursor: 'pointer', padding: 0
357 },
358 onClick: function () { setAttributes({ events: updateEvent(a.events, i, 'color', c) }); }
359 });
360 })
361 )
362 )
363 )
364 );
365 }),
366 el(Button, {
367 variant: 'secondary',
368 style: { marginTop: '8px', width: '100%' },
369 onClick: addEvent
370 }, __('+ Add Event', 'blockenberg'))
371 ),
372
373 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
374 getTypoControl(__('Title', 'blockenberg'), 'titleTypo', a, setAttributes),
375 getTypoControl(__('Event', 'blockenberg'), 'eventTypo', a, setAttributes)
376 ),
377 el(PanelColorSettings, {
378 title: __('Colors', 'blockenberg'),
379 initialOpen: false,
380 colorSettings: [
381 { value: a.bgColor, onChange: function (c) { setAttributes({ bgColor: c || '#ffffff' }); }, label: __('Background', 'blockenberg') },
382 { value: a.headerBg, onChange: function (c) { setAttributes({ headerBg: c || '#f8fafc' }); }, label: __('Header Background', 'blockenberg') },
383 { value: a.headerColor, onChange: function (c) { setAttributes({ headerColor: c || '#1e293b' }); }, label: __('Header Text', 'blockenberg') },
384 { value: a.gridColor, onChange: function (c) { setAttributes({ gridColor: c || '#f1f5f9' }); }, label: __('Grid Lines', 'blockenberg') },
385 { value: a.borderColor, onChange: function (c) { setAttributes({ borderColor: c || '#e2e8f0' }); }, label: __('Border', 'blockenberg') },
386 { value: a.timeBg, onChange: function (c) { setAttributes({ timeBg: c || '#f8fafc' }); }, label: __('Time Column Background', 'blockenberg') },
387 { value: a.timeColor, onChange: function (c) { setAttributes({ timeColor: c || '#64748b' }); }, label: __('Time Text', 'blockenberg') }
388 ]
389 })
390 );
391
392 var blockProps = useBlockProps((function () {
393 var s = getTypoCssVars(a);
394 return { className: 'bkbg-editor-wrap', 'data-block-label': 'Weekly Schedule', style: s };
395 })());
396
397 return el('div', blockProps,
398 inspector,
399 el(ScheduleGrid, { a: a })
400 );
401 },
402
403 save: function (props) {
404 return el('div', useBlockProps.save({ style: getTypoCssVars(props.attributes) }),
405 el('div', { className: 'bkbg-wscd-app', 'data-opts': JSON.stringify(props.attributes) })
406 );
407 }
408 });
409 }() );
410