fix: add a per-widget action menu (#27)
CI / build-and-test (pull_request) Successful in 2m25s
CI / build-and-test (pull_request) Successful in 2m25s
A GridStackItem now carries an overflow menu next to the grip and the close button: refresh, maximize, duplicate, remove. Maximize and remove are handled by the item itself; refresh and duplicate are only reported, since what they mean depends on the widget. Maximizing only sets a class — the item's gs-* attributes and its gridstack node are untouched, so restoring is by definition the position it had. The menu sits outside the drag-handle selector, like the close button, so opening it never starts a drag; a Playwright test drags it to prove it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
@@ -3,6 +3,7 @@ package com.example.views;
|
||||
import com.example.components.Card;
|
||||
import com.example.components.Fa;
|
||||
import com.example.components.GridStackItem;
|
||||
import com.example.components.GridStackItem.Action;
|
||||
import com.example.components.GridStackLayout;
|
||||
import com.example.components.KpiTile;
|
||||
import com.example.data.ChartDataService;
|
||||
@@ -11,6 +12,7 @@ import com.example.data.KpiData;
|
||||
import com.example.widgets.DashboardContext;
|
||||
import com.example.widgets.WidgetDefinition;
|
||||
import com.example.widgets.WidgetRegistry;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
@@ -78,7 +80,16 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
|
||||
KpiData kpi = kpis.get(i);
|
||||
KpiTile tile = feed(new KpiTile(getTranslation(kpi.labelKey()), ""), kpi);
|
||||
kpiTiles.put(kpi.id(), tile);
|
||||
grid.add(new GridStackItem(kpi.id(), i * KPI_WIDTH, 0, KPI_WIDTH, 1, tile));
|
||||
GridStackItem item = new GridStackItem(kpi.id(), i * KPI_WIDTH, 0, KPI_WIDTH, 1, tile);
|
||||
// A tile can be refreshed (this view feeds it) but not duplicated:
|
||||
// a second copy of the same KPI would be the same number twice.
|
||||
item.setActionEnabled(Action.REFRESH, true);
|
||||
item.addActionListener(e -> {
|
||||
if (e.getAction() == Action.REFRESH) {
|
||||
refreshKpiTile(kpi.id());
|
||||
}
|
||||
});
|
||||
grid.add(item);
|
||||
}
|
||||
context.addFilterChangeListener(this::updateKpiTiles);
|
||||
|
||||
@@ -153,10 +164,32 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
|
||||
return widget(type, widgets.require(type), x, y, w, h);
|
||||
}
|
||||
|
||||
/** Registry widgets support the whole action menu: they know how to
|
||||
* re-request their data, and the definition they were built from is what
|
||||
* duplicating one needs. */
|
||||
private GridStackItem widget(String id, WidgetDefinition definition,
|
||||
int x, int y, int w, int h) {
|
||||
return new GridStackItem(id, x, y, w, h, new Card(getTranslation(definition.titleKey()),
|
||||
definition.factory().apply(context)));
|
||||
Component content = definition.factory().apply(context);
|
||||
GridStackItem item = new GridStackItem(id, x, y, w, h,
|
||||
new Card(getTranslation(definition.titleKey()), content));
|
||||
item.setActions(Action.REFRESH, Action.MAXIMIZE, Action.DUPLICATE, Action.REMOVE);
|
||||
item.addActionListener(e -> {
|
||||
switch (e.getAction()) {
|
||||
case REFRESH -> WidgetRegistry.refresh(content);
|
||||
case DUPLICATE -> addWidget(definition);
|
||||
default -> { }
|
||||
}
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
/** Re-feeds a single tile from the current filter — the action menu's
|
||||
* refresh, which asks for one widget, not for the dashboard. */
|
||||
private void refreshKpiTile(String kpiId) {
|
||||
dataService.kpis(context.getFilter()).stream()
|
||||
.filter(kpi -> kpiId.equals(kpi.id()))
|
||||
.findFirst()
|
||||
.ifPresent(kpi -> feed(kpiTiles.get(kpi.id()), kpi));
|
||||
}
|
||||
|
||||
/** Re-feeds the tiles still on the dashboard. A closed tile keeps its entry
|
||||
|
||||
Reference in New Issue
Block a user