mirror of
https://github.com/penpot/penpot.git
synced 2026-07-22 22:17:58 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
f84a8687f6
@ -35,7 +35,7 @@ Command what should be built. Format: `[Imperative verb] [what] in/on [where]`.
|
||||
|
||||
| Field | Rule |
|
||||
|-------|------|
|
||||
| **Labels** | `bug` (crashes/regressions) · `enhancement` (new features) · `community contribution` (PRs from non-core) · skip workflow labels (`backport candidate`, `team-qa`) |
|
||||
| **Labels** | `community contribution` (PRs from non-core) · skip workflow labels (`backport candidate`, `team-qa`) · do **not** add `bug` or `enhancement` labels (use Issue Type instead) |
|
||||
| **Milestone** | Use the current or next planned milestone. Fetch available milestones: `gh api repos/penpot/penpot/milestones --jq '.[].title'`. If unsure, omit. |
|
||||
| **Project** | Always `Main` (project number 8). Use `--project "Main"` flag. |
|
||||
| **Issue Type** | See Issue Type section below. Cannot be set via `gh issue create` — use GraphQL after creation. |
|
||||
@ -114,8 +114,8 @@ Output: `https://github.com/penpot/penpot/issues/<NUMBER>`
|
||||
| Docs | `IT_kwDOAcyBPM4B_IQz` |
|
||||
|
||||
**Map:**
|
||||
- `bug` label → Bug
|
||||
- `enhancement` label → Enhancement
|
||||
- Bug report (steps to reproduce, expected vs. actual) → Bug
|
||||
- Enhancement / new feature → Enhancement
|
||||
- Feature/epic → Feature
|
||||
- Docs → Docs
|
||||
- None of the above → Task
|
||||
|
||||
@ -2,6 +2,20 @@
|
||||
|
||||
PR only on explicit request. Branch: issue/feature-specific; fallback `<type>/<short-description>` (`fix/...`, `feat/...`, `refactor/...`, `docs/...`, `chore/...`, `perf/...`).
|
||||
|
||||
## Target Branch
|
||||
|
||||
Auto-detect the base branch with `tools/detect-target-branch`:
|
||||
|
||||
```bash
|
||||
TARGET=$(tools/detect-target-branch)
|
||||
```
|
||||
|
||||
This outputs `staging` or `develop` by walking the local commit graph (pure local, no remote/network). Do not ask the user for the target branch unless the tool fails.
|
||||
|
||||
## Metadata
|
||||
|
||||
Always add the PR to the Main project (`--project "Main"`) unless the user explicitly requests a different project.
|
||||
|
||||
## Title Format
|
||||
|
||||
PR titles follow commit title conventions:
|
||||
@ -60,3 +74,24 @@ The "Note:" line is required at the top. Adjust if this is a manual (non-AI) PR.
|
||||
- Follow `mem:workflow/creating-commits` for commits
|
||||
- Run the focused tests/lints appropriate to touched modules.
|
||||
- Do not force-push during review unless the maintainer workflow explicitly asks for it.
|
||||
- When the user says the code is already pushed, trust that — do not verify remote branch existence via `git ls-remote` or `git fetch`.
|
||||
|
||||
## Creating the PR
|
||||
|
||||
```bash
|
||||
cat > /tmp/pr-body.md << 'PR_BODY'
|
||||
<body content here>
|
||||
PR_BODY
|
||||
|
||||
TARGET=$(tools/detect-target-branch)
|
||||
|
||||
gh pr create \
|
||||
--repo penpot/penpot \
|
||||
--base "$TARGET" \
|
||||
--head <branch> \
|
||||
--title "<title>" \
|
||||
--project "Main" \
|
||||
--body-file /tmp/pr-body.md
|
||||
|
||||
rm -f /tmp/pr-body.md
|
||||
```
|
||||
|
||||
@ -331,7 +331,10 @@
|
||||
|
||||
This expands to a single SQL statement with placeholders for every
|
||||
value being inserted. For large data sets, this may exceed the limit
|
||||
of sql string size and/or number of parameters."
|
||||
of sql string size and/or number of parameters.
|
||||
|
||||
See `insert-many-chunked!` for a safe alternative that automatically
|
||||
partitions rows to stay within the parameter limit."
|
||||
[ds table cols rows & {:as opts}]
|
||||
(let [conn (get-connectable ds)
|
||||
sql (sql/insert-many table cols rows opts)
|
||||
@ -341,6 +344,24 @@
|
||||
opts (update opts :return-keys boolean)]
|
||||
(jdbc/execute! conn sql opts)))
|
||||
|
||||
(def ^:private default-max-params
|
||||
"PostgreSQL PreparedStatement parameter limit."
|
||||
65535)
|
||||
|
||||
(defn insert-many-chunked!
|
||||
"Like `insert-many!` but partitions rows into chunks that stay within
|
||||
PostgreSQL's 65,535 PreparedStatement parameter limit.
|
||||
|
||||
The chunk size is computed as `floor(max-params / num-columns)`,
|
||||
so callers do not need to calculate it. All chunks execute within
|
||||
the same transaction when called inside `tx-run!`."
|
||||
[ds table cols rows & {:keys [max-params] :as opts
|
||||
:or {max-params default-max-params}}]
|
||||
(let [chunk-size (quot max-params (count cols))
|
||||
opts (dissoc opts :max-params)]
|
||||
(doseq [chunk (partition-all chunk-size rows)]
|
||||
(apply insert-many! ds table cols chunk (mapcat identity opts)))))
|
||||
|
||||
(defn update!
|
||||
"A helper that build an UPDATE SQL statement and executes it.
|
||||
|
||||
|
||||
@ -65,7 +65,6 @@
|
||||
(assert (every? string? cmd) "the command should be a vector of strings")
|
||||
|
||||
(let [executor (::wrk/executor system)
|
||||
_ (assert (some? executor) "executor is required, check ::wrk/executor")
|
||||
full-cmd (cond->> cmd
|
||||
(seq prlimit)
|
||||
(into (prlimit-cmd prlimit)))
|
||||
@ -74,6 +73,9 @@
|
||||
_ (reduce-kv set-env env-map env)
|
||||
process (.start builder)]
|
||||
|
||||
(when-not executor
|
||||
(throw (IllegalArgumentException. "invalid system/cfg provided, missing ::wrk/executor")))
|
||||
|
||||
(if in
|
||||
(px/run! executor
|
||||
(fn []
|
||||
|
||||
@ -8,12 +8,17 @@
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.util.shell :as shell]
|
||||
[app.worker :as-alias wrk]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as t]))
|
||||
[clojure.test :as t]
|
||||
[promesa.exec :as px]))
|
||||
|
||||
(def ^:private system
|
||||
{::wrk/executor (px/cached-executor)})
|
||||
|
||||
(t/deftest exec-normal-completes
|
||||
(t/testing "normal process completes within timeout"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["echo" "hello"]
|
||||
:timeout 10)]
|
||||
(t/is (= 0 (:exit result)))
|
||||
@ -21,7 +26,7 @@
|
||||
|
||||
(t/deftest exec-captures-stderr
|
||||
(t/testing "stderr is captured separately"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["bash" "-c" "echo out; echo err >&2"]
|
||||
:timeout 10)]
|
||||
(t/is (= 0 (:exit result)))
|
||||
@ -30,14 +35,14 @@
|
||||
|
||||
(t/deftest exec-non-zero-exit
|
||||
(t/testing "non-zero exit code is captured"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["bash" "-c" "exit 42"]
|
||||
:timeout 10)]
|
||||
(t/is (= 42 (:exit result))))))
|
||||
|
||||
(t/deftest exec-with-env
|
||||
(t/testing "environment variables are passed to the process"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["bash" "-c" "echo $MY_VAR"]
|
||||
:env {"MY_VAR" "test-value"}
|
||||
:timeout 10)]
|
||||
@ -46,7 +51,7 @@
|
||||
|
||||
(t/deftest exec-with-input
|
||||
(t/testing "stdin input is passed to the process"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["cat"]
|
||||
:in "hello from stdin"
|
||||
:timeout 10)]
|
||||
@ -57,7 +62,7 @@
|
||||
(t/testing "process that exceeds timeout is killed and raises exception"
|
||||
(let [start (System/currentTimeMillis)]
|
||||
(try
|
||||
(shell/exec! {}
|
||||
(shell/exec! system
|
||||
:cmd ["sleep" "60"]
|
||||
:timeout 1)
|
||||
(t/is false "should have thrown")
|
||||
@ -72,14 +77,14 @@
|
||||
|
||||
(t/deftest exec-no-timeout-waits
|
||||
(t/testing "without timeout, process runs to completion"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["sleep" "0.1"]
|
||||
:timeout nil)]
|
||||
(t/is (= 0 (:exit result))))))
|
||||
|
||||
(t/deftest exec-prlimit-normal
|
||||
(t/testing "normal process completes within prlimit"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["echo" "hello"]
|
||||
:prlimit {:mem 256 :cpu 10}
|
||||
:timeout 10)]
|
||||
@ -88,7 +93,7 @@
|
||||
|
||||
(t/deftest exec-prlimit-cpu
|
||||
(t/testing "process exceeding CPU limit is killed"
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["bash" "-c" "while true; do :; done"]
|
||||
:prlimit {:cpu 2}
|
||||
:timeout 10)]
|
||||
@ -98,7 +103,7 @@
|
||||
(t/testing "process exceeding memory limit is killed"
|
||||
;; Use python3 to allocate more memory than the limit allows.
|
||||
;; This test requires python3 to be available in the environment.
|
||||
(let [result (shell/exec! {}
|
||||
(let [result (shell/exec! system
|
||||
:cmd ["python3" "-c"
|
||||
"import sys; x = bytearray(600 * 1024 * 1024); sys.exit(0)"]
|
||||
:prlimit {:mem 256}
|
||||
|
||||
@ -500,9 +500,9 @@
|
||||
shape-transform (:transform shape)
|
||||
shape-transform-inv (:transform-inverse shape)
|
||||
shape-center (gco/shape->center shape)
|
||||
{sr-width :width sr-height :height} (:selrect shape)
|
||||
{sr-width :width sr-height :height} (safe-size-rect shape)
|
||||
|
||||
origin (cond-> (gpt/point (:selrect shape))
|
||||
origin (cond-> (gpt/point (safe-size-rect shape))
|
||||
(some? shape-transform)
|
||||
(gmt/transform-point-center shape-center shape-transform))
|
||||
|
||||
|
||||
@ -392,7 +392,9 @@
|
||||
([shape]
|
||||
(convert-to-path shape {}))
|
||||
([shape objects]
|
||||
(-> (stp/convert-to-path shape objects)
|
||||
(update :content impl/path-data))))
|
||||
(let [shape' (stp/convert-to-path shape objects)]
|
||||
(if (identical? shape shape')
|
||||
shape'
|
||||
(update shape' :content impl/path-data)))))
|
||||
|
||||
(dm/export impl/decode-segments)
|
||||
|
||||
@ -657,3 +657,35 @@
|
||||
mods (ctm/rotation (ctm/empty) (gpt/point 50 25) 45)
|
||||
result (ctm/apply-structure-modifiers shape mods)]
|
||||
(t/is (mth/close? 45.0 (:rotation result))))))
|
||||
|
||||
;; ─── change-orientation-modifiers — degenerate selrect ────────────────────────
|
||||
|
||||
(defn- make-degenerate-shape
|
||||
"Build a shape whose selrect has zero width/height, simulating a shape
|
||||
decoded from the server via map->Rect (bypasses make-rect's 0.01 floor)."
|
||||
[x y selrect-width selrect-height]
|
||||
(let [shape (make-shape x y 100 50)]
|
||||
(assoc shape :selrect (grc/map->Rect {:x x :y y
|
||||
:width selrect-width
|
||||
:height selrect-height
|
||||
:x1 x :y1 y
|
||||
:x2 (+ x selrect-width)
|
||||
:y2 (+ y selrect-height)}))))
|
||||
|
||||
(t/deftest change-orientation-zero-width-selrect-does-not-throw
|
||||
(t/testing "orientation change on a shape with zero selrect width does not throw"
|
||||
(let [shape (make-degenerate-shape 0 0 0 50)
|
||||
mods (ctm/change-orientation-modifiers shape :horiz)]
|
||||
(t/is (some? mods)))))
|
||||
|
||||
(t/deftest change-orientation-zero-height-selrect-does-not-throw
|
||||
(t/testing "orientation change on a shape with zero selrect height does not throw"
|
||||
(let [shape (make-degenerate-shape 0 0 100 0)
|
||||
mods (ctm/change-orientation-modifiers shape :vert)]
|
||||
(t/is (some? mods)))))
|
||||
|
||||
(t/deftest change-orientation-zero-width-and-height-selrect-does-not-throw
|
||||
(t/testing "orientation change on a fully degenerate selrect does not throw"
|
||||
(let [shape (make-degenerate-shape 0 0 0 0)
|
||||
mods (ctm/change-orientation-modifiers shape :horiz)]
|
||||
(t/is (some? mods)))))
|
||||
|
||||
@ -1370,6 +1370,15 @@
|
||||
;; A path shape stays a path shape unchanged
|
||||
(t/is (= :path (:type result)))))
|
||||
|
||||
(t/deftest shape-to-path-svg-raw-does-not-throw
|
||||
(let [shape {:type :svg-raw :x 0.0 :y 0.0 :width 100.0 :height 50.0
|
||||
:selrect (make-selrect 0.0 0.0 100.0 50.0)
|
||||
:content {:tag :text :attrs {:style {}}
|
||||
:content [{:tag :tspan :attrs {} :content ["x"]}]}}
|
||||
result (path/convert-to-path shape {})]
|
||||
(t/is (= :svg-raw (:type result)))
|
||||
(t/is (some? (:content result)))))
|
||||
|
||||
(t/deftest shape-to-path-rect-with-radius
|
||||
(let [shape {:type :rect :x 0.0 :y 0.0 :width 100.0 :height 100.0
|
||||
:r1 10.0 :r2 10.0 :r3 10.0 :r4 10.0
|
||||
|
||||
@ -66,7 +66,7 @@ RUN set -eux; \
|
||||
|
||||
FROM base AS setup-opencode
|
||||
|
||||
ENV OPENCODE_VERSION=1.17.13
|
||||
ENV OPENCODE_VERSION=1.17.16
|
||||
|
||||
RUN set -ex; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0",
|
||||
"draft-js": "penpot/draft-js.git#ba3b26ed63a01227a3560e440531b69d79c03f35",
|
||||
"immutable": "^5.1.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
449
frontend/packages/draft-js/pnpm-lock.yaml
generated
449
frontend/packages/draft-js/pnpm-lock.yaml
generated
@ -1,449 +0,0 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
draft-js:
|
||||
specifier: penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0
|
||||
version: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
immutable:
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4
|
||||
react:
|
||||
specifier: '>=0.17.0'
|
||||
version: 19.2.3
|
||||
react-dom:
|
||||
specifier: '>=0.17.0'
|
||||
version: 19.2.3(react@19.2.3)
|
||||
devDependencies:
|
||||
esbuild:
|
||||
specifier: ^0.27.2
|
||||
version: 0.27.2
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.2':
|
||||
resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.2':
|
||||
resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.2':
|
||||
resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.2':
|
||||
resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.2':
|
||||
resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.2':
|
||||
resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.2':
|
||||
resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.2':
|
||||
resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.2':
|
||||
resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.2':
|
||||
resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.2':
|
||||
resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.2':
|
||||
resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.2':
|
||||
resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.2':
|
||||
resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.2':
|
||||
resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
asap@2.0.6:
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
|
||||
cross-fetch@3.2.0:
|
||||
resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==}
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0:
|
||||
resolution: {tarball: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0}
|
||||
version: 0.11.7
|
||||
peerDependencies:
|
||||
react: '>=0.14.0'
|
||||
react-dom: '>=0.14.0'
|
||||
|
||||
esbuild@0.27.2:
|
||||
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fbjs-css-vars@1.0.2:
|
||||
resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==}
|
||||
|
||||
fbjs@3.0.5:
|
||||
resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==}
|
||||
|
||||
immutable@3.7.6:
|
||||
resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
immutable@5.1.4:
|
||||
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
promise@7.3.1:
|
||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
||||
|
||||
react-dom@19.2.3:
|
||||
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
|
||||
peerDependencies:
|
||||
react: ^19.2.3
|
||||
|
||||
react@19.2.3:
|
||||
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
setimmediate@1.0.5:
|
||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
ua-parser-js@1.0.41:
|
||||
resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==}
|
||||
hasBin: true
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
asap@2.0.6: {}
|
||||
|
||||
cross-fetch@3.2.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
fbjs: 3.0.5
|
||||
immutable: 3.7.6
|
||||
object-assign: 4.1.1
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
esbuild@0.27.2:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.2
|
||||
'@esbuild/android-arm': 0.27.2
|
||||
'@esbuild/android-arm64': 0.27.2
|
||||
'@esbuild/android-x64': 0.27.2
|
||||
'@esbuild/darwin-arm64': 0.27.2
|
||||
'@esbuild/darwin-x64': 0.27.2
|
||||
'@esbuild/freebsd-arm64': 0.27.2
|
||||
'@esbuild/freebsd-x64': 0.27.2
|
||||
'@esbuild/linux-arm': 0.27.2
|
||||
'@esbuild/linux-arm64': 0.27.2
|
||||
'@esbuild/linux-ia32': 0.27.2
|
||||
'@esbuild/linux-loong64': 0.27.2
|
||||
'@esbuild/linux-mips64el': 0.27.2
|
||||
'@esbuild/linux-ppc64': 0.27.2
|
||||
'@esbuild/linux-riscv64': 0.27.2
|
||||
'@esbuild/linux-s390x': 0.27.2
|
||||
'@esbuild/linux-x64': 0.27.2
|
||||
'@esbuild/netbsd-arm64': 0.27.2
|
||||
'@esbuild/netbsd-x64': 0.27.2
|
||||
'@esbuild/openbsd-arm64': 0.27.2
|
||||
'@esbuild/openbsd-x64': 0.27.2
|
||||
'@esbuild/openharmony-arm64': 0.27.2
|
||||
'@esbuild/sunos-x64': 0.27.2
|
||||
'@esbuild/win32-arm64': 0.27.2
|
||||
'@esbuild/win32-ia32': 0.27.2
|
||||
'@esbuild/win32-x64': 0.27.2
|
||||
|
||||
fbjs-css-vars@1.0.2: {}
|
||||
|
||||
fbjs@3.0.5:
|
||||
dependencies:
|
||||
cross-fetch: 3.2.0
|
||||
fbjs-css-vars: 1.0.2
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
promise: 7.3.1
|
||||
setimmediate: 1.0.5
|
||||
ua-parser-js: 1.0.41
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
immutable@3.7.6: {}
|
||||
|
||||
immutable@5.1.4: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
promise@7.3.1:
|
||||
dependencies:
|
||||
asap: 2.0.6
|
||||
|
||||
react-dom@19.2.3(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
scheduler: 0.27.0
|
||||
|
||||
react@19.2.3: {}
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
setimmediate@1.0.5: {}
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
ua-parser-js@1.0.41: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
10
frontend/pnpm-lock.yaml
generated
10
frontend/pnpm-lock.yaml
generated
@ -263,8 +263,8 @@ importers:
|
||||
packages/draft-js:
|
||||
dependencies:
|
||||
draft-js:
|
||||
specifier: penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0
|
||||
version: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
specifier: penpot/draft-js.git#ba3b26ed63a01227a3560e440531b69d79c03f35
|
||||
version: https://codeload.github.com/penpot/draft-js/tar.gz/ba3b26ed63a01227a3560e440531b69d79c03f35(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
immutable:
|
||||
specifier: ^5.1.9
|
||||
version: 5.1.9
|
||||
@ -2886,8 +2886,8 @@ packages:
|
||||
resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0:
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0}
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/ba3b26ed63a01227a3560e440531b69d79c03f35:
|
||||
resolution: {gitHosted: true, integrity: sha512-IN2r8sw36jcH32WPP9eBFXNglirhkFIsqgoKzwwSDxiCbeipumeKXjYB3vw82CxYRwKc+oRTMcZ5jNnCWnmnBw==, tarball: https://codeload.github.com/penpot/draft-js/tar.gz/ba3b26ed63a01227a3560e440531b69d79c03f35}
|
||||
version: 0.11.7
|
||||
peerDependencies:
|
||||
react: '>=0.14.0'
|
||||
@ -8117,7 +8117,7 @@ snapshots:
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
draft-js@https://codeload.github.com/penpot/draft-js/tar.gz/ba3b26ed63a01227a3560e440531b69d79c03f35(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
fbjs: 3.0.5(encoding@0.1.13)
|
||||
immutable: 3.8.3
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.layout :as dwlo]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.viewport-wasm :as dwvw]
|
||||
[app.main.data.workspace.zoom :as dwz]
|
||||
[app.main.repo :as rp]
|
||||
@ -72,7 +71,7 @@
|
||||
|
||||
(rx/take-until stopper-s))))))
|
||||
|
||||
(defn- handle-interrupt
|
||||
(defn handle-interrupt
|
||||
[]
|
||||
(ptk/reify ::handle-interrupt
|
||||
ptk/WatchEvent
|
||||
@ -87,8 +86,7 @@
|
||||
;; tool is active. When comments are merely visible during design,
|
||||
;; `select-shape` emits `:interrupt` and this would otherwise wipe
|
||||
;; the freshly selected shape, breaking click selection.
|
||||
comments-mode? (rx/of (dwe/clear-edition-mode)
|
||||
(dws/deselect-all true))
|
||||
comments-mode? (rx/of (dwe/clear-edition-mode))
|
||||
:else (rx/empty))))))
|
||||
|
||||
;; Event responsible of the what should be executed when user clicked
|
||||
|
||||
@ -256,8 +256,8 @@
|
||||
|
||||
(defn assoc-position-data
|
||||
[shape position-data old-shape]
|
||||
(let [deltav (gpt/to-vec (gpt/point (:selrect old-shape))
|
||||
(gpt/point (:selrect shape)))
|
||||
(let [deltav (gpt/to-vec (gpt/point (ctm/safe-size-rect old-shape))
|
||||
(gpt/point (ctm/safe-size-rect shape)))
|
||||
position-data
|
||||
(-> position-data
|
||||
(gsh/move-position-data deltav))]
|
||||
|
||||
@ -35,8 +35,8 @@
|
||||
(if-let [modifiers (:modifiers shape)]
|
||||
(let [shape' (gsh/transform-shape shape modifiers)
|
||||
|
||||
old-sr (dm/get-prop shape :selrect)
|
||||
new-sr (dm/get-prop shape' :selrect)
|
||||
old-sr (ctm/safe-size-rect shape)
|
||||
new-sr (ctm/safe-size-rect shape')
|
||||
|
||||
;; We need to remove the movement because the dynamic modifiers will have move it
|
||||
deltav (gpt/to-vec (gpt/point new-sr)
|
||||
|
||||
@ -522,7 +522,7 @@
|
||||
[:*
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
(for [[pos prop] (map-indexed vector props-first)]
|
||||
(let [mixed-value? (not-every? #(= (:value prop) (:value (nth % pos))) properties)
|
||||
(let [mixed-value? (not-every? #(= (:value prop) (:value (get % pos))) properties)
|
||||
options (get-options (:name prop))
|
||||
boolean-pair (ctv/find-boolean-pair (mapv :id options))
|
||||
options (cond-> options
|
||||
|
||||
@ -1059,6 +1059,9 @@
|
||||
:setPluginData
|
||||
(fn [key value]
|
||||
(cond
|
||||
(not= file-id (:current-file-id @st/state))
|
||||
(u/not-valid plugin-id :setPluginData-non-local-library file-id)
|
||||
|
||||
(not (string? key))
|
||||
(u/not-valid plugin-id :setPluginData-key key)
|
||||
|
||||
@ -1092,6 +1095,9 @@
|
||||
:setSharedPluginData
|
||||
(fn [namespace key value]
|
||||
(cond
|
||||
(not= file-id (:current-file-id @st/state))
|
||||
(u/not-valid plugin-id :setSharedPluginData-non-local-library file-id)
|
||||
|
||||
(not (string? namespace))
|
||||
(u/not-valid plugin-id :setSharedPluginData-namespace namespace)
|
||||
|
||||
|
||||
@ -272,13 +272,13 @@
|
||||
(st/emit! (dp/set-plugin-data file-id :page id (keyword "shared" namespace) key value))))
|
||||
|
||||
:getSharedPluginDataKeys
|
||||
(fn [self namespace]
|
||||
(fn [namespace]
|
||||
(cond
|
||||
(not (string? namespace))
|
||||
(u/not-valid plugin-id :page-plugin-data-namespace namespace)
|
||||
|
||||
:else
|
||||
(let [page (u/proxy->page self)]
|
||||
(let [page (u/locate-page file-id id)]
|
||||
(apply array (keys (dm/get-in page [:plugin-data (keyword "shared" namespace)]))))))
|
||||
|
||||
:openPage
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
;; 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 Sucursal en España SL
|
||||
|
||||
(ns frontend-tests.data.workspace-comments-test
|
||||
(:require
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.workspace.comments :as dwcm]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/deftest test-handle-interrupt-draft
|
||||
(t/async
|
||||
done
|
||||
(let [event (dwcm/handle-interrupt)
|
||||
state {:comments-local {:draft {:id "draft-id"}}}
|
||||
result (ptk/watch event state (rx/empty))]
|
||||
(->> result
|
||||
(rx/subs!
|
||||
(fn [evt]
|
||||
(t/is (= ::dcmt/close-comment-thread (ptk/type evt))))
|
||||
(fn [err]
|
||||
(done)
|
||||
(js/console.error err)
|
||||
(t/do-report {:type :error :message "Stream error" :actual err}))
|
||||
(fn [_]
|
||||
(done)))))))
|
||||
|
||||
(t/deftest test-handle-interrupt-open
|
||||
(t/async
|
||||
done
|
||||
(let [event (dwcm/handle-interrupt)
|
||||
state {:comments-local {:open {:id "thread-id"}}}
|
||||
result (ptk/watch event state (rx/empty))]
|
||||
(->> result
|
||||
(rx/subs!
|
||||
(fn [evt]
|
||||
(t/is (= ::dcmt/close-comment-thread (ptk/type evt))))
|
||||
(fn [err]
|
||||
(done)
|
||||
(js/console.error err)
|
||||
(t/do-report {:type :error :message "Stream error" :actual err}))
|
||||
(fn [_]
|
||||
(done)))))))
|
||||
|
||||
(t/deftest test-handle-interrupt-comments-mode
|
||||
(t/async
|
||||
done
|
||||
(let [event (dwcm/handle-interrupt)
|
||||
state {:workspace-drawing {:tool :comments}}
|
||||
result (ptk/watch event state (rx/empty))]
|
||||
(->> result
|
||||
(rx/subs!
|
||||
(fn [evt]
|
||||
(t/is (= ::dwe/clear-edition-mode (ptk/type evt))))
|
||||
(fn [err]
|
||||
(done)
|
||||
(js/console.error err)
|
||||
(t/do-report {:type :error :message "Stream error" :actual err}))
|
||||
(fn [_]
|
||||
(done)))))))
|
||||
|
||||
(t/deftest test-handle-interrupt-noop
|
||||
(t/async
|
||||
done
|
||||
(let [event (dwcm/handle-interrupt)
|
||||
state {}
|
||||
result (ptk/watch event state (rx/empty))
|
||||
emitted? (atom false)]
|
||||
(->> result
|
||||
(rx/subs!
|
||||
(fn [_]
|
||||
(reset! emitted? true))
|
||||
(fn [err]
|
||||
(done)
|
||||
(js/console.error err)
|
||||
(t/do-report {:type :error :message "Stream error" :actual err}))
|
||||
(fn [_]
|
||||
(t/is (false? @emitted?) "should not emit any events")
|
||||
(done)))))))
|
||||
@ -9,9 +9,11 @@
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.text :as txt]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.ui.workspace.shapes.text.viewport-texts-html :as vth]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
||||
@ -374,3 +376,34 @@
|
||||
"exactly one typography was added")
|
||||
(t/is (= "0.1" (:letter-spacing (first typographies)))
|
||||
"float letter-spacing is normalised to 2-decimal string")))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Tests: fix-position with degenerate selrect
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(t/deftest fix-position-zero-width-selrect-does-not-throw
|
||||
(t/testing "fix-position on a shape with zero selrect width does not throw"
|
||||
(let [shape (make-degenerate-text-shape :x 0 :y 0 :width 0 :height 50)
|
||||
modifiers (ctm/change-dimensions-modifiers shape :width 200 {:ignore-lock? true})
|
||||
shape' (assoc shape :modifiers modifiers)
|
||||
result (vth/fix-position shape')]
|
||||
(t/is (some? result))
|
||||
(t/is (some? (:selrect result))))))
|
||||
|
||||
(t/deftest fix-position-zero-height-selrect-does-not-throw
|
||||
(t/testing "fix-position on a shape with zero selrect height does not throw"
|
||||
(let [shape (make-degenerate-text-shape :x 0 :y 0 :width 100 :height 0)
|
||||
modifiers (ctm/change-dimensions-modifiers shape :height 80 {:ignore-lock? true})
|
||||
shape' (assoc shape :modifiers modifiers)
|
||||
result (vth/fix-position shape')]
|
||||
(t/is (some? result))
|
||||
(t/is (some? (:selrect result))))))
|
||||
|
||||
(t/deftest fix-position-zero-width-and-height-selrect-does-not-throw
|
||||
(t/testing "fix-position on a fully degenerate selrect does not throw"
|
||||
(let [shape (make-degenerate-text-shape :x 0 :y 0 :width 0 :height 0)
|
||||
modifiers (ctm/change-dimensions-modifiers shape :width 150 {:ignore-lock? true})
|
||||
shape' (assoc shape :modifiers modifiers)
|
||||
result (vth/fix-position shape')]
|
||||
(t/is (some? result))
|
||||
(t/is (some? (:selrect result))))))
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
[frontend-tests.data.uploads-test]
|
||||
[frontend-tests.data.viewer-test]
|
||||
[frontend-tests.data.workspace-colors-test]
|
||||
[frontend-tests.data.workspace-comments-test]
|
||||
[frontend-tests.data.workspace-interactions-test]
|
||||
[frontend-tests.data.workspace-mcp-test]
|
||||
[frontend-tests.data.workspace-media-test]
|
||||
@ -83,6 +84,7 @@
|
||||
'frontend-tests.data.uploads-test
|
||||
'frontend-tests.data.viewer-test
|
||||
'frontend-tests.data.workspace-colors-test
|
||||
'frontend-tests.data.workspace-comments-test
|
||||
'frontend-tests.data.workspace-interactions-test
|
||||
'frontend-tests.data.workspace-mcp-test
|
||||
'frontend-tests.data.workspace-media-test
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
|
||||
"packageManager": "pnpm@11.10.0+sha512.0b7f8b98060031904c017e3a41eb187a16d40eeb829b95c4f8cb03681761fc4ab53dd219115b9b447f4dce1a05a214764461e7d3703392a9f32f9511ce8c86c8",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
@ -52,6 +52,8 @@
|
||||
- **plugins-runtime**: `penpot.openPage()` (and `Page.openPage()`) now resolves immediately when the target page is already active, instead of waiting forever for a page-initialization event that never fires.
|
||||
- **plugins-runtime**: `Shape.shadows`, `Shape.exports` and grid `rows`/`columns` now return live proxies, so writing a member on a returned shadow/export/track (e.g. `shape.shadows[0].blur = 7`) persists to the shape instead of mutating a detached snapshot that was silently discarded. The shadow `color` remains a plain snapshot (reconfigure it by assigning `shadow.color`).
|
||||
- **plugins-runtime**: Setting a variant component's `path` now renames the whole variant (its container and every main instance), like the `name` setter already did, instead of renaming only the component and leaving the file referentially inconsistent (which the backend rejected on save with a `variant-component-bad-name` error).
|
||||
- **plugins-runtime**: `Page.getSharedPluginDataKeys(namespace)` now works instead of always raising a namespace validation error: the implementation expected a spurious leading argument, so the caller's `namespace` was read as a missing second argument.
|
||||
- **plugins-runtime**: Storing plugin data on a connected (non-local) shared library is now consistently rejected with a `setPluginData-non-local-library` error on the `Library` object as well as its assets (colors, typographies, components). Previously the `Library` object accepted the write and applied it optimistically, but plugin data is not part of library synchronization and the change only persists when the caller can edit the library file — on a read-only shared library it failed silently and was lost on reload. Plugin data can only be stored on the file currently being edited.
|
||||
|
||||
## 1.4.2 (2026-01-21)
|
||||
|
||||
|
||||
@ -34,6 +34,24 @@ describe('Library', () => {
|
||||
expect(Array.isArray(ctx.penpot.library.connected)).toBe(true);
|
||||
});
|
||||
|
||||
// The Library object itself carries plugin data (it extends PluginData),
|
||||
// stored on the underlying file. Exercised on the local library; the
|
||||
// connected-library case (writing to a shared library's file/assets) shares
|
||||
// the same code path but can't be fixtured here (connecting a library hangs).
|
||||
test('local library stores plugin data', (ctx) => {
|
||||
const lib = ctx.penpot.library.local;
|
||||
lib.setPluginData('k', 'v');
|
||||
expect(lib.getPluginData('k')).toBe('v');
|
||||
expect(lib.getPluginDataKeys()).toContain('k');
|
||||
});
|
||||
|
||||
test('local library stores shared plugin data', (ctx) => {
|
||||
const lib = ctx.penpot.library.local;
|
||||
lib.setSharedPluginData('ns', 'k', 'v');
|
||||
expect(lib.getSharedPluginData('ns', 'k')).toBe('v');
|
||||
expect(lib.getSharedPluginDataKeys('ns')).toContain('k');
|
||||
});
|
||||
|
||||
test('library elements expose a libraryId', (ctx) => {
|
||||
const color = ctx.penpot.library.local.createColor();
|
||||
expect(typeof color.libraryId).toBe('string');
|
||||
@ -136,6 +154,13 @@ describe('Library', () => {
|
||||
expect(color.getPluginData('k')).toBe('v');
|
||||
expect(color.getPluginDataKeys()).toContain('k');
|
||||
});
|
||||
|
||||
test('color shared plugin data round-trips', (ctx) => {
|
||||
const color = ctx.penpot.library.local.createColor();
|
||||
color.setSharedPluginData('ns', 'k', 'v');
|
||||
expect(color.getSharedPluginData('ns', 'k')).toBe('v');
|
||||
expect(color.getSharedPluginDataKeys('ns')).toContain('k');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Typographies', () => {
|
||||
@ -170,6 +195,13 @@ describe('Library', () => {
|
||||
expect(typo.getPluginDataKeys()).toContain('k');
|
||||
});
|
||||
|
||||
test('typography shared plugin data round-trips', (ctx) => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
typo.setSharedPluginData('ns', 'k', 'v');
|
||||
expect(typo.getSharedPluginData('ns', 'k')).toBe('v');
|
||||
expect(typo.getSharedPluginDataKeys('ns')).toContain('k');
|
||||
});
|
||||
|
||||
test('typography fontFamily and fontId round-trip', (ctx) => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
expect(typeof typo.fontFamily).toBe('string');
|
||||
@ -256,6 +288,15 @@ describe('Library', () => {
|
||||
expect(comp.getPluginDataKeys()).toContain('k');
|
||||
});
|
||||
|
||||
test('component shared plugin data round-trips', (ctx) => {
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
ctx.board.appendChild(rect);
|
||||
const comp = ctx.penpot.library.local.createComponent([rect]);
|
||||
comp.setSharedPluginData('ns', 'k', 'v');
|
||||
expect(comp.getSharedPluginData('ns', 'k')).toBe('v');
|
||||
expect(comp.getSharedPluginDataKeys('ns')).toContain('k');
|
||||
});
|
||||
|
||||
test('component instance and mainInstance return shapes', (ctx) => {
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
ctx.board.appendChild(rect);
|
||||
|
||||
@ -26,6 +26,41 @@ describe('Plugin data', () => {
|
||||
if (file) {
|
||||
file.setPluginData('fileKey', 'fileValue');
|
||||
expect(file.getPluginData('fileKey')).toBe('fileValue');
|
||||
expect(file.getPluginDataKeys()).toContain('fileKey');
|
||||
}
|
||||
});
|
||||
|
||||
test('shared plugin data round-trips on the file', (ctx) => {
|
||||
const file = ctx.penpot.currentFile;
|
||||
expect(file).not.toBeNull();
|
||||
if (file) {
|
||||
file.setSharedPluginData('ns', 'fileShared', 'fileSharedValue');
|
||||
expect(file.getSharedPluginData('ns', 'fileShared')).toBe(
|
||||
'fileSharedValue',
|
||||
);
|
||||
expect(file.getSharedPluginDataKeys('ns')).toContain('fileShared');
|
||||
}
|
||||
});
|
||||
|
||||
test('plugin data round-trips on a page', (ctx) => {
|
||||
const page = ctx.penpot.currentPage;
|
||||
expect(page).not.toBeNull();
|
||||
if (page) {
|
||||
page.setPluginData('pageKey', 'pageValue');
|
||||
expect(page.getPluginData('pageKey')).toBe('pageValue');
|
||||
expect(page.getPluginDataKeys()).toContain('pageKey');
|
||||
}
|
||||
});
|
||||
|
||||
test('shared plugin data round-trips on a page', (ctx) => {
|
||||
const page = ctx.penpot.currentPage;
|
||||
expect(page).not.toBeNull();
|
||||
if (page) {
|
||||
page.setSharedPluginData('ns', 'pageShared', 'pageSharedValue');
|
||||
expect(page.getSharedPluginData('ns', 'pageShared')).toBe(
|
||||
'pageSharedValue',
|
||||
);
|
||||
expect(page.getSharedPluginDataKeys('ns')).toContain('pageShared');
|
||||
}
|
||||
});
|
||||
|
||||
@ -45,6 +80,47 @@ describe('Plugin data', () => {
|
||||
expect(rect.getPluginDataKeys()).toContain('');
|
||||
});
|
||||
|
||||
// Keys are opaque strings: no case normalization is applied. A camelCase key
|
||||
// and its kebab-case spelling are distinct entries that each round-trip
|
||||
// independently. Pins that behaviour so a future key-normalization regression
|
||||
// (reported as camelCase keys "behaving incorrectly") would be caught here.
|
||||
test('camelCase and kebab-case keys are distinct and both round-trip', (ctx) => {
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
ctx.board.appendChild(rect);
|
||||
rect.setPluginData('myKey', 'camel');
|
||||
rect.setPluginData('my-key', 'kebab');
|
||||
expect(rect.getPluginData('myKey')).toBe('camel');
|
||||
expect(rect.getPluginData('my-key')).toBe('kebab');
|
||||
const keys = rect.getPluginDataKeys();
|
||||
expect(keys).toContain('myKey');
|
||||
expect(keys).toContain('my-key');
|
||||
});
|
||||
|
||||
// Same guarantee at the file-data storage location (a distinct code path from
|
||||
// shape/page objects), covering a camelCase key on the file itself.
|
||||
test('a camelCase key round-trips on the file', (ctx) => {
|
||||
const file = ctx.penpot.currentFile;
|
||||
expect(file).not.toBeNull();
|
||||
if (file) {
|
||||
file.setPluginData('camelCaseFileKey', 'value');
|
||||
expect(file.getPluginData('camelCaseFileKey')).toBe('value');
|
||||
expect(file.getPluginDataKeys()).toContain('camelCaseFileKey');
|
||||
}
|
||||
});
|
||||
|
||||
// camelCase is likewise preserved in the shared namespace and the shared key.
|
||||
test('camelCase shared namespace and key round-trip', (ctx) => {
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
ctx.board.appendChild(rect);
|
||||
rect.setSharedPluginData('myNamespace', 'myKey', 'camel');
|
||||
rect.setSharedPluginData('myNamespace', 'my-key', 'kebab');
|
||||
expect(rect.getSharedPluginData('myNamespace', 'myKey')).toBe('camel');
|
||||
expect(rect.getSharedPluginData('myNamespace', 'my-key')).toBe('kebab');
|
||||
const keys = rect.getSharedPluginDataKeys('myNamespace');
|
||||
expect(keys).toContain('myKey');
|
||||
expect(keys).toContain('my-key');
|
||||
});
|
||||
|
||||
test('setPluginData with a non-string value throws', (ctx) => {
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
ctx.board.appendChild(rect);
|
||||
|
||||
74
tools/detect-target-branch
Executable file
74
tools/detect-target-branch
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# detect-target-branch
|
||||
#
|
||||
# Determines the target branch for a PR of the current branch.
|
||||
#
|
||||
# Uses git name-rev to find the nearest ref (branch/tag) that is an ancestor of
|
||||
# HEAD, falling back to a graph walk if name-rev returns the current branch.
|
||||
#
|
||||
# Pure local — no remote, no SSH, no network. Works even when the current
|
||||
# branch shares its tip commit with the target (not yet diverged).
|
||||
#
|
||||
# Usage:
|
||||
# tools/detect-target-branch # print branch name
|
||||
# tools/detect-target-branch --verbose # print "branch~N"
|
||||
#
|
||||
# Exit status:
|
||||
# 0 — target found
|
||||
# 1 — no target found (orphan commit, no other branches)
|
||||
|
||||
verbose=false
|
||||
|
||||
for arg do
|
||||
case "$arg" in
|
||||
--verbose|-v) verbose=true ;;
|
||||
--name-only) ;;
|
||||
--help|-h)
|
||||
sed -n '/^#/,/^$/p' "$0" | sed 's/^# //; s/^#$//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $arg" >&2
|
||||
echo "Usage: $(basename "$0") [--verbose|-v] [--name-only] [--help|-h]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
current=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# Use name-rev to find the nearest ref — it has smart tie-breaking
|
||||
raw=$(git name-rev --name-only HEAD 2>/dev/null)
|
||||
target=${raw%%~[0-9]*} # strip ~N distance suffix
|
||||
|
||||
# If name-rev returned the current branch itself, walk backwards
|
||||
if [ "$target" = "$current" ] || [ -z "$target" ]; then
|
||||
target=$(git rev-list HEAD | while read commit; do
|
||||
branch=$(git branch --points-at="$commit" --format='%(refname:short)' 2>/dev/null \
|
||||
| grep -v "^$current$" \
|
||||
| head -1 || true)
|
||||
if [ -n "$branch" ]; then
|
||||
echo "$branch"
|
||||
break
|
||||
fi
|
||||
done || true)
|
||||
|
||||
if [ -z "$target" ]; then
|
||||
echo "error: unable to determine target branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$verbose" = true ]; then
|
||||
target_commit=$(git rev-parse "$target" 2>/dev/null || true)
|
||||
if [ -n "$target_commit" ]; then
|
||||
distance=$(git rev-list --count HEAD ^"$target_commit" 2>/dev/null || echo "0")
|
||||
echo "${target}~${distance}"
|
||||
else
|
||||
echo "${target}~?"
|
||||
fi
|
||||
else
|
||||
echo "$target"
|
||||
fi
|
||||
Loading…
x
Reference in New Issue
Block a user