simple-ui: add support for side panel vs grid details (#2428)

This fixes a major issue with the jumpy UI when using the details view for each cell.
This commit is contained in:
Mohamed Zenadi
2025-06-06 10:07:28 +02:00
committed by GitHub
parent ebdcab2aaa
commit c020cc9d92
2 changed files with 77 additions and 9 deletions

View File

@@ -106,9 +106,6 @@ public class TargetView extends TableView<MgmtTarget, String> {
grid.addColumn(MgmtTarget::getControllerId).setHeader(CONTROLLER_ID).setAutoWidth(true); grid.addColumn(MgmtTarget::getControllerId).setHeader(CONTROLLER_ID).setAutoWidth(true);
grid.addColumn(MgmtTarget::getName).setHeader(Constants.NAME).setAutoWidth(true); grid.addColumn(MgmtTarget::getName).setHeader(Constants.NAME).setAutoWidth(true);
grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true); grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true);
grid.setItemDetailsRenderer(new ComponentRenderer<>(
() -> new TargetDetailedView(hawkbitClient), TargetDetailedView::setItem));
} }
}, },
(query, filter) -> hawkbitClient.getTargetRestApi() (query, filter) -> hawkbitClient.getTargetRestApi()
@@ -124,6 +121,11 @@ public class TargetView extends TableView<MgmtTarget, String> {
selectionGrid.getSelectedItems().forEach(toDelete -> selectionGrid.getSelectedItems().forEach(toDelete ->
hawkbitClient.getTargetRestApi().deleteTarget(toDelete.getControllerId())); hawkbitClient.getTargetRestApi().deleteTarget(toDelete.getControllerId()));
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
},
(target) -> {
var targetDetailedView = new TargetDetailedView(hawkbitClient);
targetDetailedView.setItem(target);
return targetDetailedView;
} }
); );
@@ -317,7 +319,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
} }
} }
private static class TargetDetailedView extends TabSheet { protected static class TargetDetailedView extends TabSheet {
private final TargetDetails targetDetails; private final TargetDetails targetDetails;
private final TargetAssignedInstalled targetAssignedInstalled; private final TargetAssignedInstalled targetAssignedInstalled;
@@ -483,7 +485,6 @@ public class TargetView extends TableView<MgmtTarget, String> {
tagSelector.setWidthFull(); tagSelector.setWidthFull();
tagSelector.setItemLabelGenerator(MgmtTag::getName); tagSelector.setItemLabelGenerator(MgmtTag::getName);
tagSelector.setClearButtonVisible(true); tagSelector.setClearButtonVisible(true);
tagSelector.setItems(fetchAvailableTags());
final HorizontalLayout tagSelectorLayout = new HorizontalLayout(tagSelector, createTagButton); final HorizontalLayout tagSelectorLayout = new HorizontalLayout(tagSelector, createTagButton);
tagSelectorLayout.setWidthFull(); tagSelectorLayout.setWidthFull();
@@ -499,6 +500,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
@Override @Override
protected void onAttach(AttachEvent attachEvent) { protected void onAttach(AttachEvent attachEvent) {
tagSelector.setItems(fetchAvailableTags());
if (changeListener != null) { if (changeListener != null) {
changeListener.remove(); changeListener.remove();
} }

View File

@@ -14,11 +14,20 @@ import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
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.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.splitlayout.SplitLayoutVariant;
import com.vaadin.flow.data.provider.Query; import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableFunction;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.theme.lumo.LumoUtility;
import org.eclipse.hawkbit.ui.simple.view.Constants; import org.eclipse.hawkbit.ui.simple.view.Constants;
@@ -30,6 +39,9 @@ public class TableView<T, ID> extends Div implements Constants {
private final Filter filter; private final Filter filter;
final VerticalLayout gridLayout; final VerticalLayout gridLayout;
protected final HorizontalLayout controlsLayout; protected final HorizontalLayout controlsLayout;
protected final SplitLayout splitLayout;
protected final Div detailsPanel = new Div();
protected Button currentSelectionButton;
public TableView( public TableView(
final Filter.Rsql rsql, final Filter.Rsql rsql,
@@ -43,7 +55,7 @@ public class TableView<T, ID> extends Div implements Constants {
final Filter.Rsql alternativeRsql, final Filter.Rsql alternativeRsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation, final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,
final BiFunction<Query<T, Void>, String, Stream<T>> queryFn) { final BiFunction<Query<T, Void>, String, Stream<T>> queryFn) {
this(rsql, alternativeRsql, entityRepresentation, queryFn, null, null); this(rsql, alternativeRsql, entityRepresentation, queryFn, null, null, null);
} }
public TableView( public TableView(
@@ -52,7 +64,7 @@ public class TableView<T, ID> extends Div implements Constants {
final BiFunction<Query<T, Void>, String, Stream<T>> queryFn, final BiFunction<Query<T, Void>, String, Stream<T>> queryFn,
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> addHandler, final Function<SelectionGrid<T, ID>, CompletionStage<Void>> addHandler,
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler) { final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler) {
this(rsql, null, entityRepresentation, queryFn, addHandler, removeHandler); this(rsql, null, entityRepresentation, queryFn, addHandler, removeHandler, null);
} }
public TableView( public TableView(
@@ -60,13 +72,28 @@ public class TableView<T, ID> extends Div implements Constants {
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation, final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,
final BiFunction<Query<T, Void>, String, Stream<T>> queryFn, final BiFunction<Query<T, Void>, String, Stream<T>> queryFn,
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> addHandler, final Function<SelectionGrid<T, ID>, CompletionStage<Void>> addHandler,
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler) { final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler,
final Function<T, Component> detailsButtonHandler
) {
selectionGrid = new SelectionGrid<>(entityRepresentation, queryFn); selectionGrid = new SelectionGrid<>(entityRepresentation, queryFn);
selectionGrid.setSizeFull();
filter = new Filter(selectionGrid::setRsqlFilter, rsql, alternativeRsql); filter = new Filter(selectionGrid::setRsqlFilter, rsql, alternativeRsql);
setSizeFull(); setSizeFull();
gridLayout = new VerticalLayout(filter, selectionGrid); splitLayout = new SplitLayout();
splitLayout.setSizeFull();
splitLayout.setOrientation(SplitLayout.Orientation.HORIZONTAL);
splitLayout.addThemeVariants(SplitLayoutVariant.LUMO_SMALL);
splitLayout.setSplitterPosition(100);
splitLayout.addToPrimary(selectionGrid);
if (detailsButtonHandler != null) {
ComponentRenderer<Button, T> renderer = new ComponentRenderer<>(renderDetailsButton(detailsButtonHandler));
selectionGrid.addColumn(renderer).setHeader("Details").setAutoWidth(true).setFlexGrow(0);
}
gridLayout = new VerticalLayout(filter, splitLayout);
gridLayout.setSizeFull(); gridLayout.setSizeFull();
gridLayout.setPadding(false); gridLayout.setPadding(false);
gridLayout.setSpacing(false); gridLayout.setSpacing(false);
@@ -81,4 +108,43 @@ public class TableView<T, ID> extends Div implements Constants {
gridLayout.add(controlsLayout); gridLayout.add(controlsLayout);
add(gridLayout); add(gridLayout);
} }
private SerializableFunction<T, Button> renderDetailsButton(final Function<T, Component> selectionHandler) {
return (selectedItem) -> {
int DEFAULT_OPEN_POSITION_SIZE = 50;
final Button button = new Button(VaadinIcon.EYE.create());
button.getStyle().set("color", "var(--lumo-secondary-text-color)");
button.addClickListener(event -> {
final Icon eyeIcon = VaadinIcon.EYE.create();
final Icon closeIcon = VaadinIcon.CLOSE_SMALL.create();
if (button == currentSelectionButton) {
button.setIcon(eyeIcon);
button.getStyle().set("color", "var(--lumo-secondary-text-color)");
detailsPanel.removeAll();
splitLayout.remove(detailsPanel);
splitLayout.setSplitterPosition(100);
currentSelectionButton = null;
} else {
button.setIcon(closeIcon);
button.getStyle().set("color", "var(--lumo-primary-color)");
if (currentSelectionButton == null) {
splitLayout.addToSecondary(detailsPanel);
} else {
currentSelectionButton.setIcon(eyeIcon);
currentSelectionButton.getStyle().set("color", "var(--lumo-secondary-text-color)");
}
detailsPanel.removeAll();
splitLayout.setSplitterPosition(DEFAULT_OPEN_POSITION_SIZE);
detailsPanel.add(selectionHandler.apply(selectedItem));
currentSelectionButton = button;
}
});
button.setTooltipText("Show details");
button.addThemeVariants(ButtonVariant.LUMO_SMALL);
return button;
};
}
} }