mirror of
https://github.com/penpot/penpot.git
synced 2026-04-05 02:42:35 +02:00
Merge remote-tracking branch 'origin/main' into staging
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -57,6 +57,7 @@
|
||||
/frontend/package-lock.json
|
||||
/frontend/resources/fonts/experiments
|
||||
/frontend/resources/public/*
|
||||
/frontend/src/app/render_wasm/api/shared.js
|
||||
/frontend/storybook-static/
|
||||
/frontend/target/
|
||||
/frontend/test-results/
|
||||
|
||||
@@ -1777,6 +1777,13 @@
|
||||
in the destination shape will not be copied."
|
||||
[changes dest-shape origin-shape dest-root origin-root container omit-touched?]
|
||||
|
||||
;; (when (or (= #uuid "fe6ff6fc-0062-8060-8001-ad3331737f7d" (:id origin-shape))
|
||||
;; (= #uuid "fe6ff6fc-0062-8060-8001-ad3331737f7d" (:id dest-shape)))
|
||||
;; (println "=========================")
|
||||
;; (app.common.pprint/pprint origin-shape)
|
||||
;; (println "-------------------------")
|
||||
;; (app.common.pprint/pprint dest-shape))
|
||||
|
||||
(shape-log :info (:id dest-shape) container
|
||||
:msg (str "SYNC "
|
||||
(:name origin-shape)
|
||||
@@ -1789,7 +1796,12 @@
|
||||
" "
|
||||
(pretty-uuid (:id dest-shape))))
|
||||
|
||||
(let [;; To synchronize geometry attributes we need to make a prior
|
||||
(let [is-my-shape? (or (= #uuid "fe6ff6fc-0062-8060-8001-ad3331737f7d" (:id origin-shape))
|
||||
(= #uuid "fe6ff6fc-0062-8060-8001-ad3331737f7d" (:id dest-shape)))
|
||||
|
||||
is-my-shape? (= #uuid "fe6ff6fc-0062-8060-8001-ad3331737f7d" (:id dest-shape))
|
||||
|
||||
;; To synchronize geometry attributes we need to make a prior
|
||||
;; operation, because coordinates are absolute, but we need to
|
||||
;; sync only the position relative to the origin of the component.
|
||||
;; We solve this by moving the origin shape so it is aligned with
|
||||
@@ -1804,24 +1816,33 @@
|
||||
uoperations '()]
|
||||
|
||||
(let [attr (first attrs)]
|
||||
(if (nil? attr)
|
||||
(cond-> changes
|
||||
(seq roperations)
|
||||
(add-update-attr-changes dest-shape container roperations uoperations)
|
||||
:always
|
||||
(check-detached-main dest-shape origin-shape)
|
||||
:always
|
||||
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil))
|
||||
|
||||
(let [attr-group (get ctk/sync-attrs attr)
|
||||
(if (nil? attr)
|
||||
(do
|
||||
|
||||
(app.common.pprint/pprint roperations)
|
||||
|
||||
(cond-> changes
|
||||
(seq roperations)
|
||||
(add-update-attr-changes dest-shape container roperations uoperations)
|
||||
:always
|
||||
(check-detached-main dest-shape origin-shape)
|
||||
:always
|
||||
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil)))
|
||||
|
||||
(let [attr-group
|
||||
(get ctk/sync-attrs attr)
|
||||
|
||||
;; position-data is a special case because can be affected by
|
||||
;; :geometry-group and :content-group so, if the position-data
|
||||
;; changes but the geometry is touched we need to reset the position-data
|
||||
;; so it's calculated again
|
||||
reset-pos-data? (and (cfh/text-shape? origin-shape)
|
||||
(= attr :position-data)
|
||||
(not= (:position-data origin-shape) (:position-data dest-shape))
|
||||
(touched :geometry-group))
|
||||
;; :geometry-group and :content-group so, if the
|
||||
;; position-data changes but the geometry is touched
|
||||
;; we need to reset the position-data so it's
|
||||
;; calculated again
|
||||
reset-pos-data?
|
||||
(and (cfh/text-shape? origin-shape)
|
||||
(= attr :position-data)
|
||||
(not= (:position-data origin-shape) (:position-data dest-shape))
|
||||
(touched :geometry-group))
|
||||
|
||||
;; On texts, when we want to omit the touched attrs, both text (the actual letters)
|
||||
;; and attrs (bold, font, etc) are in the same attr :content.
|
||||
@@ -1843,25 +1864,41 @@
|
||||
;; the attributes, omiting the other part
|
||||
(not text-content-change?)))
|
||||
|
||||
attr-val (when-not skip-operations?
|
||||
(cond
|
||||
;; If position data changes and the geometry group is touched
|
||||
;; we need to put to nil so we can regenerate it
|
||||
reset-pos-data?
|
||||
nil
|
||||
_ (when (and is-my-shape? (= attr :type))
|
||||
(prn "PROCESS ATTR" attr (touched attr-group) omit-touched?))
|
||||
|
||||
text-content-change?
|
||||
(text-change-value (:content dest-shape)
|
||||
(:content origin-shape)
|
||||
touched)
|
||||
attr-val
|
||||
(when-not skip-operations?
|
||||
(cond
|
||||
;; If position data changes and the geometry group is touched
|
||||
;; we need to put to nil so we can regenerate it
|
||||
reset-pos-data?
|
||||
nil
|
||||
|
||||
:else
|
||||
(get origin-shape attr)))
|
||||
text-content-change?
|
||||
(text-change-value (:content dest-shape)
|
||||
(:content origin-shape)
|
||||
touched)
|
||||
|
||||
:else
|
||||
(get origin-shape attr)))
|
||||
|
||||
;; If the final attr-value is the actual value, skip
|
||||
skip-operations? (or skip-operations?
|
||||
(= attr-val (get dest-shape attr)))
|
||||
skip-operations?
|
||||
(or skip-operations?
|
||||
(= attr-val (get dest-shape attr)))
|
||||
|
||||
_ (when (and is-my-shape? (not skip-operations?))
|
||||
(println "ATTR:" attr " FROM:" (get dest-shape attr) " TO:" attr-val)
|
||||
#_(when (nil? attr-val)
|
||||
(prn (:id dest-shape))))
|
||||
|
||||
;; _ (when (and (not skip-operations?) (= attr :type))
|
||||
;; (println "=========================")
|
||||
;; (prn
|
||||
;; #_(app.common.pprint/pprint origin-shape)
|
||||
;; #_(println "-------------------------")
|
||||
;; #_(app.common.pprint/pprint dest-shape))
|
||||
|
||||
;; On a text-partial-change, we want to force a position-data reset
|
||||
;; so it's calculated again
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
{:name :name-group
|
||||
:fills :fill-group
|
||||
:hide-fill-on-export :fill-group
|
||||
:content :content-group
|
||||
:content :geometry-group
|
||||
:position-data :content-group
|
||||
:hidden :visibility-group
|
||||
:blocked :modifiable-group
|
||||
|
||||
@@ -190,10 +190,14 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn get-points
|
||||
"Returns points for the given segment, faster version of
|
||||
the `content->points`."
|
||||
"Returns points for the given content. Accepts PathData instances or
|
||||
plain segment vectors. Returns nil for nil content."
|
||||
[content]
|
||||
(some-> content segment/get-points))
|
||||
(when (some? content)
|
||||
(let [content (if (impl/path-data? content)
|
||||
content
|
||||
(impl/path-data content))]
|
||||
(segment/get-points content))))
|
||||
|
||||
(defn calc-selrect
|
||||
"Calculate selrect from a content. The content can be in a PathData
|
||||
|
||||
@@ -52,14 +52,15 @@
|
||||
[target key & expr]
|
||||
(if (:ns &env)
|
||||
(let [target (with-meta target {:tag 'js})]
|
||||
`(let [~'cache (.-cache ~target)
|
||||
~'result (.get ~'cache ~key)]
|
||||
(if ~'result
|
||||
(do
|
||||
~'result)
|
||||
(let [~'result (do ~@expr)]
|
||||
(.set ~'cache ~key ~'result)
|
||||
~'result))))
|
||||
`(let [~'cache (.-cache ~target)]
|
||||
(if (some? ~'cache)
|
||||
(let [~'result (.get ~'cache ~key)]
|
||||
(if ~'result
|
||||
~'result
|
||||
(let [~'result (do ~@expr)]
|
||||
(.set ~'cache ~key ~'result)
|
||||
~'result)))
|
||||
(do ~@expr))))
|
||||
`(do ~@expr)))
|
||||
|
||||
(defn- impl-transform-segment
|
||||
|
||||
@@ -279,6 +279,12 @@
|
||||
(t/is (some? points))
|
||||
(t/is (= 3 (count points))))))
|
||||
|
||||
(t/deftest path-get-points-plain-vector-safe
|
||||
(t/testing "path/get-points does not throw for plain vector content"
|
||||
(let [points (path/get-points sample-content)]
|
||||
(t/is (some? points))
|
||||
(t/is (= 3 (count points))))))
|
||||
|
||||
(defn calculate-extremities
|
||||
"Calculate extremities for the provided content.
|
||||
A legacy implementation used mainly as reference for testing"
|
||||
|
||||
@@ -70,9 +70,11 @@
|
||||
</main>
|
||||
|
||||
<div class="pre-footer">
|
||||
<a href="https://github.com/penpot/penpot/blob/main/docs/{{ page.inputPath }}">Edit this page on GitHub</a>
|
||||
or ask a
|
||||
<a href="https://penpot.app/talk-to-us" target="_blank">question</a>.
|
||||
<div>Found an issue or want to improve this page?<br><br>
|
||||
<a href="https://github.com/penpot/penpot/blob/main/docs/{{ page.inputPath }}">Edit this page on GitHub</a>
|
||||
·
|
||||
<a href="https://github.com/penpot/penpot/issues/new/choose" target="_blank">Open an issue</a>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="footer-inside">
|
||||
|
||||
@@ -57,5 +57,5 @@ eleventyNavigation:
|
||||
|
||||
<div class="contact-block">
|
||||
<h2>Contact us</h2>
|
||||
<p>Need help? <a href="https://penpot.app/talk-to-us" target="_blank">Talk to us</a> or join our <a href="https://community.penpot.app/" target="_blank">community</a>.</p>
|
||||
<p>Write us at <a href="mailto:support@penpot.app" target="_blank">support@penpot.app</a> or join our <a href="https://community.penpot.app/" target="_blank">Community</a>.</p>
|
||||
</div>
|
||||
|
||||
@@ -187,7 +187,54 @@ python3 manage.py create-profile --skip-tutorial --skip-walkthrough
|
||||
python3 manage.py create-profile -n "Jane Doe" -e jane@example.com -p secretpassword --skip-tutorial --skip-walkthrough
|
||||
```
|
||||
|
||||
## Team Feature Flags
|
||||
## Feature Flags
|
||||
|
||||
### Frontend flags via config.js
|
||||
|
||||
You can enable or disable feature flags on the frontend by creating (or editing) a
|
||||
`config.js` file at `frontend/resources/public/js/config.js`. This file is
|
||||
**gitignored**, so it has to be created manually. Your local flags won't affect other developers.
|
||||
|
||||
Set the `penpotFlags` variable with a space-separated list of flags:
|
||||
|
||||
```js
|
||||
var penpotFlags = "enable-mcp enable-webhooks enable-access-tokens";
|
||||
```
|
||||
|
||||
Each flag entry uses the format `enable-<flag>` or `disable-<flag>`. They are
|
||||
merged on top of the built-in defaults, so you only need to list the flags you want
|
||||
to change.
|
||||
|
||||
Some examples of commonly used flags:
|
||||
|
||||
- `enable-access-tokens` — enables the Access Tokens section under profile settings.
|
||||
- `enable-mcp` — enables the MCP server configuration section.
|
||||
- `enable-webhooks` — enables webhooks configuration.
|
||||
- `enable-login-with-ldap` — enables LDAP login.
|
||||
|
||||
The full list of available flags can be found in `common/src/app/common/flags.cljc`.
|
||||
|
||||
After creating or modifying this file, **reload the browser** (no need to restart anything).
|
||||
|
||||
### Backend flags via PENPOT_FLAGS
|
||||
|
||||
Backend feature flags are controlled through the `PENPOT_FLAGS` environment
|
||||
variable using the same `enable-<flag>` / `disable-<flag>` format. You can set
|
||||
this in the `docker/devenv/docker-compose.yaml` file under the `main` service
|
||||
`environment` section:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- PENPOT_FLAGS=enable-access-tokens enable-mcp
|
||||
```
|
||||
|
||||
This requires **restarting the backend** to take effect.
|
||||
|
||||
> **Note**: Some features (e.g., access tokens, webhooks) need both frontend and
|
||||
> backend flags enabled to work end-to-end. The frontend flag enables the UI, while
|
||||
> the backend flag enables the corresponding API endpoints.
|
||||
|
||||
### Team Feature Flags
|
||||
|
||||
To test a Feature Flag, you can enable or disable them by team through the `dbg` page:
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
(let [content (st/get-path state :content)
|
||||
content (if (and (not preserve-move-to)
|
||||
(= (-> content last :command) :move-to))
|
||||
(into [] (take (dec (count content)) content))
|
||||
(path/content (take (dec (count content)) content))
|
||||
content)]
|
||||
(st/set-content state content)))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.common.types.path :as path]
|
||||
[app.main.data.workspace.path.state :as pst]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.store :as st]
|
||||
@@ -167,7 +167,7 @@
|
||||
ranges-stream
|
||||
(->> content-stream
|
||||
(rx/filter some?)
|
||||
(rx/map path.segm/get-points)
|
||||
(rx/map path/get-points)
|
||||
(rx/map snap/create-ranges))]
|
||||
|
||||
(->> ms/mouse-position
|
||||
|
||||
@@ -214,10 +214,11 @@
|
||||
(mf/use-effect
|
||||
deps
|
||||
(fn []
|
||||
(let [sub (->> stream (rx/subs! on-subscribe))]
|
||||
#(do
|
||||
(rx/dispose! sub)
|
||||
(when on-dispose (on-dispose))))))))
|
||||
(when stream
|
||||
(let [sub (->> stream (rx/subs! on-subscribe))]
|
||||
#(do
|
||||
(rx/dispose! sub)
|
||||
(when on-dispose (on-dispose)))))))))
|
||||
|
||||
;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
|
||||
;; FIXME: replace with rumext
|
||||
|
||||
Reference in New Issue
Block a user