mirror of
https://github.com/penpot/penpot.git
synced 2026-08-26 22:58:43 +00:00
This reverts commit 94119159d8c83048dd9229a2b9f2551966ac9596.
This commit is contained in:
parent
e8e0d68019
commit
7c19ace0f0
@ -448,6 +448,33 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||||||
await pagesToggle.click();
|
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) {
|
async moveSelectionToShape(name) {
|
||||||
await this.page.locator("rect.viewport-selrect").hover();
|
await this.page.locator("rect.viewport-selrect").hover();
|
||||||
await this.page.mouse.down();
|
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
|
// and use it to calculate the target position
|
||||||
await workspacePage.clickWithDragViewportAt(600, 500, 0, 300);
|
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 }) => {
|
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);
|
const variant = await findVariant(workspacePage, 0);
|
||||||
|
|
||||||
//Create a component
|
//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.clickWithDragViewportAt(500, 500, 20, 20);
|
||||||
await workspacePage.clickLeafLayer("Ellipse");
|
await workspacePage.clickLeafLayer("Ellipse");
|
||||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
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);
|
const variant = await findVariant(workspacePage, 0);
|
||||||
|
|
||||||
// Create a component
|
// 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.clickWithDragViewportAt(500, 500, 20, 20);
|
||||||
await workspacePage.clickLeafLayer("Ellipse");
|
await workspacePage.clickLeafLayer("Ellipse");
|
||||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
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);
|
const variant = findVariantNoWait(workspacePage, 0);
|
||||||
|
|
||||||
//Create a component
|
//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.clickWithDragViewportAt(500, 500, 20, 20);
|
||||||
await workspacePage.clickLeafLayer("Ellipse");
|
await workspacePage.clickLeafLayer("Ellipse");
|
||||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
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);
|
await setupVariantsFileWithVariant(workspacePage);
|
||||||
|
|
||||||
// Create anothe variant
|
// 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.clickWithDragViewportAt(500, 500, 20, 20);
|
||||||
await workspacePage.clickLeafLayer("Ellipse");
|
await workspacePage.clickLeafLayer("Ellipse");
|
||||||
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
await workspacePage.page.keyboard.press("ControlOrMeta+k");
|
||||||
|
|||||||
@ -23,22 +23,49 @@
|
|||||||
[:icon
|
[:icon
|
||||||
[:and :string [:fn #(contains? icon-list %)]]]
|
[:and :string [:fn #(contains? icon-list %)]]]
|
||||||
[:aria-label :string]
|
[:aria-label :string]
|
||||||
|
[:has-tooltip {:optional true} [:maybe :boolean]]
|
||||||
[:tooltip-placement {:optional true}
|
[:tooltip-placement {:optional true}
|
||||||
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]
|
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]
|
||||||
|
;; Indicates that the button has a flyout menu, and should display an indicator
|
||||||
|
[:flyout-indicator {:optional true} [:maybe :boolean]]
|
||||||
[:variant {:optional true}
|
[:variant {:optional true}
|
||||||
[:maybe [:enum "primary" "secondary" "ghost" "destructive" "action"]]]])
|
[:maybe [:enum "primary" "secondary" "ghost" "destructive" "action"]]]])
|
||||||
|
|
||||||
|
(def ^:private schema:icon-button-internal
|
||||||
|
[:map
|
||||||
|
[:icon-class {:optional true} [:maybe :string]]
|
||||||
|
[:icon-size {:optional true} [:maybe [:enum "s" "m" "l"]]]
|
||||||
|
[:icon
|
||||||
|
[:and :string [:fn #(contains? icon-list %)]]]
|
||||||
|
;; Indicates that the button has a flyout menu, and should display an indicator
|
||||||
|
[:flyout-indicator {:optional true} [:maybe :boolean]]])
|
||||||
|
|
||||||
|
(mf/defc icon-button-internal*
|
||||||
|
{::mf/schema schema:icon-button-internal
|
||||||
|
::mf/memo true}
|
||||||
|
[{:keys [icon icon-class icon-size flyout-indicator children] :rest props}]
|
||||||
|
[:> :button props
|
||||||
|
[:> icon* {:icon-id icon
|
||||||
|
:aria-hidden true
|
||||||
|
:class icon-class
|
||||||
|
:size icon-size}]
|
||||||
|
(when flyout-indicator
|
||||||
|
[:svg {:view-box "0 0 6 6"
|
||||||
|
:aria-hidden true
|
||||||
|
:class (stl/css :flyout-indicator)}
|
||||||
|
[:path {:d "M4,2 L4,3.15 C4,3.62 3.62,4 3.15,4 L2,4"
|
||||||
|
:stroke-linecap "round"}]])
|
||||||
|
children])
|
||||||
|
|
||||||
(mf/defc icon-button*
|
(mf/defc icon-button*
|
||||||
{::mf/schema schema:icon-button
|
{::mf/schema schema:icon-button
|
||||||
::mf/memo true}
|
::mf/memo true}
|
||||||
[{:keys [class icon icon-class icon-size variant aria-label children tooltip-placement tooltip-class type] :rest props}]
|
[{:keys [class icon icon-class icon-size variant aria-label children tooltip-placement tooltip-class type flyout-indicator has-tooltip] :rest props}]
|
||||||
(let [variant
|
(let [variant (d/nilv variant "primary")
|
||||||
(d/nilv variant "primary")
|
flyout-indicator (d/nilv flyout-indicator false)
|
||||||
|
has-tooltip (d/nilv has-tooltip true)
|
||||||
button-ref (mf/use-ref nil)
|
button-ref (mf/use-ref nil)
|
||||||
|
tooltip-id (mf/use-id)
|
||||||
tooltip-id
|
|
||||||
(mf/use-id)
|
|
||||||
|
|
||||||
button-class
|
button-class
|
||||||
(stl/css-case :icon-button true
|
(stl/css-case :icon-button true
|
||||||
@ -53,13 +80,19 @@
|
|||||||
{:class [class button-class]
|
{:class [class button-class]
|
||||||
:ref button-ref
|
:ref button-ref
|
||||||
:type (d/nilv type "button")
|
:type (d/nilv type "button")
|
||||||
:aria-labelledby tooltip-id})]
|
:icon icon
|
||||||
|
:icon-size icon-size
|
||||||
|
:icon-class icon-class
|
||||||
|
:flyout-indicator flyout-indicator})]
|
||||||
|
|
||||||
[:> tooltip* {:content aria-label
|
(if has-tooltip
|
||||||
:class tooltip-class
|
(let [tooltip-props (mf/spread-props props {:aria-labelledby tooltip-id})]
|
||||||
:trigger-ref button-ref
|
[:> tooltip* {:content aria-label
|
||||||
:placement tooltip-placement
|
:class tooltip-class
|
||||||
:id tooltip-id}
|
:trigger-ref button-ref
|
||||||
[:> :button props
|
:placement tooltip-placement
|
||||||
[:> icon* {:icon-id icon :aria-hidden true :class icon-class :size icon-size}]
|
:id tooltip-id}
|
||||||
children]]))
|
[:> icon-button-internal* tooltip-props children]])
|
||||||
|
|
||||||
|
(let [no-tooltip-props (mf/spread-props props {:aria-label aria-label})]
|
||||||
|
[:> icon-button-internal* no-tooltip-props children]))))
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-button-primary {
|
.icon-button-primary {
|
||||||
@ -49,3 +50,13 @@
|
|||||||
--button-width: #{$sz-24};
|
--button-width: #{$sz-24};
|
||||||
--button-height: #{$sz-24};
|
--button-height: #{$sz-24};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flyout-indicator {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-end: var(--sp-xs);
|
||||||
|
inset-inline-end: var(--sp-xs);
|
||||||
|
stroke: currentcolor;
|
||||||
|
fill: none;
|
||||||
|
inline-size: $sz-6;
|
||||||
|
block-size: $sz-6;
|
||||||
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export default {
|
|||||||
options: iconList,
|
options: iconList,
|
||||||
control: { type: "select" },
|
control: { type: "select" },
|
||||||
},
|
},
|
||||||
|
flyoutIndicator: { control: "boolean" },
|
||||||
disabled: { control: "boolean" },
|
disabled: { control: "boolean" },
|
||||||
variant: {
|
variant: {
|
||||||
options: ["primary", "secondary", "ghost", "destructive", "action"],
|
options: ["primary", "secondary", "ghost", "destructive", "action"],
|
||||||
@ -35,6 +36,7 @@ export default {
|
|||||||
variant: undefined,
|
variant: undefined,
|
||||||
"aria-label": "Lorem ipsum",
|
"aria-label": "Lorem ipsum",
|
||||||
icon: "effects",
|
icon: "effects",
|
||||||
|
flyoutIndicator: false,
|
||||||
},
|
},
|
||||||
render: ({ ...args }) => <IconButton {...args} />,
|
render: ({ ...args }) => <IconButton {...args} />,
|
||||||
};
|
};
|
||||||
@ -70,3 +72,10 @@ export const Destructive = {
|
|||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Flyout = {
|
||||||
|
args: {
|
||||||
|
variant: "ghost",
|
||||||
|
flyoutIndicator: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -20,79 +20,172 @@
|
|||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.dropdown-menu :refer [dropdown-menu* dropdown-menu-item*]]
|
[app.main.ui.components.dropdown-menu :refer [dropdown-menu* dropdown-menu-item*]]
|
||||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
[app.main.ui.components.file-uploader :as file-uploader]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||||
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc mcp-indicator*
|
(def ^:private toolbar-hidden-ref
|
||||||
[]
|
(l/derived (fn [state]
|
||||||
(let [mcp (mf/deref refs/mcp)
|
(let [visibility (get state :hide-toolbar)
|
||||||
|
path-edit-state (get state :edit-path)
|
||||||
|
selected (get state :selected)
|
||||||
|
edition (get state :edition)
|
||||||
|
|
||||||
conn-status (get mcp :connection-status)
|
is-single (= (count selected) 1)
|
||||||
has-valid-token? (get mcp :token-valid)
|
is-path-editing (and is-single (some? (get path-edit-state edition)))]
|
||||||
|
|
||||||
enabled? (get mcp :enabled)
|
(if is-path-editing true visibility)))
|
||||||
|
refs/workspace-local))
|
||||||
|
|
||||||
mcp-connected? (= "connected" conn-status)
|
(def grouped-tools
|
||||||
show-indicator? (and enabled? has-valid-token?)
|
{:shapes {:default-tool :rect
|
||||||
|
:tools {:rect {:icon i/rectangle}
|
||||||
|
:circle {:icon i/ellipse}}}
|
||||||
|
:free-draw {:default-tool :path
|
||||||
|
:tools {:path {:icon i/path}
|
||||||
|
:curve {:icon i/curve}}}})
|
||||||
|
|
||||||
menu-open* (mf/use-state false)
|
(defn- tool-label
|
||||||
menu-open? (deref menu-open*)
|
[tool]
|
||||||
|
(case tool
|
||||||
|
:move (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||||
|
:frame (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
|
||||||
|
:rect (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
|
||||||
|
:circle (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
|
||||||
|
:text (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
|
||||||
|
:path (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
|
||||||
|
:image (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
|
||||||
|
:curve (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
|
||||||
|
:plugins (tr "workspace.toolbar.plugins" (sc/get-tooltip :plugins))
|
||||||
|
:debug "Debugging tool"
|
||||||
|
(name tool)))
|
||||||
|
|
||||||
toggle-menu
|
(defn- active-group-tool
|
||||||
|
[group drawtool]
|
||||||
|
(if (contains? (:tools group) drawtool)
|
||||||
|
drawtool
|
||||||
|
(:default-tool group)))
|
||||||
|
|
||||||
|
(defn- is-selected-group
|
||||||
|
[group drawtool]
|
||||||
|
(contains? (:tools group) drawtool))
|
||||||
|
|
||||||
|
(defn- group-menu-label
|
||||||
|
[group drawtool]
|
||||||
|
(let [tool-id (active-group-tool group drawtool)]
|
||||||
|
(str (tr "labels.options") ": " (tool-label tool-id))))
|
||||||
|
|
||||||
|
(defn- cancel-timer!
|
||||||
|
[timer-ref*]
|
||||||
|
(when-let [timer (mf/ref-val timer-ref*)]
|
||||||
|
(ts/dispose! timer)
|
||||||
|
(mf/set-ref-val! timer-ref* nil)))
|
||||||
|
|
||||||
|
(mf/defc group-tool*
|
||||||
|
{::mf/private true
|
||||||
|
::mf/wrap [mf/memo]}
|
||||||
|
[{:keys [group drawtool on-select-tool]}]
|
||||||
|
(let [default-tool* (mf/use-state (active-group-tool group drawtool))
|
||||||
|
default-tool (deref default-tool*)
|
||||||
|
|
||||||
|
open* (mf/use-state false)
|
||||||
|
open (deref open*)
|
||||||
|
|
||||||
|
open-timer* (mf/use-ref nil)
|
||||||
|
close-timer* (mf/use-ref nil)
|
||||||
|
|
||||||
|
default-icon (:icon (get-in group [:tools default-tool]))
|
||||||
|
subtools (:tools group)
|
||||||
|
menu-label (group-menu-label group drawtool)
|
||||||
|
selected (boolean (is-selected-group group drawtool))
|
||||||
|
|
||||||
|
on-select-tool
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(let [tool (-> (dom/get-current-target event)
|
||||||
(swap! menu-open* not)))
|
(dom/get-data "tool")
|
||||||
|
(keyword))]
|
||||||
|
(reset! default-tool* tool)
|
||||||
|
(on-select-tool event))))
|
||||||
|
|
||||||
close-menu
|
on-display-menu
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
#(reset! menu-open* false))
|
(fn []
|
||||||
|
(cancel-timer! close-timer*)
|
||||||
|
(cancel-timer! open-timer*)
|
||||||
|
(mf/set-ref-val!
|
||||||
|
open-timer*
|
||||||
|
(ts/schedule 350
|
||||||
|
#(do
|
||||||
|
(reset! open* true)
|
||||||
|
(mf/set-ref-val! open-timer* nil))))))
|
||||||
|
|
||||||
connect-mcp
|
on-hide-menu
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
#(st/emit! (mcp/connect-mcp)
|
(fn []
|
||||||
(ev/event {::ev/name "connect-mcp-plugin"
|
(cancel-timer! open-timer*)
|
||||||
::ev/origin "workspace:toolbar"})))]
|
(cancel-timer! close-timer*)
|
||||||
(when show-indicator?
|
(mf/set-ref-val!
|
||||||
[:li
|
close-timer*
|
||||||
[:button
|
(ts/schedule 350
|
||||||
{:title (tr "workspace.toolbar.mcp")
|
#(do
|
||||||
:aria-label (tr "workspace.toolbar.mcp")
|
(reset! open* false)
|
||||||
:class (stl/css-case :main-toolbar-options-button true
|
(mf/set-ref-val! close-timer* nil))))))]
|
||||||
:mcp-button true
|
|
||||||
:selected menu-open?)
|
|
||||||
:on-click toggle-menu
|
|
||||||
:data-tool "mcp"
|
|
||||||
:data-testid "mcp-btn"}
|
|
||||||
[:span {:class (stl/css-case :mcp-status-dot true
|
|
||||||
:connected mcp-connected?)}]
|
|
||||||
[:span {:class (stl/css-case :mcp-button-label true
|
|
||||||
:connected mcp-connected?)}
|
|
||||||
(tr "workspace.toolbar.mcp")]]
|
|
||||||
[:> dropdown-menu* {:show menu-open?
|
|
||||||
:on-close close-menu
|
|
||||||
:class (stl/css :mcp-menu)}
|
|
||||||
(if mcp-connected?
|
|
||||||
[:li {:class (stl/css :mcp-menu-info)
|
|
||||||
:role "presentation"}
|
|
||||||
(tr "workspace.toolbar.mcp-connected")]
|
|
||||||
[:> dropdown-menu-item* {:class (stl/css :mcp-menu-item)
|
|
||||||
:on-click connect-mcp}
|
|
||||||
(tr "workspace.toolbar.mcp-connect-here")])]])))
|
|
||||||
|
|
||||||
(mf/defc image-upload*
|
(mf/with-effect []
|
||||||
{::mf/wrap [mf/memo]}
|
(fn []
|
||||||
|
(cancel-timer! open-timer*)
|
||||||
|
(cancel-timer! close-timer*)))
|
||||||
|
|
||||||
|
[:li {:class (stl/css :toolbar-group)
|
||||||
|
:on-pointer-enter on-display-menu
|
||||||
|
:on-pointer-leave on-hide-menu}
|
||||||
|
[:div {:role "group"
|
||||||
|
:aria-label menu-label}
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:flyout-indicator true
|
||||||
|
:aria-label (tool-label default-tool)
|
||||||
|
:aria-haspopup true
|
||||||
|
:aria-pressed selected
|
||||||
|
:aria-expanded open
|
||||||
|
:has-tooltip false
|
||||||
|
:icon default-icon
|
||||||
|
:on-click on-select-tool
|
||||||
|
:data-tool (name default-tool)}]
|
||||||
|
|
||||||
|
[:ul {:role "menu"
|
||||||
|
:class (stl/css-case :toolbar-group-flyout true
|
||||||
|
:open open)
|
||||||
|
:aria-label menu-label}
|
||||||
|
|
||||||
|
(for [[id {:keys [icon]}] subtools]
|
||||||
|
[:li {:key (name id)
|
||||||
|
:role "none"}
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:tooltip-placement "bottom"
|
||||||
|
:role "menuitemradio"
|
||||||
|
:aria-label (tool-label id)
|
||||||
|
:aria-pressed (= drawtool id)
|
||||||
|
:aria-checked (= drawtool id)
|
||||||
|
:icon icon
|
||||||
|
:on-click on-select-tool
|
||||||
|
:data-tool (name id)}]])]]]))
|
||||||
|
|
||||||
|
(mf/defc image-upload-tool*
|
||||||
|
{::mf/private true
|
||||||
|
::mf/wrap [mf/memo]}
|
||||||
[]
|
[]
|
||||||
(let [ref (mf/use-ref nil)
|
(let [ref (mf/use-ref nil)
|
||||||
file-id (mf/use-ctx ctx/current-file-id)
|
file-id (mf/use-ctx ctx/current-file-id)
|
||||||
|
|
||||||
on-click
|
on-display-uploader
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! :interrupt (dw/clear-edition-mode))
|
(st/emit! :interrupt (dw/clear-edition-mode))
|
||||||
@ -111,50 +204,115 @@
|
|||||||
:blobs (seq blobs)
|
:blobs (seq blobs)
|
||||||
:position (gpt/point x y)}]
|
:position (gpt/point x y)}]
|
||||||
(st/emit! (dwm/upload-media-workspace params)))))]
|
(st/emit! (dwm/upload-media-workspace params)))))]
|
||||||
[:li
|
|
||||||
[:button
|
|
||||||
{:title (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
|
|
||||||
:aria-label (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
|
|
||||||
:on-click on-click
|
|
||||||
:class (stl/css :main-toolbar-options-button)}
|
|
||||||
deprecated-icon/img
|
|
||||||
[:& file-uploader
|
|
||||||
{:input-id "image-upload"
|
|
||||||
:accept dwm/accept-image-types
|
|
||||||
:multi true
|
|
||||||
:ref ref
|
|
||||||
:on-selected on-selected}]]]))
|
|
||||||
|
|
||||||
(def ^:private toolbar-hidden-ref
|
[:*
|
||||||
(l/derived (fn [state]
|
[:> icon-button* {:variant "ghost"
|
||||||
(let [visibility (get state :hide-toolbar)
|
:tooltip-placement "bottom"
|
||||||
path-edit-state (get state :edit-path)
|
:aria-label (tool-label :image)
|
||||||
|
:icon i/img
|
||||||
|
:on-click on-display-uploader}]
|
||||||
|
[:& file-uploader/file-uploader {:input-id "image-upload"
|
||||||
|
:accept dwm/accept-image-types
|
||||||
|
:multi true
|
||||||
|
:ref ref
|
||||||
|
:on-selected on-selected}]]))
|
||||||
|
|
||||||
selected (get state :selected)
|
(mf/defc mcp-tool*
|
||||||
edition (get state :edition)
|
{::mf/private true
|
||||||
single? (= (count selected) 1)
|
::mf/wrap [mf/memo]}
|
||||||
|
[{:keys [is-mcp-connected]}]
|
||||||
|
(let [menu-open* (mf/use-state false)
|
||||||
|
menu-open? (deref menu-open*)
|
||||||
|
|
||||||
path-editing? (and single? (some? (get path-edit-state edition)))]
|
on-toggle-menu
|
||||||
(if path-editing? true visibility)))
|
(mf/use-fn
|
||||||
refs/workspace-local))
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(swap! menu-open* not)))
|
||||||
|
|
||||||
|
on-close-menu
|
||||||
|
(mf/use-fn
|
||||||
|
#(reset! menu-open* false))
|
||||||
|
|
||||||
|
on-connect
|
||||||
|
(mf/use-fn
|
||||||
|
#(st/emit! (mcp/connect-mcp)
|
||||||
|
(ev/event {::ev/name "connect-mcp-plugin"
|
||||||
|
::ev/origin "workspace:toolbar"})))]
|
||||||
|
|
||||||
|
[:*
|
||||||
|
[:> button* {:variant "ghost"
|
||||||
|
:on-click on-toggle-menu
|
||||||
|
:aria-pressed menu-open?
|
||||||
|
:data-tool "mcp"
|
||||||
|
:data-testid "mcp-btn"}
|
||||||
|
[:div {:class (stl/css-case :toolbar-mcp-button true
|
||||||
|
:selected menu-open?)}
|
||||||
|
[:span {:class (stl/css-case :toolbar-mcp-button-dot true
|
||||||
|
:connected is-mcp-connected)}]
|
||||||
|
[:span {:class (stl/css-case :toolbar-mcp-button-label true
|
||||||
|
:connected is-mcp-connected)}
|
||||||
|
(tr "workspace.toolbar.mcp")]]]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :toolbar-mcp-menu)}
|
||||||
|
[:> dropdown-menu* {:show menu-open?
|
||||||
|
:on-close on-close-menu
|
||||||
|
:class (stl/css :toolbar-mcp-dropdown)}
|
||||||
|
(if is-mcp-connected
|
||||||
|
[:li {:class (stl/css :toolbar-mcp-dropdown-info)
|
||||||
|
:role "presentation"}
|
||||||
|
(tr "workspace.toolbar.mcp-connected")]
|
||||||
|
[:> dropdown-menu-item* {:class (stl/css :toolbar-mcp-dropdown-item)
|
||||||
|
:on-click on-connect}
|
||||||
|
(tr "workspace.toolbar.mcp-connect-here")])]]]))
|
||||||
|
|
||||||
(mf/defc top-toolbar*
|
(mf/defc top-toolbar*
|
||||||
{::mf/memo true}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [layout]}]
|
[{:keys [layout]}]
|
||||||
(let [drawtool (mf/deref refs/selected-drawing-tool)
|
(let [selected-drawing-tool (mf/deref refs/selected-drawing-tool)
|
||||||
edition (mf/deref refs/selected-edition)
|
selected-edition (mf/deref refs/selected-edition)
|
||||||
|
rulers-enabled (mf/deref refs/rulers?)
|
||||||
|
toolbar-hidden (mf/deref toolbar-hidden-ref)
|
||||||
|
mcp (mf/deref refs/mcp)
|
||||||
|
|
||||||
profile (mf/deref refs/profile)
|
plugins-enabled? (features/active-feature? @st/state "plugins/runtime")
|
||||||
props (get profile :props)
|
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||||
|
|
||||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
mcp-conn-status (get mcp :connection-status)
|
||||||
rulers? (mf/deref refs/rulers?)
|
mcp-valid-token? (get mcp :token-valid)
|
||||||
hide-toolbar? (mf/deref toolbar-hidden-ref)
|
mcp-enabled? (get mcp :enabled)
|
||||||
|
|
||||||
interrupt
|
mcp-connected? (= "connected" mcp-conn-status)
|
||||||
(mf/use-fn #(st/emit! :interrupt (dw/clear-edition-mode)))
|
mcp-show? (and (contains? cf/flags :mcp)
|
||||||
|
mcp-enabled?
|
||||||
|
mcp-valid-token?)
|
||||||
|
|
||||||
select-drawtool
|
separator? (or plugins-enabled? *assert* mcp-show?)
|
||||||
|
|
||||||
|
on-display-plugins-manager
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (ev/event {::ev/name "open-plugins-manager"
|
||||||
|
::ev/origin "workspace:toolbar"})
|
||||||
|
(modal/show :plugin-management {}))))
|
||||||
|
|
||||||
|
on-toggle-debug-panel
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps layout)
|
||||||
|
(fn []
|
||||||
|
(let [is-sidebar-closed (contains? layout :collapse-left-sidebar)]
|
||||||
|
(when is-sidebar-closed
|
||||||
|
(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||||
|
(st/emit! (dw/remove-layout-flag :shortcuts)
|
||||||
|
(-> (dw/toggle-layout-flag :debug-panel)
|
||||||
|
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))
|
||||||
|
|
||||||
|
on-interrupt
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! :interrupt (dw/clear-edition-mode))))
|
||||||
|
|
||||||
|
on-select-tool
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [tool (-> (dom/get-current-target event)
|
(let [tool (-> (dom/get-current-target event)
|
||||||
@ -163,131 +321,91 @@
|
|||||||
(st/emit! :interrupt (dw/clear-edition-mode))
|
(st/emit! :interrupt (dw/clear-edition-mode))
|
||||||
|
|
||||||
;; Delay so anything that launched :interrupt can finish
|
;; Delay so anything that launched :interrupt can finish
|
||||||
(ts/schedule 100 #(st/emit! (dw/select-for-drawing tool))))))
|
(ts/schedule 100
|
||||||
|
#(st/emit! (dw/select-for-drawing tool))))))
|
||||||
|
|
||||||
toggle-debug-panel
|
on-toggle-toolbar
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps layout)
|
|
||||||
(fn []
|
|
||||||
(let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)]
|
|
||||||
(when is-sidebar-closed?
|
|
||||||
(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
|
||||||
(st/emit!
|
|
||||||
(dw/remove-layout-flag :shortcuts)
|
|
||||||
(-> (dw/toggle-layout-flag :debug-panel)
|
|
||||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))
|
|
||||||
|
|
||||||
toggle-toolbar
|
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/blur! (dom/get-target event))
|
(dom/blur! (dom/get-target event))
|
||||||
(st/emit! (dwc/toggle-toolbar-visibility))))
|
(st/emit! (dwc/toggle-toolbar-visibility))))]
|
||||||
|
|
||||||
test-tooltip-board-text
|
|
||||||
(if (not (:workspace-visited props))
|
|
||||||
(tr "workspace.toolbar.frame-first-time" (sc/get-tooltip :draw-frame))
|
|
||||||
(tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)))]
|
|
||||||
|
|
||||||
(when-not ^boolean read-only?
|
(when-not ^boolean read-only?
|
||||||
[:aside {:class (stl/css-case :main-toolbar true
|
[:div {:role "toolbar"
|
||||||
:main-toolbar-no-rulers (not rulers?)
|
:aria-label (tr "workspace.toolbar.label")
|
||||||
:main-toolbar-hidden hide-toolbar?)}
|
:tabindex "0"
|
||||||
[:ul {:class (stl/css :main-toolbar-options)
|
:class (stl/css-case :toolbar true
|
||||||
|
:no-rulers (not rulers-enabled)
|
||||||
|
:hidden toolbar-hidden)}
|
||||||
|
[:ul {:class (stl/css :toolbar-options)
|
||||||
:data-testid "toolbar-options"}
|
:data-testid "toolbar-options"}
|
||||||
[:li
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
[:button
|
[:> icon-button* {:variant "ghost"
|
||||||
{:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
:tooltip-placement "bottom"
|
||||||
:aria-label (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
:aria-pressed (and (nil? selected-drawing-tool)
|
||||||
:class (stl/css-case :main-toolbar-options-button true
|
(not selected-edition))
|
||||||
:selected (and (nil? drawtool)
|
:aria-label (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||||
(not edition)))
|
:icon i/move
|
||||||
:on-click interrupt}
|
:on-click on-interrupt}]]
|
||||||
deprecated-icon/move]]
|
|
||||||
[:*
|
|
||||||
[:li
|
|
||||||
[:button
|
|
||||||
{:title test-tooltip-board-text
|
|
||||||
:aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
|
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :frame))
|
|
||||||
:on-click select-drawtool
|
|
||||||
:data-tool "frame"
|
|
||||||
:data-testid "artboard-btn"}
|
|
||||||
deprecated-icon/board]]
|
|
||||||
[:li
|
|
||||||
[:button
|
|
||||||
{:title (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
|
|
||||||
:aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
|
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :rect))
|
|
||||||
:on-click select-drawtool
|
|
||||||
:data-tool "rect"
|
|
||||||
:data-testid "rect-btn"}
|
|
||||||
deprecated-icon/rectangle]]
|
|
||||||
[:li
|
|
||||||
[:button
|
|
||||||
{:title (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
|
|
||||||
:aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
|
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :circle))
|
|
||||||
:on-click select-drawtool
|
|
||||||
:data-tool "circle"
|
|
||||||
:data-testid "ellipse-btn"}
|
|
||||||
deprecated-icon/ellipse]]
|
|
||||||
[:li
|
|
||||||
[:button
|
|
||||||
{:title (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
|
|
||||||
:aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
|
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :text))
|
|
||||||
:on-click select-drawtool
|
|
||||||
:data-tool "text"}
|
|
||||||
deprecated-icon/text]]
|
|
||||||
|
|
||||||
[:> image-upload*]
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:tooltip-placement "bottom"
|
||||||
|
:aria-pressed (= selected-drawing-tool :frame)
|
||||||
|
:aria-label (tool-label :frame)
|
||||||
|
:icon i/board
|
||||||
|
:on-click on-select-tool
|
||||||
|
:data-tool "frame"}]]
|
||||||
|
|
||||||
[:li
|
[:> group-tool* {:key :shapes
|
||||||
[:button
|
:group (get grouped-tools :shapes)
|
||||||
{:title (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
|
:drawtool selected-drawing-tool
|
||||||
:aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
|
:on-select-tool on-select-tool}]
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :curve))
|
|
||||||
:on-click select-drawtool
|
|
||||||
:data-tool "curve"
|
|
||||||
:data-testid "curve-btn"}
|
|
||||||
deprecated-icon/curve]]
|
|
||||||
[:li
|
|
||||||
[:button
|
|
||||||
{:title (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
|
|
||||||
:aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
|
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (= drawtool :path))
|
|
||||||
:on-click select-drawtool
|
|
||||||
:data-tool "path"
|
|
||||||
:data-testid "path-btn"}
|
|
||||||
deprecated-icon/path]]
|
|
||||||
|
|
||||||
(when (features/active-feature? @st/state "plugins/runtime")
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
[:li
|
[:> icon-button* {:variant "ghost"
|
||||||
[:button
|
:tooltip-placement "bottom"
|
||||||
{:title (tr "workspace.toolbar.plugins" (sc/get-tooltip :plugins))
|
:aria-pressed (= selected-drawing-tool :text)
|
||||||
:aria-label (tr "workspace.toolbar.plugins" (sc/get-tooltip :plugins))
|
:aria-label (tool-label :text)
|
||||||
:class (stl/css :main-toolbar-options-button)
|
:icon i/text
|
||||||
:on-click #(st/emit!
|
:on-click on-select-tool
|
||||||
(ev/event {::ev/name "open-plugins-manager"
|
:data-tool "text"}]]
|
||||||
::ev/origin "workspace:toolbar"})
|
|
||||||
(modal/show :plugin-management {}))
|
|
||||||
:data-tool "plugins"
|
|
||||||
:data-testid "plugins-btn"}
|
|
||||||
deprecated-icon/puzzle]])
|
|
||||||
|
|
||||||
(when *assert*
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
[:li
|
[:> image-upload-tool*]]
|
||||||
[:button
|
|
||||||
{:title "Debugging tool"
|
|
||||||
:class (stl/css-case :main-toolbar-options-button true :selected (contains? layout :debug-panel))
|
|
||||||
:on-click toggle-debug-panel}
|
|
||||||
deprecated-icon/bug]])
|
|
||||||
|
|
||||||
(when (contains? cf/flags :mcp)
|
[:> group-tool* {:key :free-draw
|
||||||
[:> mcp-indicator*])]]
|
:group (get grouped-tools :free-draw)
|
||||||
|
:drawtool selected-drawing-tool
|
||||||
|
:on-select-tool on-select-tool}]
|
||||||
|
|
||||||
|
(when separator?
|
||||||
|
[:div {:class (stl/css :toolbar-separator)}])
|
||||||
|
|
||||||
|
(when plugins-enabled?
|
||||||
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:tooltip-placement "bottom"
|
||||||
|
:aria-label (tool-label :plugins)
|
||||||
|
:icon i/puzzle
|
||||||
|
:on-click on-display-plugins-manager
|
||||||
|
:data-tool "plugins"}]])
|
||||||
|
|
||||||
|
(when *assert*
|
||||||
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:tooltip-placement "bottom"
|
||||||
|
:aria-pressed (contains? layout :debug-panel)
|
||||||
|
:aria-label (tool-label :debug)
|
||||||
|
:icon i/bug
|
||||||
|
:on-click on-toggle-debug-panel}]])
|
||||||
|
|
||||||
|
(when mcp-show?
|
||||||
|
[:li {:class (stl/css :toolbar-option)}
|
||||||
|
[:> mcp-tool* {:is-mcp-connected mcp-connected?}]])]
|
||||||
|
|
||||||
[:button {:title (tr "workspace.toolbar.toggle-toolbar")
|
[:button {:title (tr "workspace.toolbar.toggle-toolbar")
|
||||||
:aria-label (tr "workspace.toolbar.toggle-toolbar")
|
:aria-label (tr "workspace.toolbar.toggle-toolbar")
|
||||||
:class (stl/css :toolbar-handler)
|
:class (stl/css :toolbar-handler)
|
||||||
:on-click toggle-toolbar}
|
:on-click on-toggle-toolbar}
|
||||||
[:div {:class (stl/css :toolbar-handler-btn)}]]])))
|
[:div {:class (stl/css :toolbar-handler-indicator)}]]])))
|
||||||
|
|||||||
@ -4,109 +4,118 @@
|
|||||||
//
|
//
|
||||||
// Copyright (c) KALEIDOS INC Sucursal en España SL
|
// Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
|
||||||
@use "ds/_borders.scss" as *;
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
@use "ds/_utils.scss" as *;
|
@use "ds/_utils.scss" as *;
|
||||||
|
@use "ds/spacing.scss" as *;
|
||||||
@use "ds/typography.scss" as t;
|
@use "ds/typography.scss" as t;
|
||||||
|
@use "ds/z-index.scss" as *;
|
||||||
|
|
||||||
.main-toolbar {
|
.toolbar {
|
||||||
cursor: initial;
|
--toolbar-position-y: #{$sz-28};
|
||||||
|
--toolbar-offset-y: 0px;
|
||||||
|
--menu-border-color: var(--color-background-quaternary);
|
||||||
|
--menu-background-color: var(--color-background-primary);
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
inset-inline-start: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: deprecated.$s-56;
|
padding: var(--sp-m);
|
||||||
padding: deprecated.$s-8 deprecated.$s-16;
|
border-radius: $br-8;
|
||||||
border-radius: deprecated.$s-8;
|
border: $b-2 solid var(--menu-border-color);
|
||||||
border: deprecated.$s-2 solid var(--panel-border-color);
|
z-index: var(--z-index-panels);
|
||||||
z-index: deprecated.$z-index-1;
|
background-color: var(--menu-background-color);
|
||||||
background-color: var(--color-background-primary);
|
|
||||||
transition:
|
transition:
|
||||||
top 0.3s,
|
inset-block-start 0.3s,
|
||||||
height 0.3s,
|
block-size 0.3s,
|
||||||
opacity 0.3s;
|
opacity 0.3s;
|
||||||
|
inset-block-start: calc(var(--toolbar-position-y) + var(--toolbar-offset-y));
|
||||||
|
will-change: auto;
|
||||||
|
|
||||||
--toolbar-position-y: #{deprecated.$s-28};
|
&.no-rulers {
|
||||||
--toolbar-offset-y: 0px;
|
--toolbar-position-y: 0px;
|
||||||
|
--toolbar-offset-y: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
top: calc(var(--toolbar-position-y) + var(--toolbar-offset-y));
|
&.hidden {
|
||||||
}
|
--toolbar-offset-y: -4px;
|
||||||
|
|
||||||
.main-toolbar-no-rulers {
|
block-size: $sz-16;
|
||||||
--toolbar-position-y: 0px;
|
z-index: 1;
|
||||||
--toolbar-offset-y: #{deprecated.$s-8};
|
border-radius: 0 0 $br-8 $br-8;
|
||||||
}
|
border-block-start: 0;
|
||||||
|
|
||||||
.main-toolbar-hidden {
|
.toolbar-options {
|
||||||
--toolbar-offset-y: calc(-1 * #{deprecated.$s-4});
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
height: deprecated.$s-16;
|
}
|
||||||
z-index: deprecated.$z-index-1;
|
|
||||||
border-radius: 0 0 deprecated.$s-8 deprecated.$s-8;
|
|
||||||
border-block-start: 0;
|
|
||||||
|
|
||||||
.main-toolbar-options {
|
|
||||||
opacity: deprecated.$op-0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-toolbar-options {
|
.toolbar-options {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: var(--sp-xs);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
opacity: deprecated.$op-10;
|
opacity: 1;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
.toolbar-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-group {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-group-flyout {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: 140%;
|
||||||
|
inset-inline-start: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
margin-block-start: var(--sp-xxs);
|
||||||
|
padding: var(--sp-xxs) 0;
|
||||||
|
border-radius: $br-8;
|
||||||
|
border: #{$b-1} solid var(--menu-border-color);
|
||||||
|
background-color: var(--menu-background-color);
|
||||||
|
z-index: var(--z-index-panels);
|
||||||
|
display: flex;
|
||||||
|
transition:
|
||||||
|
opacity 80ms ease-out,
|
||||||
|
visibility 80ms linear;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 0s;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-toolbar-options-button {
|
.toolbar-mcp-button {
|
||||||
@extend %button-tertiary;
|
|
||||||
|
|
||||||
height: deprecated.$s-36;
|
|
||||||
width: deprecated.$s-36;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: deprecated.$s-8;
|
|
||||||
margin: 0 deprecated.$s-2;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
@extend %button-icon;
|
|
||||||
|
|
||||||
stroke: var(--color-foreground-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
@extend %button-icon-selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mcp-button {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--sp-xs);
|
gap: var(--sp-xs);
|
||||||
width: fit-content;
|
|
||||||
margin-inline-start: var(--sp-xs);
|
|
||||||
padding-inline: var(--sp-s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mcp-button-label {
|
.toolbar-mcp-button-label {
|
||||||
@include t.use-typography("body-small");
|
|
||||||
|
|
||||||
&.connected {
|
&.connected {
|
||||||
color: var(--color-accent-primary);
|
color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mcp-status-dot {
|
.toolbar-mcp-button-dot {
|
||||||
// Connection indicator placed before the label, vertically centered:
|
|
||||||
// a muted gray when disconnected, the primary accent when connected.
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
inline-size: px2rem(6);
|
inline-size: px2rem(6);
|
||||||
block-size: px2rem(6);
|
block-size: px2rem(6);
|
||||||
@ -143,23 +152,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mcp-menu {
|
.toolbar-mcp-menu {
|
||||||
@include deprecated.menu-shadow;
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset-block-start: calc(100% + var(--sp-xs));
|
left: 0;
|
||||||
inset-inline-start: var(--sp-xs);
|
top: $sz-36;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-mcp-dropdown {
|
||||||
|
box-shadow: 0 0 $sz-12 0 var(--color-shadow-dark);
|
||||||
z-index: var(--z-index-dropdown);
|
z-index: var(--z-index-dropdown);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--sp-xs);
|
padding: var(--sp-xs);
|
||||||
list-style: none;
|
|
||||||
border: $b-1 solid var(--panel-border-color);
|
border: $b-1 solid var(--panel-border-color);
|
||||||
border-radius: $br-8;
|
border-radius: $br-8;
|
||||||
background-color: var(--menu-background-color);
|
background-color: var(--menu-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-interactive informational text inside the menu (no hover, not focusable).
|
.toolbar-mcp-dropdown-info {
|
||||||
.mcp-menu-info {
|
|
||||||
@include t.use-typography("body-small");
|
@include t.use-typography("body-small");
|
||||||
|
|
||||||
padding: var(--sp-s) var(--sp-m);
|
padding: var(--sp-s) var(--sp-m);
|
||||||
@ -167,7 +176,7 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mcp-menu-item {
|
.toolbar-mcp-dropdown-item {
|
||||||
@include t.use-typography("body-small");
|
@include t.use-typography("body-small");
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -182,26 +191,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbar-separator {
|
||||||
|
--separator-color: var(--color-background-quaternary);
|
||||||
|
|
||||||
|
margin: 0 var(--sp-m);
|
||||||
|
border-inline-start: $b-1 solid var(--separator-color);
|
||||||
|
block-size: $sz-32;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar-handler {
|
.toolbar-handler {
|
||||||
@include deprecated.flex-center;
|
block-size: $sz-12;
|
||||||
@include deprecated.button-style;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
inset-inline: 0;
|
||||||
bottom: 0;
|
inset-block-end: 0;
|
||||||
height: deprecated.$s-12;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.toolbar-handler-btn {
|
|
||||||
height: deprecated.$s-4;
|
|
||||||
width: 100%;
|
|
||||||
max-width: deprecated.$s-64;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: deprecated.$s-4;
|
|
||||||
background-color: var(--palette-handler-background-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.main-toolbar-panels {
|
.toolbar-handler-indicator {
|
||||||
display: none;
|
block-size: px2rem(4);
|
||||||
|
inline-size: $sz-64;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: $br-4;
|
||||||
|
background-color: var(--color-accent-default);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,9 @@
|
|||||||
[app.main.ui.workspace.viewport.selection :as selection]
|
[app.main.ui.workspace.viewport.selection :as selection]
|
||||||
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
||||||
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
||||||
[app.main.ui.workspace.viewport.top-bar :refer [grid-edition-bar* path-edition-bar* view-only-bar*]]
|
[app.main.ui.workspace.viewport.top-bar :refer [grid-edition-bar*
|
||||||
|
path-edition-bar*
|
||||||
|
view-only-bar*]]
|
||||||
[app.main.ui.workspace.viewport.utils :as utils]
|
[app.main.ui.workspace.viewport.utils :as utils]
|
||||||
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
|
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
|
||||||
[app.main.ui.workspace.viewport.widgets :as widgets]
|
[app.main.ui.workspace.viewport.widgets :as widgets]
|
||||||
|
|||||||
@ -51,7 +51,9 @@
|
|||||||
[app.main.ui.workspace.viewport.selection :as selection]
|
[app.main.ui.workspace.viewport.selection :as selection]
|
||||||
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
||||||
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
||||||
[app.main.ui.workspace.viewport.top-bar :refer [path-edition-bar* grid-edition-bar* view-only-bar*]]
|
[app.main.ui.workspace.viewport.top-bar :refer [grid-edition-bar*
|
||||||
|
path-edition-bar*
|
||||||
|
view-only-bar*]]
|
||||||
[app.main.ui.workspace.viewport.utils :as utils]
|
[app.main.ui.workspace.viewport.utils :as utils]
|
||||||
[app.main.ui.workspace.viewport.viewport-ref :as vp-ref :refer [create-viewport-ref]]
|
[app.main.ui.workspace.viewport.viewport-ref :as vp-ref :refer [create-viewport-ref]]
|
||||||
[app.main.ui.workspace.viewport.widgets :as widgets]
|
[app.main.ui.workspace.viewport.widgets :as widgets]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user