Compare commits

...

48 Commits

Author SHA1 Message Date
Andrés Moya
adc6af129c wip 2023-06-22 11:32:31 +02:00
Pablo Alba
0ae4988908 🐛 Fix Internal server error occurred after clicking on '3 dots' menu of copy component on Design tab 2023-06-22 10:08:26 +02:00
Alejandro Alonso
a53176489a 🐛 Fix extra line framing dashboard cards 2023-06-22 09:27:46 +02:00
Andrés Moya
d8121364ad 🐛 Fix touched on adding shapes to a component copy and undo 2023-06-22 09:27:27 +02:00
Alejandro Alonso
d4fe810813 🐛 Fix shared link broken 2023-06-22 08:01:15 +02:00
Alejandro Alonso
64ddfa0c31 📎 Update CHANGES.md file 2023-06-21 17:06:29 +02:00
Andrés Moya
e8dde477a5 🐛 Fix restore remote component 2023-06-21 17:04:46 +02:00
Alejandro Alonso
1b0848389c 📎 Update CHANGES.md file 2023-06-21 17:03:42 +02:00
Pablo Alba
4f02cc3e86 Merge pull request #3331 from penpot/hiru-restore-comp-missing-lib
🐛 Disallow restore component when the library has been detached
2023-06-21 16:46:16 +02:00
Andrés Moya
749d60be48 🐛 Disallow restore component when the library has been detached 2023-06-21 16:39:17 +02:00
Alejandro Alonso
bb8a523208 📎 Update CHANGES.md file 2023-06-21 12:52:13 +02:00
Alejandro Alonso
4d3e7f9a75 Merge remote-tracking branch 'origin/staging' into develop 2023-06-21 12:50:49 +02:00
Alejandro Alonso
2edbc10851 📎 Update CHANGES.md file 2023-06-21 12:50:04 +02:00
Alejandro Alonso
5fc303a05d Merge remote-tracking branch 'origin/staging' into develop 2023-06-21 12:45:54 +02:00
Alejandro Alonso
9a45ce80a6 🐛 Fix comments navigation 2023-06-21 12:41:49 +02:00
Pablo Alba
3645d1af20 Merge pull request #3327 from penpot/superalex-fix-right-click-options-over-layer-or-shape
🐛 Fix right click options over layer or shape
2023-06-21 12:22:24 +02:00
Alejandro Alonso
d2bfd98a05 🐛 Fix right click options over layer or shape 2023-06-20 13:29:07 +02:00
Andrey Antukh
ecedf46c2a 📎 Add missing changelog entries for the 1.18.5 2023-06-20 11:21:09 +02:00
Andrey Antukh
73d42c03d5 Allow override the default nginx resolver
using the PENPOT_INTERNAL_RESOLVER environment variable
2023-06-20 11:21:09 +02:00
Pablo Alba
e96bedc1c8 🎉 Create multiple componentes 2023-06-20 11:07:33 +02:00
Aitor Moreno
c5f37fadba Merge pull request #3323 from penpot/alotor-fix-reload
 Not hotreload cursors
2023-06-19 16:15:59 +02:00
Aitor
8052c5f973 📎 Add [data-test] to page-items 2023-06-19 16:13:48 +02:00
Andrés Moya
c499c8a323 🐛 Small fix 2023-06-19 16:09:16 +02:00
alonso.torres
6b9962b2b3 Not hotreload cursors 2023-06-19 14:57:51 +02:00
Eva Marco
0a81ae1ea0 Merge pull request #3313 from penpot/azazeln28-fix-cursors
🐛 Fix creation cursors not being displayed
2023-06-19 13:55:19 +02:00
Alejandro
c6d71ea902 Merge pull request #3321 from penpot/niwinz-bugfixes-export
Niwinz bugfixes export
2023-06-19 13:16:44 +02:00
Andrey Antukh
4d850ebe6e 🐛 Add proper features initialization on render entrypoint 2023-06-19 13:08:11 +02:00
Andrey Antukh
dac18e876f 🐛 Fix validation error on password recovery submit operation 2023-06-19 13:07:46 +02:00
Andrey Antukh
d016876710 🐛 Add missing file-id validation on get-page rpc method 2023-06-19 13:07:26 +02:00
Andrey Antukh
ddeb540df6 🐛 Fix pointer map related issues on get-page rpc method
mainly used on render.html endpoint which is used by exporter
2023-06-19 13:06:44 +02:00
Pablo Alba
7733bc4419 🐛 Fix ungroup component 2023-06-19 12:29:54 +02:00
Alejandro Alonso
128fe29619 Show interactions on click as default setting at the view mode 2023-06-19 12:00:08 +02:00
Alejandro Alonso
23e200dece 🐛 Fix user select layer mode 2023-06-19 11:05:51 +02:00
Pablo Alba
d9375c1dd1 Fix duplicate shape in a component copy maintains its ref 2023-06-19 10:33:17 +02:00
Alejandro Alonso
aeebed6ef7 Merge remote-tracking branch 'origin/staging' into develop 2023-06-16 14:13:51 +02:00
Pablo Alba
498ba257b6 Merge pull request #3290 from penpot/hiru-fix-update-notifications
🐛 Solve error in notification of library changes
2023-06-16 14:07:35 +02:00
Andrés Moya
6edba71c12 🐛 Fix calculation of component modified and remove unneeded check 2023-06-16 13:24:41 +02:00
Andrés Moya
a559e7310a 🐛 Solve error in notification of library changes
(See main.data.workspace.notifications/schema:handle-file-change)
2023-06-16 12:23:11 +02:00
Pablo Alba
cdc3367d1b Merge pull request #3286 from penpot/superalex-fix-add-flow-option-for-frames
🐛 Fix add flow option in contextual menu for frames
2023-06-16 12:17:48 +02:00
Pablo Alba
8d37d63a27 Merge pull request #3292 from penpot/hiru-fix-export-components
🐛 Fix export components for v2
2023-06-16 12:12:01 +02:00
Aitor
95f0f63276 🐛 Fix creation cursors not being displayed 2023-06-16 12:04:16 +02:00
Pablo Alba
5cab599a06 Merge pull request #3285 from penpot/hiru-fill-problems
🐛 Revert #9de962bb and solve the fill issues in a different way
2023-06-16 11:56:39 +02:00
Alejandro Alonso
24715a85e5 Deleted fonts auto match 2023-06-16 11:07:16 +02:00
Alejandro
559c03550d Merge pull request #3298 from penpot/superalex-improve-invitations-validation
 Improve invitations validation
2023-06-16 10:51:57 +02:00
Alejandro Alonso
8a9a3cbf37 Improve invitations validation 2023-06-13 11:51:03 +02:00
Andrés Moya
f2fcd0f82f 🐛 Fix export components for v2 2023-06-12 17:13:10 +02:00
Andrés Moya
a43d439b31 🐛 Revert #9de962bb and solve the fill issues in a different way 2023-06-09 21:13:43 +02:00
Alejandro Alonso
bc64fdb1bc 🐛 Fix add flow option in contextual menu for frames 2023-06-09 09:28:27 +02:00
51 changed files with 738 additions and 383 deletions

View File

@@ -1,6 +1,6 @@
# CHANGELOG
## :rocket: Next
## :rocket: 1.19.0
### :boom: Breaking changes & Deprecations
@@ -9,6 +9,7 @@
- Create typography style from a selected text layer[Taiga #3041](https://tree.taiga.io/project/penpot/us/3041)
- Board as ruler origin [Taiga #4833](https://tree.taiga.io/project/penpot/us/4833)
- Access tokens support [Taiga #4460](https://tree.taiga.io/project/penpot/us/4460)
- Show interactions setting at the view mode [Taiga #1330](https://tree.taiga.io/project/penpot/issue/1330)
### :bug: Bugs fixed
- Fix files can be opened from multiple urls [Taiga #5310](https://tree.taiga.io/project/penpot/issue/5310)
@@ -31,6 +32,26 @@
- Distribute fix enabled when two elements were selected (by @dfelinto) [Github #3266](https://github.com/penpot/penpot/pull/3266)
- Distribute vertical spacing failing for overlapped text (by @dfelinto) [Github #3267](https://github.com/penpot/penpot/pull/3267)
## 1.18.6
### :bug: Bugs fixed
- Fix comments navigation from workspace [Taiga #5504](https://tree.taiga.io/project/penpot/issue/5504)
### :sparkles: Enhancements
- Add the ability to overwrite internal resolver with `PENPOT_INTERNAL_RESOLVER` environment
variable [GH #3310](https://github.com/penpot/penpot/issues/3310)
## 1.18.5
### :bug: Bugs fixed
- Fix add flow option in contextual menu for frames
- Fix issues related with invitations
- Fix problem with undefined gaps
- Add deleted fonts auto match mechanism
## 1.18.4
### :bug: Bugs fixed
@@ -70,6 +91,7 @@
## 1.18.0
### :sparkles: New features
- Adds more accessibility improvements in dashboard [Taiga #4577](https://tree.taiga.io/project/penpot/us/4577)
- Adds paddings and gaps prediction on layout creation [Taiga #4838](https://tree.taiga.io/project/penpot/task/4838)
- Add visual feedback when proportionally scaling text elements with **K** [Taiga #3415](https://tree.taiga.io/project/penpot/us/3415)

View File

@@ -169,14 +169,16 @@
[{:keys [::db/pool] :as cfg} params]
(when-not (contains? cf/flags :registration)
(if-not (contains? params :invitation-token)
(when-not (contains? params :invitation-token)
(ex/raise :type :restriction
:code :registration-disabled)
(let [invitation (tokens/verify (::main/props cfg) {:token (:invitation-token params) :iss :team-invitation})]
(when-not (= (:email params) (:member-email invitation))
(ex/raise :type :restriction
:code :email-does-not-match-invitation
:hint "email should match the invitation")))))
:code :registration-disabled)))
(when (contains? params :invitation-token)
(let [invitation (tokens/verify (::main/props cfg) {:token (:invitation-token params) :iss :team-invitation})]
(when-not (= (:email params) (:member-email invitation))
(ex/raise :type :restriction
:code :email-does-not-match-invitation
:hint "email should match the invitation"))))
(when-let [domains (cf/get :registration-domain-whitelist)]
(when-not (email-domain-in-whitelist? domains (:email params))

View File

@@ -490,13 +490,17 @@
(let [file (get-file conn file-id features)
page-id (or page-id (-> file :data :pages first))
page (dm/get-in file [:data :pages-index page-id])]
page (dm/get-in file [:data :pages-index page-id])
page (if (pmap/pointer-map? page)
(deref page)
page)]
(cond-> (prune-thumbnails page)
(uuid? object-id)
(prune-objects object-id))))
(sm/def! ::get-page
[:map {:title "GetPage"}
[:file-id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:object-id {:optional true} ::sm/uuid]
[:features {:optional true} ::features]])
@@ -516,7 +520,10 @@
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(dm/with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(get-page conn params)))
(binding [pmap/*load-fn* (partial load-pointer conn file-id)]
(get-page conn params))))
;; --- COMMAND QUERY: get-team-shared-files
@@ -892,7 +899,7 @@
;; TODO: improve naming
(sv/defmethod ::update-file-library-sync-status
"Update the synchronization statos of a file->library link"
"Update the synchronization status of a file->library link"
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]

View File

@@ -8,14 +8,14 @@
(:require
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.comments :as comments]
[app.rpc.commands.files :as files]
[app.rpc.cond :as-alias cond]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
[app.util.services :as sv]))
;; --- QUERY: View Only Bundle
@@ -79,18 +79,19 @@
:always
(update :data select-keys [:id :options :pages :pages-index :components]))))))
(s/def ::get-view-only-bundle
(s/keys :req-un [::files/file-id]
:opt-un [::files/share-id
::files/features]
:opt [::rpc/profile-id]))
(sm/def! ::get-view-only-bundle
[:map {:title "get-view-only-bundle"}
[:file-id ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]
[:features {:optional true} ::files/features]])
(sv/defmethod ::get-view-only-bundle
{::rpc/auth false
::cond/get-object #(files/get-minimal-file %1 (:file-id %2))
::cond/key-fn files/get-file-etag
::cond/reuse-key? true
::doc/added "1.17"}
::doc/added "1.17"
::sm/params ::get-view-only-bundle}
[{:keys [::db/pool]} {:keys [::rpc/profile-id] :as params}]
(dm/with-open [conn (db/open pool)]
(get-view-only-bundle conn (assoc params :profile-id profile-id))))

View File

@@ -216,8 +216,9 @@
[:type [:= :del-typography]]
[:id ::sm/uuid]]]]])
(sm/def! ::changes
[:sequential {:gen/max 2} ::change])
(def change?
(sm/pred-fn ::change))
@@ -302,34 +303,34 @@
(defmethod process-change :mod-obj
[data {:keys [id page-id component-id operations]}]
(let [objects (if page-id
(-> data :pages-index (get page-id) :objects)
(-> data :components (get component-id) :objects))
(let [changed? (atom false)
modified-component-ids (atom #{})
on-touched (fn [shape]
;; When a shape is modified, if it belongs to a main component instance,
;; the component needs to be marked as modified.
(let [component-root (ctn/get-component-shape objects shape {:allow-main? true})]
(when (ctk/main-instance? component-root)
(swap! modified-component-ids conj (:component-id component-root)))))
process-and-register (partial process-operation
(fn [_shape] (reset! changed? true)))
update-fn (fn [objects]
(if-let [obj (get objects id)]
(let [result (reduce (partial process-operation on-touched) obj operations)]
(assoc objects id result))
objects))
(d/update-when objects id
#(reduce process-and-register % operations)))
modify-components (fn [data]
(reduce ctkl/set-component-modified
data @modified-component-ids))]
check-modify-component (fn [data]
(if @changed?
;; When a shape is modified, if it belongs to a main component instance,
;; the component needs to be marked as modified.
(let [objects (if page-id
(-> data :pages-index (get page-id) :objects)
(-> data :components (get component-id) :objects))
shape (get objects id)
component-root (ctn/get-component-shape objects shape {:allow-main? true})]
(if (and (some? component-root) (ctk/main-instance? component-root))
(ctkl/set-component-modified data (:component-id component-root))
data))
data))]
(as-> data $
(if page-id
(d/update-in-when $ [:pages-index page-id :objects] update-fn)
(d/update-in-when $ [:components component-id :objects] update-fn))
(modify-components $))))
(check-modify-component $))))
(defmethod process-change :del-obj
[data {:keys [page-id component-id id ignore-touched]}]
@@ -583,7 +584,7 @@
;; === Operations
(defmethod process-operation :set
[on-touched shape op]
[on-changed shape op]
(let [attr (:attr op)
group (get component-sync-attrs attr)
val (:val op)
@@ -608,10 +609,10 @@
(gsh/close-attrs? attr val shape-val 1)
(gsh/close-attrs? attr val shape-val))]
(when (and group (not ignore) (not equal?)
(not (and ignore-geometry is-geometry?)))
;; Notify touched even if it's not copy, because it may be a main instance
(on-touched shape))
;; Notify when value has changed, except when it has not moved relative to the
;; component head.
(when (and group (not equal?) (not (and ignore-geometry is-geometry?)))
(on-changed shape))
(cond-> shape
;; Depending on the origin of the attribute change, we need or not to

View File

@@ -15,6 +15,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]))
@@ -216,9 +217,14 @@
([changes obj {:keys [index ignore-touched] :or {index ::undefined ignore-touched false}}]
(assert-page-id changes)
(assert-objects changes)
(let [obj (cond-> obj
(not= index ::undefined)
(assoc ::index index))
objects (lookup-objects changes)
parent (get objects (:parent-id obj))
add-change
{:type :add-obj
:id (:id obj)
@@ -232,10 +238,20 @@
del-change
{:type :del-obj
:id (:id obj)
:page-id (::page-id (meta changes))}]
:page-id (::page-id (meta changes))}
restore-touched-change
{:type :mod-obj
:page-id (::page-id (meta changes))
:id (:id parent)
:operations [{:type :set-touched
:touched (:touched parent)}]}]
(-> changes
(update :redo-changes conj add-change)
(cond->
(and (ctk/in-component-copy? parent) (not ignore-touched))
(update :undo-changes d/preconj restore-touched-change))
(update :undo-changes d/preconj del-change)
(apply-changes-local)))))
@@ -256,6 +272,8 @@
(assert-page-id changes)
(assert-objects changes)
(let [objects (lookup-objects changes)
parent (get objects parent-id)
set-parent-change
(cond-> {:type :mov-objects
:parent-id parent-id
@@ -275,10 +293,20 @@
:parent-id (:parent-id shape)
:shapes [(:id shape)]
:after-shape prev-sibling
:index 0})))] ; index is used in case there is no after-shape (moving bottom shapes)
:index 0}))) ; index is used in case there is no after-shape (moving bottom shapes)
restore-touched-change
{:type :mod-obj
:page-id (::page-id (meta changes))
:id (:id parent)
:operations [{:type :set-touched
:touched (:touched parent)}]}]
(-> changes
(update :redo-changes conj set-parent-change)
(cond->
(ctk/in-component-copy? parent)
(update :undo-changes d/preconj restore-touched-change))
(update :undo-changes #(reduce mk-undo-change % shapes))
(apply-changes-local)))))

View File

@@ -39,9 +39,7 @@
(-> parent
(update :shapes update-parent-shapes)
(update :shapes d/vec-without-nils)
(cond-> (and (:shape-ref parent)
(not= (:id parent) frame-id)
(not ignore-touched))
(cond-> (and (ctk/in-component-copy? parent) (not ignore-touched))
(-> (update :touched cph/set-touched-group :shapes-group)
(dissoc :remote-synced?)))))
@@ -328,6 +326,9 @@
The list of objects are returned in tree traversal order, respecting
the order of the children of each parent."
([object parent-id objects]
(clone-object object parent-id objects (fn [object _] object) (fn [object _] object) nil false))
([object parent-id objects update-new-object]
(clone-object object parent-id objects update-new-object (fn [object _] object) nil false))

View File

@@ -21,7 +21,8 @@ update_flags /var/www/app/js/config.js
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060};
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061};
export PENPOT_INTERNAL_RESOLVER=${PENPOT_INTERNAL_RESOLVER:-127.0.0.11};
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI" < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_INTERNAL_RESOLVER" < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
exec "$@";

View File

@@ -38,7 +38,7 @@ http {
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json;
resolver 127.0.0.11;
resolver $PENPOT_INTERNAL_RESOLVER;
map $http_upgrade $connection_upgrade {
default upgrade;

View File

@@ -362,11 +362,6 @@
height: 556px;
}
.info-wrapper {
border: 1px solid $color-gray-10;
border-radius: 3px;
}
&.project-th.library {
height: 610px;
width: 300px;

View File

@@ -18,7 +18,7 @@
[app.main.ui :as ui]
[app.main.ui.alert]
[app.main.ui.confirm]
[app.main.ui.cursors :as cursors]
[app.main.ui.css-cursors :as cur]
[app.main.ui.delete-shared]
[app.main.ui.modal :refer [modal]]
[app.main.ui.routes :as rt]
@@ -45,7 +45,6 @@
(defn init-ui
[]
(cursors/init-styles)
(mf/mount (mf/element ui/app) (dom/get-element "app"))
(mf/mount (mf/element modal) (dom/get-element "modal")))
@@ -80,6 +79,7 @@
(worker/init!)
(i18n/init! cf/translations)
(theme/init! cf/themes)
(cur/init-styles)
(init-ui)
(st/emit! (initialize)))

View File

@@ -500,7 +500,7 @@
(defn recover-profile
[data]
(dm/assert! (sm/valid? ::recover-profile data))
(dm/assert! (sm/valid? schema:recover-profile data))
(ptk/reify ::recover-profile
ptk/WatchEvent
(watch [_ _ _]

View File

@@ -30,7 +30,7 @@
default-local-state
{:zoom 1
:fullscreen? false
:interactions-mode :hide
:interactions-mode :show-on-click
:interactions-show? false
:comments-mode :all
:comments-show :unresolved
@@ -53,7 +53,7 @@
[:page-id {:optional true} ::sm/uuid]])
(defn initialize
[{:keys [file-id share-id] :as params}]
[{:keys [file-id share-id interactions-show?] :as params}]
(dm/assert! (sm/valid? schema:initialize params))
(ptk/reify ::initialize
ptk/UpdateEvent
@@ -61,11 +61,12 @@
(-> state
(assoc :current-file-id file-id)
(update :viewer-local
(fn [lstate]
(if (nil? lstate)
default-local-state
lstate)))
(assoc-in [:viewer-local :share-id] share-id)))
(fn [lstate]
(if (nil? lstate)
default-local-state
lstate)))
(assoc-in [:viewer-local :share-id] share-id)
(assoc-in [:viewer-local :interactions-show?] interactions-show?)))
ptk/WatchEvent
(watch [_ _ _]
@@ -400,7 +401,14 @@
(assoc-in [:viewer-local :interactions-show?] (case mode
:hide false
:show true
:show-on-click false))))))
:show-on-click false))))
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
screen (-> route :data :name keyword)
qparams (:query-params route)
pparams (:path-params route)]
(rx/of (rt/nav screen pparams (assoc qparams :interactions-mode mode)))))))
(declare flash-done)

View File

@@ -43,6 +43,7 @@
[app.main.data.workspace.drawing.common :as dwdc]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.fix-bool-contents :as fbc]
[app.main.data.workspace.fix-deleted-fonts :as fdf]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.guides :as dwgu]
[app.main.data.workspace.highlight :as dwh]
@@ -129,6 +130,7 @@
components-v2 (features/active-feature? state :components-v2)]
(rx/merge
(rx/of (fbc/fix-bool-contents))
(rx/of (fdf/fix-deleted-fonts))
(if (and has-graphics? components-v2)
(rx/of (remove-graphics (:id file) (:name file)))
(rx/empty)))))))
@@ -232,13 +234,15 @@
ptk/WatchEvent
(watch [_ state _]
(let [file-data (:workspace-data state)
(let [file-id (dm/get-in state [:workspace-file :id])
ignore-until (dm/get-in state [:workspace-file :ignore-sync-until])
file-id (dm/get-in state [:workspace-file :id])
needs-update? (seq (filter #(dwl/assets-need-sync % file-data ignore-until)
libraries))]
(when needs-update?
(rx/of (dwl/notify-sync-file file-id)))))))
needs-check? (some #(and (> (:modified-at %) (:synced-at %))
(or (not ignore-until)
(> (:modified-at %) ignore-until)))
libraries)]
(when needs-check?
(rx/concat (rx/timer 1000)
(rx/of (dwl/notify-sync-file file-id))))))))
(defn- fetch-thumbnail-blob-uri
[uri]
@@ -1373,17 +1377,18 @@
(not (contains? #{:group :bool} (:type head))))
no-bool-shapes? (->> all-selected (some (comp #{:frame :text} :type)))]
(rx/concat
(when (and (some? shape) (not (contains? selected (:id shape))))
(rx/of (dws/select-shape (:id shape))))
(rx/of (show-context-menu
(-> params
(assoc
:kind :shape
:disable-booleans? (or no-bool-shapes? not-group-like?)
:disable-flatten? no-bool-shapes?
:selected (conj selected (:id shape)))))))))))
(if (and (some? shape) (not (contains? selected (:id shape))))
(rx/concat
(rx/of (dws/select-shape (:id shape)))
(rx/of (show-shape-context-menu params)))
(rx/of (show-context-menu
(-> params
(assoc
:kind :shape
:disable-booleans? (or no-bool-shapes? not-group-like?)
:disable-flatten? no-bool-shapes?
:selected (conj selected (:id shape)))))))))))
(defn show-page-item-context-menu
[{:keys [position page] :as params}]

View File

@@ -195,6 +195,7 @@
(update [_ state]
(log/info :msg "commit-changes"
:js/undo-group (str undo-group)
:js/file-id (str (or file-id "nil"))
:js/redo-changes redo-changes
:js/undo-changes undo-changes)
(let [current-file-id (get state :current-file-id)

View File

@@ -0,0 +1,129 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.fix-deleted-fonts
(:require
[app.common.data :as d]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.fonts :as fonts]
[beicon.core :as rx]
[potok.core :as ptk]))
;; This event will update the file so the texts with non existing custom fonts try to be fixed.
;; This can happen when:
;; - Exporting/importing files to different teams or penpot instances
;; - Moving files from one team to another in the same instance
;; - Custom fonts are explicitly deleted in the team area
(defn has-invalid-font-family
[node]
(let [fonts (deref fonts/fontsdb)]
(and
(some? (:font-family node))
(nil? (get fonts (:font-id node))))))
(defn calculate-alternative-font-id
[value]
(let [fonts (deref fonts/fontsdb)]
(->> (vals fonts)
(filter #(= (:family %) value))
(first)
:id)))
(defn should-fix-deleted-font-shape?
[shape]
(let [text-nodes (txt/node-seq txt/is-text-node? (:content shape))]
(and (cph/text-shape? shape) (some has-invalid-font-family text-nodes))))
(defn should-fix-deleted-font-component?
[component]
(->> (:objects component)
(vals)
(d/seek should-fix-deleted-font-shape?)))
(defn should-fix-deleted-font-typography?
[typography]
(let [fonts (deref fonts/fontsdb)]
(nil? (get fonts (:font-id typography)))))
(defn fix-deleted-font
[node]
(let [alternative-font-id (calculate-alternative-font-id (:font-family node))]
(cond-> node
(some? alternative-font-id) (assoc :font-id alternative-font-id))))
(defn fix-deleted-font-shape
[shape]
(let [transform (partial txt/transform-nodes has-invalid-font-family fix-deleted-font)]
(update shape :content transform)))
(defn fix-deleted-font-component
[component]
(update component
:objects
(fn [objects]
(d/mapm #(fix-deleted-font-shape %2) objects))))
(defn fix-deleted-font-typography
[typography]
(let [alternative-font-id (calculate-alternative-font-id (:font-family typography))]
(cond-> typography
(some? alternative-font-id) (assoc :font-id alternative-font-id))))
(defn fix-deleted-fonts
[]
(ptk/reify ::fix-deleted-fonts
ptk/WatchEvent
(watch [it state _]
(let [objects (wsh/lookup-page-objects state)
ids (into #{}
(comp (filter should-fix-deleted-font-shape?) (map :id))
(vals objects))
components (->> (wsh/lookup-local-components state)
(vals)
(filter should-fix-deleted-font-component?))
component-changes
(into []
(map (fn [component]
{:type :mod-component
:id (:id component)
:objects (-> (fix-deleted-font-component component) :objects)}))
components)
typographies (->> (get-in state [:workspace-data :typographies])
(vals)
(filter should-fix-deleted-font-typography?))
typography-changes
(into []
(map (fn [typography]
{:type :mod-typography
:typography (fix-deleted-font-typography typography)}))
typographies)]
(rx/concat
(rx/of (dch/update-shapes ids #(fix-deleted-font-shape %) {:reg-objects? false
:save-undo? false
:ignore-tree true}))
(if (empty? component-changes)
(rx/empty)
(rx/of (dch/commit-changes {:origin it
:redo-changes component-changes
:undo-changes []
:save-undo? false})))
(if (empty? typography-changes)
(rx/empty)
(rx/of (dch/commit-changes {:origin it
:redo-changes typography-changes
:undo-changes []
:save-undo? false}))))))))

View File

@@ -12,10 +12,14 @@
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[beicon.core :as rx]
@@ -140,7 +144,7 @@
(mapv #(get objects %)))
parent-id (cph/get-parent-id objects (:id frame))
idx-in-parent (->> (:id frame)
(cph/get-position-on-parent objects)
(cph/get-position-on-parent objects)
inc)]
(-> (pcb/empty-changes it page-id)
@@ -150,6 +154,46 @@
(pcb/change-parent parent-id children idx-in-parent)
(pcb/remove-objects [(:id frame)]))))
(defn- clone-component-shapes-changes
[changes shape objects]
(let [shape-parent-id (:parent-id shape)
new-shape-id (uuid/next)
[_ new-shapes _]
(ctst/clone-object shape
shape-parent-id
objects
(fn [object _]
(cond-> object
(= new-shape-id (:parent-id object))
(assoc :parent-id shape-parent-id)))
(fn [object _] object)
new-shape-id
false)
new-shapes (->> new-shapes
(filter #(not= (:id %) new-shape-id)))]
(reduce
(fn [changes shape]
(pcb/add-object changes shape))
changes
new-shapes)))
(defn remove-component-changes
[it page-id shape objects file-data file]
(let [page (ctpl/get-page file-data page-id)
components-v2 (dm/get-in file-data [:options :components-v2])
;; In order to ungroup a component, we first make a clone of its shapes,
;; and then we delete it
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/with-library-data file-data)
(pcb/with-page page)
(clone-component-shapes-changes shape objects)
(dwsh/delete-shapes-changes file page objects [(:id shape)] it components-v2))]
;; TODO: Should we call detach-comment-thread ?
changes))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GROUPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -173,13 +217,18 @@
(ptk/reify ::ungroup-selected
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
file-data (get state :workspace-data)
file (wsh/get-local-file state)
prepare
(fn [shape-id]
(let [shape (get objects shape-id)]
(cond
(ctk/main-instance? shape)
(remove-component-changes it page-id shape objects file-data file)
(or (cph/group-shape? shape) (cph/bool-shape? shape))
(remove-group-changes it page-id shape objects)

View File

@@ -43,7 +43,7 @@
[potok.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
(log/set-level! :debug)
(defn- log-changes
[changes file]
@@ -317,6 +317,26 @@
components-v2 (features/active-feature? state :components-v2)]
(rx/of (add-component2 selected components-v2))))))
(defn add-multiple-components
"Add several new components to current file library, from the currently selected shapes."
[]
(ptk/reify ::add-multiple-components
ptk/WatchEvent
(watch [_ state _]
(let [components-v2 (features/active-feature? state :components-v2)
objects (wsh/lookup-page-objects state)
selected (->> (wsh/lookup-selected state)
(cph/clean-loops objects))
added-components (map
#(add-component2 [%] components-v2)
selected)
undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/from added-components)
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn rename-component
"Rename the component with the given id, in the current file library."
[id new-name]
@@ -433,9 +453,9 @@
(ptk/reify ::restore-component
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
current-page (dm/get-in state [:workspace-data :pages-index page-id])
objects (wsh/lookup-page-objects state page-id)
(let [page-id (:current-page-id state)
current-page (dm/get-in state [:workspace-data :pages-index page-id])
objects (wsh/lookup-page-objects state page-id)
library-data (wsh/get-file state library-id)
{:keys [changes shape]} (dwlh/prepare-restore-component library-data component-id current-page it)
parent-id (:parent-id shape)
@@ -444,14 +464,13 @@
(update-in [parent-id :shapes]
#(conj % (:id shape))))
;; Adds a resize-parents operation so the groups are updated. We add all the new objects
new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id))
changes (-> changes
(pcb/with-objects objects)
(pcb/resize-parents new-objects-ids))]
(rx/of (dch/commit-changes changes))))))
(rx/of (dch/commit-changes (assoc changes :file-id library-id)))))))
(defn instantiate-component
"Create a new shape in the current page, from the component with the given id
@@ -460,14 +479,14 @@
(dm/assert! (uuid? file-id))
(dm/assert! (uuid? component-id))
(dm/assert! (gpt/point? position))
(ptk/reify ::instantiate-component
ptk/WatchEvent
(watch [it state _]
(let [page (wsh/lookup-page state)
libraries (wsh/get-libraries state)
changes (pcb/empty-changes it (:id page))
changes (-> (pcb/empty-changes it (:id page))
(pcb/with-objects (:objects page)))
[new-shape changes]
(dwlh/generate-instantiate-component changes
@@ -842,9 +861,9 @@
The sequence items are tuples of (page-id shape-id asset-id asset-type)."
([library file-data] (assets-need-sync library file-data nil))
([library file-data ignore-until]
(let [sync-date (max (:synced-at library) (or ignore-until 0))]
(when (> (:modified-at library) sync-date)
(ctf/used-assets-changed-since file-data library sync-date)))))
(let [sync-date (max (:synced-at library) (or ignore-until 0))]
(when (> (:modified-at library) sync-date)
(ctf/used-assets-changed-since file-data library sync-date)))))
(defn notify-sync-file
[file-id]
@@ -853,7 +872,8 @@
ptk/WatchEvent
(watch [_ state _]
(let [file-data (:workspace-data state)
libraries-need-sync (filter #(seq (assets-need-sync % file-data))
ignore-until (dm/get-in state [:workspace-file :ignore-sync-until])
libraries-need-sync (filter #(seq (assets-need-sync % file-data ignore-until))
(vals (get state :workspace-libraries)))
do-update #(do (apply st/emit! (map (fn [library]
(sync-file (:current-file-id state)
@@ -904,10 +924,15 @@
check-changes
(fn [[event [old-data _mid_data _new-data]]]
(when old-data
(let [{:keys [changes save-undo? undo-group]} (deref event)
components-changed (reduce #(into %1 (ch/components-changed old-data %2))
#{}
changes)]
(let [{:keys [file-id changes save-undo? undo-group]}
(deref event)
components-changed
(when (or (nil? file-id) (= file-id (:id old-data)))
(reduce #(into %1 (ch/components-changed old-data %2))
#{}
changes))]
(when (and (d/not-empty? components-changed) save-undo?)
(log/info :msg "DETECTED COMPONENTS CHANGED"
:ids (map str components-changed)

View File

@@ -181,12 +181,14 @@
(not (nil? parent-id))
(assoc :parent-id parent-id))
changes (cond-> (pcb/add-object changes first-shape {:ignore-touched true})
(some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
changes
(rest new-shapes))]
changes (as-> changes $
(pcb/add-object $ first-shape {:ignore-touched true})
(cond-> $
(some? old-id)
(pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers
(reduce #(pcb/add-object %1 %2 {:ignore-touched true})
$
(rest new-shapes)))]
[new-shape changes])))
@@ -228,6 +230,7 @@
(assoc :parent-id parent-id))
changes (-> (or changes (pcb/empty-changes it))
(pcb/with-page page)
(pcb/with-objects (:objects page))
(pcb/with-library-data library-data))
changes (cond-> (pcb/add-object changes first-shape {:ignore-touched true})
(some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers

View File

@@ -428,11 +428,12 @@
:frame-id frame-id)
(dissoc :shapes
:main-instance?
:shape-ref
:use-for-thumbnail?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
changes (-> (pcb/add-object changes new-obj {:ignore-touched true})
changes (-> (pcb/add-object changes new-obj)
(pcb/amend-last-change #(assoc % :old-id (:id obj))))
changes (cond-> changes

View File

@@ -108,7 +108,8 @@
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
changes (pcb/empty-changes it page-id)
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects))
[shape changes]
(prepare-add-shape changes attrs objects selected)
@@ -214,126 +215,140 @@
(real-delete-shapes file page objects ids-to-delete it components-v2)
(rx/of (dwu/commit-undo-transaction undo-id))))))))
(defn- real-delete-shapes
[file page objects ids it components-v2]
(let [lookup (d/getf objects)
groups-to-unmask
(reduce (fn [group-ids id]
(defn- real-delete-shapes-changes
([file page objects ids it components-v2]
(let [changes (-> (pcb/empty-changes it (:id page))
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file))]
(real-delete-shapes-changes changes file page objects ids it components-v2)))
([changes file page objects ids _it components-v2]
(let [lookup (d/getf objects)
groups-to-unmask
(reduce (fn [group-ids id]
;; When the shape to delete is the mask of a masked group,
;; the mask condition must be removed, and it must be
;; converted to a normal group.
(let [obj (lookup id)
parent (lookup (:parent-id obj))]
(if (and (:masked-group? parent)
(= id (first (:shapes parent))))
(conj group-ids (:id parent))
group-ids)))
#{}
ids)
(let [obj (lookup id)
parent (lookup (:parent-id obj))]
(if (and (:masked-group? parent)
(= id (first (:shapes parent))))
(conj group-ids (:id parent))
group-ids)))
#{}
ids)
interacting-shapes
(filter (fn [shape]
interacting-shapes
(filter (fn [shape]
;; If any of the deleted shapes is the destination of
;; some interaction, this must be deleted, too.
(let [interactions (:interactions shape)]
(some #(and (ctsi/has-destination %)
(contains? ids (:destination %)))
interactions)))
(vals objects))
(let [interactions (:interactions shape)]
(some #(and (ctsi/has-destination %)
(contains? ids (:destination %)))
interactions)))
(vals objects))
;; If any of the deleted shapes is a frame with guides
guides (into {}
(comp (map second)
(remove #(contains? ids (:frame-id %)))
(map (juxt :id identity)))
(dm/get-in page [:options :guides]))
guides (into {}
(comp (map second)
(remove #(contains? ids (:frame-id %)))
(map (juxt :id identity)))
(dm/get-in page [:options :guides]))
starting-flows
(filter (fn [flow]
starting-flows
(filter (fn [flow]
;; If any of the deleted is a frame that starts a flow,
;; this must be deleted, too.
(contains? ids (:starting-frame flow)))
(-> page :options :flows))
(contains? ids (:starting-frame flow)))
(-> page :options :flows))
all-parents
(reduce (fn [res id]
all-parents
(reduce (fn [res id]
;; All parents of any deleted shape must be resized.
(into res (cph/get-parent-ids objects id)))
(d/ordered-set)
ids)
(into res (cph/get-parent-ids objects id)))
(d/ordered-set)
ids)
all-children
(->> ids ;; Children of deleted shapes must be also deleted.
(reduce (fn [res id]
(into res (cph/get-children-ids objects id)))
[])
(reverse)
(into (d/ordered-set)))
all-children
(->> ids ;; Children of deleted shapes must be also deleted.
(reduce (fn [res id]
(into res (cph/get-children-ids objects id)))
[])
(reverse)
(into (d/ordered-set)))
find-all-empty-parents
(fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter cph/group-shape?)
(remove #(->> (:shapes %) (remove contains?) seq))
(map :id))
parents (into #{} xform all-parents)]
(if (= empty-parents parents)
empty-parents
(recursive-find-empty-parents parents))))
find-all-empty-parents
(fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter cph/group-shape?)
(remove #(->> (:shapes %) (remove contains?) seq))
(map :id))
parents (into #{} xform all-parents)]
(if (= empty-parents parents)
empty-parents
(recursive-find-empty-parents parents))))
empty-parents
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
(into (d/ordered-set) (find-all-empty-parents #{}))
(into (d/ordered-set) (find-all-empty-parents #{}))
components-to-delete
(if components-v2
(reduce (fn [components id]
(let [shape (get objects id)]
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
(:main-instance? shape)) ;; but check anyway
(conj components (:component-id shape))
components)))
[]
(into ids all-children))
[])
components-to-delete
(if components-v2
(reduce (fn [components id]
(let [shape (get objects id)]
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
(:main-instance? shape)) ;; but check anyway
(conj components (:component-id shape))
components)))
[]
(into ids all-children))
[])
changes (-> (pcb/empty-changes it (:id page))
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file)
(pcb/set-page-option :guides guides))
changes (-> changes
(pcb/set-page-option :guides guides))
changes (reduce (fn [changes component-id]
changes (reduce (fn [changes component-id]
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
(pcb/delete-component changes component-id))
changes
components-to-delete)
(pcb/delete-component changes component-id))
changes
components-to-delete)
changes (-> changes
(pcb/remove-objects all-children {:ignore-touched true})
(pcb/remove-objects ids)
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group? false)))
(pcb/update-shapes (map :id interacting-shapes)
(fn [shape]
(d/update-when shape :interactions
(fn [interactions]
(into []
(remove #(and (ctsi/has-destination %)
(contains? ids (:destination %))))
interactions)))))
(cond-> (seq starting-flows)
(pcb/update-page-option :flows (fn [flows]
(->> (map :id starting-flows)
(reduce ctp/remove-flow flows))))))
undo-id (js/Symbol)]
changes (-> changes
(pcb/remove-objects all-children {:ignore-touched true})
(pcb/remove-objects ids)
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group? false)))
(pcb/update-shapes (map :id interacting-shapes)
(fn [shape]
(d/update-when shape :interactions
(fn [interactions]
(into []
(remove #(and (ctsi/has-destination %)
(contains? ids (:destination %))))
interactions)))))
(cond-> (seq starting-flows)
(pcb/update-page-option :flows (fn [flows]
(->> (map :id starting-flows)
(reduce ctp/remove-flow flows))))))]
[changes all-parents])))
(defn delete-shapes-changes
[changes file page objects ids it components-v2]
(let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2)]
changes))
(defn- real-delete-shapes
[file page objects ids it components-v2]
(let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2)
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dc/detach-comment-thread ids)
(dch/commit-changes changes)
@@ -419,7 +434,8 @@
selected (wsh/lookup-selected state)
selected (cph/clean-loops objects selected)
changes (pcb/empty-changes it page-id)
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects))
[frame-shape changes]
(prepare-create-artboard-from-selection changes
@@ -467,7 +483,7 @@
;; We have change only the hidden behaviour, to hide only the
;; selected shape, block behaviour remains the same.
ids (if (boolean? blocked)
(into ids (->> ids (mapcat #(cph/get-children-ids objects %))))
(into ids (->> ids (mapcat #(cph/get-children-ids objects %))))
ids)]
(rx/of (dch/update-shapes ids update-fn))))))

View File

@@ -20,6 +20,7 @@
[app.common.geom.shapes.bounds :as gsb]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.types.file :as ctf]
[app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst]
[app.config :as cfg]
@@ -378,18 +379,20 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc component-symbol
[{:keys [id data] :as props}]
(let [name (:name data)
path (:path data)
objects (-> (:objects data)
(adapt-objects-for-shape id))
root-shape (get objects id)
[{:keys [component] :as props}]
(let [name (:name component)
path (:path component)
root-id (or (:main-instance-id component)
(:id component))
objects (adapt-objects-for-shape (:objects component)
root-id)
root-shape (get objects root-id)
selrect (:selrect root-shape)
main-instance-id (:main-instance-id data)
main-instance-page (:main-instance-page data)
main-instance-x (:main-instance-x data)
main-instance-y (:main-instance-y data)
main-instance-id (:main-instance-id component)
main-instance-page (:main-instance-page component)
main-instance-x (:main-instance-x component)
main-instance-y (:main-instance-y component)
vbox
(format-viewbox
@@ -406,7 +409,7 @@
(mf/deps objects)
(fn [] (frame-wrapper-factory objects)))]
[:> "symbol" #js {:id (str id)
[:> "symbol" #js {:id (str root-id)
:viewBox vbox
"penpot:path" path
"penpot:main-instance-id" main-instance-id
@@ -436,9 +439,10 @@
:style {:display (when-not (some? children) "none")}
:fill "none"}
[:defs
(for [[id data] (source data)]
[:& component-symbol {:id id :key (dm/str id) :data data}])]
(for [[id component] (source data)]
(let [component (ctf/load-component-objects data component)]
[:& component-symbol {:key (dm/str id) :component component}]))]
children]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -498,6 +502,7 @@
(let [;; Join all components objects into a single map
objects (->> (source data)
(vals)
(map (partial ctf/load-component-objects data))
(map :objects)
(reduce conj))]
(rx/concat

View File

@@ -106,7 +106,7 @@
:viewer
(let [{:keys [query-params path-params]} route
{:keys [index share-id section page-id] :or {section :interactions}} query-params
{:keys [index share-id section page-id interactions-mode] :or {section :interactions interactions-mode :show-on-click}} query-params
{:keys [file-id]} path-params]
(if (:token query-params)
[:& viewer/breaking-change-notice]
@@ -114,7 +114,12 @@
:file-id file-id
:section section
:index index
:share-id share-id}]))
:share-id share-id
:interactions-mode (keyword interactions-mode)
:interactions-show? (case (keyword interactions-mode)
:hide false
:show true
:show-on-click false)}]))
:workspace
(let [project-id (some-> params :path :project-id uuid)

View File

@@ -43,7 +43,6 @@
(mf/use-fn
(mf/deps on-change)
(fn [event]
(js/console.log event)
(let [value (-> (dom/get-current-target event)
(dom/get-data "value")
(d/read-string))]

View File

@@ -0,0 +1,70 @@
(ns app.main.ui.css-cursors
(:require
[app.common.data.macros :as dm]
[app.main.ui.cursors :as cur]
[app.util.css :as css]))
;; angle per rotation
(def angle-step 10)
(defn get-static
"Returns the class name of a static cursor"
[name]
(dm/str "cursor-" name))
(defn get-dynamic
"Returns the class name of a dynamic cursor (with rotation)"
[name rotation]
(dm/str "cursor-" name "-" (mod (* (.floor js/Math (/ rotation angle-step)) angle-step) 360)))
(defn init-static-cursor-style
"Initializes a static cursor style"
[style name value]
(.add style (dm/str ".cursor-" name) (js-obj "cursor" (dm/str value " !important"))))
(defn init-dynamic-cursor-style
"Initializes a dynamic cursor style"
[style name fn]
(let [rotations (seq (range 0 360 angle-step))]
(doseq [rotation rotations]
(.add style (dm/str ".cursor-" name "-" rotation) (js-obj "cursor" (dm/str (fn rotation) " !important"))))))
(defn init-styles
"Initializes all cursor styles"
[]
(let [style (css/create-style "css-cursors")]
;; static
(init-static-cursor-style style "comments" cur/comments)
(init-static-cursor-style style "create-artboard" cur/create-artboard)
(init-static-cursor-style style "create-ellipse" cur/create-ellipse)
(init-static-cursor-style style "create-polygon" cur/create-polygon)
(init-static-cursor-style style "create-rectangle" cur/create-rectangle)
(init-static-cursor-style style "create-shape" cur/create-shape)
(init-static-cursor-style style "duplicate" cur/duplicate)
(init-static-cursor-style style "hand" cur/hand)
(init-static-cursor-style style "move-pointer" cur/move-pointer)
(init-static-cursor-style style "pen" cur/pen)
(init-static-cursor-style style "pen-node" cur/pen-node)
(init-static-cursor-style style "pencil" cur/pencil)
(init-static-cursor-style style "picker" cur/picker)
(init-static-cursor-style style "pointer-inner" cur/pointer-inner)
(init-static-cursor-style style "pointer-move" cur/pointer-move)
(init-static-cursor-style style "pointer-node" cur/pointer-node)
(init-static-cursor-style style "resize-alt" cur/resize-alt)
(init-static-cursor-style style "zoom" cur/zoom)
(init-static-cursor-style style "zoom-in" cur/zoom-in)
(init-static-cursor-style style "zoom-out" cur/zoom-out)
;; dynamic
(init-dynamic-cursor-style style "resize-ew" cur/resize-ew)
(init-dynamic-cursor-style style "resize-nesw" cur/resize-nesw)
(init-dynamic-cursor-style style "resize-ns" cur/resize-ns)
(init-dynamic-cursor-style style "resize-nwse" cur/resize-nwse)
(init-dynamic-cursor-style style "rotate" cur/rotate)
(init-dynamic-cursor-style style "text" cur/text)
(init-dynamic-cursor-style style "scale-ew" cur/scale-ew)
(init-dynamic-cursor-style style "scale-nesw" cur/scale-nesw)
(init-dynamic-cursor-style style "scale-ns" cur/scale-ns)
(init-dynamic-cursor-style style "scale-nwse" cur/scale-nwse)
(init-dynamic-cursor-style style "resize-ew-2" cur/resize-ew-2)
(init-dynamic-cursor-style style "resize-ns-2" cur/resize-ns-2)))

View File

@@ -7,8 +7,6 @@
(ns app.main.ui.cursors
(:require-macros [app.main.ui.cursors :refer [cursor-ref cursor-fn]])
(:require
[app.common.data.macros :as dm]
[app.util.css :as css]
[app.util.timers :as ts]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@@ -43,7 +41,7 @@
(def rotate (cursor-fn :rotate 90))
(def text (cursor-fn :text 0))
;; text
;; Text
(def scale-ew (cursor-fn :scale-h 0))
(def scale-nesw (cursor-fn :scale-h 45))
(def scale-ns (cursor-fn :scale-h 90))
@@ -52,63 +50,6 @@
;;
(def resize-ew-2 (cursor-fn :resize-h-2 0))
(def resize-ns-2 (cursor-fn :resize-h-2 90))
(defn get-static
[name]
(dm/str "cursor-" name))
(defn get-dynamic
[name rotation]
(dm/str "cursor-" name "-" (.floor js/Math rotation)))
(defn init-static-cursor-style
[style name value]
(.add style (dm/str ".cursor-" name) (js-obj "cursor" (dm/str value " !important"))))
(defn init-dynamic-cursor-style
[style name fn]
(let [rotations (seq (range 0 360 1))]
(doseq [rotation rotations]
(.add style (dm/str ".cursor-" name "-" rotation) (js-obj "cursor" (dm/str (fn rotation) " !important"))))))
(defn init-styles
[]
(let [style (css/create-style)]
;; static
(init-static-cursor-style style "comments" comments)
(init-static-cursor-style style "create-artboard" create-artboard)
(init-static-cursor-style style "create-ellipse" create-ellipse)
(init-static-cursor-style style "create-polygon" create-polygon)
(init-static-cursor-style style "create-rectangle" create-rectangle)
(init-static-cursor-style style "create-shape" create-shape)
(init-static-cursor-style style "duplicate" duplicate)
(init-static-cursor-style style "hand" hand)
(init-static-cursor-style style "move-pointer" move-pointer)
(init-static-cursor-style style "pen" pen)
(init-static-cursor-style style "pen-node" pen-node)
(init-static-cursor-style style "pencil" pencil)
(init-static-cursor-style style "picker" picker)
(init-static-cursor-style style "pointer-inner" pointer-inner)
(init-static-cursor-style style "pointer-move" pointer-move)
(init-static-cursor-style style "pointer-node" pointer-node)
(init-static-cursor-style style "resize-alt" resize-alt)
(init-static-cursor-style style "zoom" zoom)
(init-static-cursor-style style "zoom-in" zoom-in)
(init-static-cursor-style style "zoom-out" zoom-out)
;; dynamic
(init-dynamic-cursor-style style "resize-ew" resize-ew)
(init-dynamic-cursor-style style "resize-nesw" resize-nesw)
(init-dynamic-cursor-style style "resize-ns" resize-ns)
(init-dynamic-cursor-style style "resize-nwse" resize-nwse)
(init-dynamic-cursor-style style "rotate" rotate)
(init-dynamic-cursor-style style "text" text)
(init-dynamic-cursor-style style "scale-ew" scale-ew)
(init-dynamic-cursor-style style "scale-nesw" scale-nesw)
(init-dynamic-cursor-style style "scale-ns" scale-ns)
(init-dynamic-cursor-style style "scale-nwse" scale-nwse)
(init-dynamic-cursor-style style "resize-ew-2" resize-ew-2)
(init-dynamic-cursor-style style "resize-ns-2" resize-ns-2)))
(mf/defc debug-preview
{::mf/wrap-props false}

View File

@@ -20,7 +20,7 @@
[app.main.data.workspace.state-helpers :as wsh]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.formats :as fmt]
[app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]]
[app.util.dom :as dom]
@@ -516,9 +516,9 @@
:on-pointer-down on-pointer-down
:on-lost-pointer-capture on-lost-pointer-capture
:on-pointer-move on-pointer-move
:class (when (or hover? selected?)
(if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90)))
:style {:fill (if (or hover? selected?) warning-color "none")
:cursor (when (or hover? selected?)
(if (= (:resize-axis rect-data) :x) (cur/resize-ew 0) (cur/resize-ew 90)))
:opacity (if selected? 0.5 0.25)}}]))
(mf/defc margin-rects [{:keys [shape frame zoom alt? shift?]}]

View File

@@ -194,8 +194,13 @@
(obj/set! "fill" (obj/get svg-attrs "fill"))
(obj/set! "fillOpacity" (obj/get svg-attrs "fillOpacity")))
;; If contains svg-attrs the origin is svg. If it's not svg origin
;; we setup the default fill as black
;; If the shape comes from an imported SVG (we know because it has
;; the :svg-attrs atribute), and it does not have an own fill, we
;; set a default black fill. This will be inherited by child nodes,
;; and is for emulating the behavior of standard SVG, in that a node
;; that has no explicit fill has a default fill of black.
;; This may be reset to normal if a Penpot frame shape appears below
;; (see main.ui.shapes.frame/frame-container).
(and (contains? shape :svg-attrs)
(#{:svg-raw :group} (:type shape))
(empty? (:fills shape)))

View File

@@ -370,9 +370,7 @@
(-> props
(obj/set! "style" style)))
(and (some? svg-attrs)
(obj/contains? svg-attrs "fill")
(empty? (:fills shape)))
(and (some? svg-attrs) (empty? (:fills shape)))
(let [style
(-> (obj/get child "props")
(obj/get "style")
@@ -407,7 +405,7 @@
(obj/set! "style" style)))
:else
nil)))
(obj/create))))
(defn build-stroke-props [position child value render-id]
(let [props (-> (obj/get child "props")
@@ -430,11 +428,10 @@
position (or (obj/get props "position") 0)
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))
fill-props (build-fill-props shape child position render-id)]
(when (some? fill-props)
[:g.fills {:id (dm/fmt "fills-%" (:id shape))}
[:> elem-name (-> (obj/get child "props")
(obj/clone)
(obj/merge! fill-props))]])))
[:g.fills {:id (dm/fmt "fills-%" (:id shape))}
[:> elem-name (-> (obj/get child "props")
(obj/clone)
(obj/merge! fill-props))]]))
(mf/defc shape-strokes
{::mf/wrap-props false}

View File

@@ -69,7 +69,10 @@
:className "frame-background"}))
path? (some? (.-d props))]
[:*
[:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))}
[:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))
:fill "none"} ;; A frame sets back normal fill behavior (default transparent). It may have
;; been changed to default black if a shape coming from an imported SVG file
;; is rendered. See main.ui.shapes.attrs/add-style-attrs.
[:& frame-clip-def {:shape shape :render-id render-id}]
[:& shape-fills {:shape shape}

View File

@@ -158,7 +158,8 @@
:index index
:page page
:users users
:frame frame}]
:frame frame
:interactions-mode interactions-mode}]
[:div.viewer-wrapper
{:style {:width (:width wrapper-size)
@@ -178,7 +179,7 @@
:size orig-size
:page page
:users users
:interactions-mode :hide}]])
:interactions-mode interactions-mode}]])
[:div.viewport-container
{:ref current-viewport-ref
@@ -214,7 +215,7 @@
(mf/defc viewer
[{:keys [params data]}]
(let [{:keys [page-id section index]} params
(let [{:keys [page-id section index interactions-mode]} params
{:keys [file users project permissions]} data
allowed (or
@@ -281,9 +282,6 @@
(mf/with-memo [size orig-size zoom]
(calculate-wrapper size orig-size zoom))
interactions-mode
(:interactions-mode local)
click-on-screen
(mf/use-callback
(fn [event]
@@ -484,7 +482,8 @@
:frame frame
:permissions permissions
:zoom zoom
:section section}]
:section section
:interactions-mode interactions-mode}]
[:div.thumbnail-close {:on-click #(st/emit! dv/close-thumbnails-panel)
:class (dom/classnames :invisible (not (:show-thumbnails local false)))}]
[:& thumbnails-panel {:frames frames
@@ -516,7 +515,8 @@
:local local
:size size
:index index
:viewer-pagination viewer-pagination}]
:viewer-pagination viewer-pagination
:interactions-mode interactions-mode}]
[:& (mf/provider ctx/current-zoom) {:value zoom}

View File

@@ -70,7 +70,7 @@
(mf/defc header-options
[{:keys [section zoom page file index permissions]}]
[{:keys [section zoom page file index permissions interactions-mode]}]
(let [fullscreen? (mf/deref fullscreen-ref)
toggle-fullscreen
@@ -95,7 +95,7 @@
:interactions [:*
(when index
[:& flows-menu {:page page :index index}])
[:& interactions-menu]]
[:& interactions-menu {:interactions-mode interactions-mode}]]
:comments [:& comments-menu]
[:div.view-options])
@@ -184,7 +184,7 @@
(mf/defc header
[{:keys [project file page frame zoom section permissions index]}]
[{:keys [project file page frame zoom section permissions index interactions-mode]}]
(let [go-to-dashboard
#(st/emit! (dv/go-to-dashboard))
@@ -238,4 +238,5 @@
:page page
:file file
:index index
:zoom zoom}]]))
:zoom zoom
:interactions-mode interactions-mode}]]))

View File

@@ -16,7 +16,6 @@
[app.common.uuid :as uuid]
[app.main.data.comments :as dcm]
[app.main.data.viewer :as dv]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.hooks :as h]
@@ -201,18 +200,19 @@
[:span.label (:name flow)]])]]])))
(mf/defc interactions-menu
[]
(let [local (mf/deref refs/viewer-local)
mode (:interactions-mode local)
show-dropdown? (mf/use-state false)
[{:keys [interactions-mode]}]
(let [show-dropdown? (mf/use-state false)
toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
select-mode
(mf/use-callback
(fn [mode]
(st/emit! (dv/set-interactions-mode mode))))]
(mf/use-fn
(fn [event]
(let [mode (some-> (dom/get-current-target event)
(dom/get-data "mode")
(keyword))]
(dom/stop-propagation event)
(st/emit! (dv/set-interactions-mode mode)))))]
[:div.view-options {:on-click toggle-dropdown}
[:span.label (tr "viewer.header.interactions")]
@@ -220,18 +220,21 @@
[:& dropdown {:show @show-dropdown?
:on-close hide-dropdown}
[:ul.dropdown.with-check
[:li {:class (dom/classnames :selected (= mode :hide))
:on-click #(select-mode :hide)}
[:li {:class (dom/classnames :selected (= interactions-mode :hide))
:on-click select-mode
:data-mode :hide}
[:span.icon i/tick]
[:span.label (tr "viewer.header.dont-show-interactions")]]
[:li {:class (dom/classnames :selected (= mode :show))
:on-click #(select-mode :show)}
[:li {:class (dom/classnames :selected (= interactions-mode :show))
:on-click select-mode
:data-mode :show}
[:span.icon i/tick]
[:span.label (tr "viewer.header.show-interactions")]]
[:li {:class (dom/classnames :selected (= mode :show-on-click))
:on-click #(select-mode :show-on-click)}
[:li {:class (dom/classnames :selected (= interactions-mode :show-on-click))
:on-click select-mode
:data-mode :show-on-click}
[:span.icon i/tick]
[:span.label (tr "viewer.header.show-interactions-on-click")]]]]]))

View File

@@ -77,6 +77,7 @@
on-thread-click
(mf/use-callback
(mf/deps page-id)
(fn [thread]
(when (not= page-id (:page-id thread))
(st/emit! (dw/go-to-page (:page-id thread))))

View File

@@ -388,7 +388,7 @@
(mf/defc context-menu-prototype
[{:keys [shapes]}]
(let [options (mf/deref refs/workspace-page-options)
options-mode (mf/deref refs/options-mode)
options-mode (mf/deref refs/options-mode-global)
do-add-flow #(st/emit! (dwi/add-flow-selected-frame))
do-remove-flow #(st/emit! (dwi/remove-flow (:id %)))
flows (:flows options)
@@ -439,7 +439,8 @@
has-component? (some true? (map #(contains? % :component-id) shapes))
is-component? (and single? (-> shapes first :component-id some?))
is-non-root? (and single? (ctk/in-component-copy-not-root? (first shapes)))
in-copy-not-root? (some true? (map #(ctk/in-component-copy-not-root? %) shapes))
objects (deref refs/workspace-page-objects)
touched? (and single? (cph/component-touched? objects (:id (first shapes))))
can-update-main? (or (not components-v2) touched?)
@@ -460,8 +461,11 @@
is-dangling? (nil? (if local-component?
(ctkl/get-component workspace-data component-id)
(ctf/get-component workspace-libraries component-file component-id)))
lib-exists? (and (not local-component?)
(some? (get workspace-libraries component-file)))
do-add-component #(st/emit! (dwl/add-component))
do-add-multiple-components #(st/emit! (dwl/add-multiple-components))
do-detach-component #(st/emit! (dwl/detach-component id))
do-detach-component-in-bulk #(st/emit! dwl/detach-selected-components)
do-reset-component #(st/emit! (dwl/reset-component id))
@@ -504,12 +508,15 @@
:on-accept do-update-component-in-bulk}))))]
[:*
[:*
(when (or (not is-non-root?) (and has-component? (not single?)))
(when (or (not in-copy-not-root?) (and has-component? (not single?)))
[:& menu-separator])
(when-not is-non-root?
(when-not in-copy-not-root?
[:& menu-entry {:title (tr "workspace.shape.menu.create-component")
:shortcut (sc/get-tooltip :create-component)
:on-click do-add-component}])
(when-not (or single? in-copy-not-root?)
[:& menu-entry {:title (tr "workspace.shape.menu.create-multiple-components")
:on-click do-add-multiple-components}])
(when (and has-component? (not single?))
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.detach-instances-in-bulk")
@@ -563,7 +570,7 @@
(when can-update-main?
[:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
:on-click do-reset-component}])
(when components-v2
(when (and components-v2 lib-exists?)
[:& menu-entry {:title (tr "workspace.shape.menu.restore-main")
:on-click do-restore-component}])]
[:*

View File

@@ -16,7 +16,7 @@
[app.main.snap :as snap]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.shapes.path.common :as pc]
[app.util.dom :as dom]

View File

@@ -17,7 +17,7 @@
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.shapes.text.styles :as sts]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]

View File

@@ -167,6 +167,9 @@
(ctkl/get-component workspace-data component-id)
(ctf/get-component workspace-libraries library-id component-id))
is-dangling? (nil? component)
lib-exists? (and (not local-component?)
(some? (get workspace-libraries library-id)))
on-menu-click
(mf/use-callback
@@ -242,22 +245,22 @@
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[:*
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
[(tr "workspace.shape.menu.update-main") do-update-component]])
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when can-update-main?
[(tr "workspace.shape.menu.update-main") do-update-component])
[(tr "workspace.shape.menu.show-main") do-show-component]])
(if is-dangling?
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when components-v2
(when (and components-v2 lib-exists?)
[(tr "workspace.shape.menu.restore-main") do-restore-component])]
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
(when can-update-main?
[:*
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
[(tr "workspace.shape.menu.update-main") do-update-remote-component]])
[(tr "workspace.shape.menu.reset-overrides") do-reset-component])
(when can-update-main?
[(tr "workspace.shape.menu.update-main") do-update-remote-component])
[(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]]
(when components-v2

View File

@@ -40,7 +40,7 @@
type (unchecked-get props "type")
values (unchecked-get props "values")
current-blend-mode (d/name (or (:blend-mode values) :normal))
current-blend-mode (or (:blend-mode values) :normal)
current-opacity (:opacity values)
state* (mf/use-state
@@ -119,24 +119,24 @@
options
(mf/with-memo [current-blend-mode]
(d/concat-vec
(when (= "multiple" current-blend-mode)
[{:value "multiple" :label "--"}])
[{:value "normal" :label (tr "workspace.options.layer-options.blend-mode.normal")}
{:value "darken" :label (tr "workspace.options.layer-options.blend-mode.darken")}
{:value "multiply" :label (tr "workspace.options.layer-options.blend-mode.multiply")}
{:value "color-burn" :label (tr "workspace.options.layer-options.blend-mode.color-burn")}
{:value "lighten" :label (tr "workspace.options.layer-options.blend-mode.lighten")}
{:value "screen" :label (tr "workspace.options.layer-options.blend-mode.screen")}
{:value "color-dodge" :label (tr "workspace.options.layer-options.blend-mode.color-dodge")}
{:value "overlay" :label (tr "workspace.options.layer-options.blend-mode.overlay")}
{:value "soft-light" :label (tr "workspace.options.layer-options.blend-mode.soft-light")}
{:value "hard-light" :label (tr "workspace.options.layer-options.blend-mode.hard-light")}
{:value "difference" :label (tr "workspace.options.layer-options.blend-mode.difference")}
{:value "exclusion" :label (tr "workspace.options.layer-options.blend-mode.exclusion")}
{:value "hue" :label (tr "workspace.options.layer-options.blend-mode.hue")}
{:value "saturation" :label (tr "workspace.options.layer-options.blend-mode.saturation")}
{:value "color" :label (tr "workspace.options.layer-options.blend-mode.color")}
{:value "luminosity" :label (tr "workspace.options.layer-options.blend-mode.luminosity")}]))]
(when (= :multiple current-blend-mode)
[{:value :multiple :label "--"}])
[{:value :normal :label (tr "workspace.options.layer-options.blend-mode.normal")}
{:value :darken :label (tr "workspace.options.layer-options.blend-mode.darken")}
{:value :multiply :label (tr "workspace.options.layer-options.blend-mode.multiply")}
{:value :color-burn :label (tr "workspace.options.layer-options.blend-mode.color-burn")}
{:value :lighten :label (tr "workspace.options.layer-options.blend-mode.lighten")}
{:value :screen :label (tr "workspace.options.layer-options.blend-mode.screen")}
{:value :color-dodge :label (tr "workspace.options.layer-options.blend-mode.color-dodge")}
{:value :overlay :label (tr "workspace.options.layer-options.blend-mode.overlay")}
{:value :soft-light :label (tr "workspace.options.layer-options.blend-mode.soft-light")}
{:value :hard-light :label (tr "workspace.options.layer-options.blend-mode.hard-light")}
{:value :difference :label (tr "workspace.options.layer-options.blend-mode.difference")}
{:value :exclusion :label (tr "workspace.options.layer-options.blend-mode.exclusion")}
{:value :hue :label (tr "workspace.options.layer-options.blend-mode.hue")}
{:value :saturation :label (tr "workspace.options.layer-options.blend-mode.saturation")}
{:value :color :label (tr "workspace.options.layer-options.blend-mode.color")}
{:value :luminosity :label (tr "workspace.options.layer-options.blend-mode.luminosity")}]))]
(mf/with-effect [current-blend-mode
option-highlighted?

View File

@@ -8,6 +8,7 @@
(:require-macros [app.main.style :refer [css]])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
@@ -134,6 +135,7 @@
(dom/classnames
:element-list-body true
:selected selected?))
:data-test (dm/str "page-" id)
:tab-index "0"
:on-click navigate-fn
:on-double-click on-double-click

View File

@@ -330,7 +330,7 @@
:key (str "viewport" page-id)
:view-box (utils/format-viewbox vbox)
:ref on-viewport-ref
:class (dm/str @cursor (when drawing-tool "drawing"))
:class (dm/str @cursor (when drawing-tool " drawing"))
:style {:touch-action "none"}
:fill "none"

View File

@@ -17,7 +17,7 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.formats :as fmt]
[app.main.ui.workspace.viewport.rules :as rules]
[app.util.dom :as dom]

View File

@@ -14,7 +14,7 @@
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.workspace.shapes :as shapes]
[app.util.dom :as dom]
[app.util.http :as http]

View File

@@ -17,7 +17,7 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
[app.util.dom :as dom]
[app.util.object :as obj]

View File

@@ -9,7 +9,7 @@
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.main.ui.cursors :as cur]
[app.main.ui.css-cursors :as cur]
[app.main.ui.formats :refer [format-number]]))
(defn format-viewbox [vbox]

View File

@@ -14,7 +14,7 @@
[app.common.types.components-list :as ctkl]
[app.common.uri :as u]
[app.main.data.fonts :as df]
[app.main.features :as features]
[app.main.features :as feat]
[app.main.render :as render]
[app.main.repo :as repo]
[app.main.store :as st]
@@ -52,6 +52,7 @@
(defn ^:export init
[]
(st/emit! (feat/initialize))
(init-ui))
(defn reinit
@@ -95,7 +96,7 @@
(mf/defc object-svg
[{:keys [page-id file-id object-id render-embed?]}]
(let [components-v2 (features/use-feature :components-v2)
(let [components-v2 (feat/use-feature :components-v2)
fetch-state (mf/use-fn
(mf/deps file-id page-id object-id components-v2)
(fn []
@@ -135,7 +136,7 @@
(mf/defc objects-svg
[{:keys [page-id file-id object-ids render-embed?]}]
(let [components-v2 (features/use-feature :components-v2)
(let [components-v2 (feat/use-feature :components-v2)
fetch-state (mf/use-fn
(mf/deps file-id page-id components-v2)
(fn []

View File

@@ -13,15 +13,25 @@
(defn add-rule
"Adds a CSS rule to a CSS Style Sheet"
[styleSheet selector declarations]
(.insertRule styleSheet (dm/str selector " {" (declarations->str declarations) "}")))
(let [rule (dm/str selector " { " (declarations->str declarations) " }")]
(.insertRule styleSheet rule (.-length (.-cssRules styleSheet)))))
(defn wrap-style-sheet
[style]
#js {:add (partial add-rule (.-sheet style))})
;; FIXME: Maybe we should rename this to `create-dynamic-style`?
(defn create-style
"Creates a new CSS Style Sheet and returns an object that allows adding rules to it"
[]
(let [style (dom/create-element "style")]
(dom/set-attribute! style "type" "text/css")
(dom/append-child! js/document.head style)
(js-obj "add" (partial add-rule (.-sheet style)))))
[id]
(let [element (dom/get-element id)]
(if (some? element)
(wrap-style-sheet element)
(let [style (dom/create-element "style")]
(dom/set-attribute! style "id" id)
(dom/set-attribute! style "type" "text/css")
(dom/append-child! js/document.head style)
(wrap-style-sheet style)))))

View File

@@ -154,7 +154,8 @@
(let [page (current-page state)
libraries (wsh/get-libraries state)
changes (pcb/empty-changes nil (:id page))
changes (-> (pcb/empty-changes nil (:id page))
(pcb/with-objects (:objects page)))
[new-shape changes]
(dwlh/generate-instantiate-component changes

View File

@@ -495,7 +495,7 @@ msgid "dashboard.export.title"
msgstr "Export files"
msgid "dashboard.fonts.deleted-placeholder"
msgstr "Font deleted"
msgstr "Missing font"
#: src/app/main/ui/dashboard/fonts.cljs
msgid "dashboard.fonts.dismiss-all"
@@ -4367,6 +4367,9 @@ msgstr "Selection to board"
msgid "workspace.shape.menu.create-component"
msgstr "Create component"
msgid "workspace.shape.menu.create-multiple-components"
msgstr "Create multiple components"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.cut"
msgstr "Cut"

View File

@@ -502,7 +502,7 @@ msgid "dashboard.export.title"
msgstr "Exportar ficheros"
msgid "dashboard.fonts.deleted-placeholder"
msgstr "Fuente eliminada"
msgstr "Fuente no encontrada"
#: src/app/main/ui/dashboard/fonts.cljs
msgid "dashboard.fonts.dismiss-all"
@@ -4469,6 +4469,9 @@ msgstr "Tablero de selección"
msgid "workspace.shape.menu.create-component"
msgstr "Crear componente"
msgid "workspace.shape.menu.create-multiple-components"
msgstr "Crear múltiples componentes"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.cut"
msgstr "Cortar"

View File

@@ -181,9 +181,9 @@ function build-docker-images {
pushd ./docker/images;
docker build -t penpotapp/frontend:$CURRENT_BRANCH -f Dockerfile.frontend .;
docker build -t penpotapp/backend:$CURRENT_BRANCH -f Dockerfile.backend .;
docker build -t penpotapp/exporter:$CURRENT_BRANCH -f Dockerfile.exporter .;
docker build -t penpotapp/frontend:$CURRENT_BRANCH -t penpotapp/frontend:latest -f Dockerfile.frontend .;
docker build -t penpotapp/backend:$CURRENT_BRANCH -t penpotapp/backend:latest -f Dockerfile.backend .;
docker build -t penpotapp/exporter:$CURRENT_BRANCH -t penpotapp/exporter:latest -f Dockerfile.exporter .;
popd;
}