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 / product-360 / index.js

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

222 lines 11.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 useBlockProps = wp.blockEditor.useBlockProps;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var MediaUpload = wp.blockEditor.MediaUpload;
10 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
11 var PanelBody = wp.components.PanelBody;
12 var RangeControl = wp.components.RangeControl;
13 var ToggleControl = wp.components.ToggleControl;
14 var TextControl = wp.components.TextControl;
15 var Button = wp.components.Button;
16 var Spinner = wp.components.Spinner;
17 var Fragment = wp.element.Fragment;
18 var SelectControl = wp.components.SelectControl;
19 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20
21 registerBlockType('blockenberg/product-360', {
22 title: __('Product 360°'),
23 icon: 'image-rotate',
24 category: 'bkbg-media',
25
26 edit: function (props) {
27 var attr = props.attributes;
28 var setAttr = props.setAttributes;
29 var bp = useBlockProps({ className: 'bkbg-p360-wrap' });
30
31 var state = useState(0);
32 var previewFrame = state[0];
33 var setPreviewFrame = state[1];
34
35 function addFrames(mediaItems) {
36 var newFrames = mediaItems.map(function (m) {
37 return { url: m.url, alt: m.alt || '' };
38 });
39 setAttr({ frames: attr.frames.concat(newFrames) });
40 }
41 function removeFrame(idx) {
42 setAttr({ frames: attr.frames.filter(function (_, i) { return i !== idx; }) });
43 }
44 function moveFrame(idx, dir) {
45 var fs = attr.frames.slice();
46 var to = idx + dir;
47 if (to < 0 || to >= fs.length) return;
48 var tmp = fs[idx]; fs[idx] = fs[to]; fs[to] = tmp;
49 setAttr({ frames: fs });
50 }
51
52 var totalFrames = attr.frames.length;
53 var currentUrl = totalFrames > 0 ? attr.frames[previewFrame % totalFrames].url : '';
54
55 var inspector = el(InspectorControls, {},
56 /* Frames */
57 el(PanelBody, { title: __('Frames (' + totalFrames + ')'), initialOpen: true },
58 el('p', { style: { color: '#64748b', margin: '0 0 8px', fontSize: 12 } },
59 __('Add image frames in rotation order. More frames = smoother spin.')),
60 el(MediaUploadCheck, {},
61 el(MediaUpload, {
62 onSelect: function (m) { addFrames(Array.isArray(m) ? m : [m]); },
63 allowedTypes: ['image'],
64 multiple: true,
65 gallery: false,
66 render: function (ref) {
67 return el(Button, { variant: 'primary', onClick: ref.open,
68 style: { width: '100%', marginBottom: 8 }
69 }, __('+ Add Frames'));
70 }
71 })
72 ),
73 attr.frames.map(function (f, idx) {
74 return el('div', { key: idx, style: {
75 display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4
76 }},
77 el('img', { src: f.url, style: {
78 width: 40, height: 30, objectFit: 'cover',
79 borderRadius: 4, flexShrink: 0
80 }}),
81 el('span', { style: { flex: 1, fontSize: 11, overflow: 'hidden',
82 textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
83 }, idx + 1),
84 el(Button, { variant: 'secondary', size: 'small',
85 onClick: function () { moveFrame(idx, -1); },
86 disabled: idx === 0
87 }, ''),
88 el(Button, { variant: 'secondary', size: 'small',
89 onClick: function () { moveFrame(idx, 1); },
90 disabled: idx === totalFrames - 1
91 }, ''),
92 el(Button, { isDestructive: true, size: 'small',
93 onClick: function () { removeFrame(idx); }
94 }, '')
95 );
96 }),
97 totalFrames > 0 && el(Button, { variant: 'secondary', isDestructive: true,
98 onClick: function () { setAttr({ frames: [] }); },
99 style: { marginTop: 8 }
100 }, __('Clear All Frames'))
101 ),
102
103 /* Viewer */
104 el(PanelBody, { title: __('Viewer'), initialOpen: false },
105 el(RangeControl, { label: __('Width (px)'), value: attr.viewerWidth,
106 min: 200, max: 1400,
107 onChange: function (v) { setAttr({ viewerWidth: v }); },
108 __nextHasNoMarginBottom: true }),
109 el(RangeControl, { label: __('Height (px)'), value: attr.viewerHeight,
110 min: 120, max: 900,
111 onChange: function (v) { setAttr({ viewerHeight: v }); },
112 __nextHasNoMarginBottom: true }),
113 el(RangeControl, { label: __('Radius (px)'), value: attr.viewerRadius,
114 min: 0, max: 40,
115 onChange: function (v) { setAttr({ viewerRadius: v }); },
116 __nextHasNoMarginBottom: true })
117 ),
118
119 /* Behaviour */
120 el(PanelBody, { title: __('Behaviour'), initialOpen: false },
121 el(RangeControl, { label: __('Drag Sensitivity'), value: attr.dragSensitivity,
122 min: 1, max: 20,
123 onChange: function (v) { setAttr({ dragSensitivity: v }); },
124 __nextHasNoMarginBottom: true }),
125 el(ToggleControl, { label: __('Auto Spin'), checked: attr.autoSpin,
126 onChange: function (v) { setAttr({ autoSpin: v }); },
127 __nextHasNoMarginBottom: true }),
128 attr.autoSpin && el(RangeControl, { label: __('Auto Speed (frames/sec)'), value: attr.autoSpeed,
129 min: 5, max: 240, step: 5,
130 onChange: function (v) { setAttr({ autoSpeed: v }); },
131 __nextHasNoMarginBottom: true }),
132 el(ToggleControl, { label: __('Show Progress Bar'), checked: attr.showProgress,
133 onChange: function (v) { setAttr({ showProgress: v }); },
134 __nextHasNoMarginBottom: true }),
135 el(ToggleControl, { label: __('Show Controls (prev/next)'), checked: attr.showControls,
136 onChange: function (v) { setAttr({ showControls: v }); },
137 __nextHasNoMarginBottom: true }),
138 el(ToggleControl, { label: __('Show Hint'), checked: attr.showHint,
139 onChange: function (v) { setAttr({ showHint: v }); },
140 __nextHasNoMarginBottom: true }),
141 attr.showHint && el(TextControl, { label: __('Hint Text'), value: attr.hintText,
142 onChange: function (v) { setAttr({ hintText: v }); },
143 __nextHasNoMarginBottom: true })
144 ),
145
146 el(PanelBody, { title: __('Typography'), initialOpen: false },
147 (function () {
148 var TC = getTypoControl();
149 if (!TC) return el('p', null, 'Loading…');
150 return el(TC, { label: 'Hint', value: attr.hintTypo, onChange: function (v) { setAttr({ hintTypo: v }); } });
151 })()
152 ),
153 el(PanelColorSettings, {
154 title: __('Colors'), initialOpen: false,
155 colorSettings: [
156 { label: __('Viewer BG'), value: attr.bgColor,
157 onChange: function (v) { setAttr({ bgColor: v || '#f1f5f9' }); } },
158 { label: __('Progress'), value: attr.progressColor,
159 onChange: function (v) { setAttr({ progressColor: v || '#6366f1' }); } },
160 { label: __('Hint Text'), value: attr.hintColor,
161 onChange: function (v) { setAttr({ hintColor: v || '#64748b' }); } },
162 { label: __('Controls BG'), value: attr.controlsBg,
163 onChange: function (v) { setAttr({ controlsBg: v || '#ffffff' }); } }
164 ]
165 })
166 );
167
168 /* Editor preview */
169 var viewerStyle = {
170 width: '100%', maxWidth: attr.viewerWidth + 'px',
171 height: attr.viewerHeight + 'px',
172 borderRadius: attr.viewerRadius + 'px',
173 background: attr.bgColor,
174 position: 'relative', overflow: 'hidden',
175 margin: '0 auto'
176 };
177
178 return el(wp.element.Fragment, {}, inspector,
179 el('div', bp,
180 el('div', { style: viewerStyle },
181 totalFrames > 0
182 ? el('img', { src: currentUrl, style: {
183 width: '100%', height: '100%', objectFit: 'contain',
184 display: 'block'
185 }})
186 : el('div', { style: {
187 display: 'flex', flexDirection: 'column',
188 alignItems: 'center', justifyContent: 'center',
189 height: '100%', color: '#94a3b8', gap: 8
190 }},
191 el('span', { style: { fontSize: 40 } }, '🔄'),
192 el('span', { style: { fontSize: 14 } }, __('Add frames in the sidebar'))
193 ),
194 totalFrames > 1 && el('div', { style: {
195 position: 'absolute', bottom: 0, left: 0, right: 0,
196 display: 'flex', gap: 8, justifyContent: 'center',
197 padding: '8px', background: 'rgba(0,0,0,0.15)'
198 }},
199 el(Button, { variant: 'secondary', size: 'small',
200 onClick: function () { setPreviewFrame((previewFrame - 1 + totalFrames) % totalFrames); }
201 }, ''),
202 el('span', { style: { color: '#fff', lineHeight: '28px', fontSize: 12 }},
203 (previewFrame + 1) + ' / ' + totalFrames),
204 el(Button, { variant: 'secondary', size: 'small',
205 onClick: function () { setPreviewFrame((previewFrame + 1) % totalFrames); }
206 }, '')
207 )
208 )
209 )
210 );
211 },
212
213 save: function (props) {
214 var attr = props.attributes;
215 var bp = useBlockProps.save();
216 return el('div', bp,
217 el('div', { className: 'bkbg-p360-app', 'data-opts': JSON.stringify(attr) })
218 );
219 }
220 });
221 }() );
222