mirror of
https://github.com/penpot/penpot.git
synced 2026-02-06 12:42:21 -05:00
Compare commits
15 Commits
fix-ipv6-f
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4f2641cc9 | ||
|
|
989eb12139 | ||
|
|
a5e36dbb3d | ||
|
|
8acd031ab2 | ||
|
|
a7c1de6478 | ||
|
|
184487f568 | ||
|
|
c00d512193 | ||
|
|
af5dbf2fbc | ||
|
|
7c7e32d85f | ||
|
|
2ccb33ba89 | ||
|
|
ee88ee63a2 | ||
|
|
fd3d549f9c | ||
|
|
53c2acb3e6 | ||
|
|
8a72eb64c3 | ||
|
|
1d45ca7019 |
@@ -10,6 +10,7 @@
|
|||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.migrations.clj.migration-0023 :as mg0023]
|
[app.migrations.clj.migration-0023 :as mg0023]
|
||||||
|
[app.migrations.clj.migration-0145 :as mg0145]
|
||||||
[app.util.migrations :as mg]
|
[app.util.migrations :as mg]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
@@ -459,7 +460,11 @@
|
|||||||
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}
|
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}
|
||||||
|
|
||||||
{:name "0144-mod-server-error-report-table"
|
{:name "0144-mod-server-error-report-table"
|
||||||
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}])
|
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}
|
||||||
|
|
||||||
|
{:name "0145-fix-plugins-uri-on-profile"
|
||||||
|
:fn mg0145/migrate}])
|
||||||
|
|
||||||
|
|
||||||
(defn apply-migrations!
|
(defn apply-migrations!
|
||||||
[pool name migrations]
|
[pool name migrations]
|
||||||
|
|||||||
83
backend/src/app/migrations/clj/migration_0145.clj
Normal file
83
backend/src/app/migrations/clj/migration_0145.clj
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
;; 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 app.migrations.clj.migration-0145
|
||||||
|
"Migrate plugins references on profiles"
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.db :as db]
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(def ^:private replacements
|
||||||
|
{"https://colors-to-tokens-plugin.pages.dev"
|
||||||
|
"https://colors-to-tokens.plugins.penpot.app"
|
||||||
|
|
||||||
|
"https://contrast-penpot-plugin.pages.dev"
|
||||||
|
"https://contrast.plugins.penpot.app"
|
||||||
|
|
||||||
|
"https://create-palette-penpot-plugin.pages.dev"
|
||||||
|
"https://create-palette.plugins.penpot.app"
|
||||||
|
|
||||||
|
"https://icons-penpot-plugin.pages.dev"
|
||||||
|
"https://icons.plugins.penpot.app"
|
||||||
|
|
||||||
|
"https://lorem-ipsum-penpot-plugin.pages.dev"
|
||||||
|
"https://lorem-ipsum.plugins.penpot.app"
|
||||||
|
|
||||||
|
"https://rename-layers-penpot-plugin.pages.dev"
|
||||||
|
"https://rename-layers.plugins.penpot.app"
|
||||||
|
|
||||||
|
"https://table-penpot-plugin.pages.dev"
|
||||||
|
"https://table.plugins.penpot.app"})
|
||||||
|
|
||||||
|
(defn- fix-url
|
||||||
|
[url]
|
||||||
|
(reduce-kv (fn [url prefix replacement]
|
||||||
|
(if (str/starts-with? url prefix)
|
||||||
|
(reduced (str replacement (subs url (count prefix))))
|
||||||
|
url))
|
||||||
|
url
|
||||||
|
replacements))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- fix-manifest
|
||||||
|
[manifest]
|
||||||
|
(-> manifest
|
||||||
|
(d/update-when :url fix-url)
|
||||||
|
(d/update-when :host fix-url)))
|
||||||
|
|
||||||
|
(defn- fix-plugins-data
|
||||||
|
[props]
|
||||||
|
(d/update-in-when props [:plugins :data]
|
||||||
|
(fn [data]
|
||||||
|
(reduce-kv (fn [data id manifest]
|
||||||
|
(let [manifest' (fix-manifest manifest)]
|
||||||
|
(if (= manifest manifest')
|
||||||
|
data
|
||||||
|
(assoc data id manifest'))))
|
||||||
|
data
|
||||||
|
data))))
|
||||||
|
|
||||||
|
(def ^:private sql:get-profiles
|
||||||
|
"SELECT id, props FROM profile
|
||||||
|
WHERE props ?? '~:plugins'
|
||||||
|
ORDER BY created_at
|
||||||
|
FOR UPDATE")
|
||||||
|
|
||||||
|
(defn migrate
|
||||||
|
[conn]
|
||||||
|
(->> (db/plan conn [sql:get-profiles])
|
||||||
|
(run! (fn [{:keys [id props]}]
|
||||||
|
(when-let [props (some-> props db/decode-transit-pgobject)]
|
||||||
|
(let [props' (fix-plugins-data props)]
|
||||||
|
(when (not= props props')
|
||||||
|
(l/inf :hint "fixing plugins data on profile props" :profile-id (str id))
|
||||||
|
(db/update! conn :profile
|
||||||
|
{:props (db/tjson props')}
|
||||||
|
{:id id}
|
||||||
|
{::db/return-keys false}))))))))
|
||||||
|
|
||||||
@@ -407,17 +407,19 @@
|
|||||||
(defn change-text
|
(defn change-text
|
||||||
"Changes the content of the text shape to use the text as argument. Will use the styles of the
|
"Changes the content of the text shape to use the text as argument. Will use the styles of the
|
||||||
first paragraph and text that is present in the shape (and override the rest)"
|
first paragraph and text that is present in the shape (and override the rest)"
|
||||||
[content text]
|
[content text & {:as styles}]
|
||||||
(let [root-styles (select-keys content root-attrs)
|
(let [root-styles (select-keys content root-attrs)
|
||||||
|
|
||||||
paragraph-style
|
paragraph-style
|
||||||
(merge
|
(merge
|
||||||
default-text-attrs
|
default-text-attrs
|
||||||
|
styles
|
||||||
(select-keys (->> content (node-seq is-paragraph-node?) first) text-all-attrs))
|
(select-keys (->> content (node-seq is-paragraph-node?) first) text-all-attrs))
|
||||||
|
|
||||||
text-style
|
text-style
|
||||||
(merge
|
(merge
|
||||||
default-text-attrs
|
default-text-attrs
|
||||||
|
styles
|
||||||
(select-keys (->> content (node-seq is-text-node?) first) text-all-attrs))
|
(select-keys (->> content (node-seq is-text-node?) first) text-all-attrs))
|
||||||
|
|
||||||
paragraph-texts
|
paragraph-texts
|
||||||
|
|||||||
146
frontend/playwright/data/components/get-file-13267.json
Normal file
146
frontend/playwright/data/components/get-file-13267.json
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"~:features": {
|
||||||
|
"~#set": [
|
||||||
|
"fdata/path-data",
|
||||||
|
"plugins/runtime",
|
||||||
|
"design-tokens/v1",
|
||||||
|
"variants/v1",
|
||||||
|
"layout/grid",
|
||||||
|
"styles/v2",
|
||||||
|
"fdata/objects-map",
|
||||||
|
"render-wasm/v1",
|
||||||
|
"components/v2",
|
||||||
|
"fdata/shape-data-type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"~:team-id": "~u99e49e93-362f-80ef-8007-3450ea52c9a4",
|
||||||
|
"~:permissions": {
|
||||||
|
"~:type": "~:membership",
|
||||||
|
"~:is-owner": true,
|
||||||
|
"~:is-admin": true,
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:can-read": true,
|
||||||
|
"~:is-logged": true
|
||||||
|
},
|
||||||
|
"~:has-media-trimmed": false,
|
||||||
|
"~:comment-thread-seqn": 0,
|
||||||
|
"~:name": "BUG 13267",
|
||||||
|
"~:revn": 3,
|
||||||
|
"~:modified-at": "~m1770302832804",
|
||||||
|
"~:vern": 0,
|
||||||
|
"~:id": "~ue9c84e12-dd29-80fc-8007-86d559dced7f",
|
||||||
|
"~:is-shared": false,
|
||||||
|
"~:migrations": {
|
||||||
|
"~#ordered-set": [
|
||||||
|
"legacy-2",
|
||||||
|
"legacy-3",
|
||||||
|
"legacy-5",
|
||||||
|
"legacy-6",
|
||||||
|
"legacy-7",
|
||||||
|
"legacy-8",
|
||||||
|
"legacy-9",
|
||||||
|
"legacy-10",
|
||||||
|
"legacy-11",
|
||||||
|
"legacy-12",
|
||||||
|
"legacy-13",
|
||||||
|
"legacy-14",
|
||||||
|
"legacy-16",
|
||||||
|
"legacy-17",
|
||||||
|
"legacy-18",
|
||||||
|
"legacy-19",
|
||||||
|
"legacy-25",
|
||||||
|
"legacy-26",
|
||||||
|
"legacy-27",
|
||||||
|
"legacy-28",
|
||||||
|
"legacy-29",
|
||||||
|
"legacy-31",
|
||||||
|
"legacy-32",
|
||||||
|
"legacy-33",
|
||||||
|
"legacy-34",
|
||||||
|
"legacy-36",
|
||||||
|
"legacy-37",
|
||||||
|
"legacy-38",
|
||||||
|
"legacy-39",
|
||||||
|
"legacy-40",
|
||||||
|
"legacy-41",
|
||||||
|
"legacy-42",
|
||||||
|
"legacy-43",
|
||||||
|
"legacy-44",
|
||||||
|
"legacy-45",
|
||||||
|
"legacy-46",
|
||||||
|
"legacy-47",
|
||||||
|
"legacy-48",
|
||||||
|
"legacy-49",
|
||||||
|
"legacy-50",
|
||||||
|
"legacy-51",
|
||||||
|
"legacy-52",
|
||||||
|
"legacy-53",
|
||||||
|
"legacy-54",
|
||||||
|
"legacy-55",
|
||||||
|
"legacy-56",
|
||||||
|
"legacy-57",
|
||||||
|
"legacy-59",
|
||||||
|
"legacy-62",
|
||||||
|
"legacy-65",
|
||||||
|
"legacy-66",
|
||||||
|
"legacy-67",
|
||||||
|
"0001-remove-tokens-from-groups",
|
||||||
|
"0002-normalize-bool-content-v2",
|
||||||
|
"0002-clean-shape-interactions",
|
||||||
|
"0003-fix-root-shape",
|
||||||
|
"0003-convert-path-content-v2",
|
||||||
|
"0005-deprecate-image-type",
|
||||||
|
"0006-fix-old-texts-fills",
|
||||||
|
"0008-fix-library-colors-v4",
|
||||||
|
"0009-clean-library-colors",
|
||||||
|
"0009-add-partial-text-touched-flags",
|
||||||
|
"0010-fix-swap-slots-pointing-non-existent-shapes",
|
||||||
|
"0011-fix-invalid-text-touched-flags",
|
||||||
|
"0012-fix-position-data",
|
||||||
|
"0013-fix-component-path",
|
||||||
|
"0013-clear-invalid-strokes-and-fills",
|
||||||
|
"0014-fix-tokens-lib-duplicate-ids",
|
||||||
|
"0014-clear-components-nil-objects",
|
||||||
|
"0015-fix-text-attrs-blank-strings",
|
||||||
|
"0015-clean-shadow-color",
|
||||||
|
"0016-copy-fills-from-position-data-to-text-node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"~:version": 67,
|
||||||
|
"~:project-id": "~ufc576d2f-8d02-8101-8007-70ec5793bd81",
|
||||||
|
"~:created-at": "~m1770302800755",
|
||||||
|
"~:backend": "legacy-db",
|
||||||
|
"~:data": {
|
||||||
|
"~:pages": [
|
||||||
|
"~ue9c84e12-dd29-80fc-8007-86d559dced80"
|
||||||
|
],
|
||||||
|
"~:pages-index": {
|
||||||
|
"~ue9c84e12-dd29-80fc-8007-86d559dced80": {
|
||||||
|
"~:objects": {
|
||||||
|
"~#penpot/objects-map/v2": {
|
||||||
|
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~udc075bef-4a1f-8056-8007-86d562cf43b7\"]]]",
|
||||||
|
"~udc075bef-4a1f-8056-8007-86d55e028ccb": "[\"~#shape\",[\"^ \",\"~:y\",234,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",117,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",574,\"~:y\",234]],[\"^<\",[\"^ \",\"~:x\",691,\"~:y\",234]],[\"^<\",[\"^ \",\"~:x\",691,\"~:y\",316]],[\"^<\",[\"^ \",\"~:x\",574,\"~:y\",316]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:constraints-v\",\"~:scale\",\"~:constraints-h\",\"^B\",\"~:r1\",0,\"~:id\",\"~udc075bef-4a1f-8056-8007-86d55e028ccb\",\"~:parent-id\",\"~udc075bef-4a1f-8056-8007-86d562cf43b7\",\"~:frame-id\",\"~udc075bef-4a1f-8056-8007-86d562cf43b7\",\"~:strokes\",[],\"~:x\",574,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",574,\"~:y\",234,\"^8\",117,\"~:height\",82,\"~:x1\",574,\"~:y1\",234,\"~:x2\",691,\"~:y2\",316]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^M\",82,\"~:flip-y\",null]]",
|
||||||
|
"~udc075bef-4a1f-8056-8007-86d562cf43b7": "[\"~#shape\",[\"^ \",\"~:y\",234,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:hide-in-viewer\",true,\"~:name\",\"A Component\",\"~:width\",117,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",574,\"~:y\",234]],[\"^;\",[\"^ \",\"~:x\",691,\"~:y\",234]],[\"^;\",[\"^ \",\"~:x\",691,\"~:y\",316]],[\"^;\",[\"^ \",\"~:x\",574,\"~:y\",316]]],\"~:r2\",0,\"~:component-root\",true,\"~:show-content\",true,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~udc075bef-4a1f-8056-8007-86d562cf43b7\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:component-id\",\"~udc075bef-4a1f-8056-8007-86d562d06904\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",574,\"~:main-instance\",true,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",574,\"~:y\",234,\"^7\",117,\"~:height\",82,\"~:x1\",574,\"~:y1\",234,\"~:x2\",691,\"~:y2\",316]],\"~:fills\",[],\"~:flip-x\",null,\"^M\",82,\"~:component-file\",\"~ue9c84e12-dd29-80fc-8007-86d559dced7f\",\"~:flip-y\",null,\"~:shapes\",[\"~udc075bef-4a1f-8056-8007-86d55e028ccb\"]]]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~ue9c84e12-dd29-80fc-8007-86d559dced80",
|
||||||
|
"~:name": "Page 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~ue9c84e12-dd29-80fc-8007-86d559dced7f",
|
||||||
|
"~:options": {
|
||||||
|
"~:components-v2": true,
|
||||||
|
"~:base-font-size": "16px"
|
||||||
|
},
|
||||||
|
"~:components": {
|
||||||
|
"~udc075bef-4a1f-8056-8007-86d562d06904": {
|
||||||
|
"~:id": "~udc075bef-4a1f-8056-8007-86d562d06904",
|
||||||
|
"~:name": "A Component",
|
||||||
|
"~:path": "",
|
||||||
|
"~:modified-at": "~m1770302824566",
|
||||||
|
"~:main-instance-id": "~udc075bef-4a1f-8056-8007-86d562cf43b7",
|
||||||
|
"~:main-instance-page": "~ue9c84e12-dd29-80fc-8007-86d559dced80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -459,8 +459,8 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||||||
await this.page.mouse.up();
|
await this.page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickLeafLayer(name, clickOptions = {}) {
|
async clickLeafLayer(name, clickOptions = {}, index = 0) {
|
||||||
const layer = this.layers.getByText(name).first();
|
const layer = this.layers.getByText(name).nth(index);
|
||||||
await layer.waitFor();
|
await layer.waitFor();
|
||||||
await layer.click(clickOptions);
|
await layer.click(clickOptions);
|
||||||
await this.page.waitForTimeout(500);
|
await this.page.waitForTimeout(500);
|
||||||
@@ -471,10 +471,11 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||||||
await this.clickLeafLayer(name, clickOptions);
|
await this.clickLeafLayer(name, clickOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickToggableLayer(name, clickOptions = {}) {
|
async clickToggableLayer(name, clickOptions = {}, index = 0) {
|
||||||
const layer = this.layers
|
const layer = this.layers
|
||||||
.getByTestId("layer-row")
|
.getByTestId("layer-row")
|
||||||
.filter({ hasText: name });
|
.filter({ hasText: name })
|
||||||
|
.nth(index);
|
||||||
const button = layer.getByTestId("toggle-content");
|
const button = layer.getByTestId("toggle-content");
|
||||||
|
|
||||||
await expect(button).toBeVisible();
|
await expect(button).toBeVisible();
|
||||||
|
|||||||
33
frontend/playwright/ui/specs/components.spec.js
Normal file
33
frontend/playwright/ui/specs/components.spec.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await WasmWorkspacePage.init(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("BUG 13267 - Component instance is not synced with parent for geometry changes", async ({ page }) => {
|
||||||
|
const workspacePage = new WasmWorkspacePage(page);
|
||||||
|
await workspacePage.setupEmptyFile(page);
|
||||||
|
await workspacePage.mockGetFile("components/get-file-13267.json");
|
||||||
|
|
||||||
|
await workspacePage.goToWorkspace({
|
||||||
|
fileId: "e9c84e12-dd29-80fc-8007-86d559dced7f",
|
||||||
|
pageId: "e9c84e12-dd29-80fc-8007-86d559dced80",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a component instance
|
||||||
|
await workspacePage.clickLeafLayer("A Component");
|
||||||
|
await workspacePage.page.keyboard.press("ControlOrMeta+d");
|
||||||
|
|
||||||
|
// Select the main component
|
||||||
|
await workspacePage.clickLeafLayer("A Component", {}, 1);
|
||||||
|
const rotationInput = workspacePage.rightSidebar.getByTestId("rotation").getByRole("textbox");
|
||||||
|
await rotationInput.fill("45");
|
||||||
|
await rotationInput.press("Enter");
|
||||||
|
|
||||||
|
// Select the instance rect
|
||||||
|
await workspacePage.clickToggableLayer("A Component", {}, 0);
|
||||||
|
await workspacePage.clickLeafLayer("Rectangle");
|
||||||
|
|
||||||
|
await expect(rotationInput).toHaveValue("45");
|
||||||
|
});
|
||||||
@@ -179,6 +179,56 @@
|
|||||||
(map #(get objects %))
|
(map #(get objects %))
|
||||||
(reduce get-ignore-tree nil))))
|
(reduce get-ignore-tree nil))))
|
||||||
|
|
||||||
|
(defn calculate-ignore-tree-wasm
|
||||||
|
"Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers"
|
||||||
|
[transforms objects]
|
||||||
|
|
||||||
|
(letfn [(get-ignore-tree
|
||||||
|
([ignore-tree shape]
|
||||||
|
(let [shape-id (dm/get-prop shape :id)
|
||||||
|
transformed-shape (gsh/apply-transform shape (get transforms shape-id))
|
||||||
|
|
||||||
|
root
|
||||||
|
(if (:component-root shape)
|
||||||
|
shape
|
||||||
|
(ctn/get-component-shape objects shape {:allow-main? true}))
|
||||||
|
|
||||||
|
transformed-root
|
||||||
|
(if (:component-root shape)
|
||||||
|
transformed-shape
|
||||||
|
(gsh/apply-transform root (get transforms (:id root))))]
|
||||||
|
|
||||||
|
(get-ignore-tree ignore-tree shape transformed-shape root transformed-root)))
|
||||||
|
|
||||||
|
([ignore-tree shape root transformed-root]
|
||||||
|
(let [shape-id (dm/get-prop shape :id)
|
||||||
|
transformed-shape (gsh/apply-transform shape (get transforms shape-id))]
|
||||||
|
(get-ignore-tree ignore-tree shape transformed-shape root transformed-root)))
|
||||||
|
|
||||||
|
([ignore-tree shape transformed-shape root transformed-root]
|
||||||
|
(let [shape-id (dm/get-prop shape :id)
|
||||||
|
|
||||||
|
ignore-tree
|
||||||
|
(cond-> ignore-tree
|
||||||
|
(and (some? root) (ctk/in-component-copy? shape))
|
||||||
|
(assoc
|
||||||
|
shape-id
|
||||||
|
(check-delta shape root transformed-shape transformed-root)))
|
||||||
|
|
||||||
|
set-child
|
||||||
|
(fn [ignore-tree child]
|
||||||
|
(get-ignore-tree ignore-tree child root transformed-root))]
|
||||||
|
|
||||||
|
(->> (:shapes shape)
|
||||||
|
(map (d/getf objects))
|
||||||
|
(reduce set-child ignore-tree)))))]
|
||||||
|
|
||||||
|
;; we check twice because we want only to search parents of components but once the
|
||||||
|
;; tree is traversed we only want to process the objects in components
|
||||||
|
(->> (keys transforms)
|
||||||
|
(map #(get objects %))
|
||||||
|
(reduce get-ignore-tree nil))))
|
||||||
|
|
||||||
(defn assoc-position-data
|
(defn assoc-position-data
|
||||||
[shape position-data old-shape]
|
[shape position-data old-shape]
|
||||||
(let [deltav (gpt/to-vec (gpt/point (:selrect old-shape))
|
(let [deltav (gpt/to-vec (gpt/point (:selrect old-shape))
|
||||||
@@ -625,17 +675,6 @@
|
|||||||
|
|
||||||
(let [objects (dsh/lookup-page-objects state)
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
|
|
||||||
ignore-tree
|
|
||||||
(calculate-ignore-tree modif-tree objects)
|
|
||||||
|
|
||||||
options
|
|
||||||
(-> params
|
|
||||||
(assoc :reg-objects? true)
|
|
||||||
(assoc :ignore-tree ignore-tree)
|
|
||||||
;; Attributes that can change in the transform. This
|
|
||||||
;; way we don't have to check all the attributes
|
|
||||||
(assoc :attrs transform-attrs))
|
|
||||||
|
|
||||||
geometry-entries
|
geometry-entries
|
||||||
(parse-geometry-modifiers modif-tree)
|
(parse-geometry-modifiers modif-tree)
|
||||||
|
|
||||||
@@ -645,6 +684,17 @@
|
|||||||
transforms
|
transforms
|
||||||
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?))
|
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?))
|
||||||
|
|
||||||
|
ignore-tree
|
||||||
|
(calculate-ignore-tree-wasm transforms objects)
|
||||||
|
|
||||||
|
options
|
||||||
|
(-> params
|
||||||
|
(assoc :reg-objects? true)
|
||||||
|
(assoc :ignore-tree ignore-tree)
|
||||||
|
;; Attributes that can change in the transform. This
|
||||||
|
;; way we don't have to check all the attributes
|
||||||
|
(assoc :attrs transform-attrs))
|
||||||
|
|
||||||
modif-tree
|
modif-tree
|
||||||
(propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state))
|
(propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state))
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [page-id (or page-id (:current-page-id state))
|
(let [page-id (or page-id (:current-page-id state))
|
||||||
objects (dsh/lookup-page-objects state page-id)
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
ids (->> ids (filter #(contains? objects %)))]
|
ids (->> ids (remove uuid/zero?) (filter #(contains? objects %)))]
|
||||||
(if (d/not-empty? ids)
|
(if (d/not-empty? ids)
|
||||||
(let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))]
|
(let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))]
|
||||||
(if (features/active-feature? state "render-wasm/v1")
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
|
|||||||
@@ -776,11 +776,7 @@
|
|||||||
(rx/of (v2-update-text-editor-styles id attrs)))
|
(rx/of (v2-update-text-editor-styles id attrs)))
|
||||||
|
|
||||||
(when (features/active-feature? state "render-wasm/v1")
|
(when (features/active-feature? state "render-wasm/v1")
|
||||||
;; This delay is to give time for the font to be correctly rendered
|
(rx/of (dwwt/resize-wasm-text-debounce id)))))))
|
||||||
;; in wasm.
|
|
||||||
(cond->> (rx/of (dwwt/resize-wasm-text id))
|
|
||||||
(contains? attrs :font-id)
|
|
||||||
(rx/delay 200)))))))
|
|
||||||
|
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state _]
|
(effect [_ state _]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
This exists to avoid circular deps:
|
This exists to avoid circular deps:
|
||||||
workspace.texts -> workspace.libraries -> workspace.texts"
|
workspace.texts -> workspace.libraries -> workspace.texts"
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.workspace.modifiers :as dwm]
|
[app.main.data.workspace.modifiers :as dwm]
|
||||||
[app.render-wasm.api :as wasm.api]
|
[app.render-wasm.api :as wasm.api]
|
||||||
|
[app.render-wasm.api.fonts :as wasm.fonts]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
@@ -62,6 +64,84 @@
|
|||||||
(rx/of (dwm/apply-wasm-modifiers (resize-wasm-text-modifiers shape)))
|
(rx/of (dwm/apply-wasm-modifiers (resize-wasm-text-modifiers shape)))
|
||||||
(rx/empty))))))
|
(rx/empty))))))
|
||||||
|
|
||||||
|
(defn resize-wasm-text-debounce-commit
|
||||||
|
[]
|
||||||
|
(ptk/reify ::resize-wasm-text-debounce-commit
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [ids (get state ::resize-wasm-text-debounce-ids)
|
||||||
|
objects (dsh/lookup-page-objects state)
|
||||||
|
|
||||||
|
modifiers
|
||||||
|
(reduce
|
||||||
|
(fn [modifiers id]
|
||||||
|
(let [shape (get objects id)]
|
||||||
|
(cond-> modifiers
|
||||||
|
(and (some? shape)
|
||||||
|
(cfh/text-shape? shape)
|
||||||
|
(not= :fixed (:grow-type shape)))
|
||||||
|
(merge (resize-wasm-text-modifiers shape)))))
|
||||||
|
{}
|
||||||
|
ids)]
|
||||||
|
(if (not (empty? modifiers))
|
||||||
|
(rx/of (dwm/apply-wasm-modifiers modifiers))
|
||||||
|
(rx/empty))))))
|
||||||
|
|
||||||
|
;; This event will debounce the resize events so, if there are many, they
|
||||||
|
;; are processed at the same time and not one-by-one. This will improve
|
||||||
|
;; performance because it's better to make only one layout calculation instead
|
||||||
|
;; of (potentialy) hundreds.
|
||||||
|
(defn resize-wasm-text-debounce-inner
|
||||||
|
[id]
|
||||||
|
(let [cur-event (js/Symbol)]
|
||||||
|
(ptk/reify ::resize-wasm-text-debounce-inner
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(-> state
|
||||||
|
(update ::resize-wasm-text-debounce-ids (fnil conj []) id)
|
||||||
|
(cond-> (nil? (::resize-wasm-text-debounce-event state))
|
||||||
|
(assoc ::resize-wasm-text-debounce-event cur-event))))
|
||||||
|
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(if (= (::resize-wasm-text-debounce-event state) cur-event)
|
||||||
|
(let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize)))]
|
||||||
|
(rx/concat
|
||||||
|
(rx/merge
|
||||||
|
(->> stream
|
||||||
|
(rx/filter (ptk/type? ::resize-wasm-text-debounce-inner))
|
||||||
|
(rx/debounce 40)
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/map #(resize-wasm-text-debounce-commit))
|
||||||
|
(rx/take-until stopper))
|
||||||
|
(rx/of (resize-wasm-text-debounce-inner id)))
|
||||||
|
(rx/of #(dissoc %
|
||||||
|
::resize-wasm-text-debounce-ids
|
||||||
|
::resize-wasm-text-debounce-event))))
|
||||||
|
(rx/empty))))))
|
||||||
|
|
||||||
|
(defn resize-wasm-text-debounce
|
||||||
|
[id]
|
||||||
|
(ptk/reify ::resize-wasm-text-debounce
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
|
content (dm/get-in objects [id :content])
|
||||||
|
fonts (wasm.fonts/get-content-fonts content)
|
||||||
|
|
||||||
|
fonts-loaded?
|
||||||
|
(->> fonts
|
||||||
|
(every?
|
||||||
|
(fn [font]
|
||||||
|
(let [font-data (wasm.fonts/make-font-data font)]
|
||||||
|
(wasm.fonts/font-stored? font-data (:emoji? font-data))))))]
|
||||||
|
|
||||||
|
(if (not fonts-loaded?)
|
||||||
|
(->> (rx/of (resize-wasm-text-debounce id))
|
||||||
|
(rx/delay 20))
|
||||||
|
(rx/of (resize-wasm-text-debounce-inner id)))))))
|
||||||
|
|
||||||
(defn resize-wasm-text-all
|
(defn resize-wasm-text-all
|
||||||
"Resize all text shapes (auto-width/auto-height) from a collection of ids."
|
"Resize all text shapes (auto-width/auto-height) from a collection of ids."
|
||||||
[ids]
|
[ids]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
(def ^:private schema:icon-button
|
(def ^:private schema:icon-button
|
||||||
[:map
|
[:map
|
||||||
[:class {:optional true} :string]
|
[:class {:optional true} :string]
|
||||||
|
[:tooltip-class {:optional true} [:maybe :string]]
|
||||||
[:icon-class {:optional true} :string]
|
[:icon-class {:optional true} :string]
|
||||||
[:icon
|
[:icon
|
||||||
[:and :string [:fn #(contains? icon-list %)]]]
|
[:and :string [:fn #(contains? icon-list %)]]]
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
(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 variant aria-label children tooltip-placement] :rest props}]
|
[{:keys [class icon icon-class variant aria-label children tooltip-placement tooltip-class] :rest props}]
|
||||||
(let [variant
|
(let [variant
|
||||||
(d/nilv variant "primary")
|
(d/nilv variant "primary")
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
:aria-labelledby tooltip-id})]
|
:aria-labelledby tooltip-id})]
|
||||||
|
|
||||||
[:> tooltip* {:content aria-label
|
[:> tooltip* {:content aria-label
|
||||||
|
:class tooltip-class
|
||||||
:placement tooltip-placement
|
:placement tooltip-placement
|
||||||
:id tooltip-id}
|
:id tooltip-id}
|
||||||
[:> :button props
|
[:> :button props
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
|
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
|
||||||
[app.main.ui.ds.controls.utilities.token-field :refer [token-field*]]
|
[app.main.ui.ds.controls.utilities.token-field :refer [token-field*]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
|
||||||
[app.main.ui.ds.tooltip :refer [tooltip*]]
|
|
||||||
[app.main.ui.formats :as fmt]
|
[app.main.ui.formats :as fmt]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
@@ -638,27 +637,17 @@
|
|||||||
:on-change store-raw-value
|
:on-change store-raw-value
|
||||||
:variant "comfortable"
|
:variant "comfortable"
|
||||||
:disabled disabled
|
:disabled disabled
|
||||||
:slot-start (when (or icon text-icon)
|
:icon icon
|
||||||
|
:aria-label property
|
||||||
|
:slot-start (when text-icon
|
||||||
(mf/html
|
(mf/html
|
||||||
[:> tooltip*
|
[:div {:class (stl/css :text-icon)}
|
||||||
{:content property
|
text-icon]))
|
||||||
:id property}
|
|
||||||
(cond
|
|
||||||
icon
|
|
||||||
[:> icon*
|
|
||||||
{:icon-id icon
|
|
||||||
:size "s"
|
|
||||||
:aria-labelledby property
|
|
||||||
:class (stl/css :icon)}]
|
|
||||||
|
|
||||||
text-icon
|
|
||||||
[:div {:class (stl/css :text-icon)
|
|
||||||
:aria-labelledby property}
|
|
||||||
text-icon])]))
|
|
||||||
:slot-end (when-not disabled
|
:slot-end (when-not disabled
|
||||||
(when (some? tokens)
|
(when (some? tokens)
|
||||||
(mf/html [:> icon-button* {:variant "ghost"
|
(mf/html [:> icon-button* {:variant "ghost"
|
||||||
:icon i/tokens
|
:icon i/tokens
|
||||||
|
:tooltip-class (stl/css :button-tooltip)
|
||||||
:class (stl/css :invisible-button)
|
:class (stl/css :invisible-button)
|
||||||
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
|
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
|
||||||
:ref open-dropdown-ref
|
:ref open-dropdown-ref
|
||||||
@@ -686,23 +675,19 @@
|
|||||||
:disabled disabled
|
:disabled disabled
|
||||||
:on-blur on-blur
|
:on-blur on-blur
|
||||||
:class inner-class
|
:class inner-class
|
||||||
|
:property property
|
||||||
:slot-start (when (or icon text-icon)
|
:slot-start (when (or icon text-icon)
|
||||||
(mf/html
|
(mf/html
|
||||||
[:> tooltip*
|
(cond
|
||||||
{:content property
|
icon
|
||||||
:id property}
|
[:> icon*
|
||||||
(cond
|
{:icon-id icon
|
||||||
icon
|
:size "s"
|
||||||
[:> icon*
|
:class (stl/css :icon)}]
|
||||||
{:icon-id icon
|
|
||||||
:size "s"
|
|
||||||
:aria-labelledby property
|
|
||||||
:class (stl/css :icon)}]
|
|
||||||
|
|
||||||
text-icon
|
text-icon
|
||||||
[:div {:class (stl/css :text-icon)
|
[:div {:class (stl/css :text-icon)}
|
||||||
:aria-labelledby property}
|
text-icon])))
|
||||||
text-icon])]))
|
|
||||||
:token-wrapper-ref token-wrapper-ref
|
:token-wrapper-ref token-wrapper-ref
|
||||||
:token-detach-btn-ref token-detach-btn-ref
|
:token-detach-btn-ref token-detach-btn-ref
|
||||||
:detach-token detach-token})))]
|
:detach-token detach-token})))]
|
||||||
@@ -737,40 +722,21 @@
|
|||||||
(mf/with-effect [dropdown-options]
|
(mf/with-effect [dropdown-options]
|
||||||
(mf/set-ref-val! options-ref dropdown-options))
|
(mf/set-ref-val! options-ref dropdown-options))
|
||||||
|
|
||||||
(if (some? icon)
|
[:div {:class [class (stl/css :input-wrapper)]
|
||||||
[:div {:class [class (stl/css :input-wrapper)]
|
:ref wrapper-ref}
|
||||||
:ref wrapper-ref}
|
|
||||||
|
|
||||||
(if (and (some? token-applied)
|
(if (and (some? token-applied)
|
||||||
(not= :multiple token-applied))
|
(not= :multiple token-applied))
|
||||||
[:> token-field* token-props]
|
[:> token-field* token-props]
|
||||||
[:> input-field* input-props])
|
[:> input-field* input-props])
|
||||||
|
|
||||||
(when ^boolean is-open
|
(when ^boolean is-open
|
||||||
(let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)]
|
(let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)]
|
||||||
[:> options-dropdown* {:on-click on-option-click
|
[:> options-dropdown* {:on-click on-option-click
|
||||||
:id listbox-id
|
:id listbox-id
|
||||||
:options options
|
:options options
|
||||||
:selected selected-id
|
:selected selected-id
|
||||||
:focused focused-id
|
:focused focused-id
|
||||||
:align align
|
:align align
|
||||||
:empty-to-end empty-to-end
|
:empty-to-end empty-to-end
|
||||||
:ref set-option-ref}]))]
|
:ref set-option-ref}]))]))
|
||||||
[:div {:class [class (stl/css :input-wrapper)]
|
|
||||||
:ref wrapper-ref}
|
|
||||||
|
|
||||||
(if (and (some? token-applied)
|
|
||||||
(not= :multiple token-applied))
|
|
||||||
[:> token-field* token-props]
|
|
||||||
[:> input-field* input-props])
|
|
||||||
|
|
||||||
(when ^boolean is-open
|
|
||||||
(let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)]
|
|
||||||
[:> options-dropdown* {:on-click on-option-click
|
|
||||||
:id listbox-id
|
|
||||||
:options options
|
|
||||||
:selected selected-id
|
|
||||||
:focused focused-id
|
|
||||||
:align align
|
|
||||||
:empty-to-end empty-to-end
|
|
||||||
:ref set-option-ref}]))])))
|
|
||||||
|
|||||||
@@ -55,3 +55,8 @@
|
|||||||
--opacity-button: 1;
|
--opacity-button: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-tooltip {
|
||||||
|
inline-size: var($sz-28);
|
||||||
|
block-size: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
type (d/nilv type "text")
|
type (d/nilv type "text")
|
||||||
variant (d/nilv variant "dense")
|
variant (d/nilv variant "dense")
|
||||||
tooltip-id (mf/use-id)
|
tooltip-id (mf/use-id)
|
||||||
|
|
||||||
props (mf/spread-props props
|
props (mf/spread-props props
|
||||||
{:class [class
|
{:class [class
|
||||||
(stl/css-case
|
(stl/css-case
|
||||||
@@ -54,15 +53,11 @@
|
|||||||
"true")
|
"true")
|
||||||
:aria-describedby (when has-hint
|
:aria-describedby (when has-hint
|
||||||
(str id "-hint"))
|
(str id "-hint"))
|
||||||
|
:aria-labelledby tooltip-id
|
||||||
:type (d/nilv type "text")
|
:type (d/nilv type "text")
|
||||||
:id id
|
:id id
|
||||||
:max-length (d/nilv max-length max-input-length)})
|
:max-length (d/nilv max-length max-input-length)})
|
||||||
|
|
||||||
props (if (and aria-label (not (some? icon)))
|
|
||||||
(mf/spread-props props
|
|
||||||
{:aria-label aria-label})
|
|
||||||
(mf/spread-props props
|
|
||||||
{:aria-labelledby tooltip-id}))
|
|
||||||
inside-class (stl/css-case :input-wrapper true
|
inside-class (stl/css-case :input-wrapper true
|
||||||
:has-hint has-hint
|
:has-hint has-hint
|
||||||
:hint-type-hint (= hint-type "hint")
|
:hint-type-hint (= hint-type "hint")
|
||||||
@@ -83,11 +78,14 @@
|
|||||||
(when (some? slot-start)
|
(when (some? slot-start)
|
||||||
slot-start)
|
slot-start)
|
||||||
(when (some? icon)
|
(when (some? icon)
|
||||||
(if aria-label
|
[:> icon* {:icon-id icon
|
||||||
[:> tooltip* {:content aria-label
|
:class (stl/css :icon)
|
||||||
:id tooltip-id}
|
:size "s"
|
||||||
[:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}]]
|
:on-click on-icon-click}])
|
||||||
[:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}]))
|
(if aria-label
|
||||||
[:> "input" props]
|
[:> tooltip* {:content aria-label
|
||||||
|
:id tooltip-id}
|
||||||
|
[:> "input" props]]
|
||||||
|
[:> "input" props])
|
||||||
(when (some? slot-end)
|
(when (some? slot-end)
|
||||||
slot-end)]))
|
slot-end)]))
|
||||||
|
|||||||
@@ -118,4 +118,5 @@
|
|||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: var(--color-foreground-secondary);
|
color: var(--color-foreground-secondary);
|
||||||
|
min-inline-size: var(--sp-l);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
[:class {:optional true} [:maybe :string]]
|
[:class {:optional true} [:maybe :string]]
|
||||||
[:id {:optional true} [:maybe :string]]
|
[:id {:optional true} [:maybe :string]]
|
||||||
[:label {:optional true} [:maybe :string]]
|
[:label {:optional true} [:maybe :string]]
|
||||||
|
[:property {:optional true} [:maybe :string]]
|
||||||
[:value :any]
|
[:value :any]
|
||||||
[:disabled {:optional true} :boolean]
|
[:disabled {:optional true} :boolean]
|
||||||
[:slot-start {:optional true} [:maybe some?]]
|
[:slot-start {:optional true} [:maybe some?]]
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
{::mf/schema schema:token-field}
|
{::mf/schema schema:token-field}
|
||||||
[{:keys [id label value slot-start disabled class
|
[{:keys [id label value slot-start disabled class
|
||||||
on-click on-token-key-down on-blur detach-token
|
on-click on-token-key-down on-blur detach-token
|
||||||
token-wrapper-ref token-detach-btn-ref on-focus]}]
|
token-wrapper-ref token-detach-btn-ref on-focus property]}]
|
||||||
(let [set-active? (some? id)
|
(let [set-active? (some? id)
|
||||||
content (if set-active?
|
content (if set-active?
|
||||||
label
|
label
|
||||||
@@ -50,37 +51,42 @@
|
|||||||
(when-not ^boolean disabled
|
(when-not ^boolean disabled
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(dom/focus! (mf/ref-val token-wrapper-ref)))))]
|
(dom/focus! (mf/ref-val token-wrapper-ref)))))]
|
||||||
|
[:> tooltip* {:content property
|
||||||
|
:class (stl/css :token-field-wrapper)
|
||||||
|
:id (dm/str default-id "-input")}
|
||||||
|
[:div {:class [class (stl/css-case :token-field true
|
||||||
|
:with-icon (some? slot-start)
|
||||||
|
:token-field-disabled disabled)]
|
||||||
|
:on-click focus-wrapper
|
||||||
|
:disabled disabled
|
||||||
|
:on-key-down on-token-key-down
|
||||||
|
:ref token-wrapper-ref
|
||||||
|
:on-blur on-blur
|
||||||
|
:on-focus on-focus
|
||||||
|
:aria-labelledby (dm/str default-id "-input")
|
||||||
|
:tab-index (if disabled -1 0)}
|
||||||
|
|
||||||
[:div {:class [class (stl/css-case :token-field true
|
(when (some? slot-start) slot-start)
|
||||||
:with-icon (some? slot-start)
|
|
||||||
:token-field-disabled disabled)]
|
|
||||||
:on-click focus-wrapper
|
|
||||||
:disabled disabled
|
|
||||||
:on-key-down on-token-key-down
|
|
||||||
:ref token-wrapper-ref
|
|
||||||
:on-blur on-blur
|
|
||||||
:on-focus on-focus
|
|
||||||
:tab-index (if disabled -1 0)}
|
|
||||||
|
|
||||||
(when (some? slot-start) slot-start)
|
[:div {:class (stl/css :content-wrapper)}
|
||||||
|
[:> tooltip* {:content content
|
||||||
|
:id (dm/str id "-pill")}
|
||||||
|
[:button {:on-click on-click
|
||||||
|
:class (stl/css-case :pill true
|
||||||
|
:no-set-pill (not set-active?)
|
||||||
|
:pill-disabled disabled)
|
||||||
|
:disabled disabled
|
||||||
|
:aria-labelledby (dm/str id "-pill")
|
||||||
|
:on-key-down on-token-key-down}
|
||||||
|
value
|
||||||
|
(when-not set-active?
|
||||||
|
[:div {:class (stl/css :pill-dot)}])]]]
|
||||||
|
|
||||||
[:> tooltip* {:content content
|
(when-not ^boolean disabled
|
||||||
:id (dm/str id "-pill")}
|
[:> icon-button* {:variant "ghost"
|
||||||
[:button {:on-click on-click
|
:class (stl/css :invisible-button)
|
||||||
:class (stl/css-case :pill true
|
:tooltip-class (stl/css :button-tooltip)
|
||||||
:no-set-pill (not set-active?)
|
:icon i/broken-link
|
||||||
:pill-disabled disabled)
|
:ref token-detach-btn-ref
|
||||||
:disabled disabled
|
:aria-label (tr "ds.inputs.token-field.detach-token")
|
||||||
:aria-labelledby (dm/str id "-pill")
|
:on-click detach-token}])]]))
|
||||||
:on-key-down on-token-key-down}
|
|
||||||
value
|
|
||||||
(when-not set-active?
|
|
||||||
[:div {:class (stl/css :pill-dot)}])]]
|
|
||||||
|
|
||||||
(when-not ^boolean disabled
|
|
||||||
[:> icon-button* {:variant "ghost"
|
|
||||||
:class (stl/css :invisible-button)
|
|
||||||
:icon i/broken-link
|
|
||||||
:ref token-detach-btn-ref
|
|
||||||
:aria-label (tr "ds.inputs.token-field.detach-token")
|
|
||||||
:on-click detach-token}])]))
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
@use "ds/typography.scss" as t;
|
@use "ds/typography.scss" as t;
|
||||||
@use "ds/colors.scss" as *;
|
@use "ds/colors.scss" as *;
|
||||||
@use "ds/mixins.scss" as *;
|
@use "ds/mixins.scss" as *;
|
||||||
|
@use "ds/_utils.scss" as *;
|
||||||
|
|
||||||
.token-field {
|
.token-field {
|
||||||
--token-field-bg-color: var(--color-background-tertiary);
|
--token-field-bg-color: var(--color-background-tertiary);
|
||||||
@@ -37,6 +38,9 @@
|
|||||||
--token-field-outline-color: var(--color-accent-primary);
|
--token-field-outline-color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.token-field-wrapper {
|
||||||
|
inline-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.with-icon {
|
.with-icon {
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
@@ -132,3 +136,12 @@
|
|||||||
--opacity-button: 1;
|
--opacity-button: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
inline-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-tooltip {
|
||||||
|
inline-size: px2rem(28);
|
||||||
|
block-size: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
|
|
||||||
(def ^:private schema:tooltip
|
(def ^:private schema:tooltip
|
||||||
[:map
|
[:map
|
||||||
[:class {:optional true} :string]
|
[:class {:optional true} [:maybe :string]]
|
||||||
[:id {:optional true} :string]
|
[:id {:optional true} :string]
|
||||||
[:offset {:optional true} :int]
|
[:offset {:optional true} :int]
|
||||||
[:delay {:optional true} :int]
|
[:delay {:optional true} :int]
|
||||||
@@ -184,6 +184,7 @@
|
|||||||
[{:keys [class id children content placement offset delay] :rest props}]
|
[{:keys [class id children content placement offset delay] :rest props}]
|
||||||
(let [internal-id
|
(let [internal-id
|
||||||
(mf/use-id)
|
(mf/use-id)
|
||||||
|
trigger-ref (mf/use-ref nil)
|
||||||
|
|
||||||
id
|
id
|
||||||
(d/nilv id internal-id)
|
(d/nilv id internal-id)
|
||||||
@@ -204,19 +205,23 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps id placement offset)
|
(mf/deps id placement offset)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(clear-schedule schedule-ref)
|
|
||||||
(when-let [tooltip (dom/get-element id)]
|
|
||||||
(let [origin-brect
|
|
||||||
(->> (dom/get-target event)
|
|
||||||
(dom/get-bounding-rect))
|
|
||||||
|
|
||||||
update-position
|
(let [current (dom/get-current-target event)
|
||||||
(fn []
|
related (dom/get-related-target event)
|
||||||
(let [new-placement (update-tooltip-position tooltip placement origin-brect offset)]
|
is-node? (fn [node] (and node (.-nodeType node)))]
|
||||||
(when (not= new-placement placement)
|
(when-not (and related (is-node? related) (.contains current related))
|
||||||
(reset! placement* new-placement))))]
|
(clear-schedule schedule-ref)
|
||||||
|
(when-let [tooltip (dom/get-element id)]
|
||||||
|
(let [origin-brect
|
||||||
|
(dom/get-bounding-rect (mf/ref-val trigger-ref))
|
||||||
|
|
||||||
(add-schedule schedule-ref delay update-position)))))
|
update-position
|
||||||
|
(fn []
|
||||||
|
(let [new-placement (update-tooltip-position tooltip placement origin-brect offset)]
|
||||||
|
(when (not= new-placement placement)
|
||||||
|
(reset! placement* new-placement))))]
|
||||||
|
|
||||||
|
(add-schedule schedule-ref delay update-position)))))))
|
||||||
|
|
||||||
on-hide
|
on-hide
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@@ -252,6 +257,7 @@
|
|||||||
:on-focus on-show
|
:on-focus on-show
|
||||||
:on-blur on-hide
|
:on-blur on-hide
|
||||||
:on-key-down handle-key-down
|
:on-key-down handle-key-down
|
||||||
|
:ref trigger-ref
|
||||||
:class [class (stl/css :tooltip-trigger)]
|
:class [class (stl/css :tooltip-trigger)]
|
||||||
:aria-describedby id})
|
:aria-describedby id})
|
||||||
content
|
content
|
||||||
|
|||||||
@@ -95,10 +95,10 @@
|
|||||||
[]
|
[]
|
||||||
|
|
||||||
(let [plugins-state* (mf/use-state #(preg/plugins-list))
|
(let [plugins-state* (mf/use-state #(preg/plugins-list))
|
||||||
plugins-state @plugins-state*
|
plugins-state (deref plugins-state*)
|
||||||
|
|
||||||
plugin-url* (mf/use-state "")
|
plugin-url* (mf/use-state "")
|
||||||
plugin-url @plugin-url*
|
plugin-url (deref plugin-url*)
|
||||||
|
|
||||||
fetching-manifest? (mf/use-state false)
|
fetching-manifest? (mf/use-state false)
|
||||||
|
|
||||||
|
|||||||
@@ -300,7 +300,7 @@
|
|||||||
|
|
||||||
on-drop
|
on-drop
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps id index objects expanded? selected)
|
(mf/deps id objects expanded? selected)
|
||||||
(fn [side _data]
|
(fn [side _data]
|
||||||
(let [single? (= (count selected) 1)
|
(let [single? (= (count selected) 1)
|
||||||
same? (and single? (= (first selected) id))]
|
same? (and single? (= (first selected) id))]
|
||||||
@@ -321,14 +321,18 @@
|
|||||||
|
|
||||||
[parent-id _] (ctn/find-valid-parent-and-frame-ids parent-id objects (map #(get objects %) selected) false files)
|
[parent-id _] (ctn/find-valid-parent-and-frame-ids parent-id objects (map #(get objects %) selected) false files)
|
||||||
|
|
||||||
parent (get objects parent-id)
|
parent (get objects parent-id)
|
||||||
|
current-index (d/index-of (:shapes parent) id)
|
||||||
|
|
||||||
to-index (cond
|
to-index (cond
|
||||||
(= side :center) 0
|
(= side :center) 0
|
||||||
(and expanded? (= side :bot) (d/not-empty? (:shapes shape))) (count (:shapes parent))
|
(and expanded? (= side :bot) (d/not-empty? (:shapes shape))) (count (:shapes parent))
|
||||||
(= side :top) (inc index)
|
;; target not found in parent (while lazy loading)
|
||||||
:else index)]
|
(neg? current-index) nil
|
||||||
(st/emit! (dw/relocate-selected-shapes parent-id to-index)))))))
|
(= side :top) (inc current-index)
|
||||||
|
:else current-index)]
|
||||||
|
(when (some? to-index)
|
||||||
|
(st/emit! (dw/relocate-selected-shapes parent-id to-index))))))))
|
||||||
|
|
||||||
on-hold
|
on-hold
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@@ -417,11 +421,7 @@
|
|||||||
current @children-count*
|
current @children-count*
|
||||||
new-count (min total (max current chunk-size min-count))]
|
new-count (min total (max current chunk-size min-count))]
|
||||||
(reset! children-count* new-count))
|
(reset! children-count* new-count))
|
||||||
(reset! children-count* 0)))
|
(reset! children-count* 0))))
|
||||||
(fn []
|
|
||||||
(when-let [obs ^js @observer-var]
|
|
||||||
(.disconnect obs)
|
|
||||||
(reset! observer-var nil))))
|
|
||||||
|
|
||||||
;; Re-observe sentinel whenever children-count changes (sentinel moves)
|
;; Re-observe sentinel whenever children-count changes (sentinel moves)
|
||||||
;; and (shapes item) to reconnect observer after shape changes
|
;; and (shapes item) to reconnect observer after shape changes
|
||||||
@@ -502,4 +502,4 @@
|
|||||||
:component-child? component-tree?}])))
|
:component-child? component-tree?}])))
|
||||||
(when (< children-count (count (:shapes item)))
|
(when (< children-count (count (:shapes item)))
|
||||||
[:div {:ref lazy-ref
|
[:div {:ref lazy-ref
|
||||||
:style {:min-height 1}}])])]))
|
:class (stl/css :lazy-load-sentinel)}])])]))
|
||||||
|
|||||||
@@ -298,3 +298,11 @@
|
|||||||
.filtered {
|
.filtered {
|
||||||
min-inline-size: $sz-12;
|
min-inline-size: $sz-12;
|
||||||
}
|
}
|
||||||
|
.lazy-load-sentinel {
|
||||||
|
min-height: 1px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.lazy-load-sentinel {
|
||||||
|
min-height: 1px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -521,8 +521,7 @@
|
|||||||
[:& filters-tree {:objects filtered-objects
|
[:& filters-tree {:objects filtered-objects
|
||||||
:key (dm/str (:id page))
|
:key (dm/str (:id page))
|
||||||
:parent-size size-parent}]
|
:parent-size size-parent}]
|
||||||
[:div {:ref lazy-load-ref
|
[:div {:ref lazy-load-ref}]]
|
||||||
:style {:min-height 16}}]]
|
|
||||||
[:div {:on-scroll on-scroll
|
[:div {:on-scroll on-scroll
|
||||||
:class (stl/css :tool-window-content)
|
:class (stl/css :tool-window-content)
|
||||||
:data-scroll-container true
|
:data-scroll-container true
|
||||||
|
|||||||
@@ -541,7 +541,8 @@
|
|||||||
:value (get values :rotation)}]
|
:value (get values :rotation)}]
|
||||||
|
|
||||||
[:div {:class (stl/css :rotation)
|
[:div {:class (stl/css :rotation)
|
||||||
:title (tr "workspace.options.rotation")}
|
:title (tr "workspace.options.rotation")
|
||||||
|
:data-testid "rotation"}
|
||||||
[:span {:class (stl/css :icon)} deprecated-icon/rotation]
|
[:span {:class (stl/css :icon)} deprecated-icon/rotation]
|
||||||
[:> deprecated-input/numeric-input*
|
[:> deprecated-input/numeric-input*
|
||||||
{:no-validate true
|
{:no-validate true
|
||||||
|
|||||||
@@ -75,32 +75,31 @@
|
|||||||
[{:keys [points] :as shape} zoom grid-edition?]
|
[{:keys [points] :as shape} zoom grid-edition?]
|
||||||
(let [leftmost (->> points (reduce left?))
|
(let [leftmost (->> points (reduce left?))
|
||||||
topmost (->> points (remove #{leftmost}) (reduce top?))
|
topmost (->> points (remove #{leftmost}) (reduce top?))
|
||||||
rightmost (->> points (remove #{leftmost topmost}) (reduce right?))
|
rightmost (->> points (remove #{leftmost topmost}) (reduce right?))]
|
||||||
|
(when (and (some? leftmost) (some? topmost) (some? rightmost))
|
||||||
|
(let [left-top (gpt/to-vec leftmost topmost)
|
||||||
|
left-top-angle (gpt/angle left-top)
|
||||||
|
|
||||||
left-top (gpt/to-vec leftmost topmost)
|
top-right (gpt/to-vec topmost rightmost)
|
||||||
left-top-angle (gpt/angle left-top)
|
top-right-angle (gpt/angle top-right)
|
||||||
|
|
||||||
top-right (gpt/to-vec topmost rightmost)
|
;; Choose the position that creates the less angle between left-side and top-side
|
||||||
top-right-angle (gpt/angle top-right)
|
[label-pos angle h-pos v-pos]
|
||||||
|
(if (< (mth/abs left-top-angle) (mth/abs top-right-angle))
|
||||||
|
[leftmost left-top-angle left-top (gpt/perpendicular left-top)]
|
||||||
|
[topmost top-right-angle top-right (gpt/perpendicular top-right)])
|
||||||
|
|
||||||
;; Choose the position that creates the less angle between left-side and top-side
|
delta-x (if grid-edition? 40 0)
|
||||||
[label-pos angle h-pos v-pos]
|
delta-y (if grid-edition? 50 10)
|
||||||
(if (< (mth/abs left-top-angle) (mth/abs top-right-angle))
|
|
||||||
[leftmost left-top-angle left-top (gpt/perpendicular left-top)]
|
|
||||||
[topmost top-right-angle top-right (gpt/perpendicular top-right)])
|
|
||||||
|
|
||||||
delta-x (if grid-edition? 40 0)
|
label-pos
|
||||||
delta-y (if grid-edition? 50 10)
|
(-> label-pos
|
||||||
|
(gpt/subtract (gpt/scale (gpt/unit v-pos) (/ delta-y zoom)))
|
||||||
label-pos
|
(gpt/subtract (gpt/scale (gpt/unit h-pos) (/ delta-x zoom))))]
|
||||||
(-> label-pos
|
(dm/fmt "rotate(% %,%) scale(%, %) translate(%, %)"
|
||||||
(gpt/subtract (gpt/scale (gpt/unit v-pos) (/ delta-y zoom)))
|
;; rotate
|
||||||
(gpt/subtract (gpt/scale (gpt/unit h-pos) (/ delta-x zoom))))]
|
angle (:x label-pos) (:y label-pos)
|
||||||
|
;; scale
|
||||||
(dm/fmt "rotate(% %,%) scale(%, %) translate(%, %)"
|
(/ 1 zoom) (/ 1 zoom)
|
||||||
;; rotate
|
;; translate
|
||||||
angle (:x label-pos) (:y label-pos)
|
(* zoom (:x label-pos)) (* zoom (:y label-pos)))))))
|
||||||
;; scale
|
|
||||||
(/ 1 zoom) (/ 1 zoom)
|
|
||||||
;; translate
|
|
||||||
(* zoom (:x label-pos)) (* zoom (:y label-pos)))))
|
|
||||||
|
|||||||
@@ -705,8 +705,8 @@
|
|||||||
[:& grid-layout/editor
|
[:& grid-layout/editor
|
||||||
{:zoom zoom
|
{:zoom zoom
|
||||||
:objects objects-modified
|
:objects objects-modified
|
||||||
:shape (or (get base-objects edition)
|
:shape (or (get objects-modified edition)
|
||||||
(get base-objects @hover-top-frame-id))
|
(get objects-modified @hover-top-frame-id))
|
||||||
:view-only (not show-grid-editor?)}])]
|
:view-only (not show-grid-editor?)}])]
|
||||||
|
|
||||||
[:g.scrollbar-wrapper {:clipPath "url(#clip-handlers)"}
|
[:g.scrollbar-wrapper {:clipPath "url(#clip-handlers)"}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
[app.main.data.workspace.groups :as dwg]
|
[app.main.data.workspace.groups :as dwg]
|
||||||
[app.main.data.workspace.media :as dwm]
|
[app.main.data.workspace.media :as dwm]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
|
[app.main.data.workspace.wasm-text :as dwwt]
|
||||||
[app.main.fonts :refer [fetch-font-css]]
|
[app.main.fonts :refer [fetch-font-css]]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
@@ -348,9 +349,14 @@
|
|||||||
|
|
||||||
:else
|
:else
|
||||||
(let [page (dsh/lookup-page @st/state)
|
(let [page (dsh/lookup-page @st/state)
|
||||||
shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
shape (-> (cts/setup-shape {:type :text
|
||||||
(update :content txt/change-text text)
|
:x 0 :y 0
|
||||||
(assoc :position-data nil))
|
:width 1 :height 1
|
||||||
|
:grow-type :auto-width})
|
||||||
|
(update :content txt/change-text text
|
||||||
|
;; Text should be given a color by default
|
||||||
|
{:fills [{:fill-color "#000000" :fill-opacity 1}]})
|
||||||
|
(dissoc :position-data))
|
||||||
|
|
||||||
changes
|
changes
|
||||||
(-> (cb/empty-changes)
|
(-> (cb/empty-changes)
|
||||||
@@ -358,9 +364,10 @@
|
|||||||
(cb/with-objects (:objects page))
|
(cb/with-objects (:objects page))
|
||||||
(cb/add-object shape))]
|
(cb/add-object shape))]
|
||||||
|
|
||||||
(st/emit!
|
(st/emit! (ch/commit-changes changes)
|
||||||
(ch/commit-changes changes)
|
(se/event plugin-id "create-shape" :type :text)
|
||||||
(se/event plugin-id "create-shape" :type :text))
|
(dwwt/resize-wasm-text-debounce (:id shape)))
|
||||||
|
|
||||||
(shape/shape-proxy plugin-id (:id shape)))))
|
(shape/shape-proxy plugin-id (:id shape)))))
|
||||||
|
|
||||||
:createShapeFromSvg
|
:createShapeFromSvg
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
desc (obj/get manifest "description")
|
desc (obj/get manifest "description")
|
||||||
code (obj/get manifest "code")
|
code (obj/get manifest "code")
|
||||||
icon (obj/get manifest "icon")
|
icon (obj/get manifest "icon")
|
||||||
|
vers (d/nilv (obj/get manifest "version") 1)
|
||||||
|
|
||||||
permissions (into #{} (obj/get manifest "permissions" []))
|
permissions (into #{} (obj/get manifest "permissions" []))
|
||||||
permissions
|
permissions
|
||||||
@@ -55,9 +56,13 @@
|
|||||||
(u/uri plugin-url)
|
(u/uri plugin-url)
|
||||||
|
|
||||||
origin
|
origin
|
||||||
(-> plugin-url
|
(if (= vers 1)
|
||||||
(u/join ".")
|
(-> plugin-url
|
||||||
(str))
|
(assoc :path "/")
|
||||||
|
(str))
|
||||||
|
(-> plugin-url
|
||||||
|
(u/join ".")
|
||||||
|
(str)))
|
||||||
|
|
||||||
prev-plugin
|
prev-plugin
|
||||||
(->> (:data @registry)
|
(->> (:data @registry)
|
||||||
|
|||||||
@@ -190,11 +190,13 @@
|
|||||||
(defn update-text-rect!
|
(defn update-text-rect!
|
||||||
[id]
|
[id]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(mw/emit!
|
(let [dimensions (get-text-dimensions id)
|
||||||
{:cmd :index/update-text-rect
|
page-id (:current-page-id @st/state)]
|
||||||
:page-id (:current-page-id @st/state)
|
(mw/emit!
|
||||||
:shape-id id
|
{:cmd :index/update-text-rect
|
||||||
:dimensions (get-text-dimensions id)})))
|
:page-id page-id
|
||||||
|
:shape-id id
|
||||||
|
:dimensions dimensions}))))
|
||||||
|
|
||||||
|
|
||||||
(defn- ensure-text-content
|
(defn- ensure-text-content
|
||||||
@@ -865,12 +867,12 @@
|
|||||||
|
|
||||||
(set-shape-vertical-align (get content :vertical-align))
|
(set-shape-vertical-align (get content :vertical-align))
|
||||||
|
|
||||||
(let [fonts (f/get-content-fonts content)
|
(let [fonts (f/get-content-fonts content)
|
||||||
fallback-fonts (fonts-from-text-content content true)
|
fallback-fonts (fonts-from-text-content content true)
|
||||||
all-fonts (concat fonts fallback-fonts)
|
all-fonts (concat fonts fallback-fonts)
|
||||||
result (f/store-fonts shape-id all-fonts)]
|
result (f/store-fonts all-fonts)]
|
||||||
(f/load-fallback-fonts-for-editor! fallback-fonts)
|
(f/load-fallback-fonts-for-editor! fallback-fonts)
|
||||||
(h/call wasm/internal-module "_update_shape_text_layout")
|
(f/update-text-layout shape-id)
|
||||||
result))
|
result))
|
||||||
|
|
||||||
(defn set-shape-grow-type
|
(defn set-shape-grow-type
|
||||||
@@ -1564,7 +1566,7 @@
|
|||||||
:text-decoration (get element :text-decoration)
|
:text-decoration (get element :text-decoration)
|
||||||
:letter-spacing (get element :letter-spacing)
|
:letter-spacing (get element :letter-spacing)
|
||||||
:font-style (get element :font-style)
|
:font-style (get element :font-style)
|
||||||
:fills (get element :fills)
|
:fills (d/nilv (get element :fills) [{:fill-color "#000000"}])
|
||||||
:text text}))))))]
|
:text text}))))))]
|
||||||
(mem/free)
|
(mem/free)
|
||||||
|
|
||||||
|
|||||||
@@ -97,9 +97,8 @@
|
|||||||
|
|
||||||
;; IMPORTANT: Only TTF fonts can be stored.
|
;; IMPORTANT: Only TTF fonts can be stored.
|
||||||
(defn- store-font-buffer
|
(defn- store-font-buffer
|
||||||
[shape-id font-data font-array-buffer emoji? fallback?]
|
[font-data font-array-buffer emoji? fallback?]
|
||||||
(let [font-id-buffer (:family-id-buffer font-data)
|
(let [font-id-buffer (:family-id-buffer font-data)
|
||||||
shape-id-buffer (uuid/get-u32 shape-id)
|
|
||||||
size (.-byteLength font-array-buffer)
|
size (.-byteLength font-array-buffer)
|
||||||
ptr (h/call wasm/internal-module "_alloc_bytes" size)
|
ptr (h/call wasm/internal-module "_alloc_bytes" size)
|
||||||
heap (gobj/get ^js wasm/internal-module "HEAPU8")
|
heap (gobj/get ^js wasm/internal-module "HEAPU8")
|
||||||
@@ -107,10 +106,6 @@
|
|||||||
|
|
||||||
(.set mem (js/Uint8Array. font-array-buffer))
|
(.set mem (js/Uint8Array. font-array-buffer))
|
||||||
(h/call wasm/internal-module "_store_font"
|
(h/call wasm/internal-module "_store_font"
|
||||||
(aget shape-id-buffer 0)
|
|
||||||
(aget shape-id-buffer 1)
|
|
||||||
(aget shape-id-buffer 2)
|
|
||||||
(aget shape-id-buffer 3)
|
|
||||||
(aget font-id-buffer 0)
|
(aget font-id-buffer 0)
|
||||||
(aget font-id-buffer 1)
|
(aget font-id-buffer 1)
|
||||||
(aget font-id-buffer 2)
|
(aget font-id-buffer 2)
|
||||||
@@ -119,24 +114,31 @@
|
|||||||
(:style font-data)
|
(:style font-data)
|
||||||
emoji?
|
emoji?
|
||||||
fallback?)
|
fallback?)
|
||||||
|
|
||||||
(update-text-layout shape-id)
|
|
||||||
|
|
||||||
true))
|
true))
|
||||||
|
|
||||||
|
;; This variable will store the fonts that are currently being fetched
|
||||||
|
;; so we don't fetch more than once the same font
|
||||||
|
(def fetching (atom #{}))
|
||||||
|
|
||||||
(defn- fetch-font
|
(defn- fetch-font
|
||||||
[shape-id font-data font-url emoji? fallback?]
|
[font-data font-url emoji? fallback?]
|
||||||
{:key font-url
|
(when-not (contains? @fetching font-url)
|
||||||
:callback #(->> (http/send! {:method :get
|
(swap! fetching conj font-url)
|
||||||
:uri font-url
|
{:key font-url
|
||||||
:response-type :buffer})
|
:callback
|
||||||
(rx/map (fn [{:keys [body]}]
|
(fn []
|
||||||
(store-font-buffer shape-id font-data body emoji? fallback?)))
|
(->> (http/send! {:method :get
|
||||||
(rx/catch (fn [cause]
|
:uri font-url
|
||||||
(log/error :hint "Could not fetch font"
|
:response-type :buffer})
|
||||||
:font-url font-url
|
(rx/map (fn [{:keys [body]}]
|
||||||
:cause cause)
|
(swap! fetching disj font-url)
|
||||||
(rx/empty))))})
|
(store-font-buffer font-data body emoji? fallback?)))
|
||||||
|
(rx/catch (fn [cause]
|
||||||
|
(swap! fetching disj font-url)
|
||||||
|
(log/error :hint "Could not fetch font"
|
||||||
|
:font-url font-url
|
||||||
|
:cause cause)
|
||||||
|
(rx/empty)))))}))
|
||||||
|
|
||||||
(defn- google-font-ttf-url
|
(defn- google-font-ttf-url
|
||||||
[font-id font-variant-id font-weight font-style]
|
[font-id font-variant-id font-weight font-style]
|
||||||
@@ -155,22 +157,31 @@
|
|||||||
:builtin
|
:builtin
|
||||||
(dm/str (u/join cf/public-uri "fonts/" asset-id))))
|
(dm/str (u/join cf/public-uri "fonts/" asset-id))))
|
||||||
|
|
||||||
|
(defn font-stored?
|
||||||
|
[font-data emoji?]
|
||||||
|
(when-let [id-buffer (uuid/get-u32 (:wasm-id font-data))]
|
||||||
|
(not= 0 (h/call wasm/internal-module "_is_font_uploaded"
|
||||||
|
(aget id-buffer 0)
|
||||||
|
(aget id-buffer 1)
|
||||||
|
(aget id-buffer 2)
|
||||||
|
(aget id-buffer 3)
|
||||||
|
(:weight font-data)
|
||||||
|
(:style font-data)
|
||||||
|
emoji?))))
|
||||||
|
|
||||||
(defn- store-font-id
|
(defn- store-font-id
|
||||||
[shape-id font-data asset-id emoji? fallback?]
|
[font-data asset-id emoji? fallback?]
|
||||||
(when asset-id
|
(when asset-id
|
||||||
(let [uri (font-id->ttf-url (:font-id font-data) asset-id (:font-variant-id font-data) (:weight font-data) (:style-name font-data))
|
(let [uri (font-id->ttf-url
|
||||||
|
(:font-id font-data) asset-id
|
||||||
|
(:font-variant-id font-data)
|
||||||
|
(:weight font-data)
|
||||||
|
(:style-name font-data))
|
||||||
id-buffer (uuid/get-u32 (:wasm-id font-data))
|
id-buffer (uuid/get-u32 (:wasm-id font-data))
|
||||||
font-data (assoc font-data :family-id-buffer id-buffer)
|
font-data (assoc font-data :family-id-buffer id-buffer)
|
||||||
font-stored? (not= 0 (h/call wasm/internal-module "_is_font_uploaded"
|
font-stored? (font-stored? font-data emoji?)]
|
||||||
(aget id-buffer 0)
|
|
||||||
(aget id-buffer 1)
|
|
||||||
(aget id-buffer 2)
|
|
||||||
(aget id-buffer 3)
|
|
||||||
(:weight font-data)
|
|
||||||
(:style font-data)
|
|
||||||
emoji?))]
|
|
||||||
(when-not font-stored?
|
(when-not font-stored?
|
||||||
(fetch-font shape-id font-data uri emoji? fallback?)))))
|
(fetch-font font-data uri emoji? fallback?)))))
|
||||||
|
|
||||||
(defn serialize-font-style
|
(defn serialize-font-style
|
||||||
[font-style]
|
[font-style]
|
||||||
@@ -280,8 +291,8 @@
|
|||||||
"regular"
|
"regular"
|
||||||
font-variant-id))
|
font-variant-id))
|
||||||
|
|
||||||
(defn store-font
|
(defn make-font-data
|
||||||
[shape-id font]
|
[font]
|
||||||
(let [font-id (get font :font-id)
|
(let [font-id (get font :font-id)
|
||||||
font-variant-id (get font :font-variant-id)
|
font-variant-id (get font :font-variant-id)
|
||||||
normalized-variant-id (when font-variant-id
|
normalized-variant-id (when font-variant-id
|
||||||
@@ -301,14 +312,21 @@
|
|||||||
(str/includes? raw-weight "italic") "italic"
|
(str/includes? raw-weight "italic") "italic"
|
||||||
:else font-style-fallback)
|
:else font-style-fallback)
|
||||||
variant-id (or (:id font-data) normalized-variant-id)
|
variant-id (or (:id font-data) normalized-variant-id)
|
||||||
asset-id (font-id->asset-id font-id variant-id raw-weight style)
|
asset-id (font-id->asset-id font-id variant-id raw-weight style)]
|
||||||
font-data {:wasm-id wasm-id
|
{:wasm-id wasm-id
|
||||||
:font-id font-id
|
:font-id font-id
|
||||||
:font-variant-id variant-id
|
:font-variant-id variant-id
|
||||||
:style (serialize-font-style style)
|
:style (serialize-font-style style)
|
||||||
:style-name style
|
:style-name style
|
||||||
:weight weight}]
|
:weight weight
|
||||||
(store-font-id shape-id font-data asset-id emoji? fallback?)))
|
:emoji? emoji?
|
||||||
|
:fallbck? fallback?
|
||||||
|
:asset-id asset-id}))
|
||||||
|
|
||||||
|
(defn store-font
|
||||||
|
[font]
|
||||||
|
(let [{:keys [asset-id emoji? fallback?] :as font-data} (make-font-data font)]
|
||||||
|
(store-font-id font-data asset-id emoji? fallback?)))
|
||||||
|
|
||||||
;; FIXME: This is a temporary function to load the fallback fonts for the editor.
|
;; FIXME: This is a temporary function to load the fallback fonts for the editor.
|
||||||
;; Once we render the editor content within wasm, we can remove this function.
|
;; Once we render the editor content within wasm, we can remove this function.
|
||||||
@@ -341,8 +359,8 @@
|
|||||||
#{}))))
|
#{}))))
|
||||||
|
|
||||||
(defn store-fonts
|
(defn store-fonts
|
||||||
[shape-id fonts]
|
[fonts]
|
||||||
(keep (fn [font] (store-font shape-id font)) fonts))
|
(keep (fn [font] (store-font font)) fonts))
|
||||||
|
|
||||||
(defn add-emoji-font
|
(defn add-emoji-font
|
||||||
[fonts]
|
[fonts]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Penpot MCP Plugin",
|
"name": "Penpot MCP Plugin",
|
||||||
"code": "plugin.js",
|
"code": "plugin.js",
|
||||||
|
"version": 2,
|
||||||
"description": "This plugin enables interaction with the Penpot MCP server",
|
"description": "This plugin enables interaction with the Penpot MCP server",
|
||||||
"permissions": ["content:read", "content:write", "library:read", "library:write", "comment:read", "comment:write"]
|
"permissions": ["content:read", "content:write", "library:read", "library:write", "comment:read", "comment:write"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/colors-to-tokens-plugin/browser" }
|
assets = { directory = "../../dist/apps/colors-to-tokens-plugin/browser" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/contrast-plugin/browser" }
|
assets = { directory = "../../dist/apps/contrast-plugin/browser" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/create-palette-plugin" }
|
assets = { directory = "../../dist/apps/create-palette-plugin" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/icons-plugin/browser" }
|
assets = { directory = "../../dist/apps/icons-plugin/browser" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" }
|
assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/rename-layers-plugin/browser" }
|
assets = { directory = "../../dist/apps/rename-layers-plugin/browser" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "../../dist/apps/table-plugin/browser" }
|
assets = { directory = "../../dist/apps/table-plugin/browser" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "dist/doc" }
|
assets = { directory = "dist/doc" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
|
|||||||
|
|
||||||
assets = { directory = "dist/apps/example-styles" }
|
assets = { directory = "dist/apps/example-styles" }
|
||||||
|
|
||||||
|
[observability]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[observability.logs]
|
||||||
|
enabled = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
persist = true
|
||||||
|
invocation_logs = true
|
||||||
|
|
||||||
|
[observability.traces]
|
||||||
|
enabled = false
|
||||||
|
persist = true
|
||||||
|
head_sampling_rate = 1
|
||||||
|
|
||||||
[[routes]]
|
[[routes]]
|
||||||
pattern = "WORKER_URI"
|
pattern = "WORKER_URI"
|
||||||
custom_domain = true
|
custom_domain = true
|
||||||
|
|||||||
@@ -346,6 +346,14 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn touch_shape(a: u32, b: u32, c: u32, d: u32) {
|
||||||
|
with_state_mut!(state, {
|
||||||
|
let shape_id = uuid_from_u32_quartet(a, b, c, d);
|
||||||
|
state.touch_shape(shape_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) {
|
pub extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
|
|||||||
@@ -1,21 +1,30 @@
|
|||||||
use skia_safe::{self as skia};
|
use skia_safe::{self as skia};
|
||||||
|
|
||||||
use crate::math::Rect;
|
|
||||||
use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::state::ShapesPoolRef;
|
use crate::state::ShapesPoolRef;
|
||||||
|
|
||||||
pub fn render_overlay(zoom: f32, canvas: &skia::Canvas, shape: &Shape, shapes: ShapesPoolRef) {
|
pub fn render_overlay(zoom: f32, canvas: &skia::Canvas, shape: &Shape, shapes: ShapesPoolRef) {
|
||||||
let cells = grid_cell_data(shape, shapes, true);
|
let cells: Vec<crate::shapes::grid_layout::CellData<'_>> = grid_cell_data(shape, shapes, true);
|
||||||
|
let bounds = shape.bounds();
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
paint.set_color(skia::Color::from_rgb(255, 111, 224));
|
paint.set_color(skia::Color::from_rgb(255, 111, 224));
|
||||||
|
paint.set_anti_alias(shape.should_use_antialias(zoom));
|
||||||
|
|
||||||
paint.set_stroke_width(1.0 / zoom);
|
paint.set_stroke_width(1.0 / zoom);
|
||||||
|
|
||||||
for cell in cells.iter() {
|
for cell in cells.iter() {
|
||||||
let rect = Rect::from_xywh(cell.anchor.x, cell.anchor.y, cell.width, cell.height);
|
let hv = bounds.hv(cell.width);
|
||||||
canvas.draw_rect(rect, &paint);
|
let vv = bounds.vv(cell.height);
|
||||||
|
let points = [
|
||||||
|
cell.anchor,
|
||||||
|
cell.anchor + hv,
|
||||||
|
cell.anchor + hv + vv,
|
||||||
|
cell.anchor + vv,
|
||||||
|
];
|
||||||
|
let polygon = skia::Path::polygon(&points, true, None, None);
|
||||||
|
canvas.draw_path(&polygon, &paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use skia_safe::{self as skia, Color4f};
|
|||||||
|
|
||||||
use super::{RenderState, ShapesPoolRef, SurfaceId};
|
use super::{RenderState, ShapesPoolRef, SurfaceId};
|
||||||
use crate::render::grid_layout;
|
use crate::render::grid_layout;
|
||||||
|
use crate::shapes::{Layout, Type};
|
||||||
|
|
||||||
pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
||||||
@@ -18,12 +19,37 @@ pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
|||||||
|
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
||||||
|
|
||||||
if let Some(id) = render_state.show_grid {
|
let show_grid_id = render_state.show_grid;
|
||||||
|
|
||||||
|
if let Some(id) = show_grid_id {
|
||||||
if let Some(shape) = shapes.get(&id) {
|
if let Some(shape) = shapes.get(&id) {
|
||||||
grid_layout::render_overlay(zoom, canvas, shape, shapes);
|
grid_layout::render_overlay(zoom, canvas, shape, shapes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render overlays for empty grid frames
|
||||||
|
for shape in shapes.iter() {
|
||||||
|
if shape.id.is_nil() || !shape.children.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_grid_id == Some(shape.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Type::Frame(frame) = &shape.shape_type else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !matches!(frame.layout, Some(Layout::GridLayout(_, _))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(shape) = shapes.get(&shape.id) {
|
||||||
|
grid_layout::render_overlay(zoom, canvas, shape, shapes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
render_state.surfaces.draw_into(
|
render_state.surfaces.draw_into(
|
||||||
SurfaceId::UI,
|
SurfaceId::UI,
|
||||||
|
|||||||
@@ -1074,6 +1074,10 @@ impl Shape {
|
|||||||
self.children.first()
|
self.children.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn children_count(&self) -> usize {
|
||||||
|
self.children_ids_iter(false).count()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn children_ids(&self, include_hidden: bool) -> Vec<Uuid> {
|
pub fn children_ids(&self, include_hidden: bool) -> Vec<Uuid> {
|
||||||
if include_hidden {
|
if include_hidden {
|
||||||
return self.children.iter().rev().copied().collect();
|
return self.children.iter().rev().copied().collect();
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ fn propagate_transform(
|
|||||||
|
|
||||||
// If this is a layout and we're only moving don't need to reflow
|
// If this is a layout and we're only moving don't need to reflow
|
||||||
if shape.has_layout() && is_resize {
|
if shape.has_layout() && is_resize {
|
||||||
entries.push_back(Modifier::reflow(shape.id));
|
entries.push_back(Modifier::reflow(shape.id, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
|
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
|
||||||
@@ -272,7 +272,7 @@ fn propagate_transform(
|
|||||||
// if the current transformation is not a move propagation.
|
// if the current transformation is not a move propagation.
|
||||||
// If it's a move propagation we don't need to reflow, the parent is already changed.
|
// If it's a move propagation we don't need to reflow, the parent is already changed.
|
||||||
if (parent.has_layout() || parent.is_group_like()) && (is_resize || !is_propagate) {
|
if (parent.has_layout() || parent.is_group_like()) && (is_resize || !is_propagate) {
|
||||||
entries.push_back(Modifier::reflow(parent.id));
|
entries.push_back(Modifier::reflow(parent.id, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +282,7 @@ fn propagate_reflow(
|
|||||||
state: &State,
|
state: &State,
|
||||||
entries: &mut VecDeque<Modifier>,
|
entries: &mut VecDeque<Modifier>,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
layout_reflows: &mut Vec<Uuid>,
|
layout_reflows: &mut HashSet<Uuid>,
|
||||||
reflown: &mut HashSet<Uuid>,
|
reflown: &mut HashSet<Uuid>,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
) {
|
) {
|
||||||
@@ -300,20 +300,7 @@ fn propagate_reflow(
|
|||||||
Type::Frame(Frame {
|
Type::Frame(Frame {
|
||||||
layout: Some(_), ..
|
layout: Some(_), ..
|
||||||
}) => {
|
}) => {
|
||||||
let mut skip_reflow = false;
|
layout_reflows.insert(*id);
|
||||||
if shape.is_layout_horizontal_fill() || shape.is_layout_vertical_fill() {
|
|
||||||
if let Some(parent_id) = shape.parent_id {
|
|
||||||
if parent_id != Uuid::nil() && !reflown.contains(&parent_id) {
|
|
||||||
// If this is a fill layout but the parent has not been reflown yet
|
|
||||||
// we wait for the next iteration for reflow
|
|
||||||
skip_reflow = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !skip_reflow {
|
|
||||||
layout_reflows.push(*id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Type::Group(Group { masked: true }) => {
|
Type::Group(Group { masked: true }) => {
|
||||||
let children_ids = shape.children_ids(true);
|
let children_ids = shape.children_ids(true);
|
||||||
@@ -340,7 +327,7 @@ fn propagate_reflow(
|
|||||||
|
|
||||||
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
|
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
|
||||||
if parent.has_layout() || parent.is_group_like() {
|
if parent.has_layout() || parent.is_group_like() {
|
||||||
entries.push_back(Modifier::reflow(parent.id));
|
entries.push_back(Modifier::reflow(parent.id, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,19 +369,20 @@ pub fn propagate_modifiers(
|
|||||||
let mut entries: VecDeque<_> = modifiers
|
let mut entries: VecDeque<_> = modifiers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
// If we receibe a identity matrix we force a reflow
|
// If we receive a identity matrix we force a reflow
|
||||||
if math::identitish(&entry.transform) {
|
if math::identitish(&entry.transform) {
|
||||||
Modifier::Reflow(entry.id)
|
Modifier::Reflow(entry.id, false)
|
||||||
} else {
|
} else {
|
||||||
Modifier::Transform(*entry)
|
Modifier::Transform(*entry)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let shapes = &state.shapes;
|
||||||
let mut modifiers = HashMap::<Uuid, Matrix>::new();
|
let mut modifiers = HashMap::<Uuid, Matrix>::new();
|
||||||
let mut bounds = HashMap::<Uuid, Bounds>::new();
|
let mut bounds = HashMap::<Uuid, Bounds>::new();
|
||||||
let mut reflown = HashSet::<Uuid>::new();
|
let mut reflown = HashSet::<Uuid>::new();
|
||||||
let mut layout_reflows = Vec::<Uuid>::new();
|
let mut layout_reflows = HashSet::<Uuid>::new();
|
||||||
|
|
||||||
// We first propagate the transforms to the children and then after
|
// We first propagate the transforms to the children and then after
|
||||||
// recalculate the layouts. The layout can create further transforms that
|
// recalculate the layouts. The layout can create further transforms that
|
||||||
@@ -412,25 +400,43 @@ pub fn propagate_modifiers(
|
|||||||
&mut bounds,
|
&mut bounds,
|
||||||
&mut modifiers,
|
&mut modifiers,
|
||||||
),
|
),
|
||||||
Modifier::Reflow(id) => propagate_reflow(
|
Modifier::Reflow(id, force_reflow) => {
|
||||||
&id,
|
if force_reflow {
|
||||||
state,
|
reflown.remove(&id);
|
||||||
&mut entries,
|
}
|
||||||
&mut bounds,
|
|
||||||
&mut layout_reflows,
|
propagate_reflow(
|
||||||
&mut reflown,
|
&id,
|
||||||
&modifiers,
|
state,
|
||||||
),
|
&mut entries,
|
||||||
|
&mut bounds,
|
||||||
|
&mut layout_reflows,
|
||||||
|
&mut reflown,
|
||||||
|
&modifiers,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for id in layout_reflows.iter() {
|
let mut layout_reflows_vec: Vec<Uuid> = layout_reflows.into_iter().collect();
|
||||||
|
|
||||||
|
// We sort the reflows so they are process first the ones that are more
|
||||||
|
// deep in the tree structure. This way we can be sure that the children layouts
|
||||||
|
// are already reflowed.
|
||||||
|
layout_reflows_vec.sort_unstable_by(|id_a, id_b| {
|
||||||
|
let da = shapes.get_depth(id_a);
|
||||||
|
let db = shapes.get_depth(id_b);
|
||||||
|
db.cmp(&da)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut bounds_temp = bounds.clone();
|
||||||
|
for id in layout_reflows_vec.iter() {
|
||||||
if reflown.contains(id) {
|
if reflown.contains(id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reflow_shape(id, state, &mut reflown, &mut entries, &mut bounds);
|
reflow_shape(id, state, &mut reflown, &mut entries, &mut bounds_temp);
|
||||||
}
|
}
|
||||||
layout_reflows = Vec::new();
|
layout_reflows = HashSet::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiers
|
modifiers
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ impl LayoutAxis {
|
|||||||
layout_data: &LayoutData,
|
layout_data: &LayoutData,
|
||||||
flex_data: &FlexData,
|
flex_data: &FlexData,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let num_child = shape.children_count();
|
||||||
if flex_data.is_row() {
|
if flex_data.is_row() {
|
||||||
Self {
|
Self {
|
||||||
main_size: layout_bounds.width(),
|
main_size: layout_bounds.width(),
|
||||||
@@ -73,8 +74,8 @@ impl LayoutAxis {
|
|||||||
padding_across_end: layout_data.padding_bottom,
|
padding_across_end: layout_data.padding_bottom,
|
||||||
gap_main: layout_data.column_gap,
|
gap_main: layout_data.column_gap,
|
||||||
gap_across: layout_data.row_gap,
|
gap_across: layout_data.row_gap,
|
||||||
is_auto_main: shape.is_layout_horizontal_auto(),
|
is_auto_main: num_child > 0 && shape.is_layout_horizontal_auto(),
|
||||||
is_auto_across: shape.is_layout_vertical_auto(),
|
is_auto_across: num_child > 0 && shape.is_layout_vertical_auto(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
@@ -88,8 +89,8 @@ impl LayoutAxis {
|
|||||||
padding_across_end: layout_data.padding_right,
|
padding_across_end: layout_data.padding_right,
|
||||||
gap_main: layout_data.row_gap,
|
gap_main: layout_data.row_gap,
|
||||||
gap_across: layout_data.column_gap,
|
gap_across: layout_data.column_gap,
|
||||||
is_auto_main: shape.is_layout_vertical_auto(),
|
is_auto_main: num_child > 0 && shape.is_layout_vertical_auto(),
|
||||||
is_auto_across: shape.is_layout_horizontal_auto(),
|
is_auto_across: num_child > 0 && shape.is_layout_horizontal_auto(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +346,10 @@ fn distribute_fill_across_space(layout_axis: &LayoutAxis, tracks: &mut [TrackDat
|
|||||||
let mut size =
|
let mut size =
|
||||||
track.across_size - child.margin_across_start - child.margin_across_end;
|
track.across_size - child.margin_across_start - child.margin_across_end;
|
||||||
size = size.clamp(child.min_across_size, child.max_across_size);
|
size = size.clamp(child.min_across_size, child.max_across_size);
|
||||||
size = f32::min(size, layout_axis.across_space());
|
|
||||||
|
if !layout_axis.is_auto_across {
|
||||||
|
size = f32::min(size, layout_axis.across_space());
|
||||||
|
}
|
||||||
child.across_size = size;
|
child.across_size = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,9 +624,12 @@ pub fn reflow_flex_layout(
|
|||||||
|
|
||||||
let mut transform = Matrix::default();
|
let mut transform = Matrix::default();
|
||||||
|
|
||||||
|
let mut force_reflow = false;
|
||||||
if (new_width - child_bounds.width()).abs() > MIN_SIZE
|
if (new_width - child_bounds.width()).abs() > MIN_SIZE
|
||||||
|| (new_height - child_bounds.height()).abs() > MIN_SIZE
|
|| (new_height - child_bounds.height()).abs() > MIN_SIZE
|
||||||
{
|
{
|
||||||
|
// When the child is fill we need to force a reflow
|
||||||
|
force_reflow = true;
|
||||||
transform.post_concat(&math::resize_matrix(
|
transform.post_concat(&math::resize_matrix(
|
||||||
layout_bounds,
|
layout_bounds,
|
||||||
child_bounds,
|
child_bounds,
|
||||||
@@ -637,7 +644,7 @@ pub fn reflow_flex_layout(
|
|||||||
|
|
||||||
result.push_back(Modifier::transform_propagate(child.id, transform));
|
result.push_back(Modifier::transform_propagate(child.id, transform));
|
||||||
if child.has_layout() {
|
if child.has_layout() {
|
||||||
result.push_back(Modifier::reflow(child.id));
|
result.push_back(Modifier::reflow(child.id, force_reflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_anchor = next_anchor(
|
shape_anchor = next_anchor(
|
||||||
|
|||||||
@@ -765,9 +765,12 @@ pub fn reflow_grid_layout(
|
|||||||
|
|
||||||
let mut transform = Matrix::default();
|
let mut transform = Matrix::default();
|
||||||
|
|
||||||
|
let mut force_reflow = false;
|
||||||
if (new_width - child_bounds.width()).abs() > MIN_SIZE
|
if (new_width - child_bounds.width()).abs() > MIN_SIZE
|
||||||
|| (new_height - child_bounds.height()).abs() > MIN_SIZE
|
|| (new_height - child_bounds.height()).abs() > MIN_SIZE
|
||||||
{
|
{
|
||||||
|
// When the child is a fill it needs to be reflown
|
||||||
|
force_reflow = true;
|
||||||
transform.post_concat(&math::resize_matrix(
|
transform.post_concat(&math::resize_matrix(
|
||||||
&layout_bounds,
|
&layout_bounds,
|
||||||
&child_bounds,
|
&child_bounds,
|
||||||
@@ -793,7 +796,7 @@ pub fn reflow_grid_layout(
|
|||||||
|
|
||||||
result.push_back(Modifier::transform_propagate(child.id, transform));
|
result.push_back(Modifier::transform_propagate(child.id, transform));
|
||||||
if child.has_layout() {
|
if child.has_layout() {
|
||||||
result.push_back(Modifier::reflow(child.id));
|
result.push_back(Modifier::reflow(child.id, force_reflow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use skia::Matrix;
|
|||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub enum Modifier {
|
pub enum Modifier {
|
||||||
Transform(TransformEntry),
|
Transform(TransformEntry),
|
||||||
Reflow(Uuid),
|
Reflow(Uuid, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modifier {
|
impl Modifier {
|
||||||
@@ -18,8 +18,8 @@ impl Modifier {
|
|||||||
pub fn parent(id: Uuid, transform: Matrix) -> Self {
|
pub fn parent(id: Uuid, transform: Matrix) -> Self {
|
||||||
Modifier::Transform(TransformEntry::parent(id, transform))
|
Modifier::Transform(TransformEntry::parent(id, transform))
|
||||||
}
|
}
|
||||||
pub fn reflow(id: Uuid) -> Self {
|
pub fn reflow(id: Uuid, force_reflow: bool) -> Self {
|
||||||
Modifier::Reflow(id)
|
Modifier::Reflow(id, force_reflow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,26 @@ impl ShapesPoolImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an id, returns the depth in the tree-shaped structure
|
||||||
|
// of shapes.
|
||||||
|
pub fn get_depth(&self, id: &Uuid) -> usize {
|
||||||
|
if id == &Uuid::nil() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(idx) = self.uuid_to_idx.get(id) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let shape = &self.shapes[*idx];
|
||||||
|
|
||||||
|
let Some(parent_id) = shape.parent_id else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.get_depth(&parent_id) + 1
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn iter(&self) -> std::slice::Iter<'_, Shape> {
|
pub fn iter(&self) -> std::slice::Iter<'_, Shape> {
|
||||||
self.shapes.iter()
|
self.shapes.iter()
|
||||||
|
|||||||
@@ -31,21 +31,17 @@ impl From<RawFontStyle> for FontStyle {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn store_font(
|
pub extern "C" fn store_font(
|
||||||
a1: u32,
|
a: u32,
|
||||||
b1: u32,
|
b: u32,
|
||||||
c1: u32,
|
c: u32,
|
||||||
d1: u32,
|
d: u32,
|
||||||
a2: u32,
|
|
||||||
b2: u32,
|
|
||||||
c2: u32,
|
|
||||||
d2: u32,
|
|
||||||
weight: u32,
|
weight: u32,
|
||||||
style: u8,
|
style: u8,
|
||||||
is_emoji: bool,
|
is_emoji: bool,
|
||||||
is_fallback: bool,
|
is_fallback: bool,
|
||||||
) {
|
) {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
let id = uuid_from_u32_quartet(a2, b2, c2, d2);
|
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||||
let font_bytes = mem::bytes();
|
let font_bytes = mem::bytes();
|
||||||
let font_style = RawFontStyle::from(style);
|
let font_style = RawFontStyle::from(style);
|
||||||
|
|
||||||
@@ -57,9 +53,6 @@ pub extern "C" fn store_font(
|
|||||||
.add(family, &font_bytes, is_emoji, is_fallback);
|
.add(family, &font_bytes, is_emoji, is_fallback);
|
||||||
|
|
||||||
mem::free_bytes();
|
mem::free_bytes();
|
||||||
|
|
||||||
let shape_id = uuid_from_u32_quartet(a1, b1, c1, d1);
|
|
||||||
state.touch_shape(shape_id);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ pub extern "C" fn update_shape_text_layout_for(a: u32, b: u32, c: u32, d: u32) {
|
|||||||
if let Some(shape) = state.shapes.get_mut(&shape_id) {
|
if let Some(shape) = state.shapes.get_mut(&shape_id) {
|
||||||
update_text_layout(shape);
|
update_text_layout(shape);
|
||||||
}
|
}
|
||||||
|
state.touch_shape(shape_id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user