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

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

321 lines 13.1 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 var _typoKeys = {
5 family:'font-family', weight:'font-weight', style:'font-style',
6 decoration:'text-decoration', transform:'text-transform',
7 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
8 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
9 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
10 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
11 };
12 function typoCssVarsForEl(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 Object.keys(_typoKeys).forEach(function (k) {
15 var v = obj[k];
16 if (v === undefined || v === '' || v === null) return;
17 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
18 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
19 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
20 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
21 el.style.setProperty(prefix + _typoKeys[k], String(v));
22 });
23 }
24
25 var DEFAULT_PALETTE = ['#000000','#ffffff','#ef4444','#f97316','#f59e0b','#22c55e','#06b6d4','#3b82f6','#8b5cf6','#ec4899','#78716c','#94a3b8'];
26
27 function initBlock(root) {
28 var optsRaw = root.getAttribute('data-opts');
29 var opts;
30 try { opts = JSON.parse(optsRaw); } catch (e) { opts = {}; }
31
32 var cols = opts.gridCols || 32;
33 var rows = opts.gridRows || 32;
34 var CS = opts.cellSize || 16;
35 var canvasBg = opts.canvasBg || '#ffffff';
36 var gridColor = opts.gridLineColor || '#e5e7eb';
37 var showGrid = opts.showGrid !== false;
38 var accent = opts.accentColor || '#6366f1';
39 var titleColor = opts.titleColor || '#1e1b4b';
40 var palette = (opts.palette && opts.palette.length) ? opts.palette : DEFAULT_PALETTE;
41
42 if (opts.sectionBg) root.style.background = opts.sectionBg;
43
44 var titleEl = root.querySelector('.bkbg-pxa-title');
45 if (titleEl) { titleEl.style.color = titleColor; typoCssVarsForEl(titleEl, opts.titleTypo, '--bkbg-pxa-tt-'); }
46 var subEl = root.querySelector('.bkbg-pxa-subtitle');
47 if (subEl) { subEl.style.color = titleColor; typoCssVarsForEl(subEl, opts.subtitleTypo, '--bkbg-pxa-st-'); }
48
49 // ---- State ----
50 var pixels = []; // rows x cols array of color strings or null
51 for (var r = 0; r < rows; r++) { pixels.push(new Array(cols).fill(null)); }
52
53 var history = []; // undo stack (array of serialized states)
54 var tool = 'draw'; // 'draw' | 'fill' | 'erase'
55 var activeColor= palette[0];
56 var drawing = false;
57
58 // ---- Toolbar ----
59 var toolbar = document.createElement('div');
60 toolbar.className = 'bkbg-pxa-toolbar';
61 root.appendChild(toolbar);
62
63 var toolBtns = {};
64 var tools = [['draw', '✏️ Draw'], ['fill', '🪣 Fill'], ['erase', '⬜ Erase']];
65 tools.forEach(function (pair) {
66 var btn = document.createElement('button');
67 btn.className = 'bkbg-pxa-tool-btn' + (pair[0] === tool ? ' bkbg-pxa-active' : '');
68 btn.style.color = accent;
69 btn.textContent = pair[1];
70 btn.addEventListener('click', function () {
71 tool = pair[0];
72 Object.values(toolBtns).forEach(function (b) { b.classList.remove('bkbg-pxa-active'); });
73 btn.classList.add('bkbg-pxa-active');
74 });
75 toolbar.appendChild(btn);
76 toolBtns[pair[0]] = btn;
77 });
78
79 // Separator
80 var sep = document.createElement('span');
81 sep.style.cssText = 'width:1px;height:24px;background:#e5e7eb;margin:0 4px;';
82 toolbar.appendChild(sep);
83
84 // Color swatches
85 var swatchEls = [];
86 palette.forEach(function (color, i) {
87 var sw = document.createElement('div');
88 sw.className = 'bkbg-pxa-swatch' + (i === 0 ? ' bkbg-pxa-selected' : '');
89 sw.style.background = color;
90 // White swatch needs dark border
91 if (color === '#ffffff' || color === '#fff') sw.style.boxShadow = 'inset 0 0 0 1px #ccc';
92 sw.addEventListener('click', function () {
93 activeColor = color;
94 tool = 'draw';
95 Object.values(toolBtns).forEach(function (b) { b.classList.remove('bkbg-pxa-active'); });
96 toolBtns['draw'].classList.add('bkbg-pxa-active');
97 swatchEls.forEach(function (s) { s.classList.remove('bkbg-pxa-selected'); });
98 sw.classList.add('bkbg-pxa-selected');
99 });
100 toolbar.appendChild(sw);
101 swatchEls.push(sw);
102 });
103
104 // Custom color picker
105 var colorPicker = document.createElement('input');
106 colorPicker.type = 'color';
107 colorPicker.value = '#ff0000';
108 colorPicker.title = 'Custom color';
109 colorPicker.style.cssText = 'width:26px;height:26px;border:none;border-radius:5px;cursor:pointer;padding:0;background:none;';
110 colorPicker.addEventListener('input', function () {
111 activeColor = colorPicker.value;
112 tool = 'draw';
113 Object.values(toolBtns).forEach(function (b) { b.classList.remove('bkbg-pxa-active'); });
114 toolBtns['draw'].classList.add('bkbg-pxa-active');
115 swatchEls.forEach(function (s) { s.classList.remove('bkbg-pxa-selected'); });
116 });
117 toolbar.appendChild(colorPicker);
118
119 // ---- Canvas ----
120 var canvasWrap = document.createElement('div');
121 canvasWrap.className = 'bkbg-pxa-canvas-wrap';
122 root.appendChild(canvasWrap);
123
124 var canvas = document.createElement('canvas');
125 canvas.className = 'bkbg-pxa-canvas';
126 canvas.width = cols * CS;
127 canvas.height = rows * CS;
128 canvasWrap.appendChild(canvas);
129 var ctx = canvas.getContext('2d');
130
131 // Size info
132 var sizeInfo = document.createElement('div');
133 sizeInfo.className = 'bkbg-pxa-size-info';
134 sizeInfo.style.color = titleColor;
135 sizeInfo.textContent = cols + ' × ' + rows + ' pixels | Cell: ' + CS + 'px';
136 root.appendChild(sizeInfo);
137
138 // ---- Drawing ----
139 function render() {
140 ctx.fillStyle = canvasBg;
141 ctx.fillRect(0, 0, canvas.width, canvas.height);
142
143 for (var r = 0; r < rows; r++) {
144 for (var c = 0; c < cols; c++) {
145 if (pixels[r][c]) {
146 ctx.fillStyle = pixels[r][c];
147 ctx.fillRect(c * CS, r * CS, CS, CS);
148 }
149 }
150 }
151
152 if (showGrid) {
153 ctx.strokeStyle = gridColor;
154 ctx.lineWidth = 0.5;
155 for (var gc = 0; gc <= cols; gc++) {
156 ctx.beginPath();
157 ctx.moveTo(gc * CS, 0);
158 ctx.lineTo(gc * CS, rows * CS);
159 ctx.stroke();
160 }
161 for (var gr = 0; gr <= rows; gr++) {
162 ctx.beginPath();
163 ctx.moveTo(0, gr * CS);
164 ctx.lineTo(cols * CS, gr * CS);
165 ctx.stroke();
166 }
167 }
168 }
169
170 function getCell(e) {
171 var rect = canvas.getBoundingClientRect();
172 var scaleX = canvas.width / rect.width;
173 var scaleY = canvas.height / rect.height;
174 var x = (e.clientX - rect.left) * scaleX;
175 var y = (e.clientY - rect.top) * scaleY;
176 return [Math.floor(y / CS), Math.floor(x / CS)];
177 }
178
179 function getTouchCell(e) {
180 var t = e.touches[0] || e.changedTouches[0];
181 return getCell({ clientX: t.clientX, clientY: t.clientY });
182 }
183
184 function saveHistory() {
185 history.push(pixels.map(function (row) { return row.slice(); }));
186 if (history.length > 40) history.shift();
187 }
188
189 function applyTool(r, c) {
190 if (r < 0 || r >= rows || c < 0 || c >= cols) return;
191 if (tool === 'draw') {
192 if (pixels[r][c] !== activeColor) {
193 pixels[r][c] = activeColor;
194 render();
195 }
196 } else if (tool === 'erase') {
197 if (pixels[r][c]) {
198 pixels[r][c] = null;
199 render();
200 }
201 }
202 }
203
204 function floodFill(r, c, targetColor, fillColor) {
205 if (r < 0 || r >= rows || c < 0 || c >= cols) return;
206 if (pixels[r][c] !== targetColor) return;
207 if (pixels[r][c] === fillColor) return;
208 pixels[r][c] = fillColor;
209 floodFill(r+1, c, targetColor, fillColor);
210 floodFill(r-1, c, targetColor, fillColor);
211 floodFill(r, c+1, targetColor, fillColor);
212 floodFill(r, c-1, targetColor, fillColor);
213 }
214
215 canvas.addEventListener('mousedown', function (e) {
216 e.preventDefault();
217 var rc = getCell(e);
218 saveHistory();
219 if (tool === 'fill') {
220 var target = pixels[rc[0]][rc[1]];
221 if (target !== activeColor) floodFill(rc[0], rc[1], target, activeColor);
222 render(); return;
223 }
224 drawing = true;
225 applyTool(rc[0], rc[1]);
226 });
227
228 canvas.addEventListener('mousemove', function (e) {
229 if (!drawing) return;
230 var rc = getCell(e);
231 applyTool(rc[0], rc[1]);
232 });
233
234 window.addEventListener('mouseup', function () { drawing = false; });
235
236 canvas.addEventListener('touchstart', function (e) {
237 e.preventDefault();
238 var rc = getTouchCell(e);
239 saveHistory();
240 if (tool === 'fill') {
241 var target = pixels[rc[0]][rc[1]];
242 if (target !== activeColor) floodFill(rc[0], rc[1], target, activeColor);
243 render(); return;
244 }
245 drawing = true;
246 applyTool(rc[0], rc[1]);
247 }, { passive: false });
248
249 canvas.addEventListener('touchmove', function (e) {
250 e.preventDefault();
251 if (!drawing) return;
252 var rc = getTouchCell(e);
253 applyTool(rc[0], rc[1]);
254 }, { passive: false });
255
256 canvas.addEventListener('touchend', function () { drawing = false; });
257
258 // ---- Action buttons ----
259 var actions = document.createElement('div');
260 actions.className = 'bkbg-pxa-actions';
261 root.appendChild(actions);
262
263 function addBtn(label, bg, textCol, fn) {
264 var btn = document.createElement('button');
265 btn.className = 'bkbg-pxa-btn';
266 btn.textContent = label;
267 btn.style.background = bg;
268 btn.style.color = textCol;
269 btn.addEventListener('click', fn);
270 actions.appendChild(btn);
271 return btn;
272 }
273
274 addBtn('↩ Undo', '#e5e7eb', '#374151', function () {
275 if (!history.length) return;
276 pixels = history.pop();
277 render();
278 });
279
280 addBtn('🗑️ Clear', '#fef2f2', '#ef4444', function () {
281 saveHistory();
282 pixels = [];
283 for (var r = 0; r < rows; r++) pixels.push(new Array(cols).fill(null));
284 render();
285 });
286
287 if (opts.showDownload !== false) {
288 addBtn('⬇️ Download PNG', '#22c55e', '#fff', function () {
289 // Render without grid lines for download
290 var tmpCanvas = document.createElement('canvas');
291 tmpCanvas.width = cols * CS;
292 tmpCanvas.height = rows * CS;
293 var tctx = tmpCanvas.getContext('2d');
294 tctx.fillStyle = canvasBg;
295 tctx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height);
296 for (var r = 0; r < rows; r++) {
297 for (var c = 0; c < cols; c++) {
298 if (pixels[r][c]) {
299 tctx.fillStyle = pixels[r][c];
300 tctx.fillRect(c * CS, r * CS, CS, CS);
301 }
302 }
303 }
304 tmpCanvas.toBlob(function (blob) {
305 if (!blob) return;
306 var url = URL.createObjectURL(blob);
307 var a = document.createElement('a');
308 a.href = url; a.download = 'pixel-art.png'; a.click();
309 URL.revokeObjectURL(url);
310 });
311 });
312 }
313
314 render();
315 }
316
317 document.querySelectorAll('.bkbg-pxa-app').forEach(function (root) {
318 initBlock(root);
319 });
320 })();
321