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 / world-clock / index.js

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

285 lines 16.1 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 useState = wp.element.useState;
4 var Fragment = wp.element.Fragment;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var RangeControl = wp.components.RangeControl;
12 var SelectControl = wp.components.SelectControl;
13 var TextControl = wp.components.TextControl;
14 var ToggleControl = wp.components.ToggleControl;
15 var Button = wp.components.Button;
16
17 /* ── Typography helpers (lazy) ───────────────────────────────── */
18 var _tc, _tvf;
19 Object.defineProperty(window, '__bkwc_tc', { get: function () { return _tc || (_tc = window.bkbgTypographyControl); } });
20 Object.defineProperty(window, '__bkwc_tvf', { get: function () { return _tvf || (_tvf = window.bkbgTypoCssVars); } });
21 function getTypoControl(label, typoObj, setAttributes, attrName) {
22 var fn = window.__bkwc_tc;
23 return fn ? fn({ label: label, value: typoObj || {}, onChange: function (v) { var o = {}; o[attrName] = v; setAttributes(o); } }) : null;
24 }
25 function getTypoCssVars(a) {
26 var fn = window.__bkwc_tvf;
27 var s = {};
28 if (fn) {
29 Object.assign(s, fn(a.titleTypo || {}, '--bkwc-tt-'));
30 Object.assign(s, fn(a.labelTypo || {}, '--bkwc-lb-'));
31 Object.assign(s, fn(a.timeTypo || {}, '--bkwc-tm-'));
32 }
33 return s;
34 }
35
36 /* ── Common TZ list used for autocomplete hint ───── */
37 var COMMON_TZ = [
38 'America/New_York','America/Chicago','America/Denver','America/Los_Angeles',
39 'America/Sao_Paulo','America/Mexico_City','America/Toronto',
40 'Europe/London','Europe/Paris','Europe/Berlin','Europe/Moscow','Europe/Istanbul',
41 'Asia/Dubai','Asia/Kolkata','Asia/Shanghai','Asia/Tokyo','Asia/Singapore','Asia/Seoul',
42 'Africa/Cairo','Africa/Johannesburg','Australia/Sydney','Pacific/Auckland'
43 ];
44
45 /* ── helpers ──────────────────────────────────────── */
46 function getTimeParts(tz, fmt, showSec) {
47 try {
48 var now = new Date();
49 var opts12 = { timeZone: tz, hour: 'numeric', minute: '2-digit', second: showSec ? '2-digit' : undefined, hour12: fmt === '12h' };
50 var optsDate = { timeZone: tz, month: 'short', day: 'numeric', weekday: 'short' };
51 return {
52 time: new Intl.DateTimeFormat('en-US', opts12).format(now),
53 date: new Intl.DateTimeFormat('en-US', optsDate).format(now)
54 };
55 } catch (e) {
56 return { time: '--:--', date: '' };
57 }
58 }
59
60 function getAnglesParts(tz) {
61 try {
62 var now = new Date();
63 var parts = new Intl.DateTimeFormat('en-US', {
64 timeZone: tz, hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false
65 }).format(now).split(':').map(Number);
66 var h = parts[0] % 12, m = parts[1], s = parts[2];
67 return {
68 hourAngle: (h * 30) + (m * 0.5),
69 minAngle: m * 6 + s * 0.1,
70 secAngle: s * 6
71 };
72 } catch (e) {
73 return { hourAngle: 0, minAngle: 0, secAngle: 0 };
74 }
75 }
76
77 /* ── Analog clock face SVG ──────────────────────── */
78 function AnalogClock(props) {
79 var a = props, size = a.size || 120, r = size / 2;
80 var ang = getAnglesParts(a.tz);
81 // tick marks
82 var ticks = [];
83 for (var i = 0; i < 12; i++) {
84 var rad = (i * 30 - 90) * Math.PI / 180;
85 var rIn = r * 0.82, rOut = r * 0.92;
86 ticks.push(el('line', {
87 key: i,
88 x1: r + rIn * Math.cos(rad), y1: r + rIn * Math.sin(rad),
89 x2: r + rOut * Math.cos(rad), y2: r + rOut * Math.sin(rad),
90 stroke: a.accentColor, strokeWidth: 1.5
91 }));
92 }
93 function hand(angle, length, color, width) {
94 var rad = (angle - 90) * Math.PI / 180;
95 return el('line', {
96 x1: r, y1: r,
97 x2: r + length * Math.cos(rad),
98 y2: r + length * Math.sin(rad),
99 stroke: color, strokeWidth: width, strokeLinecap: 'round'
100 });
101 }
102 return el('svg', { width: size, height: size, viewBox: '0 0 ' + size + ' ' + size },
103 el('circle', { cx: r, cy: r, r: r - 2, fill: a.clockFace, stroke: a.accentColor, strokeWidth: 2 }),
104 ticks,
105 hand(ang.hourAngle, r * 0.5, a.handHour, 3),
106 hand(ang.minAngle, r * 0.7, a.handMin, 2),
107 a.showSeconds !== false ? hand(ang.secAngle, r * 0.75, a.handSec, 1) : null,
108 el('circle', { cx: r, cy: r, r: 3, fill: a.handSec })
109 );
110 }
111
112 /* ── Single clock card ──────────────────────────── */
113 function ClockCard(props) {
114 var a = props.attrs, zone = props.zone;
115 var parts = getTimeParts(zone.tz, a.timeFormat, a.showSeconds);
116 var cardStyle = {
117 background: a.cardBg,
118 border: '1px solid ' + a.borderColor,
119 borderRadius: a.cardRadius + 'px',
120 padding: a.cardPadding + 'px',
121 display: 'flex',
122 flexDirection: 'column',
123 alignItems: 'center',
124 gap: 8
125 };
126 return el('div', { className: 'bkbg-wc-card', style: cardStyle },
127 a.clockStyle === 'analog' ? el(AnalogClock, {
128 tz: zone.tz, size: a.analogSize,
129 clockFace: a.clockFace, accentColor: a.accentColor,
130 handHour: a.handHour, handMin: a.handMin, handSec: a.handSec,
131 showSeconds: a.showSeconds
132 }) : null,
133 el('div', { className: 'bkbg-wc-label', style: { color: a.labelColor, textAlign: 'center' } }, zone.label),
134 a.clockStyle === 'digital' ? el('div', { className: 'bkbg-wc-time', style: { color: a.timeColor } }, parts.time) : null,
135 a.showDate ? el('div', { className: 'bkbg-wc-date', style: { color: a.dateColor } }, parts.date) : null
136 );
137 }
138
139 /* ── Editor preview ───────────────────────────────── */
140 function WorldClockPreview(props) {
141 var a = props.attrs;
142 var gridStyle = {
143 display: 'grid',
144 gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)',
145 gap: a.gap + 'px',
146 maxWidth: a.maxWidth + 'px',
147 margin: '0 auto'
148 };
149 return el('div', { className: 'bkbg-wc-preview', style: { paddingTop: a.paddingTop + 'px', paddingBottom: a.paddingBottom + 'px', background: a.sectionBg || undefined } },
150 (a.showTitle || a.showSubtitle) ? el('div', { style: { textAlign: 'center', marginBottom: 32, maxWidth: a.maxWidth + 'px', margin: '0 auto 32px' } },
151 a.showTitle ? el('h3', { className: 'bkbg-wc-title', style: { color: a.titleColor, margin: '0 0 8px' } }, a.title) : null,
152 a.showSubtitle ? el('p', { className: 'bkbg-wc-subtitle', style: { color: a.subtitleColor, margin: 0 } }, a.subtitle) : null
153 ) : null,
154 el('div', { style: gridStyle },
155 a.zones.map(function (zone, i) {
156 return el(ClockCard, { key: i, zone: zone, attrs: a });
157 })
158 )
159 );
160 }
161
162 /* ── Edit component ───────────────────────────────── */
163 function WorldClockEdit(props) {
164 var a = props.attributes;
165 var set = props.setAttributes;
166 var blockProps = useBlockProps((function () {
167 var s = getTypoCssVars(a);
168 return { className: 'bkbg-wc-editor', style: s };
169 })());
170
171 function s(key) { return function (v) { var o = {}; o[key] = v; set(o); }; }
172 function n(key) { return function (v) { var o = {}; o[key] = Number(v) || 0; set(o); }; }
173 function t(key) { return function (v) { var o = {}; o[key] = v; set(o); }; }
174
175 function updateZone(i, field, val) {
176 var z = a.zones.slice();
177 z[i] = Object.assign({}, z[i]);
178 z[i][field] = val;
179 set({ zones: z });
180 }
181
182 function addZone() {
183 set({ zones: a.zones.concat([{ label: 'City', tz: 'UTC' }]) });
184 }
185
186 function removeZone(i) {
187 if (a.zones.length <= 1) return;
188 var z = a.zones.filter(function (_, idx) { return idx !== i; });
189 set({ zones: z });
190 }
191
192 return el(Fragment, null,
193 el(InspectorControls, null,
194
195 /* Title */
196 el(PanelBody, { title: __('Header', 'blockenberg'), initialOpen: true },
197 el(ToggleControl, { label: __('Show Title', 'blockenberg'), checked: a.showTitle, onChange: t('showTitle'), __nextHasNoMarginBottom: true }),
198 a.showTitle ? el(TextControl, { label: __('Title', 'blockenberg'), value: a.title, onChange: s('title') }) : null,
199 el(ToggleControl, { label: __('Show Subtitle', 'blockenberg'), checked: a.showSubtitle, onChange: t('showSubtitle'), __nextHasNoMarginBottom: true }),
200 a.showSubtitle ? el(TextControl, { label: __('Subtitle', 'blockenberg'), value: a.subtitle, onChange: s('subtitle') }) : null
201 ),
202
203 /* Zones */
204 el(PanelBody, { title: __('Timezone Zones', 'blockenberg'), initialOpen: true },
205 a.zones.map(function (zone, i) {
206 return el('div', { key: i, style: { marginBottom: 16, background: '#f9f9f9', borderRadius: 8, padding: '8px 12px' } },
207 el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 } },
208 el('strong', null, 'Zone ' + (i + 1)),
209 el(Button, { isDestructive: true, isSmall: true, onClick: function () { removeZone(i); } }, '')
210 ),
211 el(TextControl, { label: __('Label', 'blockenberg'), value: zone.label, onChange: function (v) { updateZone(i, 'label', v); } }),
212 el(TextControl, { label: __('IANA Timezone', 'blockenberg'), value: zone.tz, onChange: function (v) { updateZone(i, 'tz', v); }, help: 'e.g. America/New_York, Europe/London' })
213 );
214 }),
215 el(Button, { isPrimary: true, onClick: addZone, style: { marginTop: 8 } }, __('+ Add Zone', 'blockenberg'))
216 ),
217
218 /* Clock Settings */
219 el(PanelBody, { title: __('Clock Settings', 'blockenberg'), initialOpen: false },
220 el(SelectControl, { label: __('Clock Style', 'blockenberg'), value: a.clockStyle, options: [{ label: 'Digital', value: 'digital' }, { label: 'Analog', value: 'analog' }], onChange: s('clockStyle') }),
221 el(SelectControl, { label: __('Time Format', 'blockenberg'), value: a.timeFormat, options: [{ label: '12-hour (AM/PM)', value: '12h' }, { label: '24-hour', value: '24h' }], onChange: s('timeFormat') }),
222 el(ToggleControl, { label: __('Show Date', 'blockenberg'), checked: a.showDate, onChange: t('showDate'), __nextHasNoMarginBottom: true }),
223 el(ToggleControl, { label: __('Show Seconds', 'blockenberg'), checked: a.showSeconds, onChange: t('showSeconds'), __nextHasNoMarginBottom: true })
224 ),
225
226 /* Colors */
227
228 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
229 getTypoControl( 'Title', a.titleTypo, set, 'titleTypo' ),
230 getTypoControl( 'Label', a.labelTypo, set, 'labelTypo' ),
231 getTypoControl( 'Time', a.timeTypo, set, 'timeTypo' )
232 ),
233 el(PanelColorSettings, {
234 title: __('Colors', 'blockenberg'),
235 initialOpen: false,
236 colorSettings: [
237 { value: a.accentColor, onChange: s('accentColor'), label: __('Accent', 'blockenberg') },
238 { value: a.cardBg, onChange: s('cardBg'), label: __('Card Background', 'blockenberg') },
239 { value: a.borderColor, onChange: s('borderColor'), label: __('Card Border', 'blockenberg') },
240 { value: a.timeColor, onChange: s('timeColor'), label: __('Time Color', 'blockenberg') },
241 { value: a.labelColor, onChange: s('labelColor'), label: __('City Label', 'blockenberg') },
242 { value: a.dateColor, onChange: s('dateColor'), label: __('Date Color', 'blockenberg') },
243 { value: a.clockFace, onChange: s('clockFace'), label: __('Clock Face', 'blockenberg') },
244 { value: a.handHour, onChange: s('handHour'), label: __('Hour Hand', 'blockenberg') },
245 { value: a.handMin, onChange: s('handMin'), label: __('Minute Hand', 'blockenberg') },
246 { value: a.handSec, onChange: s('handSec'), label: __('Second Hand', 'blockenberg') },
247 { value: a.titleColor, onChange: s('titleColor'), label: __('Title Color', 'blockenberg') },
248 { value: a.subtitleColor, onChange: s('subtitleColor'), label: __('Subtitle Color', 'blockenberg') },
249 { value: a.sectionBg, onChange: s('sectionBg'), label: __('Section Background', 'blockenberg') }
250 ]
251 }),
252
253 /* Layout & Sizing */
254 el(PanelBody, { title: __('Layout & Sizing', 'blockenberg'), initialOpen: false },
255 el(RangeControl, { label: __('Columns', 'blockenberg'), value: a.columns, onChange: n('columns'), min: 1, max: 6 }),
256 el(RangeControl, { label: __('Gap (px)', 'blockenberg'), value: a.gap, onChange: n('gap'), min: 4, max: 48 }),
257 el(RangeControl, { label: __('Card Radius (px)', 'blockenberg'), value: a.cardRadius, onChange: n('cardRadius'), min: 0, max: 32 }),
258 el(RangeControl, { label: __('Card Padding (px)', 'blockenberg'), value: a.cardPadding, onChange: n('cardPadding'), min: 8, max: 64 }),
259 el(RangeControl, { label: __('Time Size (px)', 'blockenberg'), value: a.timeSize, onChange: n('timeSize'), min: 16, max: 64 }),
260 a.clockStyle === 'analog' ? el(RangeControl, { label: __('Analog Clock Size (px)', 'blockenberg'), value: a.analogSize, onChange: n('analogSize'), min: 60, max: 240 }) : null,
261 el(RangeControl, { label: __('Max Width (px)', 'blockenberg'), value: a.maxWidth, onChange: n('maxWidth'), min: 400, max: 1400, step: 20 }),
262 el(RangeControl, { label: __('Padding Top (px)', 'blockenberg'), value: a.paddingTop, onChange: n('paddingTop'), min: 0, max: 160 }),
263 el(RangeControl, { label: __('Padding Bottom (px)', 'blockenberg'), value: a.paddingBottom, onChange: n('paddingBottom'), min: 0, max: 160 })
264 )
265 ),
266
267 el('div', blockProps,
268 el(WorldClockPreview, { attrs: a })
269 )
270 );
271 }
272
273 registerBlockType('blockenberg/world-clock', {
274 edit: WorldClockEdit,
275 save: function (props) {
276 var a = props.attributes;
277 return el('div', wp.blockEditor.useBlockProps.save({
278 className: 'bkbg-wc-app',
279 style: getTypoCssVars(a),
280 'data-opts': JSON.stringify(a)
281 }));
282 }
283 });
284 }() );
285