mirror of
https://github.com/penpot/penpot.git
synced 2026-04-27 20:28:11 +00:00
WIP kakota
This commit is contained in:
parent
4ca4b43973
commit
3724f5b9f3
@ -85,11 +85,10 @@
|
||||
;; :r4 nil}
|
||||
;;
|
||||
(defn get-attrs-multi
|
||||
([objs attrs]
|
||||
(get-attrs-multi objs attrs default-equal identity))
|
||||
|
||||
([objs attrs eqfn sel]
|
||||
([objs attrs origin]
|
||||
(get-attrs-multi objs attrs origin default-equal identity))
|
||||
|
||||
([objs attrs origin eqfn sel]
|
||||
(loop [attr (first attrs)
|
||||
attrs (rest attrs)
|
||||
result (transient {})]
|
||||
@ -107,7 +106,11 @@
|
||||
:multiple
|
||||
value)
|
||||
(= new-val :multiple) :multiple
|
||||
(= value ::unset) (sel new-val)
|
||||
(= value ::unset)
|
||||
(if (and (= origin :text-multi)(= attr :typography-ref-id))
|
||||
:multiple
|
||||
(sel new-val))
|
||||
|
||||
(eqfn new-val value) value
|
||||
:else :multiple)]
|
||||
|
||||
@ -134,6 +137,6 @@
|
||||
text-node-attrs (->> attrs (filter (set txt/text-node-attrs)))]
|
||||
(merge
|
||||
defaults
|
||||
(get-attrs-multi (->> (txt/node-seq txt/is-root-node? content)) root-attrs)
|
||||
(get-attrs-multi (->> (txt/node-seq txt/is-paragraph-node? content)) paragraph-attrs)
|
||||
(get-attrs-multi (->> (txt/node-seq txt/is-text-node? content)) text-node-attrs))))
|
||||
(get-attrs-multi (->> (txt/node-seq txt/is-root-node? content)) root-attrs :text-multi)
|
||||
(get-attrs-multi (->> (txt/node-seq txt/is-paragraph-node? content)) paragraph-attrs :text-multi)
|
||||
(get-attrs-multi (->> (txt/node-seq txt/is-text-node? content)) text-node-attrs :text-multi))))
|
||||
|
||||
159
common/test/common_tests/attrs_test.cljc
Normal file
159
common/test/common_tests/attrs_test.cljc
Normal file
@ -0,0 +1,159 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.attrs-test
|
||||
(:require
|
||||
[app.common.attrs :as attrs]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest get-attrs-multi-same-value
|
||||
(t/testing "returns value when all objects have the same attribute value"
|
||||
(let [objs [{:attr "red"}
|
||||
{:attr "red"}
|
||||
{:attr "red"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr "red"} result))))
|
||||
|
||||
(t/testing "returns nil when all objects have nil value"
|
||||
(let [objs [{:attr nil}
|
||||
{:attr nil}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr nil} result)))))
|
||||
|
||||
(t/deftest get-attrs-multi-different-values
|
||||
(t/testing "returns :multiple when objects have different concrete values"
|
||||
(let [objs [{:attr "red"}
|
||||
{:attr "blue"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr :multiple} result)))))
|
||||
|
||||
(t/deftest get-attrs-multi-missing-key
|
||||
(t/testing "returns :multiple when one object has the attribute and another doesn't"
|
||||
(let [objs [{:attr "red"}
|
||||
{:other "value"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr :multiple} result))))
|
||||
|
||||
(t/testing "returns :multiple when one object has UUID and another is missing"
|
||||
(let [uuid #uuid "550e8400-e29b-41d4-a716-446655440000"
|
||||
objs [{:attr uuid}
|
||||
{:other "value"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr :multiple} result))))
|
||||
|
||||
(t/testing "returns value when missing key comes first and is not text"
|
||||
(let [objs [{:other "value"}
|
||||
{:attr "red"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr "red"} result))))
|
||||
|
||||
(t/testing "returns :multiple when some objects have the key and some don't"
|
||||
(let [objs [{:attr "red"}
|
||||
{:other "value"}
|
||||
{:attr "blue"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr :multiple} result))))
|
||||
|
||||
(t/testing "returns :multiple when one object has nil and another is missing"
|
||||
(let [objs [{:attr nil}
|
||||
{:other "value"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {:attr :multiple} result)))))
|
||||
|
||||
(t/deftest get-attrs-multi-all-missing
|
||||
(t/testing "all missing → attribute NOT included in result"
|
||||
(let [objs [{:other "value"}
|
||||
{:different "data"}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {} result)
|
||||
"Attribute should not be in result when all objects are missing")))
|
||||
|
||||
(t/testing "all missing with empty maps → attribute NOT included"
|
||||
(let [objs [{} {}]
|
||||
result (attrs/get-attrs-multi objs [:attr] :multi)]
|
||||
(t/is (= {} result)
|
||||
"Attribute should not be in result"))))
|
||||
|
||||
(t/deftest get-attrs-multi-multiple-attributes
|
||||
(t/testing "handles multiple attributes with different merge results"
|
||||
(let [objs [{:attr1 "red" :attr2 "blue"}
|
||||
{:attr1 "red" :attr2 "green"}
|
||||
{:attr1 "red"}] ; :attr2 missing
|
||||
result (attrs/get-attrs-multi objs [:attr1 :attr2] :multi)]
|
||||
(t/is (= {:attr1 "red" :attr2 :multiple} result))))
|
||||
|
||||
(t/testing "handles mixed scenarios: same, different, and missing"
|
||||
(let [uuid #uuid "550e8400-e29b-41d4-a716-446655440000"
|
||||
uuid2 #uuid "550e8400-e29b-41d4-a716-446655440001"
|
||||
objs [{:id :a :ref uuid}
|
||||
{:id :b :ref uuid2}
|
||||
{:id :c}] ; :ref missing
|
||||
result (attrs/get-attrs-multi objs [:id :ref] :multi)]
|
||||
(t/is (= {:id :multiple :ref :multiple} result)))))
|
||||
|
||||
(t/deftest get-attrs-multi-typography-ref-id-scenario
|
||||
(t/testing "the specific bug scenario: typography-ref-id with UUID vs missing"
|
||||
(let [uuid #uuid "550e8400-e29b-41d4-a716-446655440000"
|
||||
;; Shape 1 has typography-ref-id with a UUID
|
||||
shape1 {:id :shape1 :typography-ref-id uuid}
|
||||
;; Shape 2 does NOT have typography-ref-id at all
|
||||
shape2 {:id :shape2}
|
||||
result (attrs/get-attrs-multi [shape1 shape2] [:typography-ref-id] :multi)]
|
||||
(t/is (= {:typography-ref-id :multiple} result)
|
||||
"Expected :multiple when one shape has the UUID and another is missing the key")))
|
||||
|
||||
(t/testing "both shapes missing → attribute NOT included in result"
|
||||
(let [shape1 {:id :shape1}
|
||||
shape2 {:id :shape2}
|
||||
result (attrs/get-attrs-multi [shape1 shape2] [:typography-ref-id] :multi)]
|
||||
(t/is (= {} result)
|
||||
"Expected empty map when all shapes are missing the attribute"))))
|
||||
|
||||
(t/deftest get-attrs-multi-bug-missing-vs-present
|
||||
(t/testing "BUG FIXED: one shape has :typography-ref-id, other does NOT → returns :multiple"
|
||||
(let [uuid #uuid "550e8400-e29b-41d4-a716-446655440000"
|
||||
shape1 {:id :shape1 :typography-ref-id uuid}
|
||||
shape2 {:id :shape2}
|
||||
result (attrs/get-attrs-multi [shape1 shape2] [:typography-ref-id] :multi)]
|
||||
(t/is (= {:typography-ref-id :multiple} result)
|
||||
"Expected :multiple when one shape has ref and another doesn't")))
|
||||
|
||||
(t/testing "both missing → empty map (attribute not in result)"
|
||||
(let [shape1 {:id :shape1}
|
||||
shape2 {:id :shape2}
|
||||
result (attrs/get-attrs-multi [shape1 shape2] [:typography-ref-id] :multi)]
|
||||
(t/is (= {} result)
|
||||
"Expected empty map when all shapes are missing the attribute")))
|
||||
|
||||
(t/testing "both equal values → return the value"
|
||||
(let [uuid #uuid "550e8400-e29b-41d4-a716-446655440000"
|
||||
shape1 {:id :shape1 :typography-ref-id uuid}
|
||||
shape2 {:id :shape2 :typography-ref-id uuid}
|
||||
result (attrs/get-attrs-multi [shape1 shape2] [:typography-ref-id] :multi-text)]
|
||||
(t/is (= {:typography-ref-id uuid} result))))
|
||||
|
||||
(t/testing "different values → return :multiple"
|
||||
(let [uuid1 #uuid "550e8400-e29b-41d4-a716-446655440000"
|
||||
uuid2 #uuid "550e8400-e29b-41d4-a716-446655440001"
|
||||
shape1 {:id :shape1 :typography-ref-id uuid1}
|
||||
shape2 {:id :shape2 :typography-ref-id uuid2}
|
||||
result (attrs/get-attrs-multi [shape1 shape2] [:typography-ref-id] :multi)]
|
||||
(t/is (= {:typography-ref-id :multiple} result)))))
|
||||
|
||||
(t/deftest get-attrs-multi-default-equal
|
||||
(t/testing "numbers use close? for equality"
|
||||
(let [objs [{:value 1.0}
|
||||
{:value 1.0000001}]
|
||||
result (attrs/get-attrs-multi objs [:value] :multi)]
|
||||
(t/is (= {:value 1.0} result)
|
||||
"Numbers within tolerance should be considered equal")))
|
||||
|
||||
(t/testing "different floating point positions beyond tolerance are :multiple"
|
||||
(let [objs [{:x -26}
|
||||
{:x -153}]
|
||||
result (attrs/get-attrs-multi objs [:x] :multi)]
|
||||
(t/is (= {:x :multiple} result)
|
||||
"Different positions should be :multiple"))))
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
#?(:clj [common-tests.fressian-test])
|
||||
[clojure.test :as t]
|
||||
[common-tests.attrs-test]
|
||||
[common-tests.buffer-test]
|
||||
[common-tests.colors-test]
|
||||
[common-tests.data-test]
|
||||
@ -85,6 +86,7 @@
|
||||
(defn -main
|
||||
[& args]
|
||||
(t/run-tests
|
||||
'common-tests.attrs-test
|
||||
'common-tests.buffer-test
|
||||
'common-tests.colors-test
|
||||
'common-tests.data-test
|
||||
|
||||
234
frontend/playwright/ui/specs/multiselection.spec.js
Normal file
234
frontend/playwright/ui/specs/multiselection.spec.js
Normal file
@ -0,0 +1,234 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WasmWorkspacePage.init(page);
|
||||
});
|
||||
|
||||
test("Multiselection - check multiple values in measures", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-copy-paste.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-copy-paste-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||
});
|
||||
|
||||
// Select first shape (single selection first)
|
||||
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
|
||||
|
||||
// === CHECK SINGLE SELECTION - ALL MEASURE FIELDS ===
|
||||
const measuresSection = workspacePage.rightSidebar.getByRole('region', { name: 'shape-measures-section' });
|
||||
await expect(measuresSection).toBeVisible();
|
||||
|
||||
// Width
|
||||
const widthInput = measuresSection.getByTitle('Width', { exact: true }).getByRole('textbox');
|
||||
await expect(widthInput).toHaveValue("360");
|
||||
|
||||
// Height
|
||||
const heightInput = measuresSection.getByTitle('Height', { exact: true }).getByRole('textbox');
|
||||
await expect(heightInput).toHaveValue("53");
|
||||
|
||||
// X Position (using "X axis" title)
|
||||
const xPosInput = measuresSection.getByTitle('X axis', { exact: true }).getByRole('textbox');
|
||||
await expect(xPosInput).toHaveValue("1094");
|
||||
|
||||
// Y Position (using "Y axis" title)
|
||||
const yPosInput = measuresSection.getByTitle('Y axis', { exact: true }).getByRole('textbox');
|
||||
await expect(yPosInput).toHaveValue("856");
|
||||
|
||||
// === CHECK MULTI-SELECTION - MIXED VALUES ===
|
||||
// Shift+click to add second layer to selection
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click({ modifiers: ['Shift'] });
|
||||
|
||||
// All measure fields should show "Mixed" placeholder when values differ
|
||||
await expect(widthInput).toHaveAttribute('placeholder', 'Mixed');
|
||||
await expect(heightInput).toHaveAttribute('placeholder', 'Mixed');
|
||||
await expect(xPosInput).toHaveAttribute('placeholder', 'Mixed');
|
||||
await expect(yPosInput).toHaveAttribute('placeholder', 'Mixed');
|
||||
});
|
||||
|
||||
test("Multiselection - check typography multiple values", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-copy-paste.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-copy-paste-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||
});
|
||||
|
||||
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
|
||||
|
||||
// Text section
|
||||
const textSection = workspacePage.rightSidebar.getByRole('region', { name: "Text section" });
|
||||
await expect(textSection).toBeVisible();
|
||||
|
||||
// Single selection - show typography name (not multiple)
|
||||
await expect(textSection.getByText("Multiple typographies")).not.toBeVisible();
|
||||
|
||||
// Multi-selection
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click({ modifiers: ['Shift'] });
|
||||
|
||||
// Should show "Multiple typographies"
|
||||
await expect(textSection.getByText("Multiple typographies")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Multiselection - check fill multiple values", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-copy-paste.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-copy-paste-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||
});
|
||||
|
||||
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
|
||||
|
||||
// Fill section
|
||||
const fillSection = workspacePage.rightSidebar.getByRole('region', { name: "Fill section" });
|
||||
await expect(fillSection).toBeVisible();
|
||||
|
||||
// Single selection - fill color should be visible (not "Mixed")
|
||||
await expect(fillSection.getByText(/Mixed/i)).not.toBeVisible();
|
||||
|
||||
// Multi-selection with Shift+click
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click({ modifiers: ['Shift'] });
|
||||
|
||||
// Should show "Mixed" for fills when shapes have different fill colors
|
||||
await expect(fillSection.getByText('Mixed')).toBeVisible();
|
||||
});
|
||||
|
||||
test("Multiselection - check stroke multiple values", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-copy-paste.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-copy-paste-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||
});
|
||||
|
||||
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
|
||||
|
||||
// Stroke section
|
||||
const strokeSection = workspacePage.rightSidebar.getByRole('region', { name: "Stroke section" });
|
||||
await expect(strokeSection).toBeVisible();
|
||||
|
||||
// Single selection - stroke should be visible (not "Mixed")
|
||||
await expect(strokeSection.getByText(/Mixed/i)).not.toBeVisible();
|
||||
|
||||
// Multi-selection
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click({ modifiers: ['Shift'] });
|
||||
|
||||
// Should show "Mixed" for strokes when shapes have different stroke colors
|
||||
await expect(strokeSection.getByText('Mixed')).toBeVisible();
|
||||
});
|
||||
|
||||
test("Multiselection - check rotation multiple values", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-copy-paste.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-copy-paste-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||
});
|
||||
|
||||
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Measures section contains rotation
|
||||
const measuresSection = workspacePage.rightSidebar.getByRole('region', { name: 'shape-measures-section' });
|
||||
await expect(measuresSection).toBeVisible();
|
||||
|
||||
// Rotation field exists
|
||||
const rotationInput = measuresSection.getByTitle('Rotation', { exact: true }).getByRole('textbox');
|
||||
await expect(rotationInput).toBeVisible();
|
||||
|
||||
// Rotate that shape
|
||||
await rotationInput.fill("45");
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(rotationInput).toHaveValue("45"); // Rotation should be 45
|
||||
|
||||
// Multi-selection
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click({ modifiers: ['Shift'] });
|
||||
|
||||
// Rotation should show "Mixed" placeholder
|
||||
await expect(rotationInput).toHaveAttribute('placeholder', 'Mixed');
|
||||
});
|
||||
|
||||
test("Multiselection typographies in reverse order", async ({ page }) => {
|
||||
const workspacePage = new WasmWorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await workspacePage.mockRPC(
|
||||
/get\-file\?/,
|
||||
"workspace/get-file-copy-paste.json",
|
||||
);
|
||||
await workspacePage.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"workspace/get-file-copy-paste-fragment.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||
});
|
||||
|
||||
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Text section
|
||||
const textSection = workspacePage.rightSidebar.getByRole('region', { name: "Text section" });
|
||||
await expect(textSection).not.toBeVisible();
|
||||
|
||||
// Single selection - show typography name (not multiple)
|
||||
await expect(textSection.getByText("Multiple typographies")).not.toBeVisible();
|
||||
|
||||
// Multi-selection
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click({ modifiers: ['Shift'] });
|
||||
await expect(textSection).toBeVisible();
|
||||
// Should show "Multiple typographies"
|
||||
await expect(textSection.getByText("Multiple typographies")).toBeVisible();
|
||||
});
|
||||
@ -738,7 +738,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Check if token pill is visible on right sidebar
|
||||
const strokeSectionSidebar = rightSidebar.getByRole("region", {
|
||||
name: "stroke-section",
|
||||
name: "Stroke section",
|
||||
});
|
||||
await expect(strokeSectionSidebar).toBeVisible();
|
||||
const firstStrokeRow = strokeSectionSidebar.getByLabel("stroke-row-0");
|
||||
|
||||
@ -1016,7 +1016,7 @@
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
selected (dsh/lookup-selected state)
|
||||
selected-obj (-> (map #(get objects %) selected))
|
||||
multi (attrs/get-attrs-multi selected-obj [:proportion-lock])
|
||||
multi (attrs/get-attrs-multi selected-obj [:proportion-lock] :lock)
|
||||
multi? (= :multiple (:proportion-lock multi))]
|
||||
(if multi?
|
||||
(rx/of (dwsh/update-shapes selected #(assoc % :proportion-lock true)))
|
||||
|
||||
@ -251,7 +251,7 @@
|
||||
(-> (merge default-text-attrs node)
|
||||
(assoc :fills fills)))
|
||||
node))))]
|
||||
(attrs/get-attrs-multi nodes attrs)))
|
||||
(attrs/get-attrs-multi nodes attrs :text)))
|
||||
|
||||
(defn current-root-values
|
||||
[{:keys [attrs shape]}]
|
||||
|
||||
@ -195,7 +195,8 @@
|
||||
(dom/set-attribute! checkbox "indeterminate" true)
|
||||
(dom/remove-attribute! checkbox "indeterminate"))))
|
||||
|
||||
[:div {:class (stl/css :fill-section)}
|
||||
[:section {:class (stl/css :fill-section)
|
||||
:aria-label "Fill section"}
|
||||
[:div {:class (stl/css :fill-title)}
|
||||
[:> title-bar* {:collapsable has-fills?
|
||||
:collapsed (not open?)
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
open? (:open @state*)
|
||||
|
||||
cells (hooks/use-equal-memo cells)
|
||||
cell (or cell (attrs/get-attrs-multi cells cell-props))
|
||||
cell (or cell (attrs/get-attrs-multi cells cell-props :grid))
|
||||
|
||||
multiple? (= :multiple (:id cell))
|
||||
cell-ids (if multiple? (->> cells (map :id)) [(:id cell)])
|
||||
|
||||
@ -177,7 +177,7 @@
|
||||
:shape-ids ids}))))]
|
||||
|
||||
[:section {:class (stl/css :stroke-section)
|
||||
:aria-label "stroke-section"}
|
||||
:aria-label "Stroke section"}
|
||||
[:div {:class (stl/css :stroke-title)}
|
||||
[:> title-bar* {:collapsable has-strokes?
|
||||
:collapsed (not open?)
|
||||
|
||||
@ -314,7 +314,8 @@
|
||||
expand-stream
|
||||
#(swap! state* assoc-in [:more-options] true))
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:section {:class (stl/css :element-set)
|
||||
:aria-label "Text section"}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:> title-bar* {:collapsable true
|
||||
:collapsed (not main-menu-open?)
|
||||
|
||||
@ -208,9 +208,9 @@
|
||||
merge-attrs
|
||||
(fn [v1 v2]
|
||||
(cond
|
||||
(= attr-group :shadow) (attrs/get-attrs-multi [v1 v2] attrs shadow-eq shadow-sel)
|
||||
(= attr-group :blur) (attrs/get-attrs-multi [v1 v2] attrs blur-eq blur-sel)
|
||||
:else (attrs/get-attrs-multi [v1 v2] attrs)))
|
||||
(= attr-group :shadow) (attrs/get-attrs-multi [v1 v2] attrs :shadow shadow-eq shadow-sel )
|
||||
(= attr-group :blur) (attrs/get-attrs-multi [v1 v2] attrs :blur blur-eq blur-sel)
|
||||
:else (attrs/get-attrs-multi [v1 v2] attrs :multi)))
|
||||
|
||||
merge-attr
|
||||
(fn [acc applied-tokens t-attr]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user