lobsterboard 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.3] - 2026-04-02
4
+
5
+ ### Fixed
6
+ - **Widget interaction** — Restored missing click-to-select, drag-to-move, and resize functionality lost in module splitting — fixes [#24](https://github.com/Curbob/LobsterBoard/issues/24)
7
+ - **Widget deletion** — Fixed non-functional delete button in properties panel by adding missing `deleteWidget()` function
8
+ - **JavaScript execution** — Fixed widget scripts not running by correcting DOM element ID matching (`widget-X` vs `preview-widget-X`)
9
+ - **Edit mode controls** — Fixed "Done" button and edit mode state management after module refactoring
10
+
11
+ ### Technical
12
+ - Added complete widget event system (click, mousedown, resize handles)
13
+ - Added `startDragWidget()` and `startResizeWidget()` functions with grid snapping
14
+ - Added `deleteWidget()` function with proper state cleanup
15
+ - Fixed widget script execution timing and element targeting
16
+ - Restored full editor interactivity matching pre-0.8.1 functionality
17
+
18
+ ## [0.8.2] - 2026-04-02
19
+
20
+ ### Fixed
21
+ - **Critical module loading** — Fixed missing `js/widgets/index.js` and `js/editor/widgets.js` entry files after v0.8.1 module splitting, resolving 404 errors that prevented editor from loading — fixes [#24](https://github.com/Curbob/LobsterBoard/issues/24)
22
+
23
+ ### Technical
24
+ - Added widget registry initialization module (`js/widgets/index.js`)
25
+ - Added editor widget library functionality (`js/editor/widgets.js`)
26
+ - Ensures clean git clone + `npm install` + `node server.cjs` works without errors
27
+
28
+ ## [0.8.1] - 2026-03-29
29
+
30
+ ### Fixed
31
+ - **Module organization** — Split monolithic files into focused modules for better maintainability
32
+
3
33
  ## [0.8.0] - 2026-03-24
4
34
 
5
35
  ### Added
package/app.html CHANGED
@@ -1042,5 +1042,26 @@
1042
1042
  </div>
1043
1043
 
1044
1044
  <script src="js/templates.js"></script>
1045
+
1046
+ <script>
1047
+ // ─────────────────────────────────────────────
1048
+ // INITIALIZATION
1049
+ // ─────────────────────────────────────────────
1050
+ document.addEventListener('DOMContentLoaded', () => {
1051
+ initCanvas();
1052
+ initDragDrop();
1053
+ initControls();
1054
+ initProperties();
1055
+ loadConfig(); // Loads config and renders widgets
1056
+ // setEditMode(false) is called inside loadConfig()
1057
+
1058
+ // Initialize button event handlers
1059
+ document.getElementById('btn-edit-layout').addEventListener('click', requestEditMode);
1060
+ document.getElementById('btn-done-editing').addEventListener('click', () => {
1061
+ saveConfig();
1062
+ setEditMode(false);
1063
+ });
1064
+ });
1065
+ </script>
1045
1066
  </body>
1046
1067
  </html>
@@ -1,4 +1,4 @@
1
- /* LobsterBoard v0.8.1 - Dashboard Styles */
1
+ /* LobsterBoard v0.8.3 - Dashboard Styles */
2
2
  /* LobsterBoard Dashboard - Generated Styles */
3
3
 
4
4
  :root {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.8.1
2
+ * LobsterBoard v0.8.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.8.1
2
+ * LobsterBoard v0.8.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.8.1
2
+ * LobsterBoard v0.8.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.8.1
2
+ * LobsterBoard v0.8.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
package/js/editor/auth.js CHANGED
@@ -258,8 +258,16 @@
258
258
  state.widgets.forEach(function(widget) {
259
259
  var el = document.getElementById(widget.id);
260
260
  if (el) {
261
- el.querySelector('.widget-render').style.pointerEvents = enable ? 'none' : 'auto';
262
- el.querySelector('.resize-handle').style.display = enable ? 'block' : 'none';
261
+ var widgetRender = el.querySelector('.widget-render');
262
+ var resizeHandle = el.querySelector('.resize-handle');
263
+
264
+ if (widgetRender) {
265
+ widgetRender.style.pointerEvents = enable ? 'none' : 'auto';
266
+ }
267
+ if (resizeHandle) {
268
+ resizeHandle.style.display = enable ? 'block' : 'none';
269
+ }
270
+
263
271
  if (enable) {
264
272
  el.style.cursor = 'move';
265
273
  el.classList.add('builder-edit-mode');
@@ -302,7 +310,7 @@
302
310
  state.widgets.forEach(function(widget) {
303
311
  var template = WIDGETS[widget.type];
304
312
  if (!template || !template.generateJs) return;
305
- var props = sanitizeProps(Object.assign({}, widget.properties, { id: 'preview-' + widget.id }));
313
+ var props = sanitizeProps(Object.assign({}, widget.properties, { id: widget.id }));
306
314
  try {
307
315
  var js = template.generateJs(props);
308
316
  new Function(js)();
@@ -115,4 +115,195 @@
115
115
  window.addEventListener('resize', function() {
116
116
  if (!state.editMode) scaleCanvasToFit();
117
117
  });
118
+
119
+ // ─────────────────────────────────────────────
120
+ // WIDGET RENDERING AND SELECTION
121
+ // ─────────────────────────────────────────────
122
+
123
+ window.renderWidget = function renderWidget(widget) {
124
+ const template = WIDGETS[widget.type];
125
+ if (!template) {
126
+ console.warn(`renderWidget: unknown widget type "${widget.type}" (${widget.id}), skipping`);
127
+ return;
128
+ }
129
+ const canvas = document.getElementById('canvas');
130
+
131
+ const el = document.createElement('div');
132
+ el.className = 'placed-widget';
133
+ el.dataset.type = widget.type;
134
+ if (widget.type === 'text-header') {
135
+ el.dataset.showBorder = widget.properties.showBorder ? 'true' : 'false';
136
+ }
137
+ if (widget.type === 'pages-menu' && widget.properties.showBorder === false) {
138
+ el.dataset.showBorder = 'false';
139
+ }
140
+
141
+ el.id = widget.id;
142
+ el.style.left = widget.x + 'px';
143
+ el.style.top = widget.y + 'px';
144
+ el.style.width = widget.width + 'px';
145
+ el.style.height = widget.height + 'px';
146
+ el.style.position = 'absolute';
147
+
148
+ // Widget content and wrapper
149
+ el.innerHTML = '<div class="widget-render"></div><div class="resize-handle"></div>';
150
+ const props = { ...widget.properties, id: widget.id };
151
+ const widgetContent = window.processWidgetHtml(template.generateHtml(props), widget.properties.showHeader);
152
+ el.querySelector('.widget-render').innerHTML = widgetContent;
153
+
154
+ canvas.appendChild(el);
155
+ window.applyWidgetFontScale(widget);
156
+
157
+ // Add click event listener for selection
158
+ el.addEventListener('click', function(e) {
159
+ if (!state.editMode) return;
160
+ e.stopPropagation();
161
+ window.selectWidget(widget.id);
162
+ });
163
+
164
+ // Add drag event listener
165
+ el.addEventListener('mousedown', function(e) {
166
+ if (!state.editMode) return;
167
+ if (e.target.classList.contains('resize-handle')) return;
168
+ window.startDragWidget(e, widget);
169
+ });
170
+
171
+ // Add resize handle event listener
172
+ const resizeHandle = el.querySelector('.resize-handle');
173
+ if (resizeHandle) {
174
+ resizeHandle.addEventListener('mousedown', function(e) {
175
+ if (!state.editMode) return;
176
+ e.stopPropagation();
177
+ window.startResizeWidget(e, widget);
178
+ });
179
+ }
180
+
181
+ // Execute widget JS after HTML is in the DOM
182
+ if (template.generateJs) {
183
+ try {
184
+ eval('(function() { var widget = arguments[0]; ' + template.generateJs(props) + ' })')(widget);
185
+ } catch (e) {
186
+ console.error(`Widget ${widget.id} JS error:`, e);
187
+ }
188
+ }
189
+ };
190
+
191
+ window.selectWidget = function selectWidget(id) {
192
+ // Deselect previous
193
+ document.querySelectorAll('.placed-widget.selected').forEach(el => {
194
+ el.classList.remove('selected');
195
+ });
196
+
197
+ state.selectedWidget = id ? state.widgets.find(w => w.id === id) : null;
198
+
199
+ if (state.selectedWidget) {
200
+ document.getElementById(id).classList.add('selected');
201
+ window.showProperties(state.selectedWidget);
202
+ } else {
203
+ window.hideProperties();
204
+ }
205
+ };
206
+
207
+ window.startDragWidget = function startDragWidget(e, widget) {
208
+ if (e.button !== 0) return;
209
+
210
+ const el = document.getElementById(widget.id);
211
+ const startX = e.clientX;
212
+ const startY = e.clientY;
213
+ const origX = widget.x;
214
+ const origY = widget.y;
215
+
216
+ function onMove(e) {
217
+ const dx = (e.clientX - startX) / state.zoom;
218
+ const dy = (e.clientY - startY) / state.zoom;
219
+
220
+ widget.x = Math.round((origX + dx) / 20) * 20;
221
+ widget.y = Math.round((origY + dy) / 20) * 20;
222
+
223
+ // Keep in bounds
224
+ widget.x = Math.max(0, Math.min(widget.x, state.canvas.width - widget.width));
225
+ if (window.isScrollableMode && window.isScrollableMode()) {
226
+ widget.y = Math.max(0, widget.y);
227
+ } else {
228
+ widget.y = Math.max(0, Math.min(widget.y, state.canvas.height - widget.height));
229
+ }
230
+
231
+ el.style.left = widget.x + 'px';
232
+ el.style.top = widget.y + 'px';
233
+
234
+ // In scrollable mode, grow canvas to fit
235
+ if (window.isScrollableMode && window.isScrollableMode()) {
236
+ window.updateCanvasSize(true);
237
+ }
238
+
239
+ window.updatePropertyInputs();
240
+ }
241
+
242
+ function onUp() {
243
+ document.removeEventListener('mousemove', onMove);
244
+ document.removeEventListener('mouseup', onUp);
245
+ }
246
+
247
+ document.addEventListener('mousemove', onMove);
248
+ document.addEventListener('mouseup', onUp);
249
+ };
250
+
251
+ window.startResizeWidget = function startResizeWidget(e, widget) {
252
+ const el = document.getElementById(widget.id);
253
+ const startX = e.clientX;
254
+ const startY = e.clientY;
255
+ const origW = widget.width;
256
+ const origH = widget.height;
257
+
258
+ function onMove(e) {
259
+ const dw = (e.clientX - startX) / state.zoom;
260
+ const dh = (e.clientY - startY) / state.zoom;
261
+
262
+ widget.width = Math.round((origW + dw) / 20) * 20;
263
+ widget.height = Math.round((origH + dh) / 20) * 20;
264
+
265
+ // Minimum size
266
+ widget.width = Math.max(100, widget.width);
267
+ widget.height = Math.max(60, widget.height);
268
+
269
+ // Keep in bounds
270
+ widget.width = Math.min(widget.width, state.canvas.width - widget.x);
271
+ if (!window.isScrollableMode || !window.isScrollableMode()) {
272
+ widget.height = Math.min(widget.height, state.canvas.height - widget.y);
273
+ }
274
+
275
+ el.style.width = widget.width + 'px';
276
+ el.style.height = widget.height + 'px';
277
+
278
+ // In scrollable mode, grow canvas to fit
279
+ if (window.isScrollableMode && window.isScrollableMode()) {
280
+ window.updateCanvasSize(true);
281
+ }
282
+
283
+ window.updatePropertyInputs();
284
+ }
285
+
286
+ function onUp() {
287
+ document.removeEventListener('mousemove', onMove);
288
+ document.removeEventListener('mouseup', onUp);
289
+ }
290
+
291
+ document.addEventListener('mousemove', onMove);
292
+ document.addEventListener('mouseup', onUp);
293
+ };
294
+
295
+ window.deleteWidget = function deleteWidget(id) {
296
+ const idx = state.widgets.findIndex(w => w.id === id);
297
+ if (idx === -1) return;
298
+
299
+ state.widgets.splice(idx, 1);
300
+ document.getElementById(id)?.remove();
301
+ window.selectWidget(null);
302
+ window.updateCanvasInfo();
303
+ window.updateEmptyState();
304
+
305
+ if (state.widgets.length === 0) {
306
+ document.getElementById('canvas').classList.remove('has-widgets');
307
+ }
308
+ };
118
309
  })();
@@ -742,4 +742,34 @@
742
742
  }
743
743
 
744
744
  window.onPropertyChange = onPropertyChange;
745
+
746
+ // ─────────────────────────────────────────────
747
+ // WIDGET FONT SCALING
748
+ // ─────────────────────────────────────────────
749
+
750
+ window.applyWidgetFontScale = function applyWidgetFontScale(widget) {
751
+ const el = document.getElementById(widget.id);
752
+ if (!el) return;
753
+ const body = el.querySelector('.dash-card-body');
754
+ const render = el.querySelector('.widget-render');
755
+ const adjustment = widget.properties.widgetFontAdjust || 0; // e.g. -25, -10, 0, +10, +25
756
+ if (adjustment !== 0) {
757
+ // Compute effective scale: global + adjustment (additive percentage points)
758
+ const globalScale = state.fontScale || 1;
759
+ const effectiveScale = globalScale + (adjustment / 100);
760
+ // Set --font-scale override on widget body content only (header stays at global)
761
+ const target = body || render;
762
+ if (target) {
763
+ target.style.setProperty('--font-scale', effectiveScale);
764
+ target.style.fontSize = (effectiveScale * 100) + '%';
765
+ }
766
+ } else {
767
+ // No adjustment — inherit global
768
+ const target = body || render;
769
+ if (target) {
770
+ target.style.removeProperty('--font-scale');
771
+ target.style.removeProperty('font-size');
772
+ }
773
+ }
774
+ };
745
775
  })();
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Editor Widget Library Module
3
+ * Handles widget library sidebar, categories, and widget item display
4
+ */
5
+ (function() {
6
+ 'use strict';
7
+
8
+ var state = window.BuilderState;
9
+
10
+ window.initWidgetLibrary = function initWidgetLibrary() {
11
+ if (!window.WIDGETS) {
12
+ console.warn('WIDGETS not loaded yet, widget library will be empty');
13
+ return;
14
+ }
15
+
16
+ // Widget library is typically populated by the main builder code
17
+ // This module can extend widget functionality if needed
18
+
19
+ // Add any widget-specific event handlers or customizations here
20
+ initWidgetCategories();
21
+ initWidgetSearch();
22
+ };
23
+
24
+ function initWidgetCategories() {
25
+ // Handle widget category expansion/collapse
26
+ var categoryHeaders = document.querySelectorAll('.widget-category-header');
27
+ categoryHeaders.forEach(function(header) {
28
+ header.addEventListener('click', function() {
29
+ var category = this.parentElement;
30
+ category.classList.toggle('collapsed');
31
+ });
32
+ });
33
+ }
34
+
35
+ function initWidgetSearch() {
36
+ // Add widget search functionality if search input exists
37
+ var searchInput = document.querySelector('.widget-search');
38
+ if (searchInput) {
39
+ searchInput.addEventListener('input', function() {
40
+ var query = this.value.toLowerCase();
41
+ var widgets = document.querySelectorAll('.widget-item');
42
+
43
+ widgets.forEach(function(widget) {
44
+ var name = widget.querySelector('.widget-name');
45
+ var visible = !query || (name && name.textContent.toLowerCase().includes(query));
46
+ widget.style.display = visible ? '' : 'none';
47
+ });
48
+ });
49
+ }
50
+ }
51
+
52
+ // Auto-initialize if DOM is ready
53
+ if (document.readyState === 'loading') {
54
+ document.addEventListener('DOMContentLoaded', initWidgetLibrary);
55
+ } else {
56
+ initWidgetLibrary();
57
+ }
58
+
59
+ })();
@@ -0,0 +1,21 @@
1
+ /**
2
+ * OpenClaw Dashboard Builder - Widget Registry Initialization
3
+ *
4
+ * Initializes the global WIDGETS object that other widget modules will populate.
5
+ * This must be loaded before any widget category modules.
6
+ */
7
+
8
+ (function() {
9
+ 'use strict';
10
+
11
+ // Initialize the global widgets registry
12
+ if (typeof window !== 'undefined') {
13
+ window.WIDGETS = {};
14
+ }
15
+
16
+ // For Node.js environments (testing, etc.)
17
+ if (typeof global !== 'undefined' && typeof module !== 'undefined') {
18
+ global.WIDGETS = {};
19
+ module.exports = global.WIDGETS;
20
+ }
21
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobsterboard",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Self-hosted drag-and-drop dashboard builder with 50 widgets, template gallery, and custom pages. Works standalone or with OpenClaw.",
5
5
  "keywords": [
6
6
  "dashboard",