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 / frontend.js

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

388 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 /* ── Date helpers ── */
5 function parseDate(s) {
6 var p = String(s).split('-');
7 return new Date(Date.UTC(+p[0], +p[1] - 1, +p[2]));
8 }
9
10 function addDays(d, n) {
11 var r = new Date(d.getTime());
12 r.setUTCDate(r.getUTCDate() + n);
13 return r;
14 }
15
16 function daysBetween(a, b) {
17 return Math.round((b - a) / 86400000);
18 }
19
20 var MONTHS_LONG = ['January','February','March','April','May','June','July','August','September','October','November','December'];
21 var MONTHS_SHORT = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
22
23 function getChartBounds(tasks) {
24 var earliest = null, latest = null;
25 tasks.forEach(function (t) {
26 try {
27 var s = parseDate(t.startDate), e = parseDate(t.endDate);
28 if (!earliest || s < earliest) earliest = s;
29 if (!latest || e > latest) latest = e;
30 } catch (err) {}
31 });
32 if (!earliest) {
33 var now = new Date();
34 earliest = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
35 latest = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 2, 0));
36 }
37 return { start: addDays(earliest, -3), end: addDays(latest, 3) };
38 }
39
40 function getMonthHeaders(start, end) {
41 var headers = [];
42 var cur = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));
43 while (cur <= end) {
44 var mStart = new Date(Date.UTC(cur.getUTCFullYear(), cur.getUTCMonth(), 1));
45 var mEnd = new Date(Date.UTC(cur.getUTCFullYear(), cur.getUTCMonth() + 1, 0));
46 var segStart = mStart < start ? start : mStart;
47 var segEnd = mEnd > end ? end : mEnd;
48 var days = daysBetween(segStart, segEnd) + 1;
49 headers.push({
50 label: MONTHS_SHORT[cur.getUTCMonth()] + ' ' + cur.getUTCFullYear(),
51 startDay: daysBetween(start, segStart),
52 days: days
53 });
54 cur = new Date(Date.UTC(cur.getUTCFullYear(), cur.getUTCMonth() + 1, 1));
55 }
56 return headers;
57 }
58
59 function getWeekHeaders(start, end) {
60 var headers = [];
61 /* Align to Monday */
62 var cur = new Date(start.getTime());
63 var dow = cur.getUTCDay();
64 cur = addDays(cur, dow === 0 ? -6 : 1 - dow);
65 while (cur <= end) {
66 var wEnd = addDays(cur, 6);
67 var segStart = cur < start ? start : cur;
68 var segEnd = wEnd > end ? end : wEnd;
69 var days = daysBetween(segStart, segEnd) + 1;
70 headers.push({
71 label: MONTHS_SHORT[cur.getUTCMonth()] + ' ' + cur.getUTCDate(),
72 startDay: daysBetween(start, segStart),
73 days: days
74 });
75 cur = addDays(cur, 7);
76 }
77 return headers;
78 }
79
80 function applyCSS(el, styles) {
81 Object.keys(styles).forEach(function (k) { el.style[k] = styles[k]; });
82 }
83
84 function makeEl(tag, className, styles) {
85 var el = document.createElement(tag);
86 if (className) el.className = className;
87 if (styles) applyCSS(el, styles);
88 return el;
89 }
90
91 function typoCssVarsForEl(typo, prefix, el) {
92 if (!typo || typeof typo !== 'object') return;
93 var map = {
94 family: 'font-family', weight: 'font-weight', style: 'font-style',
95 decoration: 'text-decoration', transform: 'text-transform',
96 sizeDesktop: 'font-size-d', sizeTablet: 'font-size-t', sizeMobile: 'font-size-m',
97 lineHeightDesktop: 'line-height-d', lineHeightTablet: 'line-height-t', lineHeightMobile: 'line-height-m',
98 letterSpacingDesktop: 'letter-spacing-d', letterSpacingTablet: 'letter-spacing-t', letterSpacingMobile: 'letter-spacing-m',
99 wordSpacingDesktop: 'word-spacing-d', wordSpacingTablet: 'word-spacing-t', wordSpacingMobile: 'word-spacing-m'
100 };
101 Object.keys(map).forEach(function (k) {
102 if (typo[k] !== undefined && typo[k] !== '') {
103 var v = typo[k];
104 if (['sizeDesktop','sizeTablet','sizeMobile'].indexOf(k) !== -1) {
105 v = v + (typo.sizeUnit || 'px');
106 } else if (['lineHeightDesktop','lineHeightTablet','lineHeightMobile'].indexOf(k) !== -1) {
107 v = v + (typo.lineHeightUnit || '');
108 } else if (['letterSpacingDesktop','letterSpacingTablet','letterSpacingMobile'].indexOf(k) !== -1) {
109 v = v + (typo.letterSpacingUnit || 'px');
110 } else if (['wordSpacingDesktop','wordSpacingTablet','wordSpacingMobile'].indexOf(k) !== -1) {
111 v = v + (typo.wordSpacingUnit || 'px');
112 }
113 el.style.setProperty(prefix + map[k], String(v));
114 }
115 });
116 }
117
118 function init() {
119 document.querySelectorAll('.bkbg-gant-app').forEach(function (appEl) {
120 if (appEl.dataset.rendered) return;
121 appEl.dataset.rendered = '1';
122
123 var opts;
124 try { opts = JSON.parse(appEl.dataset.opts || '{}'); } catch (e) { opts = {}; }
125
126 var o = Object.assign({
127 tasks: [],
128 view: 'month',
129 showPhases: true,
130 showMilestones: true,
131 showAssignee: false,
132 showProgress: true,
133 highlightToday: true,
134 showLegend: true,
135 showWeekends: true,
136 rowHeight: 40,
137 taskRadius: 4,
138 labelWidth: 200,
139 maxWidth: 0,
140 paddingTop: 0,
141 paddingBottom: 0,
142 bgColor: '',
143 headerBg: '#f3f4f6',
144 rowBg: '#ffffff',
145 rowAltBg: '#f9fafb',
146 weekendBg: '#f0f0f5',
147 todayLine: '#ef4444',
148 gridColor: '#e5e7eb',
149 headerColor: '#374151',
150 labelColor: '#111827',
151 phaseColor: '#6b7280',
152 milestoneColor: '#f59e0b'
153 }, opts);
154
155 var tasks = o.tasks;
156 if (!tasks || tasks.length === 0) return;
157
158 var bounds = getChartBounds(tasks);
159 var totalDays = daysBetween(bounds.start, bounds.end) + 1;
160 var headers = o.view === 'week' ? getWeekHeaders(bounds.start, bounds.end) : getMonthHeaders(bounds.start, bounds.end);
161
162 /* Group rows */
163 var rows = [];
164 if (o.showPhases) {
165 var phases = [], phaseMap = {};
166 tasks.forEach(function (t) {
167 var ph = t.phase || 'Tasks';
168 if (!phaseMap[ph]) { phaseMap[ph] = []; phases.push(ph); }
169 phaseMap[ph].push(t);
170 });
171 phases.forEach(function (ph) {
172 rows.push({ type: 'phase', label: ph });
173 phaseMap[ph].forEach(function (t) { rows.push({ type: 'task', task: t }); });
174 });
175 } else {
176 tasks.forEach(function (t) { rows.push({ type: 'task', task: t }); });
177 }
178
179 /* ── Container ── */
180 var container = makeEl('div', 'bkbg-gant-outer', {
181 paddingTop: o.paddingTop + 'px',
182 paddingBottom: o.paddingBottom + 'px',
183 background: o.bgColor || ''
184 });
185
186 typoCssVarsForEl(o.typoLabel, '--bkbg-gant-lb-', container);
187 typoCssVarsForEl(o.typoHeader, '--bkbg-gant-hd-', container);
188
189 var inner = container;
190 if (o.maxWidth > 0) {
191 inner = makeEl('div', '', { maxWidth: o.maxWidth + 'px', margin: '0 auto', overflowX: 'auto' });
192 container.appendChild(inner);
193 }
194
195 /* (Today calculations) */
196 var nowUTC = new Date();
197 var todayD = new Date(Date.UTC(nowUTC.getUTCFullYear(), nowUTC.getUTCMonth(), nowUTC.getUTCDate()));
198 var todayOffset = daysBetween(bounds.start, todayD);
199 var todayPct = (todayOffset / totalDays) * 100;
200 var showToday = o.highlightToday && todayOffset >= 0 && todayOffset <= totalDays;
201
202 /* ── Layout row ── */
203 var layout = makeEl('div', 'bkbg-gant-layout');
204 inner.appendChild(layout);
205
206 /* LABELS column */
207 var labelsCol = makeEl('div', 'bkbg-gant-labels', { width: o.labelWidth + 'px', flexShrink: 0, borderRight: '1px solid ' + o.gridColor });
208 layout.appendChild(labelsCol);
209
210 /* Header placeholder */
211 var labelHeader = makeEl('div', 'bkbg-gant-header-cell', {
212 height: '36px',
213 background: o.headerBg,
214 borderBottom: '1px solid ' + o.gridColor,
215 color: o.headerColor
216 });
217 labelHeader.textContent = 'Task';
218 labelsCol.appendChild(labelHeader);
219
220 /* Task label cells */
221 rows.forEach(function (row, ri) {
222 var cell = makeEl('div', 'bkbg-gant-label-cell ' + (row.type === 'phase' ? 'is-phase' : 'is-task'), {
223 height: o.rowHeight + 'px',
224 background: row.type === 'phase' ? o.headerBg : (ri % 2 === 0 ? o.rowBg : o.rowAltBg),
225 borderBottom: '1px solid ' + o.gridColor,
226 color: row.type === 'phase' ? o.phaseColor : o.labelColor,
227 boxSizing: 'border-box'
228 });
229 var textEl = makeEl('span', 'bkbg-gant-label-text');
230 textEl.textContent = row.type === 'phase' ? row.label : ((row.task.milestone ? '' : '') + (row.task.name || ''));
231 cell.appendChild(textEl);
232
233 if (row.type === 'task' && o.showAssignee && row.task.assignee) {
234 var asgn = makeEl('span', 'bkbg-gant-assignee');
235 asgn.textContent = row.task.assignee;
236 cell.appendChild(asgn);
237 }
238 labelsCol.appendChild(cell);
239 });
240
241 /* GRID column */
242 var gridCol = makeEl('div', 'bkbg-gant-grid', { position: 'relative', flex: '1' });
243 layout.appendChild(gridCol);
244
245 /* Column headers (months or weeks) */
246 var colHeadersRow = makeEl('div', 'bkbg-gant-col-headers', {
247 background: o.headerBg,
248 borderBottom: '1px solid ' + o.gridColor,
249 height: '36px'
250 });
251 headers.forEach(function (h, hi) {
252 var hCell = makeEl('div', 'bkbg-gant-col-header', {
253 flex: h.days,
254 color: o.headerColor,
255 borderLeft: hi > 0 ? '1px solid ' + o.gridColor : 'none',
256 height: '36px'
257 });
258 hCell.textContent = h.label;
259 colHeadersRow.appendChild(hCell);
260 });
261 gridCol.appendChild(colHeadersRow);
262
263 /* Rows area */
264 var rowsArea = makeEl('div', 'bkbg-gant-rows', { position: 'relative' });
265 gridCol.appendChild(rowsArea);
266
267 /* Vertical grid lines (one per column header segment) */
268 var colLinesEl = makeEl('div', 'bkbg-gant-col-lines', { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, pointerEvents: 'none', zIndex: 0 });
269 headers.forEach(function (h, hi) {
270 var line = makeEl('div', 'bkbg-gant-col-line', {
271 flex: h.days,
272 borderLeft: hi > 0 ? '1px solid ' + o.gridColor : 'none',
273 boxSizing: 'border-box'
274 });
275 colLinesEl.appendChild(line);
276 });
277 rowsArea.appendChild(colLinesEl);
278
279 /* Task rows */
280 rows.forEach(function (row, ri) {
281 var rowEl = makeEl('div', 'bkbg-gant-row', {
282 height: o.rowHeight + 'px',
283 background: row.type === 'phase' ? o.headerBg : (ri % 2 === 0 ? o.rowBg : o.rowAltBg),
284 borderBottom: '1px solid ' + o.gridColor,
285 position: 'relative',
286 boxSizing: 'border-box'
287 });
288
289 if (row.type === 'task') {
290 var t = row.task;
291 try {
292 var tStart = parseDate(t.startDate);
293 var tEnd = parseDate(t.endDate);
294 var leftPct = (daysBetween(bounds.start, tStart) / totalDays * 100);
295 var widthPct = Math.max(0.5, (daysBetween(tStart, tEnd) + 1) / totalDays * 100);
296 leftPct = Math.max(0, leftPct);
297 widthPct = Math.min(widthPct, 100 - leftPct);
298
299 if (t.milestone && o.showMilestones) {
300 var diamond = makeEl('div', 'bkbg-gant-milestone', {
301 left: 'calc(' + leftPct + '% - ' + Math.round(o.rowHeight * 0.3) + 'px)',
302 width: Math.round(o.rowHeight * 0.5) + 'px',
303 height: Math.round(o.rowHeight * 0.5) + 'px',
304 background: t.color || o.milestoneColor
305 });
306 diamond.title = t.name || '';
307 rowEl.appendChild(diamond);
308 } else if (!t.milestone) {
309 var barH = Math.round(o.rowHeight * 0.55);
310 var bar = makeEl('div', 'bkbg-gant-bar', {
311 left: leftPct + '%',
312 width: widthPct + '%',
313 height: barH + 'px',
314 background: t.color || '#6366f1',
315 borderRadius: o.taskRadius + 'px'
316 });
317 bar.title = (t.name || '') + (t.assignee ? '' + t.assignee : '') + (t.completed ? ' (' + t.completed + '%)' : '');
318
319 if (o.showProgress && t.completed > 0) {
320 var prog = makeEl('div', 'bkbg-gant-bar-progress', {
321 width: Math.min(100, t.completed) + '%'
322 });
323 bar.appendChild(prog);
324 }
325 rowEl.appendChild(bar);
326 }
327 } catch (err) {}
328 }
329
330 rowsArea.appendChild(rowEl);
331 });
332
333 /* Today line */
334 if (showToday) {
335 var todayEl = makeEl('div', 'bkbg-gant-today', {
336 left: todayPct + '%',
337 background: o.todayLine
338 });
339 rowsArea.appendChild(todayEl);
340 }
341
342 /* ── Legend ── */
343 if (o.showLegend) {
344 var legend = makeEl('div', 'bkbg-gant-legend');
345
346 /* Task bar */
347 var lBar = makeEl('div', 'bkbg-gant-legend-item');
348 var lBarSwatch = makeEl('div', 'bkbg-gant-legend-bar', { background: '#6366f1' });
349 var lBarLabel = makeEl('span');
350 lBarLabel.textContent = 'Task';
351 lBar.appendChild(lBarSwatch);
352 lBar.appendChild(lBarLabel);
353 legend.appendChild(lBar);
354
355 /* Milestone */
356 if (o.showMilestones) {
357 var lMs = makeEl('div', 'bkbg-gant-legend-item');
358 var lMsSwatch = makeEl('div', 'bkbg-gant-legend-diamond', { background: o.milestoneColor });
359 var lMsLabel = makeEl('span');
360 lMsLabel.textContent = 'Milestone';
361 lMs.appendChild(lMsSwatch);
362 lMs.appendChild(lMsLabel);
363 legend.appendChild(lMs);
364 }
365
366 /* Today */
367 if (showToday) {
368 var lTd = makeEl('div', 'bkbg-gant-legend-item');
369 var lTdLine = makeEl('div', 'bkbg-gant-legend-today', { background: o.todayLine });
370 var lTdLabel = makeEl('span');
371 lTdLabel.textContent = 'Today';
372 lTd.appendChild(lTdLine);
373 lTd.appendChild(lTdLabel);
374 legend.appendChild(lTd);
375 }
376
377 inner.appendChild(legend);
378 }
379
380 appEl.parentNode.insertBefore(container, appEl);
381 appEl.style.display = 'none';
382 });
383 }
384
385 if (document.readyState !== 'loading') { init(); }
386 else { document.addEventListener('DOMContentLoaded', init); }
387 })();
388