mirror of
https://github.com/penpot/penpot.git
synced 2026-06-15 20:02:17 +00:00
🎉 Add flyout and semantic improvements to main toolbar
This commit is contained in:
parent
8cc99f80a5
commit
511c10fa4c
@ -108,8 +108,12 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||
|
||||
async waitForIdle(options) {
|
||||
await this.page.evaluate(
|
||||
(options) => new Promise(
|
||||
(resolve) => globalThis.requestIdleCallback(resolve, options)), options);
|
||||
(options) =>
|
||||
new Promise((resolve) =>
|
||||
globalThis.requestIdleCallback(resolve, options),
|
||||
),
|
||||
options,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -229,7 +233,7 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||
|
||||
async #waitForWebSocketReadiness(pageName) {
|
||||
// TODO: find a better event to settle whether the app is ready to receive notifications via ws
|
||||
await expect(this.pageName).toHaveText(pageName, { timeout: 30000 })
|
||||
await expect(this.pageName).toHaveText(pageName, { timeout: 30000 });
|
||||
}
|
||||
|
||||
async sendPresenceMessage(fixture) {
|
||||
@ -448,6 +452,33 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||
await pagesToggle.click();
|
||||
}
|
||||
|
||||
async selectToolbarTool(workspacePage, toolName) {
|
||||
await workspacePage.page
|
||||
.getByRole("button", { name: toolName })
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
|
||||
async selectToolFromFlyout(
|
||||
workspacePage,
|
||||
{ triggerToolName, targetToolName },
|
||||
) {
|
||||
const trigger = workspacePage.page
|
||||
.getByRole("button", { name: triggerToolName })
|
||||
.first();
|
||||
|
||||
const option = workspacePage.page
|
||||
.getByRole("menuitemradio", { name: targetToolName })
|
||||
.first();
|
||||
|
||||
await trigger.hover();
|
||||
// Flyout opening is delayed by 350ms in the toolbar component.
|
||||
await workspacePage.page.waitForTimeout(450);
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "true");
|
||||
await option.waitFor({ state: "visible" });
|
||||
await option.click();
|
||||
}
|
||||
|
||||
async moveSelectionToShape(name) {
|
||||
await this.page.locator("rect.viewport-selrect").hover();
|
||||
await this.page.mouse.down();
|
||||
|
||||
88
frontend/playwright/ui/specs/toolbar.spec.js
Normal file
88
frontend/playwright/ui/specs/toolbar.spec.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
|
||||
import WorkspacePage from "../pages/WorkspacePage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WasmWorkspacePage.init(page);
|
||||
});
|
||||
|
||||
const expectLayerNamed = async (workspacePage, name) => {
|
||||
await expect(workspacePage.layers.getByText(name).last()).toBeVisible();
|
||||
};
|
||||
|
||||
test("User creates a frame with the toolbar frame tool", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.selectToolbarTool(workspacePage, "Board (B)");
|
||||
await workspacePage.clickWithDragViewportAt(100, 100, 180, 120);
|
||||
await expectLayerNamed(workspacePage, "Board");
|
||||
});
|
||||
|
||||
test("User creates a rectangle with the toolbar rect tool", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.selectToolbarTool(workspacePage, "Rectangle (R)");
|
||||
await workspacePage.clickWithDragViewportAt(350, 100, 120, 80);
|
||||
await expectLayerNamed(workspacePage, "Rectangle");
|
||||
});
|
||||
|
||||
test("User creates an ellipse from the shapes flyout", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.selectToolFromFlyout(workspacePage, {
|
||||
triggerToolName: "Rectangle (R)",
|
||||
targetToolName: "Ellipse (E)",
|
||||
});
|
||||
await workspacePage.clickWithDragViewportAt(520, 100, 100, 100);
|
||||
await expectLayerNamed(workspacePage, "Ellipse");
|
||||
});
|
||||
|
||||
test("User creates a text shape with the toolbar text tool", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.selectToolbarTool(workspacePage, "Text (T)");
|
||||
await workspacePage.clickAndMove(120, 320, 300, 380);
|
||||
await workspacePage.waitForSelectedShapeName("Text");
|
||||
await workspacePage.page.keyboard.type("toolbar test");
|
||||
await workspacePage.page.keyboard.press("Escape");
|
||||
await expectLayerNamed(workspacePage, "Text");
|
||||
});
|
||||
|
||||
test.skip("User creates a path with the toolbar path tool", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.selectToolbarTool(workspacePage, "Path (P)");
|
||||
await workspacePage.clickAndMove(120, 320, 300, 380);
|
||||
await workspacePage.page.keyboard.press("Enter");
|
||||
await expectLayerNamed(workspacePage, "Path");
|
||||
});
|
||||
|
||||
test("User creates a curve from the path flyout", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.selectToolFromFlyout(workspacePage, {
|
||||
triggerToolName: "Path (P)",
|
||||
targetToolName: "Curve (Shift+C)",
|
||||
});
|
||||
await workspacePage.clickAndMove(120, 320, 300, 380);
|
||||
await workspacePage.page.keyboard.press("Enter");
|
||||
await expectLayerNamed(workspacePage, "Path");
|
||||
});
|
||||
@ -343,7 +343,9 @@ test("User drag and drop a variant outside the container", async ({ page }) => {
|
||||
// and use it to calculate the target position
|
||||
await workspacePage.clickWithDragViewportAt(600, 500, 0, 300);
|
||||
|
||||
await expect(workspacePage.layers.getByText("Rectangle / Value 1")).toBeVisible();
|
||||
await expect(
|
||||
workspacePage.layers.getByText("Rectangle / Value 1"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User cut paste a component inside a variant", async ({ page }) => {
|
||||
@ -353,7 +355,10 @@ test("User cut paste a component inside a variant", async ({ page }) => {
|
||||
const variant = await findVariant(workspacePage, 0);
|
||||
|
||||
//Create a component
|
||||
await workspacePage.ellipseShapeButton.click();
|
||||
await workspacePage.selectToolFromFlyout(workspacePage, {
|
||||
triggerToolName: "Rectangle (R)",
|
||||
targetToolName: "Ellipse (E)",
|
||||
});
|
||||
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
|
||||
await workspacePage.clickLeafLayer("Ellipse");
|
||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
||||
@ -384,7 +389,10 @@ test("User cut paste a component with path inside a variant", async ({
|
||||
const variant = await findVariant(workspacePage, 0);
|
||||
|
||||
// Create a component
|
||||
await workspacePage.ellipseShapeButton.click();
|
||||
await workspacePage.selectToolFromFlyout(workspacePage, {
|
||||
triggerToolName: "Rectangle (R)",
|
||||
targetToolName: "Ellipse (E)",
|
||||
});
|
||||
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
|
||||
await workspacePage.clickLeafLayer("Ellipse");
|
||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
||||
@ -425,7 +433,10 @@ test("User drag and drop a component with path inside a variant", async ({
|
||||
const variant = findVariantNoWait(workspacePage, 0);
|
||||
|
||||
//Create a component
|
||||
await workspacePage.ellipseShapeButton.click();
|
||||
await workspacePage.selectToolFromFlyout(workspacePage, {
|
||||
triggerToolName: "Rectangle (R)",
|
||||
targetToolName: "Ellipse (E)",
|
||||
});
|
||||
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
|
||||
await workspacePage.clickLeafLayer("Ellipse");
|
||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
||||
@ -457,7 +468,10 @@ test("User cut paste a variant into another container", async ({ page }) => {
|
||||
await setupVariantsFileWithVariant(workspacePage);
|
||||
|
||||
// Create anothe variant
|
||||
await workspacePage.ellipseShapeButton.click();
|
||||
await workspacePage.selectToolFromFlyout(workspacePage, {
|
||||
triggerToolName: "Rectangle (R)",
|
||||
targetToolName: "Ellipse (E)",
|
||||
});
|
||||
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
|
||||
await workspacePage.clickLeafLayer("Ellipse");
|
||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
||||
|
||||
@ -217,6 +217,7 @@
|
||||
plugins-enabled (features/active-feature? @st/state "plugins/runtime")
|
||||
rulers-enabled (mf/deref refs/rulers?)
|
||||
toolbar-hidden (mf/deref toolbar-hidden-ref)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
display-plugins-manager (mf/use-fn
|
||||
(fn []
|
||||
(st/emit!
|
||||
@ -253,62 +254,64 @@
|
||||
(dom/blur! (dom/get-target event))
|
||||
(st/emit! (dwc/toggle-toolbar-visibility))))]
|
||||
|
||||
[:div {:role "toolbar"
|
||||
:aria-label (tr "workspace.toolbar.label")
|
||||
:tabindex "0"
|
||||
:class (stl/css-case :main-toolbar true
|
||||
:main-toolbar-no-rulers (not rulers-enabled)
|
||||
:main-toolbar-hidden toolbar-hidden)}
|
||||
[:ul {:class (stl/css :main-toolbar-options)}
|
||||
[:li {:class (stl/css :main-toolbar-option)}
|
||||
[:> tool-button* {:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||
:selected (and (nil? selected-drawing-tool)
|
||||
(not selected-edition))
|
||||
:icon i/move
|
||||
:on-click on-interrupt}]]
|
||||
(when-not ^boolean read-only?
|
||||
[:div {:role "toolbar"
|
||||
:aria-label (tr "workspace.toolbar.label")
|
||||
:tabindex "0"
|
||||
:class (stl/css-case :main-toolbar true
|
||||
:main-toolbar-no-rulers (not rulers-enabled)
|
||||
:main-toolbar-hidden toolbar-hidden)}
|
||||
[:ul {:class (stl/css :main-toolbar-options)
|
||||
:data-testid "toolbar-options"}
|
||||
[:li {:class (stl/css :main-toolbar-option)}
|
||||
[:> tool-button* {:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||
:selected (and (nil? selected-drawing-tool)
|
||||
(not selected-edition))
|
||||
:icon i/move
|
||||
:on-click on-interrupt}]]
|
||||
|
||||
[:li {:class (stl/css :main-toolbar-option)}
|
||||
[:> tool-button* {:title (tool-label :frame)
|
||||
:selected (= selected-drawing-tool :frame)
|
||||
:icon i/board
|
||||
:on-click on-select-tool
|
||||
:data-tool "frame"}]]
|
||||
[:li {:class (stl/css :main-toolbar-option)}
|
||||
[:> tool-button* {:title (tool-label :frame)
|
||||
:selected (= selected-drawing-tool :frame)
|
||||
:icon i/board
|
||||
:on-click on-select-tool
|
||||
:data-tool "frame"}]]
|
||||
|
||||
|
||||
[:> grouped-tool-flyout* {:key :shapes
|
||||
:group (get grouped-tools :shapes)
|
||||
:drawtool selected-drawing-tool
|
||||
:on-select-tool on-select-tool}]
|
||||
[:> grouped-tool-flyout* {:key :shapes
|
||||
:group (get grouped-tools :shapes)
|
||||
:drawtool selected-drawing-tool
|
||||
:on-select-tool on-select-tool}]
|
||||
|
||||
[:li {:class (stl/css :main-toolbar-option)}
|
||||
[:> tool-button* {:title (tool-label :text)
|
||||
:selected (= selected-drawing-tool :text)
|
||||
:icon i/text
|
||||
:on-click on-select-tool
|
||||
:data-tool "text"}]]
|
||||
[:li {:class (stl/css :main-toolbar-option)}
|
||||
[:> tool-button* {:title (tool-label :text)
|
||||
:selected (= selected-drawing-tool :text)
|
||||
:icon i/text
|
||||
:on-click on-select-tool
|
||||
:data-tool "text"}]]
|
||||
|
||||
[:> image-upload-tool]
|
||||
[:> image-upload-tool]
|
||||
|
||||
[:> grouped-tool-flyout* {:key :free-draw
|
||||
:group (get grouped-tools :free-draw)
|
||||
:drawtool selected-drawing-tool
|
||||
:on-select-tool on-select-tool}]
|
||||
[:> grouped-tool-flyout* {:key :free-draw
|
||||
:group (get grouped-tools :free-draw)
|
||||
:drawtool selected-drawing-tool
|
||||
:on-select-tool on-select-tool}]
|
||||
|
||||
(when plugins-enabled
|
||||
[:li {:class (stl/css :main-toolbar-option :main-toolbar-option-plugins)}
|
||||
[:> tool-button* {:title (tool-label :plugins)
|
||||
:icon i/puzzle
|
||||
:on-click display-plugins-manager
|
||||
:data-tool "plugins"}]])
|
||||
(when plugins-enabled
|
||||
[:li {:class (stl/css :main-toolbar-option :main-toolbar-option-plugins)}
|
||||
[:> tool-button* {:title (tool-label :plugins)
|
||||
:icon i/puzzle
|
||||
:on-click display-plugins-manager
|
||||
:data-tool "plugins"}]])
|
||||
|
||||
(when *assert*
|
||||
[:li {:class (stl/css :main-toolbar-option :main-toolbar-option-debug)}
|
||||
[:> tool-button* {:title (tool-label :debug)
|
||||
:selected (contains? layout :debug-panel)
|
||||
:icon i/bug
|
||||
:on-click toggle-debug-panel}]])]
|
||||
[:button {:title (tr "workspace.toolbar.toggle-toolbar")
|
||||
:aria-label (tr "workspace.toolbar.toggle-toolbar")
|
||||
:class (stl/css :toolbar-handler)
|
||||
:on-click toggle-toolbar}
|
||||
[:div {:class (stl/css :toolbar-handler-indicator)}]]]))
|
||||
(when *assert*
|
||||
[:li {:class (stl/css :main-toolbar-option :main-toolbar-option-debug)}
|
||||
[:> tool-button* {:title (tool-label :debug)
|
||||
:selected (contains? layout :debug-panel)
|
||||
:icon i/bug
|
||||
:on-click toggle-debug-panel}]])]
|
||||
[:button {:title (tr "workspace.toolbar.toggle-toolbar")
|
||||
:aria-label (tr "workspace.toolbar.toggle-toolbar")
|
||||
:class (stl/css :toolbar-handler)
|
||||
:on-click toggle-toolbar}
|
||||
[:div {:class (stl/css :toolbar-handler-indicator)}]]])))
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
.main-toolbar {
|
||||
--toolbar-position-y: #{$sz-28};
|
||||
--toolbar-offset-y: 0;
|
||||
--toolbar-offset-y: 0px;
|
||||
--menu-border-color: var(--color-background-quaternary);
|
||||
--menu-background-color: var(--color-background-primary);
|
||||
--stroke-color: var(--color-foreground-secondary);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user