lobsterboard 0.8.2 → 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 +15 -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/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
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
|
+
|
|
3
18
|
## [0.8.2] - 2026-04-02
|
|
4
19
|
|
|
5
20
|
### Fixed
|
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
|
})();
|
package/package.json
CHANGED