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 +30 -0
- package/app.html +21 -0
- package/dist/lobsterboard.css +1 -1
- package/dist/lobsterboard.esm.js +1 -1
- package/dist/lobsterboard.esm.min.js +1 -1
- package/dist/lobsterboard.umd.js +1 -1
- package/dist/lobsterboard.umd.min.js +1 -1
- package/js/editor/auth.js +11 -3
- package/js/editor/canvas.js +191 -0
- package/js/editor/properties.js +30 -0
- package/js/editor/widgets.js +59 -0
- package/js/widgets/index.js +21 -0
- package/package.json +1 -1
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>
|
package/dist/lobsterboard.css
CHANGED
package/dist/lobsterboard.esm.js
CHANGED
package/dist/lobsterboard.umd.js
CHANGED
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')
|
|
262
|
-
el.querySelector('.resize-handle')
|
|
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:
|
|
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)();
|
package/js/editor/canvas.js
CHANGED
|
@@ -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
|
})();
|
package/js/editor/properties.js
CHANGED
|
@@ -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