Merge remote-tracking branch 'origin/main-staging'

This commit is contained in:
Andrey Antukh 2026-05-11 08:46:25 +02:00
commit 1a212a2769
68 changed files with 736 additions and 456 deletions

View File

@ -0,0 +1,90 @@
---
name: backport-commit
description: Port changes from a specific Git commit to the current branch by manually applying the diff, avoiding cherry-pick when it would introduce complex conflicts.
---
# Backport Commit
Port changes from a specific Git commit to the current branch by manually
applying the diff, avoiding `git cherry-pick` when it would introduce
complex conflicts.
## When to Use
Use this skill whenever the user asks to backport a commit, especially when:
- The commit touches multiple modules or files with significant divergence
- `git cherry-pick` is explicitly ruled out ("do not use cherry-pick")
- The target commit is old enough that conflicts are likely
- The commit introduces both source changes AND new files (tests, etc.)
- You need full control over how each hunk is applied
## Workflow
### 1. Identify the target commit
```bash
# Verify the commit exists and understand what it does
git log --oneline -1 <commit-sha>
# Get the full diff (including new/deleted files)
git show <commit-sha>
# Capture the original commit message for later reuse
git log --format='%B' -1 <commit-sha>
```
### 2. Identify affected modules
From the file paths in the diff, determine which Penpot modules are affected
(frontend, backend, common, render-wasm, etc.) and read their `AGENTS.md`
files **before** making any changes. If a module has no `AGENTS.md`, skip
that step — verify with `ls <module>/AGENTS.md` first.
### 3. Read the current state of each affected file
For every file the diff touches, read the current version on disk to understand
context and ensure correct placement before editing.
### 4. Apply changes manually (the core of this approach)
Process every hunk in the diff using the appropriate tool:
| Diff action | Tool to use |
|-------------|-------------|
| Modify existing file | `edit` — use enough surrounding context in `oldString` to uniquely match the location |
| Add new file | `write` — include proper license header and namespace conventions matching project style |
| Delete file | `bash rm <path>` |
| Rename/move file | `bash mv <old> <new>`, then apply any content changes with `edit` |
> **Tip:** Group nearby hunks from the same file into a single `edit` call.
> Use separate calls when hunks are far apart to keep `oldString` short and
> unambiguous.
Repeat until **all** hunks in the diff are ported.
### 5. Validate
Run **lint**, **check-fmt**, and **tests** for every affected module (see each
module's `AGENTS.md` for the exact commands). If the formatter auto-fixes
indentation, verify the logic is still semantically correct. All checks must
pass before moving on.
### 6. Port the changelog entry (if any)
If the original commit added or modified a `CHANGES.md` entry, port that entry
too — adapting wording and version references for the target branch.
### 7. Commit
Ask the `commiter` sub-agent to create a commit. Stage all relevant files
(exclude unrelated untracked files) and provide the original commit message as
a reference, adapting it as needed for the target branch context.
## Key Principles
- **Context matters** — always read files before editing; never guess
indentation or surrounding code
- **Lint + format + test** — never skip validation before committing
- **Preserve intent** — keep the original commit message meaning; the
`commiter` agent handles formatting

View File

@ -25,6 +25,7 @@
- Fix layer hierarchy to match old and new SCSS [Github #9126](https://github.com/penpot/penpot/pull/9126)
- Fix multiple selection on shapes with token applied to stroke color [Github #9110](https://github.com/penpot/penpot/pull/9110)
- Fix onboarding modals appearing behind libraries and templates panel [Github #9178](https://github.com/penpot/penpot/pull/9178)
- Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) [Github #8296](https://github.com/penpot/penpot/issues/8296)
## 2.14.5
@ -32,7 +33,6 @@
- Fix incorrect invitation token handling on register process [Github #9380](https://github.com/penpot/penpot/pull/9380)
## 2.14.4
### :bug: Bugs fixed

View File

@ -287,7 +287,7 @@ RUN set -ex; \
curl -LfsSo /tmp/gh.tar.gz ${BINARY_URL}; \
mkdir /opt/gh; \
cd /opt/gh; \
tar -xf /tmp/gh.tar.gz; \
tar -xv --strip-components=1 -f /tmp/gh.tar.gz; \
rm -rf /tmp/gh.tar.gz;
# Install minio client

View File

@ -96,12 +96,13 @@
(update-in [:comments id] assoc (:id comment) comment))))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (ptk/data-event ::ev/event
{::ev/name "create-comment-thread"
::ev/origin "workspace"
:id id
:content-size (count (:content comment))}))))))
(watch [it _ _]
(rx/of (ev/event
(merge {::ev/name "create-comment-thread"
::ev/origin "workspace"
:id id
:content-size (count (:content comment))}
(meta it))))))))
(def ^:private
schema:create-thread-on-workspace
@ -119,7 +120,7 @@
(ptk/reify ::create-thread-on-workspace
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
frame-id (ctst/get-frame-id-by-position objects (:position params))
@ -129,7 +130,10 @@
(->> (rp/cmd! :create-comment-thread params)
(rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %)}))
(rx/tap on-thread-created)
(rx/map #(created-thread-on-workspace % open?))
(rx/map
(fn [data]
(-> (created-thread-on-workspace data open?)
(with-meta (meta it)))))
(rx/catch (fn [{:keys [type code] :as cause}]
(if (and (= type :restriction)
(= code :max-quote-reached))
@ -152,11 +156,11 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of (ptk/data-event ::ev/event
{::ev/name "create-comment-thread"
::ev/origin "viewer"
:id id
:content-size (count (:content comment))})))))
(rx/of (ev/event
{::ev/name "create-comment-thread"
::ev/origin "viewer"
:id id
:content-size (count (:content comment))})))))
(def ^:private
schema:create-thread-on-viewer
@ -216,11 +220,13 @@
(d/update-in-when state [:comment-threads id] assoc :is-resolved is-resolved))
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
(let [share-id (-> state :viewer-local :share-id)]
(rx/concat
(when is-resolved (rx/of
(ptk/event ::ev/event {::ev/name "resolve-comment-thread" :thread-id id})))
(when is-resolved
(rx/of (ev/event
(-> {::ev/name "resolve-comment-thread" :thread-id id}
(merge (meta it))))))
(->> (rp/cmd! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id})
(rx/catch (fn [{:keys [type code] :as cause}]
(if (and (= type :restriction)
@ -309,16 +315,18 @@
(update :comment-threads dissoc id)))
ptk/WatchEvent
(watch [_ _ _]
(watch [it _ _]
(rx/concat
(->> (rp/cmd! :delete-comment-thread {:id id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/tap on-delete)
(rx/ignore))
(rx/of (ptk/data-event ::ev/event
{::ev/name "delete-comment-thread"
::ev/origin "workspace"
:id id})))))))
(rx/of (ev/event
(merge
{::ev/name "delete-comment-thread"
::ev/origin "workspace"
:id id}
(meta it)))))))))
(defn delete-comment-thread-on-viewer
[{:keys [id] :as thread}]
@ -341,10 +349,10 @@
(->> (rp/cmd! :delete-comment-thread {:id id :share-id share-id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))
(rx/of (ptk/data-event ::ev/event
{::ev/name "delete-comment-thread"
::ev/origin "viewer"
:id id})))))))
(rx/of (ev/event
{::ev/name "delete-comment-thread"
::ev/origin "viewer"
:id id})))))))
(defn delete-comment
[{:keys [id thread-id] :as comment}]
(dm/assert!

View File

@ -479,7 +479,7 @@
(->> (rp/cmd! :get-file-summary {:id id})
(rx/map (fn [summary]
(when (-> summary :variants :count pos?)
(ptk/event ::ev/event {::ev/name "set-file-variants-shared" ::ev/origin "dashboard"})))))))))))
(ev/event {::ev/name "set-file-variants-shared" ::ev/origin "dashboard"})))))))))))
(defn set-file-thumbnail
[file-id thumbnail-id]

View File

@ -267,8 +267,7 @@
counts))
{:png 0, :jpeg 0, :webp 0, :pdf 0, :svg 0}
exports)]
(ptk/event
::ev/event (merge types
{::ev/name "export-shapes"
::ev/origin origin
:num-shapes (count exports)}))))
(ev/event (merge types
{::ev/name "export-shapes"
::ev/origin origin
:num-shapes (count exports)}))))

View File

@ -261,12 +261,12 @@
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)]
(rx/of (ptk/data-event ::ev/event {::ev/name "add-font"
:team-id team-id
:font-id (:id font)
:font-family (:font-family font)
:font-style (:font-style font)
:font-weight (:font-weight font)}))))))
(rx/of (ev/event {::ev/name "add-font"
:team-id team-id
:font-id (:id font)
:font-family (:font-family font)
:font-style (:font-style font)
:font-weight (:font-weight font)}))))))
(defn update-font
[{:keys [id name] :as params}]
@ -310,9 +310,9 @@
(rx/concat
(->> (rp/cmd! :delete-font {:id font-id :team-id team-id})
(rx/ignore))
(rx/of (ptk/data-event ::ev/event {::ev/name "delete-font"
:team-id team-id
:font-id font-id})))))))
(rx/of (ev/event {::ev/name "delete-font"
:team-id team-id
:font-id font-id})))))))
(defn delete-font-variant
[id]
@ -331,9 +331,9 @@
(rx/concat
(->> (rp/cmd! :delete-font-variant {:id id :team-id team-id})
(rx/ignore))
(rx/of (ptk/data-event ::ev/event {::ev/name "delete-font-variant"
:id id
:team-id team-id})))))))
(rx/of (ev/event {::ev/name "delete-font-variant"
:id id
:team-id team-id})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -152,10 +152,10 @@
(when (not= (:theme profile)
(:theme profile'))
(rx/of (ptk/data-event ::ev/event
{::ev/name "activate-theme"
::ev/origin "settings"
:theme (:theme profile)})))))))))
(rx/of (ev/event
{::ev/name "activate-theme"
::ev/origin "settings"
:theme (:theme profile)})))))))))
;; --- Toggle Theme
@ -186,9 +186,9 @@
(watch [it state _]
(let [profile (get state :profile)
origin (::ev/origin (meta it))]
(rx/of (ptk/data-event ::ev/event {:theme (:theme profile)
::ev/name "activate-theme"
::ev/origin origin})
(rx/of (ev/event {:theme (:theme profile)
::ev/name "activate-theme"
::ev/origin origin})
(persist-profile))))))
;; --- Request Email Change

View File

@ -176,10 +176,10 @@
(rx/of (dp/refresh-profile)
(fetch-members team-id)
(fetch-teams)
(ptk/data-event ::ev/event
{::ev/name "delete-team-member"
:team-id team-id
:member-id member-id})))))))))
(ev/event
{::ev/name "delete-team-member"
:team-id team-id
:member-id member-id})))))))))
(defn- stats-fetched
@ -240,9 +240,9 @@
(rx/tap on-success)
(rx/mapcat (fn [_]
(rx/of (fetch-teams)
(ptk/data-event ::ev/event
{::ev/name "update-team-photo"
:team-id team-id}))))
(ev/event
{::ev/name "update-team-photo"
:team-id team-id}))))
(rx/catch on-error))))))
@ -345,10 +345,10 @@
(rx/merge
(rx/of (team-leaved params)
(fetch-teams)
(ptk/data-event ::ev/event
{::ev/name "leave-team"
:reassign-to reassign-to
:team-id team-id}))
(ev/event
{::ev/name "leave-team"
:reassign-to reassign-to
:team-id team-id}))
(on-success))))
(rx/catch on-error))))))

View File

@ -82,7 +82,7 @@
(when (some? (:profile state))
(fetch-comment-threads params))
(when (:share-id params)
(rx/of (ptk/event ::ev/event {::ev/name "shared-prototipe-visited"})))))
(rx/of (ev/event {::ev/name "shared-prototipe-visited"})))))
ptk/EffectEvent
(effect [_ _ _]
;; Set the window name, the window name is used on inter-tab

View File

@ -1343,7 +1343,7 @@
(rx/concat
(rx/of (dch/commit-changes changes))
(when (nil? annotation)
(rx/of (ptk/data-event ::ev/event {::ev/name "delete-component-annotation"}))))))))
(rx/of (ev/event {::ev/name "delete-component-annotation"}))))))))
(defn set-annotations-expanded
[expanded]
@ -1365,7 +1365,7 @@
ptk/WatchEvent
(watch [_ _ _]
(when (some? id)
(rx/of (ptk/data-event ::ev/event {::ev/name "create-component-annotation"}))))))
(rx/of (ev/event {::ev/name "create-component-annotation"}))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Preview blend modes

View File

@ -960,7 +960,7 @@
(ptk/data-event :layout/update {:ids [frame-id]})
(dwu/commit-undo-transaction undo-id)
(when add-component-to-variant?
(ptk/event ::ev/event {::ev/name "add-component-to-variant"})))))))))
(ev/event {::ev/name "add-component-to-variant"})))))))))
(defn- as-content [text]
(let [paragraphs (->> (str/lines text)

View File

@ -390,7 +390,7 @@
([id-ref ids]
(ptk/reify ::add-component
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
(let [objects (dsh/lookup-page-objects state)
selected (->> (d/nilv ids (dsh/lookup-selected state))
(cfh/clean-loops objects))
@ -399,7 +399,8 @@
can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) selected-objects))]
(when can-make-component
(rx/of (add-component2 id-ref selected))))))))
(rx/of (-> (add-component2 id-ref selected)
(with-meta (meta it))))))))))
(defn add-multiple-components
"Add several new components to current file library, from the currently selected shapes."
@ -601,11 +602,12 @@
(when id-ref
(reset! id-ref (:id new-shape)))
(rx/of (ptk/event ::ev/event
{::ev/name "use-library-component"
::ev/origin origin
:external-library (not= file-id current-file-id)
:is-variant (ctk/is-variant? component)})
(rx/of (ev/event
(-> {::ev/name "use-library-component"
::ev/origin origin
:external-library (not= file-id current-file-id)
:is-variant (ctk/is-variant? component)}
(merge (meta it))))
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update {:ids [(:id new-shape)]})
@ -1399,7 +1401,7 @@
vals
(some ctk/is-variant?))]
(if has-variants?
(rx/of (ptk/event ::ev/event {::ev/name "set-file-variants-shared" ::ev/origin "workspace"}))
(rx/of (ev/event {::ev/name "set-file-variants-shared" ::ev/origin "workspace"}))
(rx/empty)))))))))
;; --- Link and unlink Files
@ -1463,11 +1465,11 @@
(when (pos? variants-count)
(->> (rp/cmd! :get-library-usage {:file-id library-id})
(rx/map (fn [library-usage]
(ptk/event ::ev/event {::ev/name "attach-library-variants"
:file-id file-id
:library-id library-id
:variants-count variants-count
:library-used-in (:used-in library-usage)}))))))))))
(ev/event {::ev/name "attach-library-variants"
:file-id file-id
:library-id library-id
:variants-count variants-count
:library-used-in (:used-in library-usage)}))))))))))
(defn unlink-file-from-library
[file-id library-id]

View File

@ -305,11 +305,11 @@
(ptk/data-event :layout/update {:ids ids})
(dwu/commit-undo-transaction undo-id)
(when (or (:layout-align-content changes) (:layout-justify-content changes))
(ptk/event ::ev/event
{::ev/name "layout-change-alignment"}))
(ev/event
{::ev/name "layout-change-alignment"}))
(when (or (:layout-padding changes) (:layout-gap changes))
(ptk/event ::ev/event
{::ev/name "layout-change-margin"}))))))))
(ev/event
{::ev/name "layout-change-margin"}))))))))
(defn add-layout-track
([ids type value]

View File

@ -602,7 +602,7 @@
:subsections [:basics]
:fn #(when (features/active-feature? @st/state "plugins/runtime")
(st/emit!
(ptk/event ::ev/event {::ev/name "open-plugins-manager" ::ev/origin "workspace:shortcuts"})
(ev/event {::ev/name "open-plugins-manager" ::ev/origin "workspace:shortcuts"})
(modal/show :plugin-management {})))}})
(def debug-shortcuts

View File

@ -897,8 +897,8 @@
(rx/concat
(rx/of (dwl/add-typography typ)
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
:asset-type "typography"}))
(ev/event {::ev/name "add-asset-to-library"
:asset-type "typography"}))
(when (not multiple?)
(rx/of (update-attrs (:id shape)

View File

@ -658,7 +658,7 @@
[{:keys [attributes attributes-to-remove token shape-ids on-update-shape]}]
(ptk/reify ::apply-token
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
;; We do not allow to apply tokens while text editor is open.
(let [edition (get-in state [:workspace-local :edition])
objects (dsh/lookup-page-objects state)
@ -702,10 +702,12 @@
type (:type token)]
(rx/concat
(rx/of
(st/emit! (ev/event {::ev/name "apply-tokens"
:type type
:applied-to attributes
:applied-to-variant any-variant?}))
(st/emit! (ev/event
(-> {::ev/name "apply-tokens"
:type type
:applied-to attributes
:applied-to-variant any-variant?}
(merge (meta it)))))
(dwu/start-undo-transaction undo-id)
(dwsh/update-shapes shape-ids (fn [shape]
(cond-> shape
@ -734,7 +736,7 @@
[{:keys [token shapes attr]}]
(ptk/reify ::apply-spacing-token-separated
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
(let [objects (dsh/lookup-page-objects state)
{:keys [attributes on-update-shape]}
@ -744,14 +746,17 @@
(group-by #(if (ctsl/any-layout-immediate-child? objects %) :frame-children :other) shapes)]
(rx/of
(apply-token {:attributes (or attr attributes)
:token token
:shape-ids (map :id other)
:on-update-shape on-update-shape})
(apply-token {:attributes ctt/spacing-margin-keys
:token token
:shape-ids (map :id frame-children)
:on-update-shape update-layout-item-margin}))))))
(-> (apply-token {:attributes (or attr attributes)
:token token
:shape-ids (map :id other)
:on-update-shape on-update-shape})
(with-meta (meta it)))
(-> (apply-token {:attributes ctt/spacing-margin-keys
:token token
:shape-ids (map :id frame-children)
:on-update-shape update-layout-item-margin})
(with-meta (meta it))))))))
(defn unapply-token
"Removes `attributes` that match `token` for `shape-ids`.
@ -773,7 +778,7 @@
[{:keys [token attrs shape-ids expand-with-children]}]
(ptk/reify ::on-toggle-token
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
(let [objects (dsh/lookup-page-objects state)
shapes (into [] (keep (d/getf objects)) shape-ids)
@ -810,15 +815,17 @@
(cond
(and (= (:type token) :spacing)
(nil? attrs))
(apply-spacing-token-separated {:token token
:attr attrs
:shapes shapes})
(-> (apply-spacing-token-separated {:token token
:attr attrs
:shapes shapes})
(with-meta (meta it)))
:else
(apply-token {:attributes (if (empty? attrs) attributes attrs)
:token token
:shape-ids shape-ids
:on-update-shape on-update-shape}))))))))
(-> (apply-token {:attributes (if (empty? attrs) attributes attrs)
:token token
:shape-ids shape-ids
:on-update-shape on-update-shape})
(with-meta (meta it))))))))))
(defn apply-token-on-selected
[color-operations token]

View File

@ -426,7 +426,8 @@
token))]
(rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "create-token" :type token-type})))
(ev/event (-> {::ev/name "create-token" :type token-type}
(merge (meta it))))))
(rx/of (create-token-with-set token)))))))
@ -454,7 +455,8 @@
id
token'))]
(rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "edit-token" :type token-type})))))))
(ev/event (-> {::ev/name "edit-token" :type token-type}
(merge (meta it))))))))))
(defn delete-token
[set-id token-id]
@ -475,7 +477,8 @@
(pcb/with-library-data data)
(pcb/set-token set-id token-id nil))]
(rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "delete-token" :type token-type}))))))
(ev/event (-> {::ev/name "delete-token" :type token-type}
(merge (meta it)))))))))
(defn bulk-delete-tokens
[set-id token-ids]
@ -491,7 +494,7 @@
(pcb/with-library-data data))
token-ids)]
(rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "delete-token-node"}))))))
(ev/event {::ev/name "delete-token-node"}))))))
(defn duplicate-token
[token-id]

View File

@ -616,7 +616,7 @@
[ids {:keys [page-id trigger variant-id]}]
(ptk/reify ::combine-as-variants
ptk/WatchEvent
(watch [_ state stream]
(watch [it state stream]
(let [current-page (:current-page-id state)
combine
@ -665,7 +665,8 @@
(dwsh/relocate-shapes #{variant-id} common-parent index)
(dwt/update-dimensions [variant-id] :width (+ (:width rect) 60))
(dwt/update-dimensions [variant-id] :height (+ (:height rect) 60))
(ev/event {::ev/name "combine-as-variants" ::ev/origin trigger :number-of-combined (count ids)}))
(ev/event (-> {::ev/name "combine-as-variants" ::ev/origin trigger :number-of-combined (count ids)}
(merge (meta it)))))
;; NOTE: we need to schedule a commit into a
;; microtask for ensure that all the scheduled
@ -701,7 +702,7 @@
[shape {:keys [pos val] :as params}]
(ptk/reify ::variant-switch
ptk/WatchEvent
(watch [_ state _]
(watch [it state _]
(let [libraries (dsh/lookup-libraries state)
component-id (:component-id shape)
component (ctf/get-component libraries (:component-file shape) component-id :include-deleted? false)]
@ -735,20 +736,23 @@
(rx/empty))
(rx/of
(dwl/component-swap shape (:component-file shape) (:id nearest-comp) true)
(ev/event {::ev/name "variant-switch" ::ev/origin "workspace:design-tab"}))))))))))
(ev/event (-> {::ev/name "variant-switch" ::ev/origin "workspace:design-tab"}
(merge (meta it)))))))))))))
(defn variants-switch
"Switch each shape (that must be a variant copy head) for the closest one with the property value passed as parameter"
[{:keys [shapes] :as params}]
(ptk/reify ::variants-switch
ptk/WatchEvent
(watch [_ _ _]
(watch [it _ _]
(let [ids (into (d/ordered-set) d/xf:map-id shapes)
undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(->> (rx/from shapes)
(rx/map #(variant-switch % params)))
(rx/map (fn [data]
(-> (variant-switch data params)
(with-meta (meta it))))))
(rx/of (dwu/commit-undo-transaction undo-id)
(dws/select-shapes ids)))))))

View File

@ -66,7 +66,7 @@
;; Force persist before creating snapshot, otherwise we could loss changes
(rx/concat
(rx/of ::dwp/force-persist
(ptk/event ::ev/event {::ev/name "create-version"}))
(ev/event {::ev/name "create-version"}))
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
(rx/filter #(or (nil? %) (= :saved %)))
@ -88,8 +88,8 @@
(let [file-id (:current-file-id state)]
(rx/merge
(rx/of (update-versions-state {:editing nil})
(ptk/event ::ev/event {::ev/name "rename-version"
:file-id file-id}))
(ev/event {::ev/name "rename-version"
:file-id file-id}))
(->> (rp/cmd! :update-file-snapshot {:id id :label label})
(rx/map fetch-versions)))))))
@ -173,7 +173,7 @@
(rx/mapcat (fn [_]
(rx/of (update-versions-state {:editing id})
(fetch-versions)
(ptk/event ::ev/event {::ev/name "pin-version"}))))))))))
(ev/event {::ev/name "pin-version"}))))))))))
(defn lock-version
[id]
@ -215,9 +215,6 @@
(let [current-file-id (:current-file-id state)]
;; Force persist before creating snapshot, otherwise we could loss changes
(->> (rx/concat
(rx/of (ptk/event ::ev/event {::ev/origin "plugins"
::ev/name "create-version"}))
(when (= file-id current-file-id)
(rx/of ::dwp/force-persist))
@ -246,20 +243,15 @@
(ptk/reify ::restore-version-from-plugins
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)]
(rx/concat
(rx/of (ev/event {::ev/name "restore-version-plugin"
:file-id file-id
:team-id team-id})
::dwp/force-persist)
(watch [_ _ _]
(rx/concat
(rx/of ::dwp/force-persist)
(->> (wait-for-persistence file-id id)
(rx/map #(initialize-version)))
(->> (wait-for-persistence file-id id)
(rx/map #(initialize-version)))
(->> (rx/of 1)
(rx/tap resolve)
(rx/ignore)))))))
(->> (rx/of 1)
(rx/tap resolve)
(rx/ignore))))))

View File

@ -76,8 +76,7 @@
(when send-event-info?
(let [route (dm/get-in match [:data :name])
params (get match :query-params)]
(rx/of (ptk/event
::ev/event
(rx/of (ev/event
(assoc params
::ev/name "navigate"
:route (name route)))))))

View File

@ -44,7 +44,6 @@
[cuerdas.core :as str]
[goog.events :as events]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc dashboard-content*
@ -216,7 +215,7 @@
(fn [plugin]
(if plugin
(do
(st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
(st/emit! (ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
(open-permissions-dialog plugin))
(st/emit! (notif/error (tr "dashboard.plugins.parse-error")))))
(fn [_]
@ -245,12 +244,12 @@
(dd/fetch-recent-files team-id)
(dd/fetch-projects team-id)
(dd/clear-selected-files)
(ptk/event ::ev/event {::ev/name "install-template-from-link-finished"
:name template-name
:url template-url}))]
(ev/event {::ev/name "install-template-from-link-finished"
:name template-name
:url template-url}))]
(if valid-url?
(st/emit!
(ptk/event ::ev/event {::ev/name "install-template-from-link" :name template-name :url template-url})
(ev/event {::ev/name "install-template-from-link" :name template-name :url template-url})
(modal/show
{:type :import
:project-id project-id

View File

@ -18,7 +18,6 @@
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon]
[app.util.i18n :as i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private comments-icon-svg
@ -81,8 +80,8 @@
(mf/deps show?)
(fn []
(when show?
(st/emit! (ptk/event ::ev/event {::ev/name "open-comment-notifications"
::ev/origin "dashboard"})))))
(st/emit! (ev/event {::ev/name "open-comment-notifications"
::ev/origin "dashboard"})))))
[:div {:class (stl/css :dashboard-comments-section)}
[:& dropdown {:show show? :on-close on-hide-comments :dropdown-id "dashboard-comments"}

View File

@ -27,7 +27,6 @@
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(log/set-level! :debug)
@ -171,14 +170,14 @@
(rx/subs!
(fn [message]
(when (some? (:error message))
(st/emit! (ptk/data-event ::ev/event {::ev/name "import-files-error"
:error (:error message)})))
(st/emit! (ev/event {::ev/name "import-files-error"
:error (:error message)})))
(swap! state update-with-analyze-result message))))))
(defn- import-files
[state project-id entries]
(st/emit! (ptk/data-event ::ev/event {::ev/name "import-files"
:num-files (count entries)}))
(st/emit! (ev/event {::ev/name "import-files"
:num-files (count entries)}))
(let [features (get @st/state :features)]
(->> (mw/ask-many!

View File

@ -16,7 +16,6 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc empty-project-placeholder*
@ -27,9 +26,9 @@
on-add-library
(mf/use-fn
(fn [_]
(st/emit! (ptk/event ::ev/event {::ev/name "explore-libraries-click"
::ev/origin "dashboard"
:section "empty-placeholder-projects"}))
(st/emit! (ev/event {::ev/name "explore-libraries-click"
::ev/origin "dashboard"
:section "empty-placeholder-projects"}))
(dom/open-new-window "https://penpot.app/penpothub/libraries-templates")))
on-import

View File

@ -31,7 +31,6 @@
[app.util.storage :as storage]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private show-more-icon
@ -344,8 +343,8 @@
(mf/use-fn
(fn []
(reset! show-team-hero* false)
(st/emit! (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
::ev/origin "dashboard"}))))]
(st/emit! (ev/event {::ev/name "dont-show-team-up-hero"
::ev/origin "dashboard"}))))]
(mf/with-effect [show-team-hero?]
(swap! storage/global assoc ::show-team-hero show-team-hero?))

View File

@ -44,7 +44,6 @@
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[goog.functions :as f]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private clear-search-icon
@ -869,8 +868,8 @@
(dom/get-data "url"))
eventname (-> (dom/get-current-target event)
(dom/get-data "eventname"))]
(st/emit! (ptk/event ::ev/event {::ev/name eventname
::ev/origin "menu:in-app"}))
(st/emit! (ev/event {::ev/name eventname
::ev/origin "menu:in-app"}))
(dom/open-new-window url))))
handle-feedback-click
@ -914,8 +913,8 @@
(dom/get-data "url"))
eventname (-> (dom/get-current-target event)
(dom/get-data "eventname"))]
(st/emit! (ptk/event ::ev/event {::ev/name eventname
::ev/origin "menu:in-app"}))
(st/emit! (ev/event {::ev/name eventname
::ev/origin "menu:in-app"}))
(dom/open-new-window url))))]
[:> dropdown-menu* {:show true
@ -942,7 +941,7 @@
show-release-notes
(mf/use-fn
(fn [event]
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version (:main version)}))
(st/emit! (ev/event {::ev/name "show-release-notes" :version (:main version)}))
(if (and (kbd/alt? event) (kbd/mod? event))
(st/emit! (modal/show {:type :onboarding}))
(st/emit! (modal/show {:type :release-notes :version (:main version)})))))
@ -954,8 +953,8 @@
(dom/get-data "url"))
eventname (-> (dom/get-current-target event)
(dom/get-data "eventname"))]
(st/emit! (ptk/event ::ev/event {::ev/name eventname
::ev/origin "menu:in-app"}))
(st/emit! (ev/event {::ev/name eventname
::ev/origin "menu:in-app"}))
(dom/open-new-window url))))]
[:> dropdown-menu* {:show true
@ -1052,7 +1051,7 @@
on-power-up-click
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-pricing-click" ::ev/origin "dashboard" :section "sidebar"}))
(st/emit! (ev/event {::ev/name "explore-pricing-click" ::ev/origin "dashboard" :section "sidebar"}))
(dom/open-new-window "https://penpot.app/pricing")))]
[:*

View File

@ -26,7 +26,6 @@
margin: 0 var(--sp-l) 0 0;
border-right: $b-1 solid var(--panel-border-color);
background-color: var(--panel-background-color);
z-index: var(--z-index-dropdown);
}
//SIDEBAR CONTENT COMPONENT

View File

@ -16,7 +16,6 @@
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[lambdaisland.uri :as u]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(defn get-subscription-type
@ -124,9 +123,9 @@
go-to-manage-subscription
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management"
::ev/origin "dashboard"
:section "team-settings"}))
(st/emit! (ev/event {::ev/name "open-subscription-management"
::ev/origin "dashboard"
:section "team-settings"}))
(let [href (-> (rt/get-current-href)
(rt/encode-url))
href (str "payments/subscriptions/show?returnUrl=" href)]

View File

@ -23,7 +23,6 @@
[app.util.keyboard :as kbd]
[app.util.storage :as storage]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private arrow-icon
@ -40,10 +39,10 @@
(letfn [(on-finish []
(st/emit!
(dd/fetch-recent-files team-id)
(ptk/event ::ev/event {::ev/name "import-template-finish"
::ev/origin "dashboard"
:template (:name template)
:section section})
(ev/event {::ev/name "import-template-finish"
::ev/origin "dashboard"
:template (:name template)
:section section})
(when-not (some? project-id)
(dcm/go-to-dashboard-recent
@ -51,10 +50,10 @@
:project-id default-project-id))))]
(st/emit!
(ptk/event ::ev/event {::ev/name "import-template-launch"
::ev/origin "dashboard"
:template (:name template)
:section section})
(ev/event {::ev/name "import-template-launch"
::ev/origin "dashboard"
:template (:name template)
:section section})
(modal/show
{:type :import
@ -145,9 +144,9 @@
(mf/use-fn
(mf/deps section)
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-libraries-click"
::ev/origin "dashboard"
:section section}))))
(st/emit! (ev/event {::ev/name "explore-libraries-click"
::ev/origin "dashboard"
:section section}))))
on-key-down
(mf/use-fn

View File

@ -31,7 +31,6 @@
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def embed-images? true)
@ -158,10 +157,10 @@
(let [origin (if (= :workspace from)
"workspace"
"viewer")]
(st/emit! (ptk/event ::ev/event
{::ev/name "copy-inspect-code"
::ev/origin origin
:type markup-type})))))
(st/emit! (ev/event
{::ev/name "copy-inspect-code"
::ev/origin origin
:type markup-type})))))
on-style-copied
(mf/use-fn
@ -170,10 +169,10 @@
(let [origin (if (= :workspace from)
"workspace"
"viewer")]
(st/emit! (ptk/event ::ev/event
{::ev/name "copy-inspect-style"
::ev/origin origin
:type style-type})))))
(st/emit! (ev/event
{::ev/name "copy-inspect-style"
::ev/origin origin
:type style-type})))))
{on-markup-pointer-down :on-pointer-down
on-markup-lost-pointer-capture :on-lost-pointer-capture
@ -206,10 +205,10 @@
(let [origin (if (= :workspace from)
"workspace"
"viewer")]
(st/emit! (ptk/event ::ev/event
{::ev/name "copy-inspect-code"
::ev/origin origin
:type "all"})))))
(st/emit! (ev/event
{::ev/name "copy-inspect-code"
::ev/origin origin
:type "all"})))))
;;handle-open-review
;;(mf/use-fn

View File

@ -24,7 +24,6 @@
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.shape-icon :as usi]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(defn- get-libraries
@ -75,7 +74,7 @@
(when on-change-section
(on-change-section (keyword new-section))
(st/emit!
(ptk/event ::ev/event {::ev/name "change-inspect-tab" :tab new-section})))))
(ev/event {::ev/name "change-inspect-tab" :tab new-section})))))
handle-expand
(mf/use-fn
@ -122,7 +121,7 @@
(mf/deps shapes handle-change-tab)
(fn []
(if (seq shapes)
(st/emit! (ptk/event ::ev/event {::ev/name "inspect-mode-click-element"}))
(st/emit! (ev/event {::ev/name "inspect-mode-click-element"}))
(handle-change-tab (if (contains? cf/flags :inspect-styles) :styles :info)))))
[:aside {:class (stl/css-case :settings-bar-right true

View File

@ -11,14 +11,13 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.main.data.event :as-alias ev]
[app.main.data.event :as ev]
[app.main.data.profile :as du]
[app.main.store :as st]
[app.main.ui.components.forms :as fm]
[app.main.ui.icons :as deprecated-icon]
[app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc step-container
@ -33,7 +32,7 @@
(assoc :label label)
(assoc :step step)
(assoc ::ev/name "onboarding-step"))]
(st/emit! (ptk/data-event ::ev/event params))
(st/emit! (ev/event params))
(on-next form event))))]
[:& fm/form {:form form

View File

@ -18,7 +18,6 @@
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.notifications.context-notification :refer [context-notification]]
[app.util.i18n :as i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc left-sidebar
@ -121,13 +120,13 @@
params {:name name}]
(st/emit! (-> (dtm/create-team (with-meta params mdata))
(with-meta {::ev/origin :onboarding-without-invitations}))
(ptk/data-event ::ev/event
{::ev/name "onboarding-step"
:label "team:create-team-and-invite-later"
:team-name name
:step 8})
(ptk/data-event ::ev/event
{::ev/name "onboarding-finish"})))))
(ev/event
{::ev/name "onboarding-step"
:label "team:create-team-and-invite-later"
:team-name name
:step 8})
(ev/event
{::ev/name "onboarding-finish"})))))
on-invite-now
(mf/use-fn
@ -137,15 +136,15 @@
(st/emit! (-> (dtm/create-team-with-invitations (with-meta params mdata))
(with-meta {::ev/origin :onboarding-with-invitations}))
(ptk/data-event ::ev/event
{::ev/name "onboarding-step"
:label "team:create-team-and-invite"
:invites (count emails)
:team-name name
:role (:role params)
:step 8})
(ptk/data-event ::ev/event
{::ev/name "onboarding-finish"})))))
(ev/event
{::ev/name "onboarding-step"
:label "team:create-team-and-invite"
:invites (count emails)
:team-name name
:role (:role params)
:step 8})
(ev/event
{::ev/name "onboarding-finish"})))))
on-submit*
(mf/use-fn
@ -160,12 +159,12 @@
(mf/use-fn
(fn []
(st/emit! (du/update-profile-props {:onboarding-viewed true})
(ptk/data-event ::ev/event
{::ev/name "onboarding-step"
:label "team:skip-team-creation"
:step 7})
(ptk/data-event ::ev/event
{::ev/name "onboarding-finish"}))))]
(ev/event
{::ev/name "onboarding-step"
:label "team:skip-team-creation"
:step 7})
(ev/event
{::ev/name "onboarding-finish"}))))]
[:*
[:div {:class (stl/css :modal-right)}
[:div {:class (stl/css :first-block)}

View File

@ -18,7 +18,6 @@
[app.main.ui.icons :as deprecated-icon]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private arrow-icon
@ -52,7 +51,7 @@
(defn- show-release-notes
[event]
(let [version (:main cf/version)]
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
(st/emit! (ev/event {::ev/name "show-release-notes" :version version}))
(if (and (kbd/alt? event) (kbd/mod? event))
(st/emit! (modal/show {:type :onboarding}))

View File

@ -18,7 +18,6 @@
[app.main.ui.notifications.badge :refer [badge-notification]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr c]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc plan-card*
@ -132,16 +131,16 @@
add-payment-details? "&quantity="
min-members "&returnUrl=" return-url)]
(reset! form nil)
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
:type "unlimited"
:quantity min-members})
(st/emit! (ev/event {::ev/name "create-trial-subscription"
:type "unlimited"
:quantity min-members})
(rt/nav-raw :href href))))))
subscribe-to-enterprise
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
:type "enterprise"}))
(st/emit! (ev/event {::ev/name "create-trial-subscription"
:type "enterprise"}))
(let [return-url (-> (rt/get-current-href) (rt/encode-url))
href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" return-url)]
(st/emit! (rt/nav-raw :href href)))))
@ -161,7 +160,7 @@
handle-close-dialog
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
(st/emit! (ev/event {::ev/name "close-subscription-modal"}))
(modal/hide!)))
show-editors-list*
@ -323,7 +322,7 @@
(let [profile (mf/deref refs/profile)
handle-close-dialog (mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "subscription-success"}))
(st/emit! (ev/event {::ev/name "subscription-success"}))
(modal/hide!)))]
[:div {:class (stl/css :modal-overlay)}
@ -434,8 +433,8 @@
^boolean show-trial-subscription-modal?
(st/emit!
(ptk/event ::ev/event {::ev/name "open-subscription-modal"
::ev/origin "settings:from-pricing-page"})
(ev/event {::ev/name "open-subscription-modal"
::ev/origin "settings:from-pricing-page"})
(modal/show :management-dialog
{:subscription-type (if (= params-subscription "subscription-to-penpot-unlimited")
"unlimited"

View File

@ -24,7 +24,6 @@
[app.util.clipboard :as clipboard]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(log/set-level! :warn)
@ -126,10 +125,10 @@
(let [params (prepare-params options)
params (assoc params :file-id (:id file))]
(st/emit! (dc/create-share-link params)
(ptk/event ::ev/event {::ev/name "create-share-link"
::ev/origin "viewer"
:can-comment (:who-comment params)
:can-inspect-code (:who-inspect params)}))))
(ev/event {::ev/name "create-share-link"
::ev/origin "viewer"
:can-comment (:who-comment params)
:can-inspect-code (:who-inspect params)}))))
copy-link
(fn [_]
@ -138,8 +137,8 @@
:type :toast
:content (tr "common.share-link.link-copied-success")
:timeout 1000})
(ptk/event ::ev/event {::ev/name "copy-share-link"
::ev/origin "viewer"})))
(ev/event {::ev/name "copy-share-link"
::ev/origin "viewer"})))
try-delete-link
(fn [_]

View File

@ -24,7 +24,6 @@
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc palette-item*
@ -37,10 +36,10 @@
(st/emit! (mdc/add-recent-color color)
(mdc/apply-color-from-palette color (kbd/alt? event))
(when (not= selected :recent)
(ptk/data-event ::ev/event
{::ev/name "use-library-color"
::ev/origin "color-palette"
:external-library (not= selected :file)})))))
(ev/event
{::ev/name "use-library-color"
::ev/origin "color-palette"
:external-library (not= selected :file)})))))
title
(uc/get-color-name color)]

View File

@ -15,7 +15,7 @@
[app.common.types.fills :as types.fills]
[app.common.types.tokens-lib :as ctob]
[app.config :as cfg]
[app.main.data.event :as-alias ev]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
[app.main.data.shortcuts :as dsc]
[app.main.data.workspace.colors :as dc]
@ -46,7 +46,6 @@
[app.util.timers :as ts]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]
[rumext.v2.util :as mfu]))
@ -174,9 +173,9 @@
(st/emit!
(dc/update-colorpicker-color {:image image} true)
(ptk/data-event ::ev/event {::ev/name "toggle-image-aspect-ratio"
::ev/origin "workspace:colorpicker"
:checked keep-aspect-ratio?})))))
(ev/event {::ev/name "toggle-image-aspect-ratio"
::ev/origin "workspace:colorpicker"
:checked keep-aspect-ratio?})))))
on-change-tab
(mf/use-fn #(reset! active-color-tab* %))

View File

@ -24,7 +24,6 @@
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc libraries
@ -87,8 +86,7 @@
(mf/deps state selected on-select-color)
(fn [event]
(when-not (= :recent selected)
(st/emit! (ptk/event
::ev/event
(st/emit! (ev/event
{::ev/name "use-library-color"
::ev/origin "colorpicker"
:external-library (not= :file selected)})))

View File

@ -41,7 +41,6 @@
[app.util.timers :as timers]
[beicon.v2.core :as rx]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def menu-ref
@ -676,7 +675,7 @@
:on-accept delete-fn}))
do-duplicate #(st/emit!
(dw/duplicate-page id)
(ptk/event ::ev/event {::ev/name "duplicate-page"}))
(ev/event {::ev/name "duplicate-page"}))
do-rename #(st/emit! (dw/start-rename-page-item id))]
[:*

View File

@ -43,7 +43,6 @@
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc shortcuts*
@ -62,43 +61,43 @@
(let [nav-to-helpc-center
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-help-center-click"
::ev/origin "workspace-menu:in-app"}))
(st/emit! (ev/event {::ev/name "explore-help-center-click"
::ev/origin "workspace-menu:in-app"}))
(dom/open-new-window "https://help.penpot.app")))
nav-to-community
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-community-click"
::ev/origin "workspace-menu:in-app"}))
(st/emit! (ev/event {::ev/name "explore-community-click"
::ev/origin "workspace-menu:in-app"}))
(dom/open-new-window "https://community.penpot.app")))
nav-to-youtube
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-tutorials-click"
::ev/origin "workspace-menu:in-app"}))
(st/emit! (ev/event {::ev/name "explore-tutorials-click"
::ev/origin "workspace-menu:in-app"}))
(dom/open-new-window "https://www.youtube.com/c/Penpot")))
nav-to-templates
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-libraries-click"
::ev/origin "workspace"}))
(st/emit! (ev/event {::ev/name "explore-libraries-click"
::ev/origin "workspace"}))
(dom/open-new-window "https://penpot.app/libraries-templates")))
nav-to-github
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-github-repository-click"
::ev/origin "workspace-menu:in-app"}))
(st/emit! (ev/event {::ev/name "explore-github-repository-click"
::ev/origin "workspace-menu:in-app"}))
(dom/open-new-window "https://github.com/penpot/penpot")))
nav-to-terms
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-terms-service-click"
::ev/origin "workspace-menu:in-app"}))
(st/emit! (ev/event {::ev/name "explore-terms-service-click"
::ev/origin "workspace-menu:in-app"}))
(dom/open-new-window "https://penpot.app/terms")))
nav-to-feedback
@ -125,8 +124,8 @@
(mf/use-fn
(fn [event]
(let [version (:main cf/version)]
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes"
:version version}))
(st/emit! (ev/event {::ev/name "show-release-notes"
:version version}))
(println version)
(if (and (kbd/alt? event) (kbd/mod? event))
(st/emit! (modal/show {:type :onboarding}))
@ -712,10 +711,10 @@
(fn [event]
(if can-open?
(do
(st/emit! (ptk/event ::ev/event {::ev/name "start-plugin"
::ev/origin "workspace:menu"
:name name
:host host}))
(st/emit! (ev/event {::ev/name "start-plugin"
::ev/origin "workspace:menu"
:name name
:host host}))
(dp/open-plugin! manifest user-can-edit?))
(dom/stop-propagation event))))
on-key-down
@ -724,10 +723,10 @@
(fn [event]
(when can-open?
(when (kbd/enter? event)
(st/emit! (ptk/event ::ev/event {::ev/name "start-plugin"
::ev/origin "workspace:menu"
:name name
:host host}))
(st/emit! (ev/event {::ev/name "start-plugin"
::ev/origin "workspace:menu"
:name name
:host host}))
(dp/open-plugin! manifest user-can-edit?)))))]
[:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx)
@ -765,8 +764,8 @@
on-nav-to-integrations
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "manage-mpc-option"
::ev/origin "workspace-menu"}))
(st/emit! (ev/event {::ev/name "manage-mpc-option"
::ev/origin "workspace-menu"}))
(dom/open-new-window "/#/settings/integrations")))
on-nav-to-integrations-key-down
@ -780,11 +779,11 @@
(fn []
(if mcp-connected?
(st/emit! (mcp/user-disconnect-mcp)
(ptk/event ::ev/event {::ev/name "disconnect-mcp-plugin"
::ev/origin "workspace-menu"}))
(ev/event {::ev/name "disconnect-mcp-plugin"
::ev/origin "workspace-menu"}))
(st/emit! (mcp/connect-mcp)
(ptk/event ::ev/event {::ev/name "connect-mcp-plugin"
::ev/origin "workspace-menu"})))))
(ev/event {::ev/name "connect-mcp-plugin"
::ev/origin "workspace-menu"})))))
on-toggle-mcp-plugin-key-down
(mf/use-fn
@ -866,8 +865,8 @@
on-power-up-click
(mf/use-fn
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "explore-pricing-click"
::ev/origin "workspace-menu"}))
(st/emit! (ev/event {::ev/name "explore-pricing-click"
::ev/origin "workspace-menu"}))
(dom/open-new-window "https://penpot.app/pricing")))
toggle-flag
@ -896,8 +895,8 @@
(reset! show-menu* false)
(reset! selected-sub-menu* nil)
(st/emit!
(ptk/event ::ev/event {::ev/name "open-plugins-manager"
::ev/origin "workspace:menu"})
(ev/event {::ev/name "open-plugins-manager"
::ev/origin "workspace:menu"})
(modal/show :plugin-management {}))))
subscription (:subscription (:props profile))

View File

@ -27,7 +27,6 @@
[app.util.i18n :as i18n :refer [tr]]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private close-icon
@ -128,7 +127,7 @@
(reset! fetching-manifest? false)
(if plugin
(do
(st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
(st/emit! (ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
(modal/show!
:plugin-permissions
{:plugin plugin
@ -148,10 +147,10 @@
handle-open-plugin
(mf/use-fn
(fn [manifest]
(st/emit! (ptk/event ::ev/event {::ev/name "start-plugin"
::ev/origin "workspace:plugins"
:name (:name manifest)
:host (:host manifest)}))
(st/emit! (ev/event {::ev/name "start-plugin"
::ev/origin "workspace:plugins"
:name (:name manifest)
:host (:host manifest)}))
(dp/open-plugin! manifest user-can-edit?)
(modal/hide!)))
@ -161,9 +160,9 @@
(fn [plugin-index]
(let [plugins-list (preg/plugins-list)
plugin (nth plugins-list plugin-index)]
(st/emit! (ptk/event ::ev/event {::ev/name "remove-plugin"
:name (:name plugin)
:host (:host plugin)}))
(st/emit! (ev/event {::ev/name "remove-plugin"
:name (:name plugin)
:host (:host plugin)}))
(dp/close-plugin! plugin)
(preg/remove-plugin! plugin)
(reset! plugins-state* (preg/plugins-list)))))]
@ -195,7 +194,7 @@
(when-not (empty? plugins-state)
[:> i18n/tr-html*
{:class (stl/css :discover)
:on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"}))
:on-click #(st/emit! (ev/event {::ev/name "open-plugins-list"}))
:content (tr "workspace.plugins.discover" cfg/plugins-list-uri)}])
[:hr]
@ -207,7 +206,7 @@
[:a {:class (stl/css :plugins-link)
:href cfg/plugins-list-uri
:target "_blank"
:on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"}))}
:on-click #(st/emit! (ev/event {::ev/name "open-plugins-list"}))}
(tr "workspace.plugins.plugin-list-link") deprecated-icon/external-link]]
[:*
@ -298,9 +297,9 @@
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "allow-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(st/emit! (ev/event {::ev/name "allow-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(modal/hide))
(when on-accept (on-accept))))
@ -308,9 +307,9 @@
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "reject-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(st/emit! (ev/event {::ev/name "reject-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(modal/hide))
(when on-close (on-close))))]
@ -353,9 +352,9 @@
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "allow-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(st/emit! (ev/event {::ev/name "allow-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(modal/hide))
(when on-accept (on-accept))))
@ -363,9 +362,9 @@
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "reject-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(st/emit! (ev/event {::ev/name "reject-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(modal/hide))
(when on-close (on-close))))]
@ -406,7 +405,7 @@
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "try-out-accept"})
(st/emit! (ev/event {::ev/name "try-out-accept"})
(modal/hide))
(when on-accept (on-accept))))
@ -414,7 +413,7 @@
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "try-out-cancel"})
(st/emit! (ev/event {::ev/name "try-out-cancel"})
(modal/hide))
(when on-close (on-close))))]

View File

@ -43,7 +43,6 @@
[app.main.ui.workspace.tokens.sidebar :refer [tokens-sidebar-tab*]]
[app.util.debug :as dbg]
[app.util.i18n :refer [tr]]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
;; --- Left Sidebar (Component)
@ -146,7 +145,7 @@
(fn [id]
(st/emit! (dcm/go-to-workspace :layout (keyword id)))
(when (= id "tokens")
(st/emit! (ptk/event ::ev/event {::ev/name "open-tokens-tab"})))))
(st/emit! (ev/event {::ev/name "open-tokens-tab"})))))
tabs
(mf/with-memo [mode-inspect? design-tokens?]

View File

@ -31,7 +31,6 @@
[app.util.keyboard :as kbd]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc color-item
@ -180,10 +179,10 @@
(mf/deps color on-asset-click read-only? file-id)
(fn [event]
(when-not read-only?
(st/emit! (ptk/data-event ::ev/event
{::ev/name "use-library-color"
::ev/origin "sidebar"
:external-library (not local?)}))
(st/emit! (ev/event
{::ev/name "use-library-color"
::ev/origin "sidebar"
:external-library (not local?)}))
(when-not (on-asset-click event (:id color))
(st/emit! (dc/apply-color-from-assets file-id color (kbd/alt? event)))))))]
@ -403,8 +402,8 @@
y-position (:top bounds)]
(st/emit! (dw/set-assets-section-open file-id :colors true)
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
:asset-type "color"})
(ev/event {::ev/name "add-asset-to-library"
:asset-type "color"})
(modal/show :colorpicker
{:x x-position
:y y-position

View File

@ -35,7 +35,6 @@
[app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def drag-data* (atom {:is-local false}))
@ -373,8 +372,8 @@
(let [params {:file-id file-id
:blobs (seq blobs)}]
(st/emit! (dwm/upload-media-components params)
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
:asset-type "components"})))))
(ev/event {::ev/name "add-asset-to-library"
:asset-type "components"})))))
on-duplicate
(mf/use-fn

View File

@ -32,7 +32,6 @@
[app.util.keyboard :as kbd]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -94,7 +93,7 @@
(mf/use-fn
(fn [ev]
(dom/stop-propagation ev)
(st/emit! (ptk/data-event ::ev/event {::ev/name "navigate-to-library-file"}))))]
(st/emit! (ev/event {::ev/name "navigate-to-library-file"}))))]
[:div {:class (stl/css-case
:library-title true

View File

@ -28,7 +28,6 @@
[app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def lens:typography-section-state
@ -97,10 +96,10 @@
(mf/deps typography on-asset-click read-only? local?)
(fn [event]
(when-not read-only?
(st/emit! (ptk/data-event ::ev/event
{::ev/name "use-library-typography"
::ev/origin "sidebar"
:external-library (not local?)}))
(st/emit! (ev/event
{::ev/name "use-library-typography"
::ev/origin "sidebar"
:external-library (not local?)}))
(when-not (on-asset-click event (:id typography))
(st/emit! (dwt/apply-typography typography file-id))))))]

View File

@ -19,7 +19,6 @@
[app.util.i18n :refer [tr]]
[app.util.object :as obj]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc typography-item
@ -38,8 +37,7 @@
:typography-ref-id (:id typography)}
(dissoc typography :id :name))]
(st/emit! (ptk/event
::ev/event
(st/emit! (ev/event
{::ev/name "use-library-typography"
::ev/origin "text-palette"
:external-library (not= file-id current-file-id)}))

View File

@ -23,7 +23,6 @@
[app.util.i18n :refer [tr]]
[app.util.webapi :as wapi]
[app.util.zip :as zip]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc export-tab*
@ -61,7 +60,7 @@
(mf/deps tokens-json)
(fn []
(when tokens-json
(st/emit! (ptk/data-event ::ev/event {::ev/name "export-tokens" :type "single"}))
(st/emit! (ev/event {::ev/name "export-tokens" :type "single"}))
(->> (wapi/create-blob (or tokens-json "{}") "application/json")
(dom/trigger-download "tokens.json")))))]
[:> export-tab* {:is-disabled is-disabled
@ -88,7 +87,7 @@
(mf/use-fn
(mf/deps files)
(fn []
(st/emit! (ptk/data-event ::ev/event {::ev/name "export-tokens" :type "multiple"}))
(st/emit! (ev/event {::ev/name "export-tokens" :type "multiple"}))
(download-tokens-zip! files)))]
[:> export-tab* {:on-export on-export
:is-disabled is-disabled}

View File

@ -27,7 +27,6 @@
[app.util.zip :as uz]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(defn- on-stream-imported
@ -35,7 +34,7 @@
(rx/sub!
tokens-lib-stream
(fn [lib]
(st/emit! (ptk/data-event ::ev/event {::ev/name "import-tokens" :type type})
(st/emit! (ev/event {::ev/name "import-tokens" :type type})
(dwtl/import-tokens-lib lib))
(modal/hide!))
(fn [err]

View File

@ -7,8 +7,7 @@
[app.main.data.notifications :as ntf]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st]
[app.util.i18n :refer [tr]]
[potok.v2.core :as ptk]))
[app.util.i18n :refer [tr]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS - Shared functions for token sets management
@ -37,7 +36,7 @@
[tokens-lib parent-set name]
(let [name (ctob/make-child-name parent-set name)
errors (sm/validation-errors name (cfo/make-token-set-name-schema tokens-lib nil))]
(st/emit! (ptk/data-event ::ev/event {::ev/name "create-token-set" :name name})
(st/emit! (ev/event {::ev/name "create-token-set" :name name})
(dwtl/clear-token-set-creation))
(if (empty? errors)
(let [token-set (ctob/make-token-set :name name)]

View File

@ -33,7 +33,6 @@
[app.util.i18n :refer [tr]]
[app.util.keyboard :as k]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
;; Form Component --------------------------------------------------------------
@ -412,7 +411,7 @@
on-save
(mf/use-fn
(fn [theme]
(st/emit! (ptk/event ::ev/event {::ev/name "create-tokens-theme"})
(st/emit! (ev/event {::ev/name "create-tokens-theme"})
(dwtl/create-token-theme theme))))
has-prev-view (has-prev-view (:prev-type state))]

View File

@ -24,7 +24,6 @@
[app.util.i18n :as i18n :refer [tr]]
[app.util.timers :as ts]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc image-upload
@ -209,8 +208,8 @@
:aria-label (tr "workspace.toolbar.plugins" (sc/get-tooltip :plugins))
:class (stl/css :main-toolbar-options-button)
:on-click #(st/emit!
(ptk/data-event ::ev/event {::ev/name "open-plugins-manager"
::ev/origin "workspace:toolbar"})
(ev/event {::ev/name "open-plugins-manager"
::ev/origin "workspace:toolbar"})
(modal/show :plugin-management {}))
:data-tool "plugins"
:data-testid "plugins-btn"}

View File

@ -273,6 +273,7 @@
(->> (dwm/upload-media-url name file-id url)
(rx/take 1)
(rx/map format/format-image)
(rx/tap #(st/emit! (se/event plugin-id "add-media")))
(rx/subs! resolve reject)))))))
:uploadMediaData
@ -289,6 +290,7 @@
:on-svg identity})
(rx/take 1)
(rx/map format/format-image)
(rx/tap #(st/emit! (se/event plugin-id "add-media")))
(rx/subs! resolve reject))))))
:group
@ -462,6 +464,7 @@
(conj acc (cg/generate-formatted-markup-code objects type resolved-shapes))))
[]))]
(st/emit! (se/event plugin-id "copy-inspect-code"))
(->> resolved-code (str/join "\n"))))))
:generateStyle
@ -508,6 +511,7 @@
(cg/generate-style-code
objects type shapes resolved-shapes {:with-prelude? prelude?}))))
[]))]
(st/emit! (se/event plugin-id "copy-inspect-style"))
(dm/str
(if prelude? (cg/prelude type) "")
(->> resolved-styles
@ -542,7 +546,8 @@
(fn []
(let [file-id (:current-file-id @st/state)
id (uuid/next)]
(st/emit! (dw/create-page {:page-id id :file-id file-id}))
(st/emit! (-> (dw/create-page {:page-id id :file-id file-id})
(se/add-event plugin-id)))
(page/page-proxy plugin-id file-id id)))
:openPage
@ -648,9 +653,10 @@
ids)]
(if valid?
(let [variant-id (uuid/next)]
(st/emit! (dwv/combine-as-variants
ids
{:trigger "plugin:combine-as-variants" :variant-id variant-id}))
(st/emit! (-> (dwv/combine-as-variants
ids
{:trigger "plugin:combine-as-variants" :variant-id variant-id})
(se/add-event plugin-id)))
(shape/shape-proxy plugin-id variant-id))
(u/not-valid plugin-id :shapes "One of the components is not on the same page or is already a variant")))))))

View File

@ -17,6 +17,7 @@
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.system-events :as se]
[app.plugins.user :as user]
[app.plugins.utils :as u]
[app.util.object :as obj]
@ -71,7 +72,13 @@
:else
(->> (rp/cmd! :update-comment {:id (:id data) :content content})
(rx/tap #(st/emit! (dc/retrieve-comment-threads file-id)))
(rx/subs! #(swap! data* assoc :content content))))))}
(rx/subs!
(fn []
(st/emit! (se/event plugin-id "update-comment"
:thread-id thread-id
:id (:id data)
:content-size (count content)))
(swap! data* assoc :content content)))))))}
;; Public methods
:remove
@ -87,7 +94,11 @@
:else
(->> (rp/cmd! :delete-comment {:id (:id data)})
(rx/tap #(st/emit! (dc/retrieve-comment-threads file-id)))
(rx/subs! #(resolve) reject)))))))))
(rx/subs!
(fn []
(st/emit! (se/event plugin-id "update-comment" :thread-id thread-id))
(resolve))
reject)))))))))
(defn comment-thread-proxy? [p]
(obj/type-of? p "CommentThreadProxy"))
@ -143,7 +154,8 @@
(u/not-valid plugin-id :resolved "Plugin doesn't have 'comment:write' permission")
:else
(do (st/emit! (dc/update-comment-thread (assoc @data* :is-resolved is-resolved)))
(do (st/emit! (-> (dc/update-comment-thread (assoc @data* :is-resolved is-resolved))
(se/add-event plugin-id)))
(swap! data* assoc :is-resolved is-resolved))))}
:findComments
@ -178,7 +190,13 @@
(js/Promise.
(fn [resolve reject]
(->> (rp/cmd! :create-comment {:thread-id (:id data) :content content})
(rx/subs! #(resolve (comment-proxy plugin-id file-id page-id (:id data) %)) reject))))))
(rx/subs!
(fn [result]
(st/emit! (se/event plugin-id "create-comment"
:thread-id (:id data)
:file-id file-id
:content-size (count content)))
(resolve (comment-proxy plugin-id file-id page-id (:id data) result))) reject))))))
:remove
(fn []
@ -194,4 +212,5 @@
:else
(js/Promise.
(fn [resolve]
(st/emit! (dc/delete-comment-thread-on-workspace {:id (:id data)} #(resolve)))))))))))
(st/emit! (-> (dc/delete-comment-thread-on-workspace {:id (:id data)} #(resolve))
(se/add-event plugin-id)))))))))))

View File

@ -22,6 +22,7 @@
[app.plugins.page :as page]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.system-events :as se]
[app.plugins.user :as user]
[app.plugins.utils :as u]
[app.util.http :as http]
@ -54,7 +55,7 @@
(do (swap! data assoc :label value :created-by "user")
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
(rx/take 1)
(rx/subs! identity)))))}
(rx/subs! #(st/emit! (se/event "rename-version" :file-id file-id)))))))}
:createdBy
{:get
@ -78,7 +79,9 @@
:else
(let [version-id (get @data :id)]
(st/emit! (dwv/restore-version-from-plugin file-id version-id resolve reject)))))))
(st/emit!
(dwv/restore-version-from-plugin file-id version-id resolve reject)
(se/event plugin-id "restore-version" :file-id file-id)))))))
:remove
(fn []
@ -110,10 +113,12 @@
:label (ct/format-inst (:created-at @data) :localized-date)}]
(->> (rx/zip (rp/cmd! :get-team-users {:file-id file-id})
(rp/cmd! :update-file-snapshot params))
(rx/subs! (fn [[users data]]
(let [users (d/index-by :id users)]
(resolve (file-version-proxy plugin-id file-id users @data))))
reject))))))))))
(rx/subs!
(fn [[users data]]
(let [users (d/index-by :id users)]
(st/emit! (se/event plugin-id "pin-version" :file-id file-id))
(resolve (file-version-proxy plugin-id file-id users @data))))
reject))))))))))
(defn file-proxy? [p]
(obj/type-of? p "FileProxy"))
@ -220,7 +225,8 @@
:else
(let [page-id (uuid/next)]
(st/emit! (dw/create-page {:page-id page-id :file-id id}))
(st/emit! (-> (dw/create-page {:page-id page-id :file-id id})
(se/add-event plugin-id)))
(page/page-proxy plugin-id id page-id))))
:export
@ -269,6 +275,7 @@
:response-type :buffer}))))
(rx/take 1)
(rx/map #(js/Uint8Array. (:body %)))
(rx/tap #(st/emit! (se/event plugin-id "export-binary-files" :format format :type type)))
(rx/subs! resolve reject))))))))
:findVersions
(fn [criteria]
@ -315,7 +322,9 @@
(u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwv/create-version-from-plugins id label resolve reject)))))]
(st/emit!
(dwv/create-version-from-plugins id label resolve reject)
(se/event plugin-id "create-version" :file-id id)))))]
(-> (js/Promise.all #js [users-promise create-version-promise])
(.then
(fn [[users data]]

View File

@ -13,6 +13,7 @@
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st]
[app.plugins.register :as r]
[app.plugins.system-events :as se]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -186,7 +187,6 @@
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}}))))}
:topPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
@ -260,11 +260,16 @@
:else
(let [child-id (obj/get child "$id")
shape (u/locate-shape file-id page-id id)
child-shape (u/locate-shape file-id page-id child-id)
index
(if (and (u/natural-child-ordering? plugin-id) (not (ctl/reverse? shape)))
0
(count (:shapes shape)))]
(st/emit! (dwsh/relocate-shapes #{child-id} id index)))))
(st/emit!
(dwsh/relocate-shapes #{child-id} id index)
(se/event plugin-id "add-layout-element"
:type (:type child-shape)
:parent-type (:type shape))))))
:horizontalSizing
{:this true

View File

@ -16,7 +16,6 @@
[app.common.types.file :as ctf]
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.main.data.event :as ev]
[app.main.data.plugins :as dp]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
@ -27,6 +26,7 @@
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.system-events :as se]
[app.plugins.text :as text]
[app.plugins.tokens :as tokens]
[app.plugins.utils :as u]
@ -164,7 +164,8 @@
(u/not-valid plugin-id :remove "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dwl/delete-color {:id id}))))
(st/emit! (-> (dwl/delete-color {:id id})
(se/add-event plugin-id)))))
:clone
(fn []
@ -176,7 +177,8 @@
(let [color-id (uuid/next)
color (-> (u/locate-library-color file-id id)
(assoc :id color-id))]
(st/emit! (dwl/add-color color {:rename? false}))
(st/emit! (-> (dwl/add-color color {:rename? false})
(se/add-event plugin-id)))
(lib-color-proxy plugin-id id color-id))))
:asFill
@ -309,7 +311,8 @@
:else
(let [typo (u/proxy->library-typography self)
value (dm/str (d/nilv (:path typo) "") " / " value)]
(st/emit! (dwl/rename-typography file-id id value)))))}
(st/emit! (-> (dwl/rename-typography file-id id value)
(se/add-event plugin-id))))))}
:path
{:this true
@ -488,7 +491,8 @@
(u/not-valid plugin-id :remove "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dwl/delete-typography {:id id}))))
(st/emit! (-> (dwl/delete-typography {:id id})
(se/add-event plugin-id)))))
:clone
(fn []
@ -500,7 +504,8 @@
(let [typo-id (uuid/next)
typo (-> (u/locate-library-typography file-id id)
(assoc :id typo-id))]
(st/emit! (dwl/add-typography typo false))
(st/emit! (-> (dwl/add-typography typo false)
(se/add-event plugin-id)))
(lib-typography-proxy plugin-id id typo-id))))
:applyToText
@ -662,13 +667,13 @@
:addVariant
(fn []
(st/emit!
(ev/event {::ev/name "add-new-variant" ::ev/origin "plugin:add-variant"})
(se/event plugin-id "add-new-variant")
(dwv/add-new-variant id)))
:addProperty
(fn []
(st/emit!
(ev/event {::ev/name "add-new-property" ::ev/origin "plugin:add-property"})
(se/event plugin-id "add-new-property")
(dwv/add-new-property id {:property-value "Value 1"})))
:removeProperty
@ -676,7 +681,7 @@
(if (not (nat-int? pos))
(u/not-valid plugin-id :pos pos)
(st/emit!
(ev/event {::ev/name "remove-property" ::ev/origin "plugin:remove-property"})
(se/event plugin-id "remove-property")
(dwv/remove-property id pos))))
:renameProperty
@ -759,7 +764,8 @@
:else
(let [id-ref (atom nil)]
(st/emit! (dwl/instantiate-component file-id id (gpt/point 0 0) {:id-ref id-ref :origin "plugin"}))
(st/emit! (-> (dwl/instantiate-component file-id id (gpt/point 0 0) {:id-ref id-ref :origin "plugin"})
(se/add-event plugin-id)))
(shape/shape-proxy plugin-id @id-ref))))
:getPluginData
@ -885,7 +891,7 @@
(when (and component
(not (ctk/is-variant? component)))
(st/emit!
(ev/event {::ev/name "transform-in-variant" ::ev/origin "plugin:transform-in-variant"})
(se/event plugin-id "transform-in-variant")
(dwv/transform-in-variant (:main-instance-id component))))))
:addVariant
@ -894,7 +900,7 @@
(when (and component
(ctk/is-variant? component))
(st/emit!
(ev/event {::ev/name "add-new-variant" ::ev/origin "plugin:add-variant-from-component"})
(se/event plugin-id "add-new-variant")
(dwv/add-new-variant (:main-instance-id component))))))
:setVariantProperty
@ -908,7 +914,7 @@
:else
(st/emit!
(ev/event {::ev/name "variant-edit-property-value" ::ev/origin "plugin:edit-property-value"})
(se/event plugin-id "variant-edit-property-value")
(dwv/update-property-value id pos value))))))
(defn library-proxy? [p]
@ -974,7 +980,8 @@
:else
(let [color-id (uuid/next)]
(st/emit! (dwl/add-color {:id color-id :name "Color" :color "#000000" :opacity 1} {:rename? false}))
(st/emit! (-> (dwl/add-color {:id color-id :name "Color" :color "#000000" :opacity 1} {:rename? false})
(se/add-event plugin-id)))
(lib-color-proxy plugin-id file-id color-id))))
:createTypography
@ -985,7 +992,8 @@
:else
(let [typography-id (uuid/next)]
(st/emit! (dwl/add-typography (ctt/make-typography {:id typography-id :name "Typography"}) false))
(st/emit! (-> (dwl/add-typography (ctt/make-typography {:id typography-id :name "Typography"}) false)
(se/add-event plugin-id)))
(lib-typography-proxy plugin-id file-id typography-id))))
:createComponent
@ -997,7 +1005,8 @@
:else
(let [id-ref (atom nil)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwl/add-component id-ref ids))
(st/emit! (-> (dwl/add-component id-ref ids)
(se/add-event plugin-id)))
(lib-component-proxy plugin-id file-id @id-ref))))
;; Plugin data
@ -1127,4 +1136,5 @@
(rx/filter (ptk/type? ::dwl/attach-library-finished))
(rx/take 1)
(rx/subs! #(resolve (library-proxy plugin-id library-id)) reject))
(st/emit! (dwl/link-file-to-library file-id library-id))))))))))
(st/emit! (-> (dwl/link-file-to-library file-id library-id)
(se/add-event plugin-id)))))))))))

View File

@ -28,6 +28,7 @@
[app.plugins.register :as r]
[app.plugins.ruler-guides :as rg]
[app.plugins.shape :as shape]
[app.plugins.system-events :as se]
[app.plugins.utils :as u]
[app.util.object :as obj]
[beicon.v2.core :as rx]
@ -283,7 +284,9 @@
:else
(let [flow-id (uuid/next)]
(st/emit! (dwi/add-flow flow-id id name (obj/get frame "$id")))
(st/emit!
(dwi/add-flow flow-id id name (obj/get frame "$id"))
(se/event plugin-id "add-flow"))
(flow-proxy plugin-id file-id id flow-id))))
:removeFlow
@ -293,7 +296,9 @@
(u/not-valid plugin-id :removeFlow-flow flow)
:else
(st/emit! (dwi/remove-flow id (obj/get flow "$id")))))
(st/emit!
(dwi/remove-flow id (obj/get flow "$id"))
(se/event plugin-id "remove-flow"))))
:addRulerGuide
(fn [orientation value board]
@ -316,12 +321,13 @@
:else
(let [ruler-id (uuid/next)]
(st/emit!
(dwgu/update-guides
(d/without-nils
{:id ruler-id
:axis (parser/orientation->axis orientation)
:position value
:frame-id (when board (obj/get board "$id"))})))
(-> (dwgu/update-guides
(d/without-nils
{:id ruler-id
:axis (parser/orientation->axis orientation)
:position value
:frame-id (when board (obj/get board "$id"))}))
(se/add-event plugin-id)))
(rg/ruler-guide-proxy plugin-id file-id id ruler-id)))))
:removeRulerGuide
@ -335,7 +341,8 @@
:else
(let [guide (u/proxy->ruler-guide value)]
(st/emit! (dwgu/remove-guide guide)))))
(st/emit! (-> (dwgu/remove-guide guide)
(se/add-event plugin-id))))))
:addCommentThread
(fn [content position board]
@ -364,15 +371,15 @@
(js/Promise.
(fn [resolve]
(st/emit!
(dc/create-thread-on-workspace
{:file-id file-id
:page-id id
:position (gpt/point position)
:content content}
(fn [data]
(resolve (pc/comment-thread-proxy plugin-id file-id id data)))
false))))))))
(-> (dc/create-thread-on-workspace
{:file-id file-id
:page-id id
:position (gpt/point position)
:content content}
(fn [data]
(resolve (pc/comment-thread-proxy plugin-id file-id id data)))
false)
(se/add-event plugin-id)))))))))
:removeCommentThread
(fn [thread]

View File

@ -135,3 +135,7 @@
(= plugin-id mcp-plugin-id)
(let [{:keys [permissions]} (dm/get-in @registry [:data plugin-id])]
(contains? permissions permission))))
(defn get-plugin-data
[state plugin-id]
(get-in state [:profile :props :plugins :data plugin-id]))

View File

@ -13,6 +13,7 @@
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.register :as r]
[app.plugins.system-events :as se]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -55,7 +56,8 @@
:else
(let [board-id (when value (obj/get value "$id"))
guide (-> self u/proxy->ruler-guide)]
(st/emit! (dwgu/update-guides (assoc guide :frame-id board-id)))))))}
(st/emit! (-> (dwgu/update-guides (assoc guide :frame-id board-id))
(se/add-event plugin-id)))))))}
:orientation
{:this true
@ -92,9 +94,11 @@
(+ board-pos value))
value)]
(st/emit! (dwgu/update-guides (assoc guide :position position))))))}
(st/emit! (-> (dwgu/update-guides (assoc guide :position position))
(se/add-event plugin-id))))))}
:remove
(fn []
(let [guide (u/locate-ruler-guide file-id page-id id)]
(st/emit! (dwgu/remove-guide guide))))))
(st/emit! (-> (dwgu/remove-guide guide)
(se/add-event plugin-id)))))))

View File

@ -53,6 +53,7 @@
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.ruler-guides :as rg]
[app.plugins.system-events :as se]
[app.plugins.text :as text]
[app.plugins.tokens :refer [applied-tokens-plugin->applied-tokens token-attr-plugin->token-attr token-attr?]]
[app.plugins.utils :as u]
@ -785,10 +786,8 @@
(when (ctl/grid-layout-immediate-child-id? objects id)
(grid/layout-cell-proxy plugin-id file-id page-id id))))}
;; Interactions
:interactions
{:this true
:get
@ -962,12 +961,17 @@
:else
(let [child-id (obj/get child "$id")
child-shape (u/locate-shape file-id page-id child-id)
is-reversed? (ctl/flex-layout? shape)
index
(if (or (not (u/natural-child-ordering? plugin-id)) is-reversed?)
0
(count (:shapes shape)))]
(st/emit! (dwsh/relocate-shapes #{child-id} id index))))))
(st/emit!
(dwsh/relocate-shapes #{child-id} id index)
(se/event plugin-id (if (ctl/any-layout? shape) "add-layout-element" "add-element")
:type (:type child-shape)
:parent-type (:type shape)))))))
:insertChild
(fn [index child]
@ -987,12 +991,17 @@
:else
(let [child-id (obj/get child "$id")
child-shape (u/locate-shape file-id page-id child-id)
is-reversed? (ctl/flex-layout? shape)
index
(if (or (not (u/natural-child-ordering? plugin-id)) is-reversed?)
(- (count (:shapes shape)) index)
index)]
(st/emit! (dwsh/relocate-shapes #{child-id} id index))))))
(st/emit!
(dwsh/relocate-shapes #{child-id} id index)
(se/event plugin-id (if (ctl/any-layout? shape) "add-layout-element" "add-element")
:type (:type child-shape)
:parent-type (:type shape)))))))
;; Only for frames
:addFlexLayout
@ -1006,7 +1015,9 @@
(u/not-valid plugin-id :addFlexLayout "Plugin doesn't have 'content:write' permission")
:else
(do (st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false))
(do (st/emit!
(dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false)
(se/event plugin-id "create-layout" :layout "flex"))
(flex/flex-layout-proxy plugin-id file-id page-id id)))))
:addGridLayout
@ -1021,6 +1032,7 @@
:else
(do (st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false))
(se/event plugin-id "create-layout" :layout "grid")
(grid/grid-layout-proxy plugin-id file-id page-id id)))))
;; Make masks for groups
@ -1035,7 +1047,9 @@
(u/not-valid plugin-id :makeMask "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwg/mask-group #{id})))))
(st/emit!
(dwg/mask-group #{id})
(se/event plugin-id "create-shape" :type "mask")))))
:removeMask
(fn []
@ -1224,8 +1238,10 @@
(rx/map :body))))
(rx/mapcat #(.arrayBuffer %))
(rx/map #(js/Uint8Array. %))
(rx/tap #(st/emit! (se/event plugin-id "export-shapes")))
(rx/subs! resolve reject))))))))
;; Interactions
:addInteraction
(fn [trigger action delay]
@ -1238,7 +1254,9 @@
:else
(let [index (-> (u/locate-shape file-id page-id id) (:interactions []) count)]
(st/emit! (dwi/add-interaction page-id id interaction))
(st/emit!
(dwi/add-interaction page-id id interaction)
(se/event plugin-id "add-interaction"))
(interaction-proxy plugin-id file-id page-id id index)))))
:removeInteraction
@ -1248,7 +1266,9 @@
(u/not-valid plugin-id :removeInteraction interaction)
:else
(st/emit! (dwi/remove-interaction {:id id} (obj/get interaction "$index")))))
(st/emit!
(dwi/remove-interaction {:id id} (obj/get interaction "$index"))
(se/event plugin-id "remove-interaction"))))
;; Ruler guides
:addRulerGuide
@ -1275,11 +1295,12 @@
board-pos (get frame axis)
position (+ board-pos value)]
(st/emit!
(dwgu/update-guides
{:id id
:axis axis
:position position
:frame-id id}))
(-> (dwgu/update-guides
{:id id
:axis axis
:position position
:frame-id id})
(se/add-event plugin-id)))
(rg/ruler-guide-proxy plugin-id file-id page-id id)))))
:removeRulerGuide
@ -1293,7 +1314,8 @@
:else
(let [guide (u/proxy->ruler-guide value)]
(st/emit! (dwgu/remove-guide guide)))))
(st/emit! (-> (dwgu/remove-guide guide)
(se/add-event plugin-id))))))
:tokens
{:this true
@ -1313,17 +1335,18 @@
{:enumerable false
:schema [:tuple
[:fn token-proxy?]
[:maybe [:set [:and ::sm/keyword [:fn token-attr?]]]]]
[:maybe [::sm/set [:and ::sm/keyword [:fn token-attr?]]]]]
:fn (fn [token attrs]
(let [token (u/locate-token file-id (obj/get token "$set-id") (obj/get token "$id"))
kw-attrs (into #{} (map token-attr-plugin->token-attr attrs))]
(if (some #(not (token-attr? %)) kw-attrs)
(u/not-valid plugin-id :applyToken attrs)
(st/emit!
(dwta/toggle-token {:token token
:attrs kw-attrs
:shape-ids [id]
:expand-with-children false})))))}
(-> (dwta/toggle-token {:token token
:attrs kw-attrs
:shape-ids [id]
:expand-with-children false})
(se/add-event plugin-id))))))}
:isVariantHead
(fn []
@ -1349,7 +1372,8 @@
(let [shape (u/locate-shape file-id page-id id)
component (u/locate-library-component file-id (:component-id shape))]
(when (and component (ctk/is-variant? component))
(st/emit! (dwv/variants-switch {:shapes [shape] :pos pos :val value}))))))
(st/emit! (-> (dwv/variants-switch {:shapes [shape] :pos pos :val value})
(se/add-event plugin-id)))))))
:combineAsVariants
(fn [ids]
@ -1371,9 +1395,10 @@
(if valid?
(let [variant-id (uuid/next)]
(st/emit! (dwv/combine-as-variants
ids
{:trigger "plugin:combine-as-variants" :variant-id variant-id}))
(st/emit! (-> (dwv/combine-as-variants
ids
{:trigger "plugin:combine-as-variants" :variant-id variant-id})
(se/add-event plugin-id)))
(shape-proxy plugin-id variant-id))
(u/not-valid plugin-id :ids "One of the components is not on the same page or is already a variant"))))))

View File

@ -7,17 +7,34 @@
(ns app.plugins.system-events
(:require
[app.main.data.event :as ev]
[app.main.store :as st]))
[app.main.store :as st]
[app.plugins.register :as r]))
;; Formats an event from the plugin system
(defn event
[plugin-id name & {:as props}]
(let [plugin-data (get-in @st/state [:profile :props :plugins :data plugin-id])]
(if (= plugin-id r/mcp-plugin-id)
(-> props
(assoc ::ev/name name)
(assoc ::ev/origin "plugin")
(assoc ::ev/context
{:plugin-name (:name plugin-data)
:plugin-url (:url plugin-data)})
(ev/event))))
(assoc ::ev/origin "mcp")
(ev/event))
(let [plugin-data (r/get-plugin-data @st/state plugin-id)]
(-> props
(assoc ::ev/name name)
(assoc ::ev/origin "plugin")
(assoc ::ev/context
{:plugin-name (:name plugin-data)
:plugin-url (:url plugin-data)})
(ev/event)))))
(defn add-event
[event plugin-id]
(let [plugin-data (r/get-plugin-data @st/state plugin-id)]
(with-meta
event
(if (= plugin-id r/mcp-plugin-id)
{::ev/origin "mcp"}
{::ev/origin "plugin"
::ev/context {:plugin-name (:name plugin-data)
:plugin-url (:url plugin-data)}}))))

View File

@ -17,6 +17,7 @@
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st]
[app.plugins.system-events :as se]
[app.plugins.utils :as u]
[app.util.object :as obj]
[clojure.datafy :refer [datafy]]
@ -49,8 +50,19 @@
(get map:token-attr->token-attr-plugin k k))
(defn token-attr-plugin->token-attr
"Resolve a plugin-side token attribute reference to its canonical
internal keyword.
Accepts either a Clojure keyword (the canonical form, e.g. `:r1`,
`:fill`) or a string (the natural shape that arrives from a JS plugin
call such as `shape.applyToken(token, [\"fill\"])`). Converts strings
to keywords first, then maps verbose plugin-side aliases (e.g.
`:border-radius-top-left`) to their internal short form (e.g. `:r1`).
Inputs that are already in canonical form (`:r1`, `:fill`, `\"fill\"`,
…) pass through unchanged."
[k]
(get map:token-attr-plugin->token-attr k k))
(let [k (cond-> k (string? k) keyword)]
(get map:token-attr-plugin->token-attr k k)))
(defn applied-tokens-plugin->applied-tokens
[value]
@ -69,10 +81,11 @@
(if (some #(not (token-attr? %)) attrs)
(u/not-valid plugin-id :applyToSelected attrs)
(st/emit!
(dwta/toggle-token {:token token
:attrs (into #{} (map token-attr-plugin->token-attr) attrs)
:shape-ids shape-ids
:expand-with-children false})))))
(-> (dwta/toggle-token {:token token
:attrs (into #{} (map token-attr-plugin->token-attr) attrs)
:shape-ids shape-ids
:expand-with-children false})
(se/add-event plugin-id))))))
(defn- get-resolved-value
[token tokens-tree]
@ -112,7 +125,8 @@
(ctob/get-tokens set-id)))
:set
(fn [_ value]
(st/emit! (dwtl/update-token set-id id {:name value})))}
(st/emit! (-> (dwtl/update-token set-id id {:name value})
(se/add-event plugin-id))))}
:type
{:this true
@ -162,7 +176,8 @@
:schema cfo/schema:token-description
:set
(fn [_ value]
(st/emit! (dwtl/update-token set-id id {:description value})))}
(st/emit! (-> (dwtl/update-token set-id id {:description value})
(se/add-event :plugin-id))))}
:duplicate
(fn []
@ -175,24 +190,26 @@
token' (ctob/make-token (-> (datafy token)
(dissoc :id
:modified-at)))]
(st/emit! (dwtl/create-token set-id token'))
(st/emit! (-> (dwtl/create-token set-id token')
(se/add-event plugin-id)))
(token-proxy plugin-id file-id set-id (:id token'))))
:remove
(fn []
(st/emit! (dwtl/delete-token set-id id)))
(st/emit! (-> (dwtl/delete-token set-id id)
(se/add-event plugin-id))))
:applyToShapes
{:enumerable false
:schema [:tuple
[:vector [:fn shape-proxy?]]
[:maybe [:set [:and ::sm/keyword [:fn token-attr?]]]]]
[:maybe [::sm/set [:and ::sm/keyword [:fn token-attr?]]]]]
:fn (fn [shapes attrs]
(apply-token-to-shapes plugin-id file-id set-id id (map #(obj/get % "$id") shapes) attrs))}
:applyToSelected
{:enumerable false
:schema [:tuple [:maybe [:set [:and ::sm/keyword [:fn token-attr?]]]]]
:schema [:tuple [:maybe [::sm/set [:and ::sm/keyword [:fn token-attr?]]]]]
:fn (fn [attrs]
(let [selected (get-in @st/state [:workspace-local :selected])]
(apply-token-to-shapes plugin-id file-id set-id id selected attrs)))}))
@ -312,7 +329,8 @@
(get resolved-tokens (:name token))]
(if resolved-value
(do (st/emit! (dwtl/create-token id token))
(do (st/emit! (-> (dwtl/create-token id token)
(se/add-event plugin-id)))
(token-proxy plugin-id file-id id (:id token)))
(do (u/not-valid plugin-id :addToken (str errors))
nil))))}

View File

@ -0,0 +1,82 @@
;; 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 frontend-tests.plugins.tokens-test
(:require
[app.plugins.tokens :as ptok]
[cljs.test :as t :include-macros true]))
;; Regression coverage for issue #9162.
;;
;; Plugin code calling `shape.applyToken(token, ["fill"])` or
;; `token.applyToShapes([rect], ["fill"])` from JavaScript supplies a JS
;; array of strings. Penpot's plugin proxies expect a Clojure set of
;; keywords. Two coupled defects made these calls silently no-op (or, with
;; `throwValidationErrors` enabled, throw a "check error"):
;;
;; 1. `token-attr-plugin->token-attr` only consulted its alias map when
;; the input was already a keyword — string inputs like "fill" or
;; "border-radius-top-left" fell through to the identity branch
;; unchanged, so the downstream `cto/token-attr?` predicate (which
;; checks against a set of keywords) returned false.
;; 2. The `applyToken` / `applyToShapes` / `applyToSelected` schemas used
;; plain `[:set ...]`, which does not have a `:decode/json`
;; transformer for the JS array → Clojure set coercion. Penpot's
;; custom `[::sm/set ...]` does. Switching to the registered set type
;; lets the standard JSON decoder pipeline turn the JS argument into
;; a set of strings, after which the `[:and ::sm/keyword [:fn
;; token-attr?]]` element schema coerces each string to a keyword and
;; validates it.
;;
;; These helper-level tests pin the string-friendly conversion contract;
;; the schema-level fix is covered by the existing plugin integration
;; suite that exercises `applyToken` end-to-end.
(t/deftest token-attr-plugin->token-attr-passes-canonical-form-through
;; Both already-canonical short names and unaliased names pass through
;; unchanged.
(t/is (= :fill (ptok/token-attr-plugin->token-attr :fill)))
(t/is (= :stroke-color (ptok/token-attr-plugin->token-attr :stroke-color)))
(t/is (= :r1 (ptok/token-attr-plugin->token-attr :r1)))
(t/is (= :p2 (ptok/token-attr-plugin->token-attr :p2))))
(t/deftest token-attr-plugin->token-attr-resolves-verbose-plugin-aliases
;; Plugin-side verbose names (e.g. `:border-radius-top-left`) map to
;; their canonical short internal form (`:r1`) so plugin authors can
;; spell the corner explicitly without the engine having to know both.
(t/is (= :r1 (ptok/token-attr-plugin->token-attr :border-radius-top-left)))
(t/is (= :r2 (ptok/token-attr-plugin->token-attr :border-radius-top-right)))
(t/is (= :r3 (ptok/token-attr-plugin->token-attr :border-radius-bottom-right)))
(t/is (= :r4 (ptok/token-attr-plugin->token-attr :border-radius-bottom-left)))
(t/is (= :p1 (ptok/token-attr-plugin->token-attr :padding-top-left)))
(t/is (= :m3 (ptok/token-attr-plugin->token-attr :margin-bottom-right))))
(t/deftest token-attr-plugin->token-attr-coerces-string-input
;; This is the actual regression — JS plugin calls supply strings.
(t/is (= :fill (ptok/token-attr-plugin->token-attr "fill")))
(t/is (= :stroke-color (ptok/token-attr-plugin->token-attr "stroke-color")))
;; Verbose plugin aliases work via the string path too.
(t/is (= :r1 (ptok/token-attr-plugin->token-attr "border-radius-top-left")))
(t/is (= :m3 (ptok/token-attr-plugin->token-attr "margin-bottom-right"))))
(t/deftest token-attr?-accepts-keyword-input
(t/is (true? (boolean (ptok/token-attr? :fill))))
(t/is (true? (boolean (ptok/token-attr? :stroke-color))))
(t/is (true? (boolean (ptok/token-attr? :r1))))
(t/is (true? (boolean (ptok/token-attr? :p2)))))
(t/deftest token-attr?-accepts-string-input
;; Same JS-array-of-strings reproducer as the issue, exercised at the
;; predicate layer the plugin schemas call into.
(t/is (true? (boolean (ptok/token-attr? "fill"))))
(t/is (true? (boolean (ptok/token-attr? "stroke-color"))))
(t/is (true? (boolean (ptok/token-attr? "r1"))))
(t/is (true? (boolean (ptok/token-attr? "m3")))))
(t/deftest token-attr?-rejects-unknown-input
(t/is (false? (boolean (ptok/token-attr? :not-a-real-attr))))
(t/is (false? (boolean (ptok/token-attr? "not-a-real-attr"))))
(t/is (false? (boolean (ptok/token-attr? nil)))))

View File

@ -19,6 +19,7 @@
[frontend-tests.logic.pasting-in-containers-test]
[frontend-tests.main-errors-test]
[frontend-tests.plugins.context-shapes-test]
[frontend-tests.plugins.tokens-test]
[frontend-tests.svg-fills-test]
[frontend-tests.tokens.import-export-test]
[frontend-tests.tokens.logic.token-actions-test]
@ -61,6 +62,7 @@
'frontend-tests.logic.groups-test
'frontend-tests.logic.pasting-in-containers-test
'frontend-tests.plugins.context-shapes-test
'frontend-tests.plugins.tokens-test
'frontend-tests.svg-fills-test
'frontend-tests.tokens.import-export-test
'frontend-tests.tokens.logic.token-actions-test

View File

@ -386,6 +386,9 @@ For many tasks, it can be critical to visually inspect the design. Remember to u
* When transferring styles from a Penpot design to code, make sure that you strictly adhere to the design.
NEVER make assumptions about missing values and don't get overly creative (e.g. don't pick your own colours and stick to
non-creative defaults such as white/black if you are lacking information).
* When creating new designs,
- ensure a clean internal structure by applying flex and grid layouts when appropriate
- ensure proper semantic naming of elements.
# Revising Designs