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 / gantt-chart / index.js

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

435 lines 26.0 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 Fragment = wp.element.Fragment;
4 var useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
10 var PanelBody = wp.components.PanelBody;
11 var ToggleControl = wp.components.ToggleControl;
12 var RangeControl = wp.components.RangeControl;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var Button = wp.components.Button;
16
17 var _gantTC, _gantTV;
18 function _tc() { return _gantTC || (_gantTC = window.bkbgTypographyControl); }
19 function _tv() { return _gantTV || (_gantTV = window.bkbgTypoCssVars); }
20
21 /* ── Date helpers ── */
22 function parseDate(s) {
23 var p = s.split('-');
24 return new Date(Date.UTC(+p[0], +p[1] - 1, +p[2]));
25 }
26
27 function addDays(d, n) {
28 var r = new Date(d.getTime());
29 r.setUTCDate(r.getUTCDate() + n);
30 return r;
31 }
32
33 function daysBetween(a, b) {
34 return Math.round((b - a) / 86400000);
35 }
36
37 function toDateStr(d) {
38 return d.getUTCFullYear() + '-' + pad2(d.getUTCMonth() + 1) + '-' + pad2(d.getUTCDate());
39 }
40
41 function pad2(n) { return (n < 10 ? '0' : '') + n; }
42
43 var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
44
45 function getChartBounds(tasks) {
46 if (!tasks || tasks.length === 0) {
47 var now = new Date();
48 var s = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
49 var e = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 3, 0));
50 return { start: s, end: e };
51 }
52 var earliest = null, latest = null;
53 tasks.forEach(function (t) {
54 try {
55 var s = parseDate(t.startDate), e = parseDate(t.endDate);
56 if (!earliest || s < earliest) earliest = s;
57 if (!latest || e > latest) latest = e;
58 } catch (err) {}
59 });
60 if (!earliest) {
61 earliest = new Date();
62 latest = addDays(earliest, 60);
63 }
64 /* Pad 3 days on each side */
65 var chartStart = addDays(earliest, -3);
66 var chartEnd = addDays(latest, 3);
67 return { start: chartStart, end: chartEnd };
68 }
69
70 function getMonthHeaders(start, end) {
71 var headers = [];
72 var cur = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));
73 var chartStart = start;
74 while (cur <= end) {
75 var monthStart = new Date(Date.UTC(cur.getUTCFullYear(), cur.getUTCMonth(), 1));
76 var monthEnd = new Date(Date.UTC(cur.getUTCFullYear(), cur.getUTCMonth() + 1, 0));
77 var segStart = monthStart < chartStart ? chartStart : monthStart;
78 var segEnd = monthEnd > end ? end : monthEnd;
79 headers.push({
80 label: MONTHS[cur.getUTCMonth()] + ' ' + cur.getUTCFullYear(),
81 startDay: daysBetween(chartStart, segStart),
82 days: daysBetween(segStart, segEnd) + 1
83 });
84 cur = new Date(Date.UTC(cur.getUTCFullYear(), cur.getUTCMonth() + 1, 1));
85 }
86 return headers;
87 }
88
89 function getTaskPosition(task, chartStart, totalDays) {
90 try {
91 var s = parseDate(task.startDate);
92 var e = parseDate(task.endDate);
93 var left = daysBetween(chartStart, s) / totalDays * 100;
94 var width = Math.max(1, (daysBetween(s, e) + 1) / totalDays * 100);
95 return { left: Math.max(0, left), width: Math.min(width, 100 - Math.max(0, left)) };
96 } catch (err) {
97 return { left: 0, width: 5 };
98 }
99 }
100
101 /* ── GanttPreview ── */
102 function GanttPreview(props) {
103 var a = props.attributes;
104 var tasks = a.tasks || [];
105 var bounds = getChartBounds(tasks);
106 var totalDays = daysBetween(bounds.start, bounds.end) + 1;
107 var headers = getMonthHeaders(bounds.start, bounds.end);
108 var labelW = a.labelWidth || 200;
109 var rowH = a.rowHeight || 40;
110 var today = new Date();
111 var todayLeft = daysBetween(bounds.start, new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()))) / totalDays * 100;
112 var showToday = a.highlightToday && todayLeft >= 0 && todayLeft <= 100;
113
114 /* Group by phase */
115 var rows = [];
116 if (a.showPhases) {
117 var phases = [];
118 var phaseMap = {};
119 tasks.forEach(function (t) {
120 var ph = t.phase || 'Tasks';
121 if (!phaseMap[ph]) { phaseMap[ph] = []; phases.push(ph); }
122 phaseMap[ph].push(t);
123 });
124 phases.forEach(function (ph) {
125 rows.push({ type: 'phase', label: ph });
126 phaseMap[ph].forEach(function (t) { rows.push({ type: 'task', task: t }); });
127 });
128 } else {
129 tasks.forEach(function (t) { rows.push({ type: 'task', task: t }); });
130 }
131
132 var previewStyle = Object.assign({
133 overflowX: 'auto',
134 background: a.bgColor || '',
135 paddingTop: (a.paddingTop || 0) + 'px',
136 paddingBottom: (a.paddingBottom || 0) + 'px'
137 }, _tv()(a.typoLabel, '--bkbg-gant-lb-'), _tv()(a.typoHeader, '--bkbg-gant-hd-'));
138
139 return el('div', { className: 'bkbg-gant-outer', style: previewStyle },
140 el('div', { style: { display: 'flex', minWidth: labelW + 400 + 'px' } },
141 /* Label column */
142 el('div', { style: { width: labelW + 'px', flexShrink: 0, borderRight: '1px solid ' + (a.gridColor || '#e5e7eb') } },
143 /* Header space */
144 el('div', { style: { height: '36px', background: a.headerBg || '#f3f4f6', borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb') } }),
145 /* Task labels */
146 rows.map(function (row, ri) {
147 if (row.type === 'phase') {
148 return el('div', { key: ri, className: 'bkbg-gant-label-cell is-phase', style: { height: rowH + 'px', display: 'flex', alignItems: 'center', paddingLeft: '8px', paddingRight: '8px', background: a.headerBg || '#f3f4f6', borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb'), color: a.phaseColor || '#6b7280', boxSizing: 'border-box' } }, row.label);
149 }
150 var t = row.task;
151 return el('div', { key: ri, className: 'bkbg-gant-label-cell is-task', style: { height: rowH + 'px', display: 'flex', alignItems: 'center', paddingLeft: t.milestone ? '8px' : '16px', paddingRight: '8px', color: a.labelColor || '#111827', borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb'), boxSizing: 'border-box', background: ri % 2 === 0 ? (a.rowBg || '#fff') : (a.rowAltBg || '#f9fafb') } },
152 el('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 } }, (t.milestone ? '' : '') + (t.name || '')),
153 a.showAssignee && t.assignee && el('span', { style: { fontSize: '10px', color: '#9ca3af', marginLeft: '6px', flexShrink: 0 } }, t.assignee)
154 );
155 })
156 ),
157
158 /* Chart area */
159 el('div', { style: { flex: 1, position: 'relative', overflow: 'hidden' } },
160 /* Month headers */
161 el('div', { style: { display: 'flex', height: '36px', background: a.headerBg || '#f3f4f6', borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb'), position: 'relative' } },
162 headers.map(function (h, hi) {
163 return el('div', {
164 key: hi,
165 className: 'bkbg-gant-col-header',
166 style: {
167 flex: h.days,
168 display: 'flex', alignItems: 'center', justifyContent: 'center',
169 color: a.headerColor || '#374151',
170 borderLeft: hi > 0 ? '1px solid ' + (a.gridColor || '#e5e7eb') : 'none',
171 overflow: 'hidden', whiteSpace: 'nowrap', paddingLeft: '4px'
172 }
173 }, h.label);
174 })
175 ),
176
177 /* Task rows (grid + bars) */
178 el('div', { style: { position: 'relative' } },
179 rows.map(function (row, ri) {
180 var isPhase = row.type === 'phase';
181 var bg = isPhase ? (a.headerBg || '#f3f4f6') : (ri % 2 === 0 ? (a.rowBg || '#fff') : (a.rowAltBg || '#f9fafb'));
182
183 if (isPhase) {
184 return el('div', { key: ri, style: { height: rowH + 'px', background: bg, borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb'), position: 'relative' } });
185 }
186
187 var t = row.task;
188 var pos = getTaskPosition(t, bounds.start, totalDays);
189
190 if (t.milestone) {
191 return el('div', { key: ri, style: { height: rowH + 'px', background: bg, borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb'), position: 'relative' } },
192 el('div', {
193 style: {
194 position: 'absolute',
195 left: 'calc(' + pos.left + '% - 8px)',
196 top: '50%', transform: 'translateY(-50%) rotate(45deg)',
197 width: '14px', height: '14px',
198 background: t.color || (a.milestoneColor || '#f59e0b'),
199 zIndex: 1
200 }
201 })
202 );
203 }
204
205 return el('div', { key: ri, style: { height: rowH + 'px', background: bg, borderBottom: '1px solid ' + (a.gridColor || '#e5e7eb'), position: 'relative', display: 'flex', alignItems: 'center' } },
206 el('div', {
207 style: {
208 position: 'absolute',
209 left: pos.left + '%',
210 width: pos.width + '%',
211 height: rowH * 0.55 + 'px',
212 background: t.color || '#6366f1',
213 borderRadius: (a.taskRadius || 4) + 'px',
214 overflow: 'hidden',
215 zIndex: 1
216 }
217 },
218 /* Progress fill */
219 a.showProgress && t.completed > 0 && el('div', {
220 style: {
221 position: 'absolute', left: 0, top: 0, bottom: 0,
222 width: t.completed + '%',
223 background: 'rgba(0,0,0,0.2)'
224 }
225 })
226 )
227 );
228 }),
229
230 /* Today line */
231 showToday && el('div', {
232 style: {
233 position: 'absolute',
234 top: 0, bottom: 0,
235 left: todayLeft + '%',
236 width: '2px',
237 background: a.todayLine || '#ef4444',
238 zIndex: 2,
239 pointerEvents: 'none'
240 }
241 })
242 )
243 )
244 ),
245
246 /* Legend */
247 a.showLegend && el('div', { style: { marginTop: '10px', display: 'flex', flexWrap: 'wrap', gap: '10px', fontSize: '11px', paddingLeft: '4px' } },
248 el('span', { style: { display: 'flex', alignItems: 'center', gap: '4px' } },
249 el('div', { style: { width: '24px', height: '8px', background: '#6366f1', borderRadius: '4px' } }),
250 'Task bar'
251 ),
252 el('span', { style: { display: 'flex', alignItems: 'center', gap: '4px' } },
253 el('div', { style: { width: '10px', height: '10px', background: a.milestoneColor || '#f59e0b', transform: 'rotate(45deg)' } }),
254 'Milestone'
255 ),
256 a.highlightToday && el('span', { style: { display: 'flex', alignItems: 'center', gap: '4px' } },
257 el('div', { style: { width: '2px', height: '14px', background: a.todayLine || '#ef4444' } }),
258 'Today'
259 )
260 )
261 );
262 }
263
264 function updateItem(arr, idx, field, val) {
265 return arr.map(function (item, i) {
266 if (i !== idx) return item;
267 var p = {}; p[field] = val;
268 return Object.assign({}, item, p);
269 });
270 }
271
272 function moveItem(arr, from, to) {
273 var a = arr.slice();
274 var item = a.splice(from, 1)[0];
275 a.splice(to, 0, item);
276 return a;
277 }
278
279 /* ── TaskEditor ── */
280 function TaskEditor(props) {
281 var tasks = props.tasks;
282 var onChange = props.onChange;
283 var activeIdx = props.activeIdx;
284 var setActiveIdx = props.setActiveIdx;
285
286 function addTask() {
287 var today = new Date();
288 var start = today.getUTCFullYear() + '-' + pad2(today.getUTCMonth() + 1) + '-' + pad2(today.getUTCDate());
289 var endD = addDays(today, 7);
290 var end = endD.getUTCFullYear() + '-' + pad2(endD.getUTCMonth() + 1) + '-' + pad2(endD.getUTCDate());
291 onChange(tasks.concat([{ id: 't' + Date.now(), name: 'New Task', phase: 'Phase 1', startDate: start, endDate: end, color: '#6366f1', milestone: false, completed: 0, assignee: '' }]));
292 setActiveIdx(tasks.length);
293 }
294
295 return el(Fragment, null,
296 tasks.map(function (t, idx) {
297 var isOpen = idx === activeIdx;
298 return el('div', {
299 key: idx,
300 style: { border: '1px solid ' + (isOpen ? '#6366f1' : '#e5e7eb'), borderRadius: '6px', marginBottom: '4px', overflow: 'hidden' }
301 },
302 el('div', {
303 style: { display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 8px', background: isOpen ? '#f0f0ff' : '#f9fafb', cursor: 'pointer' },
304 onClick: function () { setActiveIdx(isOpen ? -1 : idx); }
305 },
306 el('div', { style: { width: '10px', height: '10px', borderRadius: t.milestone ? '0' : '3px', background: t.color || '#6366f1', flexShrink: 0, transform: t.milestone ? 'rotate(45deg)' : 'none' } }),
307 el('span', { style: { flex: 1, fontSize: '11px', fontWeight: '600', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.name || 'Task ' + (idx + 1)),
308 el('span', { style: { fontSize: '10px', color: '#9ca3af', flexShrink: 0 } }, t.phase),
309 el(Button, { icon: 'arrow-up-alt2', isSmall: true, disabled: idx === 0, onClick: function (e) { e.stopPropagation(); onChange(moveItem(tasks, idx, idx - 1)); setActiveIdx(idx - 1); } }),
310 el(Button, { icon: 'arrow-down-alt2', isSmall: true, disabled: idx === tasks.length - 1, onClick: function (e) { e.stopPropagation(); onChange(moveItem(tasks, idx, idx + 1)); setActiveIdx(idx + 1); } }),
311 el(Button, { icon: 'no-alt', isSmall: true, isDestructive: true, onClick: function (e) { e.stopPropagation(); var a = tasks.slice(); a.splice(idx, 1); onChange(a); setActiveIdx(-1); } })
312 ),
313 isOpen && el('div', { style: { padding: '10px', display: 'flex', flexDirection: 'column', gap: '8px' } },
314 el(TextControl, { label: __('Task Name', 'blockenberg'), value: t.name || '', onChange: function (v) { onChange(updateItem(tasks, idx, 'name', v)); }, __nextHasNoMarginBottom: true }),
315 el(TextControl, { label: __('Phase / Group', 'blockenberg'), value: t.phase || '', onChange: function (v) { onChange(updateItem(tasks, idx, 'phase', v)); }, __nextHasNoMarginBottom: true }),
316 el('div', { style: { display: 'flex', gap: '8px' } },
317 el('div', { style: { flex: 1 } },
318 el(TextControl, { label: __('Start Date', 'blockenberg'), value: t.startDate || '', placeholder: 'YYYY-MM-DD', onChange: function (v) { onChange(updateItem(tasks, idx, 'startDate', v)); }, __nextHasNoMarginBottom: true })
319 ),
320 el('div', { style: { flex: 1 } },
321 el(TextControl, { label: __('End Date', 'blockenberg'), value: t.endDate || '', placeholder: 'YYYY-MM-DD', onChange: function (v) { onChange(updateItem(tasks, idx, 'endDate', v)); }, __nextHasNoMarginBottom: true })
322 )
323 ),
324 el('div', { style: { display: 'flex', gap: '8px', alignItems: 'flex-end' } },
325 el('div', { style: { flex: 1 } },
326 el('label', { style: { fontSize: '11px', fontWeight: '600', display: 'block', marginBottom: '4px' } }, __('Color', 'blockenberg')),
327 el('input', { type: 'color', value: t.color || '#6366f1', onChange: function (e) { onChange(updateItem(tasks, idx, 'color', e.target.value)); }, style: { width: '100%', height: '32px', border: 'none', borderRadius: '4px', cursor: 'pointer' } })
328 ),
329 el('div', { style: { flex: 1 } },
330 el(RangeControl, { label: __('Progress %', 'blockenberg'), value: t.completed || 0, min: 0, max: 100, onChange: function (v) { onChange(updateItem(tasks, idx, 'completed', v)); }, __nextHasNoMarginBottom: true })
331 )
332 ),
333 el(ToggleControl, { label: __('Milestone (diamond marker)', 'blockenberg'), checked: !!t.milestone, onChange: function (v) { onChange(updateItem(tasks, idx, 'milestone', v)); }, __nextHasNoMarginBottom: true }),
334 el(TextControl, { label: __('Assignee (optional)', 'blockenberg'), value: t.assignee || '', onChange: function (v) { onChange(updateItem(tasks, idx, 'assignee', v)); }, __nextHasNoMarginBottom: true })
335 )
336 );
337 }),
338 el(Button, { variant: 'secondary', onClick: addTask, style: { marginTop: '6px', width: '100%', justifyContent: 'center' } }, __('+ Add Task', 'blockenberg'))
339 );
340 }
341
342 registerBlockType('blockenberg/gantt-chart', {
343 edit: function (props) {
344 var a = props.attributes;
345 var set = props.setAttributes;
346 var taskIdxState = useState(-1);
347 var taskIdx = taskIdxState[0];
348 var setTaskIdx = taskIdxState[1];
349
350 var blockProps = useBlockProps({ style: { overflowX: 'auto' } });
351
352 var inspector = el(InspectorControls, null,
353 el(PanelBody, { title: __('Tasks', 'blockenberg'), initialOpen: true },
354 el(TaskEditor, {
355 tasks: a.tasks,
356 onChange: function (v) { set({ tasks: v }); },
357 activeIdx: taskIdx,
358 setActiveIdx: setTaskIdx
359 })
360 ),
361
362 el(PanelBody, { title: __('Display Options', 'blockenberg'), initialOpen: false },
363 el(SelectControl, {
364 label: __('View', 'blockenberg'),
365 value: a.view,
366 options: [{ value: 'month', label: 'Monthly' }, { value: 'week', label: 'Weekly' }],
367 onChange: function (v) { set({ view: v }); },
368 __nextHasNoMarginBottom: true
369 }),
370 el(ToggleControl, { label: __('Show Phase Groups', 'blockenberg'), checked: a.showPhases, onChange: function (v) { set({ showPhases: v }); }, __nextHasNoMarginBottom: true }),
371 el(ToggleControl, { label: __('Show Milestones', 'blockenberg'), checked: a.showMilestones, onChange: function (v) { set({ showMilestones: v }); }, __nextHasNoMarginBottom: true }),
372 el(ToggleControl, { label: __('Show Progress Fill', 'blockenberg'), checked: a.showProgress, onChange: function (v) { set({ showProgress: v }); }, __nextHasNoMarginBottom: true }),
373 el(ToggleControl, { label: __('Show Assignee', 'blockenberg'), checked: a.showAssignee, onChange: function (v) { set({ showAssignee: v }); }, __nextHasNoMarginBottom: true }),
374 el(ToggleControl, { label: __('Highlight Today', 'blockenberg'), checked: a.highlightToday, onChange: function (v) { set({ highlightToday: v }); }, __nextHasNoMarginBottom: true }),
375 el(ToggleControl, { label: __('Show Legend', 'blockenberg'), checked: a.showLegend, onChange: function (v) { set({ showLegend: v }); }, __nextHasNoMarginBottom: true })
376 ),
377
378 el(PanelBody, { title: __('Dimensions', 'blockenberg'), initialOpen: false },
379 el(RangeControl, { label: __('Row Height (px)', 'blockenberg'), value: a.rowHeight, min: 28, max: 80, onChange: function (v) { set({ rowHeight: v }); }, __nextHasNoMarginBottom: true }),
380 el('div', { style: { marginTop: '8px' } },
381 el(RangeControl, { label: __('Label Column Width (px)', 'blockenberg'), value: a.labelWidth, min: 100, max: 400, step: 10, onChange: function (v) { set({ labelWidth: v }); }, __nextHasNoMarginBottom: true })
382 ),
383 el('div', { style: { marginTop: '8px' } },
384 el(RangeControl, { label: __('Task Bar Radius', 'blockenberg'), value: a.taskRadius, min: 0, max: 20, onChange: function (v) { set({ taskRadius: v }); }, __nextHasNoMarginBottom: true })
385 ),
386 el('div', { style: { marginTop: '8px' } },
387 el(RangeControl, { label: __('Max Width (0 = full)', 'blockenberg'), value: a.maxWidth, min: 0, max: 1400, step: 20, onChange: function (v) { set({ maxWidth: v }); }, __nextHasNoMarginBottom: true })
388 ),
389 el('div', { style: { marginTop: '8px' } },
390 el(RangeControl, { label: __('Padding Top', 'blockenberg'), value: a.paddingTop, min: 0, max: 120, step: 4, onChange: function (v) { set({ paddingTop: v }); }, __nextHasNoMarginBottom: true })
391 ),
392 el('div', { style: { marginTop: '8px' } },
393 el(RangeControl, { label: __('Padding Bottom', 'blockenberg'), value: a.paddingBottom, min: 0, max: 120, step: 4, onChange: function (v) { set({ paddingBottom: v }); }, __nextHasNoMarginBottom: true })
394 )
395 ),
396
397 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
398 _tc()({ label: __('Task Label', 'blockenberg'), value: a.typoLabel, onChange: function (v) { set({ typoLabel: v }); } }),
399 _tc()({ label: __('Header', 'blockenberg'), value: a.typoHeader, onChange: function (v) { set({ typoHeader: v }); } })
400 ),
401
402 el(PanelColorSettings, {
403 title: __('Colors', 'blockenberg'),
404 initialOpen: false,
405 colorSettings: [
406 { value: a.bgColor, onChange: function (v) { set({ bgColor: v }); }, label: __('Section Background', 'blockenberg') },
407 { value: a.headerBg, onChange: function (v) { set({ headerBg: v }); }, label: __('Header / Phase Background', 'blockenberg') },
408 { value: a.rowBg, onChange: function (v) { set({ rowBg: v }); }, label: __('Row Background', 'blockenberg') },
409 { value: a.rowAltBg, onChange: function (v) { set({ rowAltBg: v }); }, label: __('Alternating Row Background', 'blockenberg') },
410 { value: a.gridColor, onChange: function (v) { set({ gridColor: v }); }, label: __('Grid Lines', 'blockenberg') },
411 { value: a.headerColor, onChange: function (v) { set({ headerColor: v }); }, label: __('Header Text', 'blockenberg') },
412 { value: a.labelColor, onChange: function (v) { set({ labelColor: v }); }, label: __('Task Label Color', 'blockenberg') },
413 { value: a.phaseColor, onChange: function (v) { set({ phaseColor: v }); }, label: __('Phase Label Color', 'blockenberg') },
414 { value: a.todayLine, onChange: function (v) { set({ todayLine: v }); }, label: __('Today Indicator', 'blockenberg') },
415 { value: a.milestoneColor, onChange: function (v) { set({ milestoneColor: v }); }, label: __('Milestone Color', 'blockenberg') }
416 ]
417 })
418 );
419
420 return el(Fragment, null,
421 inspector,
422 el('div', blockProps,
423 el(GanttPreview, { attributes: a })
424 )
425 );
426 },
427
428 save: function (props) {
429 return el('div', useBlockProps.save(),
430 el('div', { className: 'bkbg-gant-app', 'data-opts': JSON.stringify(props.attributes) })
431 );
432 }
433 });
434 }() );
435