Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add drag resize support to dashboard #6643

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
/**
* @author Vaadin Ltd
*/
@Route("vaadin-dashboard/drag-drop")
public class DashboardDragDropPage extends Div {
@Route("vaadin-dashboard/drag-reorder")
public class DashboardDragReorderPage extends Div {

public DashboardDragDropPage() {
public DashboardDragReorderPage() {
Dashboard dashboard = new Dashboard();
dashboard.setEditable(true);
dashboard.setMinimumRowHeight("100px");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright 2000-2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
* license.
*/
package com.vaadin.flow.component.dashboard.tests;

import com.vaadin.flow.component.dashboard.Dashboard;
import com.vaadin.flow.component.dashboard.DashboardSection;
import com.vaadin.flow.component.dashboard.DashboardWidget;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;

/**
* @author Vaadin Ltd
*/
@Route("vaadin-dashboard/drag-resize")
public class DashboardDragResizePage extends Div {

public DashboardDragResizePage() {
Dashboard dashboard = new Dashboard();
dashboard.setEditable(true);
dashboard.setMinimumRowHeight("200px");
dashboard.setMinimumColumnWidth("250px");
dashboard.setMaximumColumnWidth("250px");

DashboardWidget widget = new DashboardWidget();
widget.setTitle("Widget");
dashboard.add(widget);

DashboardWidget widgetInSection = new DashboardWidget();
widgetInSection.setTitle("Widget in section");
DashboardSection section = dashboard.addSection("Section");
section.add(widgetInSection);

NativeButton toggleEditable = new NativeButton("Toggle editable",
e -> dashboard.setEditable(!dashboard.isEditable()));
toggleEditable.setId("toggle-editable");

add(toggleEditable, dashboard);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
/**
* @author Vaadin Ltd
*/
@TestPath("vaadin-dashboard/drag-drop")
public class DashboardDragDropIT extends AbstractComponentIT {
@TestPath("vaadin-dashboard/drag-reorder")
public class DashboardDragReorderIT extends AbstractComponentIT {

private DashboardElement dashboardElement;

Expand All @@ -36,7 +36,7 @@ public void init() {
public void reorderWidgetOnClientSide_itemsAreReorderedCorrectly() {
var draggedWidget = dashboardElement.getWidgets().get(0);
var targetWidget = dashboardElement.getWidgets().get(1);
dragDropElement(draggedWidget, targetWidget);
dragResizeElement(draggedWidget, targetWidget);
Assert.assertEquals(draggedWidget.getTitle(),
dashboardElement.getWidgets().get(1).getTitle());
}
Expand All @@ -45,7 +45,7 @@ public void reorderWidgetOnClientSide_itemsAreReorderedCorrectly() {
public void reorderSectionOnClientSide_itemsAreReorderedCorrectly() {
var draggedSection = dashboardElement.getSections().get(1);
var targetWidget = dashboardElement.getWidgets().get(0);
dragDropElement(draggedSection, targetWidget);
dragResizeElement(draggedSection, targetWidget);
Assert.assertEquals(draggedSection.getTitle(),
dashboardElement.getSections().get(0).getTitle());
}
Expand All @@ -55,7 +55,7 @@ public void reorderWidgetInSectionOnClientSide_itemsAreReorderedCorrectly() {
var firstSection = dashboardElement.getSections().get(0);
var draggedWidget = firstSection.getWidgets().get(0);
var targetWidget = firstSection.getWidgets().get(1);
dragDropElement(draggedWidget, targetWidget);
dragResizeElement(draggedWidget, targetWidget);
firstSection = dashboardElement.getSections().get(0);
Assert.assertEquals(draggedWidget.getTitle(),
firstSection.getWidgets().get(1).getTitle());
Expand All @@ -70,22 +70,22 @@ public void detachReattach_reorderWidgetOnClientSide_itemsAreReorderedCorrectly(
}

@Test
public void setDashboardNotEditable_widgetCannotBeDragged() {
public void setDashboardNotEditable_dragHandleNotVisible() {
var widget = dashboardElement.getWidgets().get(0);
Assert.assertTrue(isHeaderActionsVisible(widget));
Assert.assertTrue(isDragHandleVisible(widget));
clickElementWithJs("toggle-editable");
Assert.assertFalse(isHeaderActionsVisible(widget));
Assert.assertFalse(isDragHandleVisible(widget));
}

@Test
public void setDashboardEditable_widgetCanBeDragged() {
public void setDashboardEditable_dragHandleNotVisible() {
clickElementWithJs("toggle-editable");
clickElementWithJs("toggle-editable");
Assert.assertTrue(
isHeaderActionsVisible(dashboardElement.getWidgets().get(0)));
isDragHandleVisible(dashboardElement.getWidgets().get(0)));
}

private void dragDropElement(TestBenchElement draggedElement,
private void dragResizeElement(TestBenchElement draggedElement,
TestBenchElement targetElement) {
var dragHandle = getDragHandle(draggedElement);

Expand All @@ -99,10 +99,8 @@ private void dragDropElement(TestBenchElement draggedElement,
.release(targetElement).build().perform();
}

private static boolean isHeaderActionsVisible(TestBenchElement element) {
TestBenchElement headerActions = element.$("*").withId("header-actions")
.first();
return !"none".equals(headerActions.getCssValue("display"));
private static boolean isDragHandleVisible(TestBenchElement element) {
return !"none".equals(getDragHandle(element).getCssValue("display"));
}

private static TestBenchElement getDragHandle(TestBenchElement element) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright 2000-2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
* license.
*/
package com.vaadin.flow.component.dashboard.tests;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.interactions.Actions;

import com.vaadin.flow.component.dashboard.testbench.DashboardElement;
import com.vaadin.flow.component.dashboard.testbench.DashboardWidgetElement;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.testbench.TestBenchElement;
import com.vaadin.tests.AbstractComponentIT;

/**
* @author Vaadin Ltd
*/
@TestPath("vaadin-dashboard/drag-resize")
public class DashboardDragResizeIT extends AbstractComponentIT {
tomivirkki marked this conversation as resolved.
Show resolved Hide resolved

private DashboardElement dashboardElement;

@Before
public void init() {
open();
getDriver().manage().window().setSize(new Dimension(1920, 1080));
dashboardElement = $(DashboardElement.class).waitForFirst();
}

@Test
public void resizeWidgetBothHorizontallyAndVertically_widgetIsResizedCorrectly() {
assertWidgetResized(0);
}

@Test
public void resizeWidgetInSectionBothHorizontallyAndVertically_widgetIsResizedCorrectly() {
assertWidgetResized(1);
}

@Test
public void setDashboardNotEditable_resizeHandleNotVisible() {
var widget = dashboardElement.getWidgets().get(0);
Assert.assertTrue(isResizeHandleVisible(widget));
clickElementWithJs("toggle-editable");
Assert.assertFalse(isResizeHandleVisible(widget));
}

@Test
public void setDashboardEditable_resizeHandleNotVisible() {
clickElementWithJs("toggle-editable");
clickElementWithJs("toggle-editable");
Assert.assertTrue(
isResizeHandleVisible(dashboardElement.getWidgets().get(0)));
}

private void assertWidgetResized(int widgetIndexToResize) {
var widgetToResize = dashboardElement.getWidgets()
.get(widgetIndexToResize);
int xResizeRatio = 2;
int yResizeRatio = 2;
var expectedWidth = widgetToResize.getSize().getWidth() * xResizeRatio;
var expectedHeight = widgetToResize.getSize().getHeight()
* yResizeRatio;
resizeWidget(widgetIndexToResize, xResizeRatio, yResizeRatio);
var resizedWidget = dashboardElement.getWidgets()
.get(widgetIndexToResize);
var delta = 20;
Assert.assertEquals(expectedWidth, resizedWidget.getSize().getWidth(),
delta);
Assert.assertEquals(expectedHeight, resizedWidget.getSize().getHeight(),
delta);
}

private void resizeWidget(int widgetIndexToResize, double xResizeRatio,
double yResizeRatio) {
var widgetToResize = dashboardElement.getWidgets()
.get(widgetIndexToResize);
var xOffset = (int) (widgetToResize.getSize().getWidth()
* (xResizeRatio - 1));
var yOffset = (int) (widgetToResize.getSize().getHeight()
* (yResizeRatio - 1));
TestBenchElement resizeHandle = getResizeHandle(widgetToResize);
int trackStartOffset = 5;
new Actions(driver).moveToElement(resizeHandle).clickAndHold()
// This is necessary for the Polymer track event to be fired.
.moveByOffset(trackStartOffset, trackStartOffset)
.moveByOffset(xOffset - trackStartOffset,
yOffset - trackStartOffset)
.release().build().perform();
}

private boolean isResizeHandleVisible(
DashboardWidgetElement widgetElement) {
return !"none"
.equals(getResizeHandle(widgetElement).getCssValue("display"));
}

private static TestBenchElement getResizeHandle(
DashboardWidgetElement widgetElement) {
return widgetElement.$("*").withClassName("resize-handle").first();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class Dashboard extends Component implements HasWidgets {
public Dashboard() {
childDetachHandler = getChildDetachHandler();
addItemReorderEndListener(this::onItemReorderEnd);
addItemResizeEndListener(this::onItemResizeEnd);
}

/**
Expand Down Expand Up @@ -325,6 +326,30 @@ public Registration addItemReorderEndListener(
return addListener(DashboardItemReorderEndEvent.class, listener);
}

/**
* Adds an item resize start listener to this dashboard.
*
* @param listener
* the listener to add, not <code>null</code>
* @return a handle that can be used for removing the listener
*/
public Registration addItemResizeStartListener(
ComponentEventListener<DashboardItemResizeStartEvent> listener) {
return addListener(DashboardItemResizeStartEvent.class, listener);
}

/**
* Adds an item resize end listener to this dashboard.
*
* @param listener
* the listener to add, not <code>null</code>
* @return a handle that can be used for removing the listener
*/
public Registration addItemResizeEndListener(
ComponentEventListener<DashboardItemResizeEndEvent> listener) {
return addListener(DashboardItemResizeEndEvent.class, listener);
}

@Override
public Stream<Component> getChildren() {
return childrenComponents.stream();
Expand Down Expand Up @@ -447,6 +472,14 @@ private void onItemReorderEnd(
updateClient();
}

private void onItemResizeEnd(
DashboardItemResizeEndEvent dashboardItemResizeEndEvent) {
DashboardWidget resizedWidget = dashboardItemResizeEndEvent
.getResizedWidget();
resizedWidget.setRowspan(dashboardItemResizeEndEvent.getRowspan());
resizedWidget.setColspan(dashboardItemResizeEndEvent.getColspan());
}

private void reorderItems(JsonArray orderedItemsFromClient) {
// Keep references to the root level children before clearing them
Map<Integer, Component> nodeIdToComponent = childrenComponents.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright 2000-2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
* license.
*/
package com.vaadin.flow.component.dashboard;

import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.DomEvent;
import com.vaadin.flow.component.EventData;

/**
* Widget resize end event of {@link Dashboard}.
*
* @author Vaadin Ltd.
* @see Dashboard#addItemResizeEndListener(ComponentEventListener)
*/
@DomEvent("dashboard-item-resize-end")
public class DashboardItemResizeEndEvent extends ComponentEvent<Dashboard> {

private final DashboardWidget resizedWidget;

private final int colspan;

private final int rowspan;

/**
* Creates a dashboard item resize end event.
*
* @param source
* Dashboard that contains the widget that was dragged
* @param fromClient
* <code>true</code> if the event originated from the client
* side, <code>false</code> otherwise
* @param nodeId
* Node ID the resized widget
* @param colspan
* New colspan of the resized widget
* @param rowspan
* New rowspan of the resized widget
*/
public DashboardItemResizeEndEvent(Dashboard source, boolean fromClient,
@EventData("event.detail.item.nodeid") int nodeId,
@EventData("event.detail.item.colspan") int colspan,
@EventData("event.detail.item.rowspan") int rowspan) {
super(source, fromClient);
this.resizedWidget = source.getWidgets().stream()
.filter(child -> nodeId == child.getElement().getNode().getId())
.findAny().orElse(null);
this.colspan = colspan;
this.rowspan = rowspan;
}

/**
* Returns the resized widget
*
* @return the resized widget
*/
public DashboardWidget getResizedWidget() {
return resizedWidget;
}

/**
* Returns the new colspan of the resized item
*
* @return new colspan of the resized item
*/
public int getColspan() {
return colspan;
}

/**
* Returns the new rowspan of the resized item
*
* @return new rowspan of the resized item
*/
public int getRowspan() {
return rowspan;
}
}
Loading