mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
@@ -84,6 +84,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
- Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696)
|
- Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696)
|
||||||
- Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353)
|
- Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353)
|
||||||
- Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313)
|
- Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313)
|
||||||
|
- Refactor clipboard behavior to assess some minor inconsistencies and make pasting binary data faster. [Taiga #12571](https://tree.taiga.io/project/penpot/task/12571)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
@@ -98,6 +99,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
|
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
|
||||||
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
|
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
|
||||||
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
||||||
|
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
||||||
|
|
||||||
## 2.11.1
|
## 2.11.1
|
||||||
|
|
||||||
|
|||||||
@@ -1679,7 +1679,7 @@ Will return a value that matches this schema:
|
|||||||
["id" {:optional true} :string]
|
["id" {:optional true} :string]
|
||||||
["name" :string]
|
["name" :string]
|
||||||
["description" :string]
|
["description" :string]
|
||||||
["isSource" :boolean]
|
["isSource" {:optional true} :boolean]
|
||||||
["selectedTokenSets"
|
["selectedTokenSets"
|
||||||
[:map-of :string [:enum "enabled" "disabled"]]]]]]
|
[:map-of :string [:enum "enabled" "disabled"]]]]]]
|
||||||
["$metadata" {:optional true}
|
["$metadata" {:optional true}
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- "valkey_data:/data"
|
- "valkey_data:/data"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- redis
|
||||||
|
|
||||||
mailer:
|
mailer:
|
||||||
image: sj26/mailcatcher:latest
|
image: sj26/mailcatcher:latest
|
||||||
restart: always
|
restart: always
|
||||||
@@ -123,6 +128,12 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "1080:1080"
|
- "1080:1080"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- mailer
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/rroemhild/docker-test-openldap
|
# https://github.com/rroemhild/docker-test-openldap
|
||||||
ldap:
|
ldap:
|
||||||
image: rroemhild/test-openldap:2.1
|
image: rroemhild/test-openldap:2.1
|
||||||
@@ -136,3 +147,9 @@ services:
|
|||||||
nofile:
|
nofile:
|
||||||
soft: 1024
|
soft: 1024
|
||||||
hard: 1024
|
hard: 1024
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- ldap
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 3.07. Abstraction levels
|
title: 3.07. Abstraction levels
|
||||||
|
desc: "Penpot Technical Guide: organize data and logic in clear abstraction layers—ADTs, file ops, event-sourced changes, business rules, and data events."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Code organization in abstraction levels
|
# Code organization in abstraction levels
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 3.06. Backend Guide
|
title: 3.06. Backend Guide
|
||||||
|
desc: "Penpot Technical Guide: Backend basics - REPL setup, loading fixtures, database migrations, and clj-kondo linting to speed development workflows."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Backend guide #
|
# Backend guide #
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 1.2 Install with Elestio
|
title: 1.2 Install with Elestio
|
||||||
|
desc: "Step-by-step guide to deploy a self-hosted Penpot on Elestio: 3-minute setup, managed DNS/SMTP/SSL/backups, Docker Compose config, updates & support."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Install with Elestio
|
# Install with Elestio
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Design Tokens
|
title: Design Tokens
|
||||||
order: 5
|
order: 5
|
||||||
|
desc: Learn how to create, manage and apply Penpot Design Tokens using W3C DTCG format, with sets, themes, aliases, equations and JSON import/export.
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1 id="design-tokens">Design Tokens</h1>
|
<h1 id="design-tokens">Design Tokens</h1>
|
||||||
|
|||||||
@@ -195,7 +195,9 @@
|
|||||||
(ptk/reify ::login-from-token
|
(ptk/reify ::login-from-token
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rx/of (logged-in (with-meta profile {::ev/source "login-with-token"})))
|
(->> (dp/on-fetch-profile-success profile)
|
||||||
|
(rx/map (fn [profile]
|
||||||
|
(logged-in (with-meta profile {::ev/source "login-with-token"}))))
|
||||||
;; NOTE: we need this to be asynchronous because the effect
|
;; NOTE: we need this to be asynchronous because the effect
|
||||||
;; should be called before proceed with the login process
|
;; should be called before proceed with the login process
|
||||||
(rx/observe-on :async)))))
|
(rx/observe-on :async)))))
|
||||||
|
|||||||
@@ -77,22 +77,25 @@
|
|||||||
(rx/of (rt/nav-raw :href href)))
|
(rx/of (rt/nav-raw :href href)))
|
||||||
(rx/throw cause))))
|
(rx/throw cause))))
|
||||||
|
|
||||||
|
(defn on-fetch-profile-success
|
||||||
|
[profile]
|
||||||
|
(if (and (contains? cf/flags :subscriptions)
|
||||||
|
(is-authenticated? profile))
|
||||||
|
(->> (rp/cmd! :get-subscription-usage {})
|
||||||
|
(rx/map (fn [{:keys [editors]}]
|
||||||
|
(update-in profile [:props :subscription] assoc :editors editors)))
|
||||||
|
(rx/catch (fn [cause]
|
||||||
|
(js/console.error "unexpected error on obtaining subscription usage" cause)
|
||||||
|
(rx/of profile))))
|
||||||
|
(rx/of profile)))
|
||||||
|
|
||||||
(defn fetch-profile
|
(defn fetch-profile
|
||||||
[]
|
[]
|
||||||
(ptk/reify ::fetch-profile
|
(ptk/reify ::fetch-profile
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rp/cmd! :get-profile)
|
(->> (rp/cmd! :get-profile)
|
||||||
(rx/mapcat (fn [profile]
|
(rx/mapcat on-fetch-profile-success)
|
||||||
(if (and (contains? cf/flags :subscriptions)
|
|
||||||
(is-authenticated? profile))
|
|
||||||
(->> (rp/cmd! :get-subscription-usage {})
|
|
||||||
(rx/map (fn [{:keys [editors]}]
|
|
||||||
(update-in profile [:props :subscription] assoc :editors editors)))
|
|
||||||
(rx/catch (fn [cause]
|
|
||||||
(js/console.error "unexpected error on obtaining subscription usage" cause)
|
|
||||||
(rx/of profile))))
|
|
||||||
(rx/of profile))))
|
|
||||||
(rx/map (partial ptk/data-event ::profile-fetched))
|
(rx/map (partial ptk/data-event ::profile-fetched))
|
||||||
(rx/catch on-fetch-profile-exception)))))
|
(rx/catch on-fetch-profile-exception)))))
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,10 @@
|
|||||||
(declare ^:private paste-svg-text)
|
(declare ^:private paste-svg-text)
|
||||||
(declare ^:private paste-shapes)
|
(declare ^:private paste-shapes)
|
||||||
|
|
||||||
|
(def ^:private default-options
|
||||||
|
#js {:decodeTransit t/decode-str
|
||||||
|
:allowHTMLPaste (features/active-feature? @st/state "text-editor/v2-html-paste")})
|
||||||
|
|
||||||
(defn create-paste-from-blob
|
(defn create-paste-from-blob
|
||||||
[in-viewport?]
|
[in-viewport?]
|
||||||
(fn [blob]
|
(fn [blob]
|
||||||
@@ -290,7 +294,7 @@
|
|||||||
(ptk/reify ::paste-from-clipboard
|
(ptk/reify ::paste-from-clipboard
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (clipboard/from-navigator)
|
(->> (clipboard/from-navigator default-options)
|
||||||
(rx/mapcat default-paste-from-blob)
|
(rx/mapcat default-paste-from-blob)
|
||||||
(rx/take 1)))))
|
(rx/take 1)))))
|
||||||
|
|
||||||
@@ -308,7 +312,7 @@
|
|||||||
;; we forbid that scenario so the default behaviour is executed
|
;; we forbid that scenario so the default behaviour is executed
|
||||||
(if is-editing?
|
(if is-editing?
|
||||||
(rx/empty)
|
(rx/empty)
|
||||||
(->> (clipboard/from-synthetic-clipboard-event event)
|
(->> (clipboard/from-synthetic-clipboard-event event default-options)
|
||||||
(rx/mapcat (create-paste-from-blob in-viewport?))))))))
|
(rx/mapcat (create-paste-from-blob in-viewport?))))))))
|
||||||
|
|
||||||
(defn copy-selected-svg
|
(defn copy-selected-svg
|
||||||
@@ -478,7 +482,7 @@
|
|||||||
(js/console.error "Clipboard error:" cause))
|
(js/console.error "Clipboard error:" cause))
|
||||||
(rx/empty)))]
|
(rx/empty)))]
|
||||||
|
|
||||||
(->> (clipboard/from-navigator)
|
(->> (clipboard/from-navigator default-options)
|
||||||
(rx/mapcat #(.text %))
|
(rx/mapcat #(.text %))
|
||||||
(rx/map decode-entry)
|
(rx/map decode-entry)
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
|
|||||||
@@ -60,10 +60,10 @@
|
|||||||
(defn render-thumbnail
|
(defn render-thumbnail
|
||||||
[file-id revn]
|
[file-id revn]
|
||||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
(->> (mw/ask! {:cmd :thumbnails/generate-for-file-wasm
|
(mw/ask! {:cmd :thumbnails/generate-for-file-wasm
|
||||||
:revn revn
|
:revn revn
|
||||||
:file-id file-id
|
:file-id file-id
|
||||||
:width thumbnail-width}))
|
:width thumbnail-width})
|
||||||
(->> (mw/ask! {:cmd :thumbnails/generate-for-file
|
(->> (mw/ask! {:cmd :thumbnails/generate-for-file
|
||||||
:revn revn
|
:revn revn
|
||||||
:file-id file-id
|
:file-id file-id
|
||||||
|
|||||||
@@ -83,74 +83,101 @@
|
|||||||
::mf/register-as :management-dialog}
|
::mf/register-as :management-dialog}
|
||||||
[{:keys [subscription-type current-subscription editors subscribe-to-trial]}]
|
[{:keys [subscription-type current-subscription editors subscribe-to-trial]}]
|
||||||
|
|
||||||
(let [unlimited-modal-step* (mf/use-state 1)
|
(let [unlimited-modal-step*
|
||||||
unlimited-modal-step (deref unlimited-modal-step*)
|
(mf/use-state 1)
|
||||||
subscription-name (if subscribe-to-trial
|
|
||||||
(if (= subscription-type "unlimited")
|
|
||||||
(tr "subscription.settings.unlimited-trial")
|
|
||||||
(tr "subscription.settings.enterprise-trial"))
|
|
||||||
(case subscription-type
|
|
||||||
"professional" (tr "subscription.settings.professional")
|
|
||||||
"unlimited" (tr "subscription.settings.unlimited")
|
|
||||||
"enterprise" (tr "subscription.settings.enterprise")))
|
|
||||||
min-editors (if (seq editors) (count editors) 1)
|
|
||||||
initial (mf/with-memo [min-editors]
|
|
||||||
{:min-members min-editors})
|
|
||||||
form (fm/use-form :schema (schema:seats-form min-editors)
|
|
||||||
:initial initial)
|
|
||||||
submit-in-progress* (mf/use-state false)
|
|
||||||
subscribe-to-unlimited (mf/use-fn
|
|
||||||
(mf/deps form)
|
|
||||||
(fn [add-payment-details]
|
|
||||||
(when (not @submit-in-progress*)
|
|
||||||
(let [data (:clean-data @form)
|
|
||||||
return-url (-> (rt/get-current-href) (rt/encode-url))
|
|
||||||
href (if add-payment-details
|
|
||||||
(dm/str "payments/subscriptions/create?type=unlimited&show=true&quantity=" (:min-members data) "&returnUrl=" return-url)
|
|
||||||
(dm/str "payments/subscriptions/create?type=unlimited&show=false&quantity=" (:min-members data) "&returnUrl=" return-url))]
|
|
||||||
(reset! submit-in-progress* true)
|
|
||||||
(reset! form nil)
|
|
||||||
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
|
|
||||||
:type "unlimited"
|
|
||||||
:quantity (:min-members data)})
|
|
||||||
(rt/nav-raw :href href))))))
|
|
||||||
|
|
||||||
subscribe-to-enterprise (mf/use-fn
|
unlimited-modal-step
|
||||||
(fn []
|
(deref unlimited-modal-step*)
|
||||||
(st/emit! (ptk/event ::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)))))
|
|
||||||
|
|
||||||
handle-accept-dialog (mf/use-fn
|
subscription-name
|
||||||
(fn []
|
(if subscribe-to-trial
|
||||||
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management"
|
(if (= subscription-type "unlimited")
|
||||||
::ev/origin "settings"
|
(tr "subscription.settings.unlimited-trial")
|
||||||
:section "subscription-management-modal"}))
|
(tr "subscription.settings.enterprise-trial"))
|
||||||
(let [current-href (rt/get-current-href)
|
(case subscription-type
|
||||||
returnUrl (js/encodeURIComponent current-href)
|
"professional" (tr "subscription.settings.professional")
|
||||||
href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)]
|
"unlimited" (tr "subscription.settings.unlimited")
|
||||||
(st/emit! (rt/nav-raw :href href)))
|
"enterprise" (tr "subscription.settings.enterprise")))
|
||||||
(modal/hide!)))
|
|
||||||
handle-close-dialog (mf/use-fn
|
|
||||||
(fn []
|
|
||||||
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
|
|
||||||
(modal/hide!)))
|
|
||||||
|
|
||||||
handle-unlimited-modal-step (mf/use-fn
|
min-editors
|
||||||
(mf/deps unlimited-modal-step)
|
(if (seq editors) (count editors) 1)
|
||||||
(fn []
|
|
||||||
(if (= unlimited-modal-step 1)
|
|
||||||
(reset! unlimited-modal-step* 2)
|
|
||||||
(reset! unlimited-modal-step* 1))))
|
|
||||||
|
|
||||||
show-editors-list* (mf/use-state false)
|
initial
|
||||||
show-editors-list (deref show-editors-list*)
|
(mf/with-memo [min-editors]
|
||||||
handle-click (mf/use-fn
|
{:min-members min-editors})
|
||||||
(fn [event]
|
|
||||||
(dom/stop-propagation event)
|
form
|
||||||
(swap! show-editors-list* not)))]
|
(fm/use-form :schema (schema:seats-form min-editors)
|
||||||
|
:initial initial)
|
||||||
|
|
||||||
|
form-data-min-editors
|
||||||
|
(-> @form :clean-data :min-members)
|
||||||
|
|
||||||
|
submit-in-progress*
|
||||||
|
(mf/use-state false)
|
||||||
|
|
||||||
|
subscribe-to-unlimited
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps form-data-min-editors)
|
||||||
|
(fn [add-payment-details]
|
||||||
|
(when (not @submit-in-progress*)
|
||||||
|
(let [return-url (-> (rt/get-current-href) (rt/encode-url))
|
||||||
|
href (if add-payment-details
|
||||||
|
(dm/str "payments/subscriptions/create?type=unlimited&show=true&quantity=" form-data-min-editors "&returnUrl=" return-url)
|
||||||
|
(dm/str "payments/subscriptions/create?type=unlimited&show=false&quantity=" form-data-min-editors "&returnUrl=" return-url))]
|
||||||
|
(reset! submit-in-progress* true)
|
||||||
|
(reset! form nil)
|
||||||
|
(st/emit! (ptk/event ::ev/event {::ev/name "create-trial-subscription"
|
||||||
|
:type "unlimited"
|
||||||
|
:quantity form-data-min-editors})
|
||||||
|
(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"}))
|
||||||
|
(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)))))
|
||||||
|
|
||||||
|
handle-accept-dialog
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management"
|
||||||
|
::ev/origin "settings"
|
||||||
|
:section "subscription-management-modal"}))
|
||||||
|
(let [current-href (rt/get-current-href)
|
||||||
|
returnUrl (js/encodeURIComponent current-href)
|
||||||
|
href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)]
|
||||||
|
(st/emit! (rt/nav-raw :href href)))
|
||||||
|
(modal/hide!)))
|
||||||
|
|
||||||
|
handle-close-dialog
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
|
||||||
|
(modal/hide!)))
|
||||||
|
|
||||||
|
handle-unlimited-modal-step
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps unlimited-modal-step)
|
||||||
|
(fn []
|
||||||
|
(if (= unlimited-modal-step 1)
|
||||||
|
(reset! unlimited-modal-step* 2)
|
||||||
|
(reset! unlimited-modal-step* 1))))
|
||||||
|
|
||||||
|
show-editors-list*
|
||||||
|
(mf/use-state false)
|
||||||
|
|
||||||
|
show-editors-list
|
||||||
|
(deref show-editors-list*)
|
||||||
|
|
||||||
|
handle-click
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(swap! show-editors-list* not)))]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-overlay)}
|
[:div {:class (stl/css :modal-overlay)}
|
||||||
[:div {:class (stl/css :modal-dialog)}
|
[:div {:class (stl/css :modal-dialog)}
|
||||||
@@ -246,7 +273,7 @@
|
|||||||
[:input
|
[:input
|
||||||
{:class (stl/css :cancel-button)
|
{:class (stl/css :cancel-button)
|
||||||
:type "button"
|
:type "button"
|
||||||
:value (tr "ubscription.settings.management-dialog.step-2-skip-button")
|
:value (tr "subscription.settings.management-dialog.step-2-skip-button")
|
||||||
:on-click #(subscribe-to-unlimited false)}]
|
:on-click #(subscribe-to-unlimited false)}]
|
||||||
|
|
||||||
[:input
|
[:input
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
@use "ds/typography.scss" as t;
|
@use "ds/typography.scss" as t;
|
||||||
@use "ds/_borders.scss" as *;
|
@use "ds/_borders.scss" as *;
|
||||||
@use "ds/spacing.scss" as *;
|
@use "ds/spacing.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
@use "ds/_utils.scss" as *;
|
||||||
|
|
||||||
.dashboard-section {
|
.dashboard-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
inline-size: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -20,10 +22,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-width: deprecated.$s-500;
|
max-inline-size: $sz-500;
|
||||||
margin-block-end: var(--sp-xxxl);
|
margin-block-end: var(--sp-xxxl);
|
||||||
width: deprecated.$s-580;
|
inline-size: px2rem(580);
|
||||||
margin: deprecated.$s-92 auto deprecated.$s-120 auto;
|
margin: px2rem(92) auto px2rem(120) auto;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,8 +100,8 @@
|
|||||||
.plan-title-icon {
|
.plan-title-icon {
|
||||||
@extend .button-icon;
|
@extend .button-icon;
|
||||||
stroke: var(--color-foreground-primary);
|
stroke: var(--color-foreground-primary);
|
||||||
height: var(--sp-xl);
|
block-size: var(--sp-xl);
|
||||||
width: var(--sp-xl);
|
inline-size: var(--sp-xl);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1.75px solid var(--color-foreground-primary);
|
border: 1.75px solid var(--color-foreground-primary);
|
||||||
stroke-width: 2.25px;
|
stroke-width: 2.25px;
|
||||||
@@ -140,7 +142,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.other-subscriptions {
|
.other-subscriptions {
|
||||||
margin-block-start: deprecated.$s-52;
|
margin-block-start: px2rem(52);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button {
|
.cta-button {
|
||||||
@@ -155,8 +157,8 @@
|
|||||||
|
|
||||||
.cta-button svg {
|
.cta-button svg {
|
||||||
@extend .button-icon;
|
@extend .button-icon;
|
||||||
height: var(--sp-l);
|
block-size: var(--sp-l);
|
||||||
width: var(--sp-l);
|
inline-size: var(--sp-l);
|
||||||
stroke: var(--color-accent-primary);
|
stroke: var(--color-accent-primary);
|
||||||
margin-inline-start: var(--sp-xs);
|
margin-inline-start: var(--sp-xs);
|
||||||
}
|
}
|
||||||
@@ -179,14 +181,12 @@
|
|||||||
|
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
@extend .modal-container-base;
|
@extend .modal-container-base;
|
||||||
display: grid;
|
max-block-size: initial;
|
||||||
grid-template-rows: auto 1fr auto;
|
min-inline-size: px2rem(548);
|
||||||
max-height: initial;
|
|
||||||
min-width: deprecated.$s-548;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog.subscription-success {
|
.modal-dialog.subscription-success {
|
||||||
min-width: deprecated.$s-648;
|
min-inline-size: px2rem(648);
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
@@ -232,11 +232,11 @@
|
|||||||
|
|
||||||
.modal-success-content {
|
.modal-success-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: deprecated.$s-40;
|
gap: $sz-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
margin-block-start: deprecated.$s-40;
|
margin-block-start: $sz-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
@@ -249,23 +249,28 @@
|
|||||||
|
|
||||||
.primary-button {
|
.primary-button {
|
||||||
@extend .modal-accept-btn;
|
@extend .modal-accept-btn;
|
||||||
|
min-block-size: $sz-32;
|
||||||
|
block-size: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-button {
|
.cancel-button {
|
||||||
@extend .modal-cancel-btn;
|
@extend .modal-cancel-btn;
|
||||||
|
min-block-size: $sz-32;
|
||||||
|
white-space: break-spaces;
|
||||||
|
block-size: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-start {
|
.modal-start {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width: deprecated.$s-220;
|
max-inline-size: $sz-224;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 100%;
|
inline-size: 100%;
|
||||||
height: auto;
|
block-size: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
@media (max-inline-size: 992px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,19 +290,19 @@
|
|||||||
list-style-position: inside;
|
list-style-position: inside;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin-inline-start: var(--sp-xl);
|
margin-inline-start: var(--sp-xl);
|
||||||
max-height: deprecated.$s-216;
|
max-block-size: px2rem(216);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
--input-icon-padding: var(--sp-s);
|
--input-icon-padding: var(--sp-s);
|
||||||
width: deprecated.$s-80;
|
inline-size: px2rem(80);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
@include t.use-typography("body-small");
|
@include t.use-typography("body-small");
|
||||||
color: var(--color-foreground-error);
|
color: var(--color-foreground-error);
|
||||||
margin-block-start: deprecated.$s-8;
|
margin-block-start: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.editors-wrapper {
|
.editors-wrapper {
|
||||||
@@ -316,7 +321,7 @@
|
|||||||
@include t.use-typography("body-small");
|
@include t.use-typography("body-small");
|
||||||
background-color: var(--color-background-tertiary);
|
background-color: var(--color-background-tertiary);
|
||||||
border-radius: var(--sp-s);
|
border-radius: var(--sp-s);
|
||||||
margin-block-start: deprecated.$s-40;
|
margin-block-start: $sz-40;
|
||||||
padding-block: var(--sp-s);
|
padding-block: var(--sp-s);
|
||||||
padding-inline: var(--sp-m);
|
padding-inline: var(--sp-m);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,11 +128,12 @@
|
|||||||
|
|
||||||
(defn update-text-rect!
|
(defn update-text-rect!
|
||||||
[id]
|
[id]
|
||||||
(mw/emit!
|
(when wasm/context-initialized?
|
||||||
{:cmd :index/update-text-rect
|
(mw/emit!
|
||||||
:page-id (:current-page-id @st/state)
|
{:cmd :index/update-text-rect
|
||||||
:shape-id id
|
:page-id (:current-page-id @st/state)
|
||||||
:dimensions (get-text-dimensions id)}))
|
:shape-id id
|
||||||
|
:dimensions (get-text-dimensions id)})))
|
||||||
|
|
||||||
|
|
||||||
(defn- ensure-text-content
|
(defn- ensure-text-content
|
||||||
@@ -198,70 +199,71 @@
|
|||||||
(defn set-shape-children
|
(defn set-shape-children
|
||||||
[children]
|
[children]
|
||||||
(perf/begin-measure "set-shape-children")
|
(perf/begin-measure "set-shape-children")
|
||||||
(case (count children)
|
(let [children (into [] (filter uuid?) children)]
|
||||||
0
|
(case (count children)
|
||||||
(h/call wasm/internal-module "_set_children_0")
|
0
|
||||||
|
(h/call wasm/internal-module "_set_children_0")
|
||||||
|
|
||||||
1
|
1
|
||||||
(let [[c1] children
|
(let [[c1] children
|
||||||
c1 (uuid/get-u32 c1)]
|
c1 (uuid/get-u32 c1)]
|
||||||
(h/call wasm/internal-module "_set_children_1"
|
(h/call wasm/internal-module "_set_children_1"
|
||||||
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)))
|
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)))
|
||||||
|
|
||||||
2
|
2
|
||||||
(let [[c1 c2] children
|
(let [[c1 c2] children
|
||||||
c1 (uuid/get-u32 c1)
|
c1 (uuid/get-u32 c1)
|
||||||
c2 (uuid/get-u32 c2)]
|
c2 (uuid/get-u32 c2)]
|
||||||
(h/call wasm/internal-module "_set_children_2"
|
(h/call wasm/internal-module "_set_children_2"
|
||||||
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
||||||
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)))
|
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)))
|
||||||
|
|
||||||
3
|
3
|
||||||
(let [[c1 c2 c3] children
|
(let [[c1 c2 c3] children
|
||||||
c1 (uuid/get-u32 c1)
|
c1 (uuid/get-u32 c1)
|
||||||
c2 (uuid/get-u32 c2)
|
c2 (uuid/get-u32 c2)
|
||||||
c3 (uuid/get-u32 c3)]
|
c3 (uuid/get-u32 c3)]
|
||||||
(h/call wasm/internal-module "_set_children_3"
|
(h/call wasm/internal-module "_set_children_3"
|
||||||
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
||||||
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)
|
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)
|
||||||
(aget c3 0) (aget c3 1) (aget c3 2) (aget c3 3)))
|
(aget c3 0) (aget c3 1) (aget c3 2) (aget c3 3)))
|
||||||
|
|
||||||
4
|
4
|
||||||
(let [[c1 c2 c3 c4] children
|
(let [[c1 c2 c3 c4] children
|
||||||
c1 (uuid/get-u32 c1)
|
c1 (uuid/get-u32 c1)
|
||||||
c2 (uuid/get-u32 c2)
|
c2 (uuid/get-u32 c2)
|
||||||
c3 (uuid/get-u32 c3)
|
c3 (uuid/get-u32 c3)
|
||||||
c4 (uuid/get-u32 c4)]
|
c4 (uuid/get-u32 c4)]
|
||||||
(h/call wasm/internal-module "_set_children_4"
|
(h/call wasm/internal-module "_set_children_4"
|
||||||
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
||||||
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)
|
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)
|
||||||
(aget c3 0) (aget c3 1) (aget c3 2) (aget c3 3)
|
(aget c3 0) (aget c3 1) (aget c3 2) (aget c3 3)
|
||||||
(aget c4 0) (aget c4 1) (aget c4 2) (aget c4 3)))
|
(aget c4 0) (aget c4 1) (aget c4 2) (aget c4 3)))
|
||||||
|
|
||||||
5
|
5
|
||||||
(let [[c1 c2 c3 c4 c5] children
|
(let [[c1 c2 c3 c4 c5] children
|
||||||
c1 (uuid/get-u32 c1)
|
c1 (uuid/get-u32 c1)
|
||||||
c2 (uuid/get-u32 c2)
|
c2 (uuid/get-u32 c2)
|
||||||
c3 (uuid/get-u32 c3)
|
c3 (uuid/get-u32 c3)
|
||||||
c4 (uuid/get-u32 c4)
|
c4 (uuid/get-u32 c4)
|
||||||
c5 (uuid/get-u32 c5)]
|
c5 (uuid/get-u32 c5)]
|
||||||
(h/call wasm/internal-module "_set_children_5"
|
(h/call wasm/internal-module "_set_children_5"
|
||||||
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
(aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3)
|
||||||
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)
|
(aget c2 0) (aget c2 1) (aget c2 2) (aget c2 3)
|
||||||
(aget c3 0) (aget c3 1) (aget c3 2) (aget c3 3)
|
(aget c3 0) (aget c3 1) (aget c3 2) (aget c3 3)
|
||||||
(aget c4 0) (aget c4 1) (aget c4 2) (aget c4 3)
|
(aget c4 0) (aget c4 1) (aget c4 2) (aget c4 3)
|
||||||
(aget c5 0) (aget c5 1) (aget c5 2) (aget c5 3)))
|
(aget c5 0) (aget c5 1) (aget c5 2) (aget c5 3)))
|
||||||
|
|
||||||
;; Dynamic call for children > 5
|
;; Dynamic call for children > 5
|
||||||
(let [heap (mem/get-heap-u32)
|
(let [heap (mem/get-heap-u32)
|
||||||
size (mem/get-alloc-size children UUID-U8-SIZE)
|
size (mem/get-alloc-size children UUID-U8-SIZE)
|
||||||
offset (mem/alloc->offset-32 size)]
|
offset (mem/alloc->offset-32 size)]
|
||||||
(reduce
|
(reduce
|
||||||
(fn [offset id]
|
(fn [offset id]
|
||||||
(mem.h32/write-uuid offset heap id))
|
(mem.h32/write-uuid offset heap id))
|
||||||
offset
|
offset
|
||||||
children)
|
children)
|
||||||
(h/call wasm/internal-module "_set_children")))
|
(h/call wasm/internal-module "_set_children"))))
|
||||||
(perf/end-measure "set-shape-children")
|
(perf/end-measure "set-shape-children")
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
@@ -468,14 +470,14 @@
|
|||||||
[attrs]
|
[attrs]
|
||||||
(let [style (:style attrs)
|
(let [style (:style attrs)
|
||||||
;; Filter to only supported attributes
|
;; Filter to only supported attributes
|
||||||
allowed-keys #{:fill :fillRule :strokeLinecap :strokeLinejoin}
|
allowed-keys #{:fill :fillRule :fill-rule :strokeLinecap :stroke-linecap :strokeLinejoin :stroke-linejoin}
|
||||||
attrs (-> attrs
|
attrs (-> attrs
|
||||||
(dissoc :style)
|
(dissoc :style)
|
||||||
(merge style)
|
(merge style)
|
||||||
(select-keys allowed-keys))
|
(select-keys allowed-keys))
|
||||||
fill-rule (-> attrs :fillRule sr/translate-fill-rule)
|
fill-rule (or (-> attrs :fill-rule sr/translate-fill-rule) (-> attrs :fillRule sr/translate-fill-rule))
|
||||||
stroke-linecap (-> attrs :strokeLinecap sr/translate-stroke-linecap)
|
stroke-linecap (or (-> attrs :stroke-linecap sr/translate-stroke-linecap) (-> attrs :strokeLinecap sr/translate-stroke-linecap))
|
||||||
stroke-linejoin (-> attrs :strokeLinejoin sr/translate-stroke-linejoin)
|
stroke-linejoin (or (-> attrs :stroke-linejoin sr/translate-stroke-linejoin) (-> attrs :strokeLinejoin sr/translate-stroke-linejoin))
|
||||||
fill-none (= "none" (-> attrs :fill))]
|
fill-none (= "none" (-> attrs :fill))]
|
||||||
(h/call wasm/internal-module "_set_shape_svg_attrs" fill-rule stroke-linecap stroke-linejoin fill-none)))
|
(h/call wasm/internal-module "_set_shape_svg_attrs" fill-rule stroke-linecap stroke-linejoin fill-none)))
|
||||||
|
|
||||||
@@ -739,7 +741,7 @@
|
|||||||
|
|
||||||
(d/nilv align-self 0)
|
(d/nilv align-self 0)
|
||||||
is-absolute
|
is-absolute
|
||||||
(d/nilv z-index))))
|
(d/nilv z-index 0))))
|
||||||
|
|
||||||
(defn clear-layout
|
(defn clear-layout
|
||||||
[]
|
[]
|
||||||
@@ -1031,8 +1033,9 @@
|
|||||||
(into full-acc full)))
|
(into full-acc full)))
|
||||||
{:thumbnails thumbnails-acc :full full-acc}))]
|
{:thumbnails thumbnails-acc :full full-acc}))]
|
||||||
(perf/end-measure "set-objects")
|
(perf/end-measure "set-objects")
|
||||||
(process-pending shapes thumbnails full render-callback
|
(process-pending shapes thumbnails full noop-fn
|
||||||
(fn []
|
(fn []
|
||||||
|
(when render-callback (render-callback))
|
||||||
(ug/dispatch! (ug/event "penpot:wasm:set-objects")))))))
|
(ug/dispatch! (ug/event "penpot:wasm:set-objects")))))))
|
||||||
|
|
||||||
(defn clear-focus-mode
|
(defn clear-focus-mode
|
||||||
|
|||||||
@@ -83,12 +83,13 @@
|
|||||||
|
|
||||||
(defn update-text-layout
|
(defn update-text-layout
|
||||||
[id]
|
[id]
|
||||||
(let [shape-id-buffer (uuid/get-u32 id)]
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_update_shape_text_layout_for"
|
(let [shape-id-buffer (uuid/get-u32 id)]
|
||||||
(aget shape-id-buffer 0)
|
(h/call wasm/internal-module "_update_shape_text_layout_for"
|
||||||
(aget shape-id-buffer 1)
|
(aget shape-id-buffer 0)
|
||||||
(aget shape-id-buffer 2)
|
(aget shape-id-buffer 1)
|
||||||
(aget shape-id-buffer 3))))
|
(aget shape-id-buffer 2)
|
||||||
|
(aget shape-id-buffer 3)))))
|
||||||
|
|
||||||
;; IMPORTANT: Only TTF fonts can be stored.
|
;; IMPORTANT: Only TTF fonts can be stored.
|
||||||
(defn- store-font-buffer
|
(defn- store-font-buffer
|
||||||
|
|||||||
@@ -18,47 +18,56 @@
|
|||||||
"image/svg+xml"])
|
"image/svg+xml"])
|
||||||
|
|
||||||
(def ^:private default-options
|
(def ^:private default-options
|
||||||
#js {:decodeTransit t/decode-str})
|
#js {:decodeTransit t/decode-str
|
||||||
|
:allowHTMLPaste false})
|
||||||
|
|
||||||
(defn- from-data-transfer
|
(defn- from-data-transfer
|
||||||
"Get clipboard stream from DataTransfer instance"
|
"Get clipboard stream from DataTransfer instance"
|
||||||
[data-transfer]
|
([data-transfer]
|
||||||
(->> (rx/from (impl/fromDataTransfer data-transfer default-options))
|
(from-data-transfer data-transfer default-options))
|
||||||
(rx/mapcat #(rx/from %))))
|
([data-transfer options]
|
||||||
|
(->> (rx/from (impl/fromDataTransfer data-transfer options))
|
||||||
|
(rx/mapcat #(rx/from %)))))
|
||||||
|
|
||||||
(defn from-navigator
|
(defn from-navigator
|
||||||
[]
|
([]
|
||||||
(->> (rx/from (impl/fromNavigator default-options))
|
(from-navigator default-options))
|
||||||
(rx/mapcat #(rx/from %))))
|
([options]
|
||||||
|
(->> (rx/from (impl/fromNavigator options))
|
||||||
|
(rx/mapcat #(rx/from %)))))
|
||||||
|
|
||||||
(defn from-clipboard-event
|
(defn from-clipboard-event
|
||||||
"Get clipboard stream from clipboard event"
|
"Get clipboard stream from clipboard event"
|
||||||
[event]
|
([event]
|
||||||
(let [cdata (.-clipboardData ^js event)]
|
(from-clipboard-event event default-options))
|
||||||
(from-data-transfer cdata)))
|
([event options]
|
||||||
|
(let [cdata (.-clipboardData ^js event)]
|
||||||
|
(from-data-transfer cdata options))))
|
||||||
|
|
||||||
(defn from-synthetic-clipboard-event
|
(defn from-synthetic-clipboard-event
|
||||||
"Get clipboard stream from syntetic clipboard event"
|
"Get clipboard stream from syntetic clipboard event"
|
||||||
[event]
|
([event options]
|
||||||
(let [target
|
(let [target
|
||||||
(dom/get-target event)
|
(dom/get-target event)
|
||||||
|
|
||||||
content-editable?
|
content-editable?
|
||||||
(dom/is-content-editable? target)
|
(dom/is-content-editable? target)
|
||||||
|
|
||||||
is-input?
|
is-input?
|
||||||
(= (dom/get-tag-name target) "INPUT")]
|
(= (dom/get-tag-name target) "INPUT")]
|
||||||
|
|
||||||
;; ignore when pasting into an editable control
|
;; ignore when pasting into an editable control
|
||||||
(when-not (or content-editable? is-input?)
|
(when-not (or content-editable? is-input?)
|
||||||
(-> event
|
(-> event
|
||||||
(dom/event->browser-event)
|
(dom/event->browser-event)
|
||||||
(from-clipboard-event)))))
|
(from-clipboard-event options))))))
|
||||||
|
|
||||||
(defn from-drop-event
|
(defn from-drop-event
|
||||||
"Get clipboard stream from drop event"
|
"Get clipboard stream from drop event"
|
||||||
[event]
|
([event]
|
||||||
(from-data-transfer (.-dataTransfer ^js event)))
|
(from-drop-event event default-options))
|
||||||
|
([event options]
|
||||||
|
(from-data-transfer (.-dataTransfer ^js event) options)))
|
||||||
|
|
||||||
;; FIXME: rename to `write-text`
|
;; FIXME: rename to `write-text`
|
||||||
(defn to-clipboard
|
(defn to-clipboard
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const exclusiveTypes = [
|
|||||||
/**
|
/**
|
||||||
* @typedef {Object} ClipboardSettings
|
* @typedef {Object} ClipboardSettings
|
||||||
* @property {Function} [decodeTransit]
|
* @property {Function} [decodeTransit]
|
||||||
|
* @property {boolean} [allowHTMLPaste]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,9 +39,7 @@ const exclusiveTypes = [
|
|||||||
*/
|
*/
|
||||||
function parseText(text, options) {
|
function parseText(text, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
const decodeTransit = options["decodeTransit"];
|
const decodeTransit = options["decodeTransit"];
|
||||||
|
|
||||||
if (decodeTransit) {
|
if (decodeTransit) {
|
||||||
try {
|
try {
|
||||||
decodeTransit(text);
|
decodeTransit(text);
|
||||||
@@ -57,18 +56,85 @@ function parseText(text, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters ClipboardItem types
|
||||||
|
*
|
||||||
|
* @param {ClipboardSettings} options
|
||||||
|
* @returns {Function<AllowedTypesFilterFunction>}
|
||||||
|
*/
|
||||||
|
function filterAllowedTypes(options) {
|
||||||
|
/**
|
||||||
|
* @param {string} type
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
return function filter(type) {
|
||||||
|
if (
|
||||||
|
(!("allowHTMLPaste" in options) || !options["allowHTMLPaste"]) &&
|
||||||
|
type === "text/html"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return allowedTypes.includes(type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters DataTransferItems
|
||||||
|
*
|
||||||
|
* @param {ClipboardSettings} options
|
||||||
|
* @returns {Function<AllowedTypesFilterFunction>}
|
||||||
|
*/
|
||||||
|
function filterAllowedItems(options) {
|
||||||
|
/**
|
||||||
|
* @param {DataTransferItem}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
return function filter(item) {
|
||||||
|
if (
|
||||||
|
(!("allowHTMLPaste" in options) || !options["allowHTMLPaste"]) &&
|
||||||
|
item.type === "text/html"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return allowedTypes.includes(item.type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts ClipboardItem types
|
||||||
|
*
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function sortTypes(a, b) {
|
||||||
|
return allowedTypes.indexOf(a) - allowedTypes.indexOf(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts DataTransferItems
|
||||||
|
*
|
||||||
|
* @param {DataTransferItem} a
|
||||||
|
* @param {DataTransferItem} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function sortItems(a, b) {
|
||||||
|
return allowedTypes.indexOf(a.type) - allowedTypes.indexOf(b.type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {ClipboardSettings} [options]
|
* @param {ClipboardSettings} [options]
|
||||||
* @returns {Promise<Array<Blob>>}
|
* @returns {Promise<Array<Blob>>}
|
||||||
*/
|
*/
|
||||||
export async function fromNavigator(options) {
|
export async function fromNavigator(options) {
|
||||||
|
options = options || {};
|
||||||
const items = await navigator.clipboard.read();
|
const items = await navigator.clipboard.read();
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Array.from(items).map(async (item) => {
|
Array.from(items).map(async (item) => {
|
||||||
const itemAllowedTypes = Array.from(item.types)
|
const itemAllowedTypes = Array.from(item.types)
|
||||||
.filter((type) => allowedTypes.includes(type))
|
.filter(filterAllowedTypes(options))
|
||||||
.sort((a, b) => allowedTypes.indexOf(a) - allowedTypes.indexOf(b));
|
.sort(sortTypes);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
itemAllowedTypes.length === 1 &&
|
itemAllowedTypes.length === 1 &&
|
||||||
@@ -96,12 +162,11 @@ export async function fromNavigator(options) {
|
|||||||
* @returns {Promise<Array<Blob>>}
|
* @returns {Promise<Array<Blob>>}
|
||||||
*/
|
*/
|
||||||
export async function fromDataTransfer(dataTransfer, options) {
|
export async function fromDataTransfer(dataTransfer, options) {
|
||||||
|
options = options || {};
|
||||||
const items = await Promise.all(
|
const items = await Promise.all(
|
||||||
Array.from(dataTransfer.items)
|
Array.from(dataTransfer.items)
|
||||||
.filter((item) => allowedTypes.includes(item.type))
|
.filter(filterAllowedItems(options))
|
||||||
.sort(
|
.sort(sortItems)
|
||||||
(a, b) => allowedTypes.indexOf(a.type) - allowedTypes.indexOf(b.type),
|
|
||||||
)
|
|
||||||
.map(async (item) => {
|
.map(async (item) => {
|
||||||
if (item.kind === "file") {
|
if (item.kind === "file") {
|
||||||
return Promise.resolve(item.getAsFile());
|
return Promise.resolve(item.getAsFile());
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.types.color :as cc]
|
[app.common.types.color :as cc]
|
||||||
[app.common.uri :as u]
|
[app.common.uri :as u]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.render :as render]
|
[app.main.render :as render]
|
||||||
@@ -125,58 +126,75 @@
|
|||||||
|
|
||||||
(def thumbnail-aspect-ratio (/ 2 3))
|
(def thumbnail-aspect-ratio (/ 2 3))
|
||||||
|
|
||||||
(defmethod impl/handler :thumbnails/generate-for-file-wasm
|
(defn render-canvas-blob
|
||||||
[{:keys [file-id revn width] :as message} _]
|
[canvas width height background-color]
|
||||||
|
(-> (.convertToBlob canvas)
|
||||||
|
(p/then
|
||||||
|
(fn [blob]
|
||||||
|
(rds/renderToStaticMarkup
|
||||||
|
(mf/element
|
||||||
|
svg-wrapper
|
||||||
|
#js {:data-uri (blob->uri blob)
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:background background-color}))))))
|
||||||
|
|
||||||
|
(defn process-wasm-thumbnail
|
||||||
|
[{:keys [id file-id revn width] :as message}]
|
||||||
(->> (rx/from @init-wasm)
|
(->> (rx/from @init-wasm)
|
||||||
(rx/mapcat #(request-data-for-thumbnail file-id revn false))
|
(rx/mapcat #(request-data-for-thumbnail file-id revn false))
|
||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(fn [{:keys [page] :as file}]
|
(fn [{:keys [page] :as file}]
|
||||||
(rx/create
|
(rx/create
|
||||||
(fn [subs]
|
(fn [subs]
|
||||||
(try
|
(let [background-color (or (:background page) cc/canvas)
|
||||||
(let [background-color (or (:background page) cc/canvas)
|
height (* width thumbnail-aspect-ratio)
|
||||||
height (* width thumbnail-aspect-ratio)
|
canvas (js/OffscreenCanvas. width height)
|
||||||
canvas (js/OffscreenCanvas. width height)
|
init? (wasm.api/init-canvas-context canvas)]
|
||||||
init? (wasm.api/init-canvas-context canvas)]
|
(if init?
|
||||||
(if init?
|
(let [objects (:objects page)
|
||||||
(let [objects (:objects page)
|
frame (some->> page :thumbnail-frame-id (get objects))
|
||||||
frame (some->> page :thumbnail-frame-id (get objects))
|
vbox (if frame
|
||||||
vbox (if frame
|
(-> (gsb/get-object-bounds objects frame)
|
||||||
(-> (gsb/get-object-bounds objects frame)
|
(grc/fix-aspect-ratio thumbnail-aspect-ratio))
|
||||||
(grc/fix-aspect-ratio thumbnail-aspect-ratio))
|
(render/calculate-dimensions objects thumbnail-aspect-ratio))
|
||||||
(render/calculate-dimensions objects thumbnail-aspect-ratio))
|
zoom (/ width (:width vbox))]
|
||||||
zoom (/ width (:width vbox))]
|
|
||||||
|
|
||||||
(wasm.api/initialize-viewport
|
(wasm.api/initialize-viewport
|
||||||
objects zoom vbox background-color
|
objects zoom vbox background-color
|
||||||
(fn []
|
(fn []
|
||||||
(if frame
|
(if frame
|
||||||
(wasm.api/render-sync-shape (:id frame))
|
(wasm.api/render-sync-shape (:id frame))
|
||||||
(wasm.api/render-sync))
|
(wasm.api/render-sync))
|
||||||
|
|
||||||
(-> (.convertToBlob canvas)
|
(-> (render-canvas-blob canvas width height background-color)
|
||||||
(p/then
|
(p/then #(rx/push! subs {:id id :data % :file-id file-id :revn revn}))
|
||||||
(fn [blob]
|
(p/catch #(rx/error! subs %))
|
||||||
(let [data
|
(p/finally #(rx/end! subs))))))
|
||||||
(rds/renderToStaticMarkup
|
|
||||||
(mf/element
|
|
||||||
svg-wrapper
|
|
||||||
#js {:data-uri (blob->uri blob)
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:background background-color}))]
|
|
||||||
(rx/push! subs {:data data :file-id file-id :revn revn}))))
|
|
||||||
(p/catch #(do (.error js/console %)
|
|
||||||
(rx/error! subs %)))
|
|
||||||
(p/finally #(rx/end! subs))))))
|
|
||||||
|
|
||||||
(do (rx/error! subs "Error loading webgl context")
|
(rx/end! subs))
|
||||||
(rx/end! subs)))
|
|
||||||
|
|
||||||
nil)
|
nil)))))))
|
||||||
|
|
||||||
(catch :default err
|
(defonce thumbs-subject (rx/subject))
|
||||||
(.error js/console err)
|
|
||||||
(rx/error! subs err)
|
(defonce thumbs-stream
|
||||||
(rx/end! subs)))))))))
|
(->> thumbs-subject
|
||||||
|
(rx/mapcat process-wasm-thumbnail)
|
||||||
|
(rx/share)))
|
||||||
|
|
||||||
|
(defmethod impl/handler :thumbnails/generate-for-file-wasm
|
||||||
|
[message _]
|
||||||
|
(rx/create
|
||||||
|
(fn [subs]
|
||||||
|
(let [id (uuid/next)
|
||||||
|
sid
|
||||||
|
(->> thumbs-stream
|
||||||
|
(rx/filter #(= id (:id %)))
|
||||||
|
(rx/subs!
|
||||||
|
#(do
|
||||||
|
(rx/push! subs %)
|
||||||
|
(rx/end! subs))))]
|
||||||
|
(rx/push! thumbs-subject (assoc message :id id))
|
||||||
|
|
||||||
|
#(rx/dispose! sid)))))
|
||||||
|
|||||||
@@ -1641,9 +1641,9 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
children_ids.sort_by(|id1, id2| {
|
children_ids.sort_by(|id1, id2| {
|
||||||
let z1 = tree.get(id1).map_or_else(|| 0, |s| s.z_index());
|
let z1 = tree.get(id1).map(|s| s.z_index()).unwrap_or(0);
|
||||||
let z2 = tree.get(id2).map_or_else(|| 0, |s| s.z_index());
|
let z2 = tree.get(id2).map(|s| s.z_index()).unwrap_or(0);
|
||||||
z1.cmp(&z2)
|
z2.cmp(&z1)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ fn propagate_reflow(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Other shapes don't have to be reflown
|
// Other shapes don't have to be reflown
|
||||||
|
reflow_parent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -291,9 +291,10 @@ pub extern "C" fn set_shape_text_content() {
|
|||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
let raw_text_data = RawParagraph::try_from(&bytes).unwrap();
|
let raw_text_data = RawParagraph::try_from(&bytes).unwrap();
|
||||||
shape
|
|
||||||
.add_paragraph(raw_text_data.into())
|
if let Err(_) = shape.add_paragraph(raw_text_data.into()) {
|
||||||
.expect("Failed to add paragraph");
|
println!("Error with set_shape_text_content on {:?}", shape.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mem::free_bytes();
|
mem::free_bytes();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user