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 / gpa-calculator / index.js

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

258 lines 14.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 var _gpaTC, _gpaTV;
18 function _tc() { return _gpaTC || (_gpaTC = window.bkbgTypographyControl); }
19 function _tv() { return _gpaTV || (_gpaTV = window.bkbgTypoCssVars); }
20
21 /* ── Grade point conversions (standard 4.0 scale) ── */
22 var GRADE_POINTS = {
23 'A+': 4.0, 'A': 4.0, 'A-': 3.7,
24 'B+': 3.3, 'B': 3.0, 'B-': 2.7,
25 'C+': 2.3, 'C': 2.0, 'C-': 1.7,
26 'D+': 1.3, 'D': 1.0, 'D-': 0.7,
27 'F': 0.0
28 };
29 var GRADE_OPTIONS = Object.keys(GRADE_POINTS).map(function (g) {
30 return { label: g + ' (' + GRADE_POINTS[g].toFixed(1) + ')', value: g };
31 });
32
33 function calcGPA(courses) {
34 var totalCredits = 0, totalPoints = 0;
35 courses.forEach(function (c) {
36 var pts = GRADE_POINTS[c.grade];
37 if (pts !== undefined && c.credits > 0) {
38 totalCredits += c.credits;
39 totalPoints += pts * c.credits;
40 }
41 });
42 return {
43 gpa: totalCredits > 0 ? totalPoints / totalCredits : 0,
44 totalCredits: totalCredits,
45 totalPoints: totalPoints
46 };
47 }
48
49 function gradeClass(gpa) {
50 if (gpa >= 3.9) return 'Summa Cum Laude';
51 if (gpa >= 3.7) return 'Magna Cum Laude';
52 if (gpa >= 3.5) return 'Cum Laude';
53 if (gpa >= 3.0) return 'Good Standing';
54 if (gpa >= 2.0) return 'Satisfactory';
55 if (gpa > 0) return 'Probation Risk';
56 return '';
57 }
58
59 function gradeClassColor(gpa, accent) {
60 if (gpa >= 3.5) return '#15803d';
61 if (gpa >= 3.0) return accent;
62 if (gpa >= 2.0) return '#d97706';
63 return '#dc2626';
64 }
65
66 /* ── Editor Preview ──────────────────────────────── */
67 function GPAPreview(props) {
68 var a = props.attrs;
69 var courses = a.defaultCourses;
70 var res = calcGPA(courses);
71 var gpa = res.gpa.toFixed(2);
72
73 var wrapStyle = {
74 paddingTop: a.paddingTop + 'px',
75 paddingBottom: a.paddingBottom + 'px',
76 background: a.sectionBg || undefined
77 };
78
79 var cardStyle = {
80 background: a.cardBg,
81 borderRadius: a.cardRadius + 'px',
82 padding: 32,
83 maxWidth: a.maxWidth + 'px',
84 margin: '0 auto',
85 boxShadow: '0 4px 24px rgba(0,0,0,0.06)'
86 };
87
88 /* result box */
89 var resultBox = el('div', {
90 style: {
91 background: a.resultBg, border: '2px solid ' + a.resultBorder,
92 borderRadius: a.cardRadius + 'px', padding: '24px 32px',
93 textAlign: 'center', marginBottom: 24
94 }
95 },
96 el('div', { className: 'bkbg-gpa-number', style: { color: a.gpaColor } }, gpa),
97 el('div', { style: { color: a.subtitleColor, marginTop: 4 } }, 'GPA (4.0 scale)'),
98 a.showGradeClass ? el('div', { style: { marginTop: 8, fontWeight: 700, color: gradeClassColor(res.gpa, a.accentColor) } }, gradeClass(res.gpa)) : null,
99 a.showTotals ? el('div', { style: { display: 'flex', justifyContent: 'center', gap: 32, marginTop: 16 } },
100 el('div', { style: { textAlign: 'center' } },
101 el('div', { style: { fontSize: 22, fontWeight: 700, color: a.labelColor } }, res.totalCredits),
102 el('div', { style: { fontSize: 12, color: a.subtitleColor } }, 'Credits')
103 ),
104 el('div', { style: { textAlign: 'center' } },
105 el('div', { style: { fontSize: 22, fontWeight: 700, color: a.labelColor } }, res.totalPoints.toFixed(1)),
106 el('div', { style: { fontSize: 12, color: a.subtitleColor } }, 'Grade Points')
107 )
108 ) : null
109 );
110
111 /* courses table */
112 var tableHeader = el('div', {
113 style: {
114 display: 'grid', gridTemplateColumns: '1fr 80px 60px',
115 background: a.tableHeaderBg, borderRadius: '8px 8px 0 0',
116 padding: '8px 12px', fontWeight: 600, fontSize: 13, color: a.subtitleColor
117 }
118 }, el('span', null, 'Course'), el('span', { style: { textAlign: 'center' } }, 'Grade'), el('span', { style: { textAlign: 'center' } }, 'Credits'));
119
120 var tableRows = courses.map(function (c, i) {
121 return el('div', {
122 key: i,
123 style: {
124 display: 'grid', gridTemplateColumns: '1fr 80px 60px',
125 padding: '8px 12px', borderBottom: '1px solid #f3f4f6',
126 fontSize: 14, color: a.labelColor,
127 background: i % 2 === 0 ? '#fff' : '#fafafa'
128 }
129 },
130 el('span', null, c.name || 'Course ' + (i + 1)),
131 el('span', { style: { textAlign: 'center', fontWeight: 600, color: a.accentColor } }, c.grade),
132 el('span', { style: { textAlign: 'center' } }, c.credits)
133 );
134 });
135
136 return el('div', { style: wrapStyle },
137 (a.showTitle || a.showSubtitle) ? el('div', { style: { textAlign: 'center', marginBottom: 32, maxWidth: a.maxWidth + 'px', margin: '0 auto 32px' } },
138 a.showTitle ? el('h3', { className: 'bkbg-gpa-title', style: { color: a.titleColor, margin: '0 0 8px' } }, a.title) : null,
139 a.showSubtitle ? el('p', { style: { color: a.subtitleColor, margin: 0 } }, a.subtitle) : null
140 ) : null,
141 el('div', { style: cardStyle },
142 resultBox,
143 tableHeader,
144 tableRows,
145 el('div', { style: { textAlign: 'center', marginTop: 16, color: a.subtitleColor, fontSize: 13 } },
146 a.defaultCourses.length + ' courses (editable in settings)'
147 )
148 )
149 );
150 }
151
152 /* ── Edit ────────────────────────────────────────── */
153 function GPAEdit(props) {
154 var a = props.attributes;
155 var set = props.setAttributes;
156 var blockProps = useBlockProps({ className: 'bkbg-gpa-editor', style: Object.assign({}, _tv()(a.typoTitle, '--bkbg-gpa-tt-'), _tv()(a.typoGpa, '--bkbg-gpa-gp-')) });
157
158 function s(key) { return function (v) { var o = {}; o[key] = v; set(o); }; }
159 function n(key) { return function (v) { var o = {}; o[key] = Number(v) || 0; set(o); }; }
160 function t(key) { return function (v) { var o = {}; o[key] = v; set(o); }; }
161
162 function updateCourse(i, field, val) {
163 var c = a.defaultCourses.slice();
164 c[i] = Object.assign({}, c[i]);
165 c[i][field] = field === 'credits' ? (Number(val) || 0) : val;
166 set({ defaultCourses: c });
167 }
168 function addCourse() { set({ defaultCourses: a.defaultCourses.concat([{ name: 'New Course', grade: 'B', credits: 3 }]) }); }
169 function removeCourse(i){ set({ defaultCourses: a.defaultCourses.filter(function (_, idx) { return idx !== i; }) }); }
170
171 return el(Fragment, null,
172 el(InspectorControls, null,
173
174 /* Header */
175 el(PanelBody, { title: __('Header', 'blockenberg'), initialOpen: true },
176 el(ToggleControl, { label: __('Show Title', 'blockenberg'), checked: a.showTitle, onChange: t('showTitle'), __nextHasNoMarginBottom: true }),
177 a.showTitle ? el(TextControl, { label: __('Title', 'blockenberg'), value: a.title, onChange: s('title') }) : null,
178 el(ToggleControl, { label: __('Show Subtitle', 'blockenberg'), checked: a.showSubtitle, onChange: t('showSubtitle'), __nextHasNoMarginBottom: true }),
179 a.showSubtitle ? el(TextControl, { label: __('Subtitle', 'blockenberg'), value: a.subtitle, onChange: s('subtitle') }) : null
180 ),
181
182 /* Default Courses */
183 el(PanelBody, { title: __('Default Courses', 'blockenberg'), initialOpen: true },
184 el('p', { style: { margin: '0 0 12px', color: '#6b7280', fontSize: 12 } },
185 __('These pre-fill the calculator. Users can edit and add more.', 'blockenberg')
186 ),
187 a.defaultCourses.map(function (c, i) {
188 return el('div', { key: i, style: { background: '#f9f9f9', borderRadius: 8, padding: '8px 12px', marginBottom: 8 } },
189 el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 } },
190 el('strong', { style: { fontSize: 12 } }, 'Course ' + (i + 1)),
191 el(Button, { isDestructive: true, isSmall: true, onClick: function () { removeCourse(i); } }, '')
192 ),
193 el(TextControl, { label: __('Course Name', 'blockenberg'), value: c.name, onChange: function (v) { updateCourse(i, 'name', v); } }),
194 el(SelectControl, { label: __('Grade', 'blockenberg'), value: c.grade, options: GRADE_OPTIONS, onChange: function (v) { updateCourse(i, 'grade', v); } }),
195 el(TextControl, { label: __('Credit Hours', 'blockenberg'), value: String(c.credits), onChange: function (v) { updateCourse(i, 'credits', v); }, type: 'number' })
196 );
197 }),
198 el(Button, { isPrimary: true, onClick: addCourse, style: { marginTop: 8 } }, __('+ Add Course', 'blockenberg'))
199 ),
200
201 /* Display */
202 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
203 el(ToggleControl, { label: __('Show Classification (Cum Laude etc.)', 'blockenberg'), checked: a.showGradeClass, onChange: t('showGradeClass'), __nextHasNoMarginBottom: true }),
204 el(ToggleControl, { label: __('Show Totals (Credits & Points)', 'blockenberg'), checked: a.showTotals, onChange: t('showTotals'), __nextHasNoMarginBottom: true }),
205 el(ToggleControl, { label: __('Show Grade Scale Help', 'blockenberg'), checked: a.showHelp, onChange: t('showHelp'), __nextHasNoMarginBottom: true })
206 ),
207
208 /* Colors */
209
210 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
211 _tc()({ label: __('Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { set({ typoTitle: v }); } }),
212 _tc()({ label: __('GPA Number', 'blockenberg'), value: a.typoGpa, onChange: function (v) { set({ typoGpa: v }); } })
213 ),
214 el(PanelColorSettings, {
215 title: __('Colors', 'blockenberg'),
216 initialOpen: false,
217 colorSettings: [
218 { value: a.accentColor, onChange: s('accentColor'), label: __('Accent', 'blockenberg') },
219 { value: a.cardBg, onChange: s('cardBg'), label: __('Card Background', 'blockenberg') },
220 { value: a.resultBg, onChange: s('resultBg'), label: __('Result Box BG', 'blockenberg') },
221 { value: a.resultBorder, onChange: s('resultBorder'), label: __('Result Box Border', 'blockenberg') },
222 { value: a.gpaColor, onChange: s('gpaColor'), label: __('GPA Number', 'blockenberg') },
223 { value: a.tableHeaderBg, onChange: s('tableHeaderBg'), label: __('Table Header BG', 'blockenberg') },
224 { value: a.titleColor, onChange: s('titleColor'), label: __('Title Color', 'blockenberg') },
225 { value: a.subtitleColor, onChange: s('subtitleColor'), label: __('Subtitle Color', 'blockenberg') },
226 { value: a.labelColor, onChange: s('labelColor'), label: __('Label Color', 'blockenberg') },
227 { value: a.sectionBg, onChange: s('sectionBg'), label: __('Section Background', 'blockenberg') }
228 ]
229 }),
230
231 /* Sizing */
232 el(PanelBody, { title: __('Sizing', 'blockenberg'), initialOpen: false },
233 el(RangeControl, { label: __('Card Radius (px)', 'blockenberg'), value: a.cardRadius, onChange: n('cardRadius'), min: 0, max: 32 }),
234 el(RangeControl, { label: __('Input Radius (px)', 'blockenberg'), value: a.inputRadius, onChange: n('inputRadius'), min: 0, max: 20 }),
235 el(RangeControl, { label: __('Max Width (px)', 'blockenberg'), value: a.maxWidth, onChange: n('maxWidth'), min: 360, max: 1000, step: 20 }),
236 el(RangeControl, { label: __('Padding Top (px)', 'blockenberg'), value: a.paddingTop, onChange: n('paddingTop'), min: 0, max: 160 }),
237 el(RangeControl, { label: __('Padding Bottom (px)', 'blockenberg'), value: a.paddingBottom, onChange: n('paddingBottom'), min: 0, max: 160 })
238 )
239 ),
240
241 el('div', blockProps,
242 el(GPAPreview, { attrs: a })
243 )
244 );
245 }
246
247 registerBlockType('blockenberg/gpa-calculator', {
248 edit: GPAEdit,
249 save: function (props) {
250 var a = props.attributes;
251 return el('div', wp.blockEditor.useBlockProps.save({
252 className: 'bkbg-gpa-app',
253 'data-opts': JSON.stringify(a)
254 }));
255 }
256 });
257 }() );
258