mirror of
https://github.com/penpot/penpot.git
synced 2025-03-11 23:31:21 -05:00
commit
59d44c41e4
34 changed files with 343 additions and 148 deletions
22
CHANGES.md
22
CHANGES.md
|
@ -9,8 +9,30 @@
|
|||
- Add advanced prototyping [#244](https://tree.taiga.io/project/penpot/us/244).
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix problem with overflow dropdown on stroke-cap [#1216](https://github.com/penpot/penpot/issues/1216)
|
||||
- Fix menu context for single element nested in components [#1186](https://github.com/penpot/penpot/issues/1186)
|
||||
- Fix error screen when operations over comments fail [#1219](https://github.com/penpot/penpot/issues/1219)
|
||||
- Fix undo problem when changing typography/color from library [#1230](https://github.com/penpot/penpot/issues/1230)
|
||||
- Fix problem with text margin while rendering [#1231](https://github.com/penpot/penpot/issues/1231)
|
||||
- Fix problem with masked texts on exporting [Taiga #2116](https://tree.taiga.io/project/penpot/issue/2116)
|
||||
- Fix text editor enter behaviour with centered texts [Taiga #2126](https://tree.taiga.io/project/penpot/issue/2126)
|
||||
- Fix residual stroke on imported svg [Taiga #2125](https://tree.taiga.io/project/penpot/issue/2125)
|
||||
- Add links for terms of service and privacy policy in register checkbox [Taiga #2020](https://tree.taiga.io/project/penpot/issue/2020)
|
||||
- Allow three character hex and web colors in color picker hex input [#1184](https://github.com/penpot/penpot/issues/1184)
|
||||
- Allow lowercase search for fonts [#1180](https://github.com/penpot/penpot/issues/1180)
|
||||
- Fix group renaming problem [Taiga #1969](https://tree.taiga.io/project/penpot/issue/1969)
|
||||
- Fix export group with shadows on children [Taiga #2036](https://tree.taiga.io/project/penpot/issue/2036)
|
||||
- Fix zoom context menu in viewer [Taiga #2041](https://tree.taiga.io/project/penpot/issue/2041)
|
||||
- Fix stroke caps adjustments in relation with stroke size [Taiga #2123](https://tree.taiga.io/project/penpot/issue/2123)
|
||||
- Fix problem duplicating paths [Taiga #2147](https://tree.taiga.io/project/penpot/issue/2147)
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
### :boom: Breaking changes
|
||||
|
||||
- Some stroke-caps can change behaviour
|
||||
- Text display bug fix could potentialy make some texts jump a line
|
||||
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
## 1.8.3-alpha
|
||||
|
|
|
@ -141,6 +141,7 @@
|
|||
(d/export gpr/points->selrect)
|
||||
(d/export gpr/points->rect)
|
||||
(d/export gpr/center->rect)
|
||||
(d/export gpr/join-rects)
|
||||
|
||||
(d/export gtr/move)
|
||||
(d/export gtr/absolute-move)
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
(ns app.common.geom.shapes.bool
|
||||
(:require
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.geom.shapes.transforms :as gtr]
|
||||
[app.common.path.bool :as pb]
|
||||
[app.common.path.shapes-to-path :as stp]))
|
||||
|
||||
|
@ -19,7 +21,12 @@
|
|||
(mapv :content)
|
||||
(pb/content-bool (:bool-type shape)))
|
||||
|
||||
[points selrect] (gsp/content->points+selrect shape content)]
|
||||
[points selrect]
|
||||
(if (empty? content)
|
||||
(let [selrect (gtr/selection-rect children)
|
||||
points (gpr/rect->points selrect)]
|
||||
[points selrect])
|
||||
(gsp/content->points+selrect shape content))]
|
||||
(-> shape
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points points))))
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
[app.common.geom.shapes.common :as gsc]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.math :as mth]
|
||||
[app.common.path.commands :as upc]))
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.subpaths :as sp]))
|
||||
|
||||
(def ^:const curve-curve-precision 0.1)
|
||||
(def ^:const curve-range-precision 2)
|
||||
|
@ -818,19 +819,33 @@
|
|||
|
||||
(defn is-point-in-content?
|
||||
[point content]
|
||||
(let [selrect (content->selrect content)
|
||||
ray-line [point (gpt/point (inc (:x point)) (:y point))]
|
||||
|
||||
(letfn [(cast-ray [cmd]
|
||||
(let [ray-line [point (gpt/point (inc (:x point)) (:y point))]]
|
||||
(case (:command cmd)
|
||||
:line-to (ray-line-intersect point (command->line cmd))
|
||||
:curve-to (ray-curve-intersect ray-line (command->bezier cmd))
|
||||
#_:else [])))]
|
||||
closed-subpaths
|
||||
(->> content
|
||||
(sp/close-subpaths)
|
||||
(sp/get-subpaths)
|
||||
(filterv sp/is-closed?))
|
||||
|
||||
(->> content
|
||||
(mapcat cast-ray)
|
||||
(map second)
|
||||
(reduce +)
|
||||
(not= 0))))
|
||||
cast-ray
|
||||
(fn [cmd]
|
||||
(case (:command cmd)
|
||||
:line-to (ray-line-intersect point (command->line cmd))
|
||||
:curve-to (ray-curve-intersect ray-line (command->bezier cmd))
|
||||
#_:else []))
|
||||
|
||||
is-point-in-subpath?
|
||||
(fn [subpath]
|
||||
(and (gpr/contains-point? (content->selrect (:data subpath)) point)
|
||||
(->> (:data subpath)
|
||||
(mapcat cast-ray)
|
||||
(map second)
|
||||
(reduce +)
|
||||
(not= 0))))]
|
||||
|
||||
(and (gpr/contains-point? selrect point)
|
||||
(some is-point-in-subpath? closed-subpaths))))
|
||||
|
||||
(defn split-line-to
|
||||
"Given a point and a line-to command will create a two new line-to commands
|
||||
|
|
|
@ -48,6 +48,16 @@
|
|||
(defn rect->selrect [rect]
|
||||
(-> rect rect->points points->selrect))
|
||||
|
||||
(defn join-rects [rects]
|
||||
(let [minx (transduce (comp (map :x) (remove nil?)) min ##Inf rects)
|
||||
miny (transduce (comp (map :y) (remove nil?)) min ##Inf rects)
|
||||
maxx (transduce (comp (map #(+ (:x %) (:width %))) (remove nil?)) max ##-Inf rects)
|
||||
maxy (transduce (comp (map #(+ (:y %) (:height %))) (remove nil?)) max ##-Inf rects)]
|
||||
{:x minx
|
||||
:y miny
|
||||
:width (- maxx minx)
|
||||
:height (- maxy miny)}))
|
||||
|
||||
(defn join-selrects [selrects]
|
||||
(let [minx (transduce (comp (map :x1) (remove nil?)) min ##Inf selrects)
|
||||
miny (transduce (comp (map :y1) (remove nil?)) min ##Inf selrects)
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
[app.common.spec :as us]
|
||||
[app.common.text :as txt]))
|
||||
|
||||
|
||||
;; --- Relative Movement
|
||||
|
||||
(defn- move-selrect [selrect {dx :x dy :y}]
|
||||
|
@ -681,3 +680,12 @@
|
|||
(assoc :resize-transform (:resize-transform parent-modifiers)
|
||||
:resize-transform-inverse (:resize-transform-inverse parent-modifiers)))))
|
||||
|
||||
|
||||
(defn selection-rect
|
||||
"Returns a rect that contains all the shapes and is aware of the
|
||||
rotation of each shape. Mainly used for multiple selection."
|
||||
[shapes]
|
||||
(->> shapes
|
||||
(transform-shape)
|
||||
(map (comp gpr/points->selrect :points))
|
||||
(gpr/join-selrects)))
|
||||
|
|
|
@ -116,6 +116,9 @@
|
|||
(->> subpaths
|
||||
(reduce merge-with-candidate [candidate []]))))
|
||||
|
||||
(defn is-closed? [subpath]
|
||||
(pt= (:from subpath) (:to subpath)))
|
||||
|
||||
(defn close-subpaths
|
||||
"Searches a path for posible supaths that can create closed loops and merge them"
|
||||
[content]
|
||||
|
@ -127,7 +130,7 @@
|
|||
|
||||
(if (some? current)
|
||||
(let [[new-current new-subpaths]
|
||||
(if (pt= (:from current) (:to current))
|
||||
(if (is-closed? current)
|
||||
[current subpaths]
|
||||
(merge-paths current subpaths))]
|
||||
|
||||
|
|
|
@ -210,11 +210,10 @@
|
|||
:height (.. attrs -height -value)
|
||||
:colors (.split colors ",")}))
|
||||
|
||||
(extract-single-node [node]
|
||||
(extract-single-node [[shot node]]
|
||||
(log/trace :fn :extract-single-node)
|
||||
|
||||
(p/let [attrs (bw/eval! node extract-element-attrs)
|
||||
shot (bw/screenshot node {:omit-background? true :type "png"})]
|
||||
(p/let [attrs (bw/eval! node extract-element-attrs)]
|
||||
{:id (unchecked-get attrs "id")
|
||||
:x (unchecked-get attrs "x")
|
||||
:y (unchecked-get attrs "y")
|
||||
|
@ -223,13 +222,21 @@
|
|||
:colors (vec (unchecked-get attrs "colors"))
|
||||
:data shot}))
|
||||
|
||||
(resolve-text-node [page node]
|
||||
(p/let [attrs (bw/eval! node extract-element-attrs)
|
||||
id (unchecked-get attrs "id")
|
||||
text-node (bw/select page (str "#screenshot-text-" id " foreignObject"))
|
||||
shot (bw/screenshot text-node {:omit-background? true :type "png"})]
|
||||
[shot node]))
|
||||
|
||||
(clean-temp-data [{:keys [tempdir] :as node}]
|
||||
(p/do!
|
||||
(sh/rmdir! tempdir)
|
||||
(dissoc node :tempdir)))
|
||||
|
||||
(process-text-node [item]
|
||||
(process-text-node [page item]
|
||||
(-> (p/resolved item)
|
||||
(p/then (partial resolve-text-node page))
|
||||
(p/then extract-single-node)
|
||||
(p/then trace-node)
|
||||
(p/then clean-temp-data)))
|
||||
|
@ -237,7 +244,7 @@
|
|||
(process-text-nodes [page]
|
||||
(log/trace :fn :process-text-nodes)
|
||||
(-> (bw/select-all page "#screenshot foreignObject")
|
||||
(p/then (fn [nodes] (p/all (map process-text-node nodes))))))
|
||||
(p/then (fn [nodes] (p/all (map (partial process-text-node page) nodes))))))
|
||||
|
||||
(extract-svg [page]
|
||||
(p/let [dom (bw/select page "#screenshot")
|
||||
|
@ -271,7 +278,7 @@
|
|||
(p/let [page (render-in-page page rctx)]
|
||||
(extract-svg page)))]
|
||||
|
||||
(let [path (str "/render-object/" file-id "/" page-id "/" object-id)
|
||||
(let [path (str "/render-object/" file-id "/" page-id "/" object-id "?render-texts=true")
|
||||
uri (-> (u/uri (cf/get :public-uri))
|
||||
(assoc :path "/")
|
||||
(assoc :fragment path))
|
||||
|
|
|
@ -874,7 +874,6 @@
|
|||
position: relative;
|
||||
top: 2px;
|
||||
width: 100%;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.btn-options {
|
||||
|
@ -1539,7 +1538,8 @@
|
|||
right: 5px;
|
||||
top: 30px;
|
||||
z-index: 12;
|
||||
min-width: 200px;
|
||||
width: 200px;
|
||||
height: 320px;
|
||||
position: fixed;
|
||||
|
||||
& li.separator {
|
||||
|
|
|
@ -57,13 +57,6 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.zoom-widget {
|
||||
.dropdown {
|
||||
top: 45px;
|
||||
left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.view-options {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -77,7 +77,8 @@
|
|||
(watch [_ _ _]
|
||||
(->> (rp/mutation :create-comment-thread params)
|
||||
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)}))
|
||||
(rx/map #(partial created %)))))))
|
||||
(rx/map #(partial created %))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))))))
|
||||
|
||||
(defn update-comment-thread-status
|
||||
[{:keys [id] :as thread}]
|
||||
|
@ -87,7 +88,8 @@
|
|||
(watch [_ _ _]
|
||||
(let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)]
|
||||
(->> (rp/mutation :update-comment-thread-status {:id id})
|
||||
(rx/map (constantly done)))))))
|
||||
(rx/map (constantly done))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))))))
|
||||
|
||||
|
||||
(defn update-comment-thread
|
||||
|
@ -104,6 +106,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved})
|
||||
(rx/catch #(rx/throw {:type :comment-error}))
|
||||
(rx/ignore)))))
|
||||
|
||||
|
||||
|
@ -118,7 +121,8 @@
|
|||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(->> (rp/mutation :add-comment {:thread-id (:id thread) :content content})
|
||||
(rx/map #(partial created %)))
|
||||
(rx/map #(partial created %))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))
|
||||
(rx/of (refresh-comment-thread thread)))))))
|
||||
|
||||
(defn update-comment
|
||||
|
@ -132,6 +136,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/mutation :update-comment {:id id :content content})
|
||||
(rx/catch #(rx/throw {:type :comment-error}))
|
||||
(rx/ignore)))))
|
||||
|
||||
(defn delete-comment-thread
|
||||
|
@ -147,6 +152,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/mutation :delete-comment-thread {:id id})
|
||||
(rx/catch #(rx/throw {:type :comment-error}))
|
||||
(rx/ignore)))))
|
||||
|
||||
(defn delete-comment
|
||||
|
@ -160,6 +166,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/mutation :delete-comment {:id id})
|
||||
(rx/catch #(rx/throw {:type :comment-error}))
|
||||
(rx/ignore)))))
|
||||
|
||||
(defn refresh-comment-thread
|
||||
|
@ -171,7 +178,8 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/query :comment-thread {:file-id file-id :id id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
(rx/map #(partial fetched %))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))))))
|
||||
|
||||
(defn retrieve-comment-threads
|
||||
[file-id]
|
||||
|
@ -182,7 +190,8 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/query :comment-threads {:file-id file-id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
(rx/map #(partial fetched %))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))))))
|
||||
|
||||
(defn retrieve-comments
|
||||
[thread-id]
|
||||
|
@ -193,7 +202,8 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/query :comments {:thread-id thread-id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
(rx/map #(partial fetched %))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))))))
|
||||
|
||||
(defn retrieve-unread-comment-threads
|
||||
"A event used mainly in dashboard for retrieve all unread threads of a team."
|
||||
|
@ -204,7 +214,8 @@
|
|||
(watch [_ _ _]
|
||||
(let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))]
|
||||
(->> (rp/query :unread-comment-threads {:team-id team-id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
(rx/map #(partial fetched %))
|
||||
(rx/catch #(rx/throw {:type :comment-error})))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -1324,10 +1324,33 @@
|
|||
(ptk/reify ::show-context-menu
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [mdata (cond-> params
|
||||
(some? shape)
|
||||
(assoc :selected
|
||||
(wsh/lookup-selected state)))]
|
||||
(let [selected (wsh/lookup-selected state)
|
||||
objects (wsh/lookup-page-objects state)
|
||||
|
||||
selected-with-children
|
||||
(into []
|
||||
(mapcat #(cp/get-object-with-children % objects))
|
||||
selected)
|
||||
|
||||
head (get objects (first selected))
|
||||
|
||||
first-not-group-like?
|
||||
(and (= (count selected) 1)
|
||||
(not (contains? #{:group :bool} (:type head))))
|
||||
|
||||
has-invalid-shapes? (->> selected-with-children
|
||||
(some (comp #{:frame :text} :type)))
|
||||
|
||||
disable-booleans? (or (empty? selected) has-invalid-shapes? first-not-group-like?)
|
||||
disable-flatten? (or (empty? selected) has-invalid-shapes?)
|
||||
|
||||
mdata
|
||||
(-> params
|
||||
(assoc :disable-booleans? disable-booleans?)
|
||||
(assoc :disable-flatten? disable-flatten?)
|
||||
(cond-> (some? shape)
|
||||
(assoc :selected selected)))]
|
||||
|
||||
(assoc-in state [:workspace-local :context-menu] mdata)))))
|
||||
|
||||
(defn show-shape-context-menu
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.libraries-helpers :as dwlh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.i18n :refer [tr]]
|
||||
|
@ -134,10 +135,12 @@
|
|||
:color color}
|
||||
uchg {:type :mod-color
|
||||
:color prev}]
|
||||
(rx/of (dch/commit-changes {:redo-changes [rchg]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes {:redo-changes [rchg]
|
||||
:undo-changes [uchg]
|
||||
:origin it})
|
||||
(sync-file (:current-file-id state) file-id))))))
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(dwu/commit-undo-transaction))))))
|
||||
|
||||
(defn delete-color
|
||||
[{:keys [id] :as params}]
|
||||
|
@ -244,10 +247,12 @@
|
|||
:typography typography}
|
||||
uchg {:type :mod-typography
|
||||
:typography prev}]
|
||||
(rx/of (dch/commit-changes {:redo-changes [rchg]
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/commit-changes {:redo-changes [rchg]
|
||||
:undo-changes [uchg]
|
||||
:origin it})
|
||||
(sync-file (:current-file-id state) file-id))))))
|
||||
(sync-file (:current-file-id state) file-id)
|
||||
(dwu/commit-undo-transaction))))))
|
||||
|
||||
(defn delete-typography
|
||||
[id]
|
||||
|
|
|
@ -420,13 +420,13 @@
|
|||
(gpt/point (+ (:width obj) 50) 0)
|
||||
(gpt/point 0 0))
|
||||
|
||||
(let [obj-original (get objects id-original)
|
||||
obj-duplicated (get objects id-duplicated)
|
||||
distance (gpt/subtract (gpt/point obj-duplicated)
|
||||
(gpt/point obj-original))
|
||||
new-pos (gpt/add (gpt/point obj-duplicated) distance)
|
||||
delta (gpt/subtract new-pos (gpt/point obj))]
|
||||
delta))))
|
||||
(let [pt-original (-> (get objects id-original) :selrect gpt/point)
|
||||
pt-duplicated (-> (get objects id-duplicated) :selrect gpt/point)
|
||||
pt-obj (-> obj :selrect gpt/point)
|
||||
distance (gpt/subtract pt-duplicated pt-original)
|
||||
new-pos (gpt/add pt-duplicated distance)]
|
||||
|
||||
(gpt/subtract new-pos pt-obj)))))
|
||||
|
||||
(defn duplicate-selected [move-delta?]
|
||||
(ptk/reify ::duplicate-selected
|
||||
|
|
|
@ -180,12 +180,10 @@
|
|||
shape (get objects id)
|
||||
|
||||
merge-fn (fn [node attrs]
|
||||
(reduce-kv (fn [node k v]
|
||||
(if (= (get node k) v)
|
||||
(dissoc node k)
|
||||
(assoc node k v)))
|
||||
node
|
||||
attrs))
|
||||
(reduce-kv
|
||||
(fn [node k v] (assoc node k v))
|
||||
node
|
||||
attrs))
|
||||
|
||||
update-fn #(update-shape % txt/is-paragraph-node? merge-fn attrs)
|
||||
shape-ids (cond (= (:type shape) :text) [id]
|
||||
|
|
|
@ -94,6 +94,14 @@
|
|||
:type :error
|
||||
:timeout 3000}))))
|
||||
|
||||
(defmethod ptk/handle-error :comment-error
|
||||
[_]
|
||||
(ts/schedule
|
||||
(st/emitf
|
||||
(dm/show {:content "There was an error with the comment"
|
||||
:type :error
|
||||
:timeout 3000}))))
|
||||
|
||||
;; This is a pure frontend error that can be caused by an active
|
||||
;; assertion (assertion that is preserved on production builds). From
|
||||
;; the user perspective this should be treated as internal error.
|
||||
|
|
|
@ -152,12 +152,14 @@
|
|||
|
||||
:render-object
|
||||
(do
|
||||
(let [file-id (uuid (get-in route [:path-params :file-id]))
|
||||
page-id (uuid (get-in route [:path-params :page-id]))
|
||||
object-id (uuid (get-in route [:path-params :object-id]))]
|
||||
(let [file-id (uuid (get-in route [:path-params :file-id]))
|
||||
page-id (uuid (get-in route [:path-params :page-id]))
|
||||
object-id (uuid (get-in route [:path-params :object-id]))
|
||||
render-texts (get-in route [:query-params :render-texts])]
|
||||
[:& render/render-object {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id}]))
|
||||
:object-id object-id
|
||||
:render-texts? (and (some? render-texts) (= render-texts "true"))}]))
|
||||
|
||||
:render-sprite
|
||||
(do
|
||||
|
|
|
@ -210,8 +210,13 @@
|
|||
[:div.fields-row
|
||||
[:& fm/input {:name :accept-terms-and-privacy
|
||||
:class "check-primary"
|
||||
:label (tr "auth.terms-privacy-agreement")
|
||||
:type "checkbox"}]]
|
||||
:type "checkbox"}
|
||||
[:span
|
||||
(tr "auth.terms-privacy-agreement")
|
||||
[:div
|
||||
[:a {:href "https://penpot.app/terms.html" :target "_blank"} (tr "auth.terms-of-service")]
|
||||
[:span ",\u00A0"]
|
||||
[:a {:href "https://penpot.app/privacy.html" :target "_blank"} (tr "auth.privacy-policy")]]]]]
|
||||
|
||||
;; (when (contains? @cf/flags :newsletter-registration-check)
|
||||
;; [:div.fields-row
|
||||
|
|
|
@ -302,21 +302,22 @@
|
|||
(when-let [node (mf/ref-val ref)]
|
||||
(.scrollIntoViewIfNeeded ^js node))))
|
||||
|
||||
[:div.thread-content
|
||||
{:style {:top (str pos-y "px")
|
||||
:left (str pos-x "px")}
|
||||
:on-click dom/stop-propagation}
|
||||
(when (some? comment)
|
||||
[:div.thread-content
|
||||
{:style {:top (str pos-y "px")
|
||||
:left (str pos-x "px")}
|
||||
:on-click dom/stop-propagation}
|
||||
|
||||
[:div.comments
|
||||
[:& comment-item {:comment comment
|
||||
:users users
|
||||
:thread thread}]
|
||||
(for [item (rest comments)]
|
||||
[:*
|
||||
[:hr]
|
||||
[:& comment-item {:comment item :users users}]])
|
||||
[:div {:ref ref}]]
|
||||
[:& reply-form {:thread thread}]]))
|
||||
[:div.comments
|
||||
[:& comment-item {:comment comment
|
||||
:users users
|
||||
:thread thread}]
|
||||
(for [item (rest comments)]
|
||||
[:*
|
||||
[:hr]
|
||||
[:& comment-item {:comment item :users users}]])
|
||||
[:div {:ref ref}]]
|
||||
[:& reply-form {:thread thread}]])))
|
||||
|
||||
(mf/defc thread-bubble
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
(def use-form fm/use-form)
|
||||
|
||||
(mf/defc input
|
||||
[{:keys [label help-icon disabled form hint trim] :as props}]
|
||||
[{:keys [label help-icon disabled form hint trim children] :as props}]
|
||||
(let [input-type (get props :type "text")
|
||||
input-name (get props :name)
|
||||
more-classes (get props :class)
|
||||
|
@ -82,7 +82,7 @@
|
|||
(swap! form assoc-in [:touched input-name] true)))
|
||||
|
||||
props (-> props
|
||||
(dissoc :help-icon :form :trim)
|
||||
(dissoc :help-icon :form :trim :children)
|
||||
(assoc :id (name input-name)
|
||||
:value value
|
||||
:auto-focus auto-focus?
|
||||
|
@ -97,7 +97,13 @@
|
|||
{:class klass}
|
||||
[:*
|
||||
[:> :input props]
|
||||
[:label {:for (name input-name)} label]
|
||||
(cond
|
||||
(some? label)
|
||||
[:label {:for (name input-name)} label]
|
||||
|
||||
(some? children)
|
||||
[:label {:for (name input-name)} children])
|
||||
|
||||
(when help-icon'
|
||||
[:div.help-icon
|
||||
{:style {:cursor "pointer"}
|
||||
|
|
|
@ -25,9 +25,26 @@
|
|||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn bounds
|
||||
[object objects]
|
||||
(if (= :group (:type object))
|
||||
(let [children-bounds
|
||||
(into []
|
||||
(comp (map #(get objects %))
|
||||
(map #(bounds % objects)))
|
||||
(:shapes object))]
|
||||
(gsh/join-rects children-bounds))
|
||||
|
||||
(let [padding (filters/calculate-padding object)]
|
||||
(-> (filters/get-filters-bounds object)
|
||||
(update :x - padding)
|
||||
(update :y - padding)
|
||||
(update :width + (* 2 padding))
|
||||
(update :height + (* 2 padding))))))
|
||||
|
||||
(mf/defc object-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects object-id zoom] :or {zoom 1} :as props}]
|
||||
[{:keys [objects object-id zoom render-texts?] :or {zoom 1} :as props}]
|
||||
(let [object (get objects object-id)
|
||||
frame-id (if (= :frame (:type object))
|
||||
(:id object)
|
||||
|
@ -47,20 +64,10 @@
|
|||
objects (reduce updt-fn objects mod-ids)
|
||||
object (get objects object-id)
|
||||
|
||||
;; We need to get the shadows/blurs paddings to create the viewbox properly
|
||||
{:keys [x y width height]} (filters/get-filters-bounds object)
|
||||
{:keys [x y width height] :as bs} (bounds object objects)
|
||||
[_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
|
||||
|
||||
x (* x zoom)
|
||||
y (* y zoom)
|
||||
width (* width zoom)
|
||||
height (* height zoom)
|
||||
|
||||
padding (* (filters/calculate-padding object) zoom)
|
||||
|
||||
vbox (str/join " " [(- x padding)
|
||||
(- y padding)
|
||||
(+ width padding padding)
|
||||
(+ height padding padding)])
|
||||
vbox (str/join " " coords)
|
||||
|
||||
frame-wrapper
|
||||
(mf/use-memo
|
||||
|
@ -76,18 +83,22 @@
|
|||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(exports/shape-wrapper-factory objects))
|
||||
]
|
||||
|
||||
text-shapes
|
||||
(->> objects
|
||||
(filter (fn [[_ shape]] (= :text (:type shape))))
|
||||
(mapv second))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps width height)
|
||||
#(dom/set-page-style {:size (str (mth/ceil (+ width padding padding)) "px "
|
||||
(mth/ceil (+ height padding padding)) "px")}))
|
||||
#(dom/set-page-style {:size (str (mth/ceil width) "px "
|
||||
(mth/ceil height) "px")}))
|
||||
|
||||
[:& (mf/provider embed/context) {:value true}
|
||||
[:svg {:id "screenshot"
|
||||
:view-box vbox
|
||||
:width (+ width padding padding)
|
||||
:height (+ height padding padding)
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
|
@ -100,7 +111,19 @@
|
|||
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
||||
:group [:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object}]]
|
||||
[:& shape-wrapper {:shape object}])]]))
|
||||
[:& shape-wrapper {:shape object}])]
|
||||
|
||||
;; Auxiliary SVG for rendering text-shapes
|
||||
(when render-texts?
|
||||
(for [object text-shapes]
|
||||
[:svg {:id (str "screenshot-text-" (:id object))
|
||||
:view-box (str "0 0 " (:width object) " " (:height object))
|
||||
:width (:width object)
|
||||
:height (:height object)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"}
|
||||
[:& shape-wrapper {:shape (-> object (assoc :x 0 :y 0))}]]))]))
|
||||
|
||||
(defn- adapt-root-frame
|
||||
[objects object-id]
|
||||
|
@ -120,7 +143,7 @@
|
|||
;; backend entry point for download only the data of single page.
|
||||
|
||||
(mf/defc render-object
|
||||
[{:keys [file-id page-id object-id] :as props}]
|
||||
[{:keys [file-id page-id object-id render-texts?] :as props}]
|
||||
(let [objects (mf/use-state nil)]
|
||||
(mf/use-effect
|
||||
(mf/deps file-id page-id object-id)
|
||||
|
@ -140,6 +163,7 @@
|
|||
(when @objects
|
||||
[:& object-svg {:objects @objects
|
||||
:object-id object-id
|
||||
:render-texts? render-texts?
|
||||
:zoom 1}])))
|
||||
|
||||
(mf/defc render-sprite
|
||||
|
|
|
@ -60,8 +60,8 @@
|
|||
:viewBox "0 0 3 6"
|
||||
:refX "2"
|
||||
:refY "3"
|
||||
:markerWidth "3"
|
||||
:markerHeight "6"
|
||||
:markerWidth "8.5"
|
||||
:markerHeight "8.5"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
|
@ -72,8 +72,8 @@
|
|||
:viewBox "0 0 3 6"
|
||||
:refX "2"
|
||||
:refY "3"
|
||||
:markerWidth "3"
|
||||
:markerHeight "6"
|
||||
:markerWidth "8.5"
|
||||
:markerHeight "8.5"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
|
@ -82,10 +82,10 @@
|
|||
(when (or (= cap-start :square-marker) (= cap-end :square-marker))
|
||||
[:marker {:id (str marker-id-prefix "-square-marker")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "5"
|
||||
:refX "3"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:markerWidth "4.2426" ;; diagonal length of a 3x3 square
|
||||
:markerHeight "4.2426"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
|
@ -94,10 +94,10 @@
|
|||
(when (or (= cap-start :circle-marker) (= cap-end :circle-marker))
|
||||
[:marker {:id (str marker-id-prefix "-circle-marker")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "5"
|
||||
:refX "3"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
:markerWidth "4"
|
||||
:markerHeight "4"
|
||||
:orient "auto-start-reverse"
|
||||
:fill stroke-color
|
||||
:fillOpacity stroke-opacity}
|
||||
|
@ -106,7 +106,7 @@
|
|||
(when (or (= cap-start :diamond-marker) (= cap-end :diamond-marker))
|
||||
[:marker {:id (str marker-id-prefix "-diamond-marker")
|
||||
:viewBox "0 0 6 6"
|
||||
:refX "5"
|
||||
:refX "3"
|
||||
:refY "3"
|
||||
:markerWidth "6"
|
||||
:markerHeight "6"
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
(let [valign (:vertical-align node "top")
|
||||
width (some-> (:width shape) (+ 1))
|
||||
base #js {:height (or (:height shape) "100%")
|
||||
:width (or width "100%")}]
|
||||
:width (or width "100%")
|
||||
:fontFamily "sourcesanspro"}]
|
||||
(cond-> base
|
||||
(= valign "top") (obj/set! "justifyContent" "flex-start")
|
||||
(= valign "center") (obj/set! "justifyContent" "center")
|
||||
|
@ -40,6 +41,7 @@
|
|||
:justifyContent "inherit"
|
||||
:minHeight (when-not (or auto-width? auto-height?) "100%")
|
||||
:minWidth (when-not auto-width? "100%")
|
||||
:marginRight "1px"
|
||||
:verticalAlign "top"}))
|
||||
|
||||
(defn generate-paragraph-styles
|
||||
|
|
|
@ -27,15 +27,27 @@
|
|||
:v (mf/use-ref nil)
|
||||
:alpha (mf/use-ref nil)}
|
||||
|
||||
setup-hex-color
|
||||
(fn [hex]
|
||||
(let [[r g b] (uc/hex->rgb hex)
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))
|
||||
on-change-hex
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val parse-hex)]
|
||||
(when (uc/hex? val)
|
||||
(let [[r g b] (uc/hex->rgb val)
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(on-change {:hex val
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))))
|
||||
(setup-hex-color val))))
|
||||
|
||||
on-blur-hex
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val)
|
||||
val (cond
|
||||
(uc/color? val) (uc/parse-color val)
|
||||
(uc/hex? (parse-hex val)) (parse-hex val))]
|
||||
(when (some? val)
|
||||
(setup-hex-color val))))
|
||||
|
||||
on-change-property
|
||||
(fn [property max-value]
|
||||
|
@ -81,9 +93,10 @@
|
|||
[:div.color-values
|
||||
{:class (when disable-opacity "disable-opacity")}
|
||||
[:input {:id "hex-value"
|
||||
:ref (:hex refs)
|
||||
:default-value hex
|
||||
:on-change on-change-hex}]
|
||||
:ref (:hex refs)
|
||||
:default-value hex
|
||||
:on-change on-change-hex
|
||||
:on-blur on-blur-hex}]
|
||||
|
||||
(if (= type :rgb)
|
||||
[:*
|
||||
|
|
|
@ -87,15 +87,15 @@
|
|||
|
||||
(mf/defc shape-context-menu
|
||||
[{:keys [mdata] :as props}]
|
||||
(let [{:keys [id] :as shape} (:shape mdata)
|
||||
selected (:selected mdata)
|
||||
(let [{:keys [shape selected disable-booleans? disable-flatten?]} mdata
|
||||
{:keys [id type]} shape
|
||||
|
||||
single? (= (count selected) 1)
|
||||
multiple? (> (count selected) 1)
|
||||
editable-shape? (#{:group :text :path} (:type shape))
|
||||
editable-shape? (#{:group :text :path} type)
|
||||
|
||||
is-group? (and (some? shape) (= :group (:type shape)))
|
||||
is-bool? (and (some? shape) (= :bool (:type shape)))
|
||||
is-group? (and (some? shape) (= :group type))
|
||||
is-bool? (and (some? shape) (= :bool type))
|
||||
|
||||
options (mf/deref refs/workspace-page-options)
|
||||
flows (:flows options)
|
||||
|
@ -235,10 +235,12 @@
|
|||
:shortcut (sc/get-tooltip :start-editing)
|
||||
:on-click do-start-editing}])
|
||||
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path")
|
||||
:on-click do-transform-to-path}]
|
||||
(when-not disable-flatten?
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path")
|
||||
:on-click do-transform-to-path}])
|
||||
|
||||
(when (or multiple? (and single? (or is-group? is-bool?)))
|
||||
(when (and (not disable-booleans?)
|
||||
(or multiple? (and single? (or is-group? is-bool?))))
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.path")}
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.union")
|
||||
:shortcut (sc/get-tooltip :boolean-union)
|
||||
|
@ -253,7 +255,7 @@
|
|||
:shortcut (sc/get-tooltip :boolean-exclude)
|
||||
:on-click (set-bool :exclude)}]
|
||||
|
||||
(when (and single? is-bool?)
|
||||
(when (and single? is-bool? (not disable-flatten?))
|
||||
[:*
|
||||
[:& menu-separator]
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.flatten")
|
||||
|
@ -279,9 +281,7 @@
|
|||
[:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start")
|
||||
:on-click (do-remove-flow flow)}])))
|
||||
|
||||
(when (and (or (nil? (:shape-ref shape))
|
||||
(> (count selected) 1))
|
||||
(not= (:type shape) :frame))
|
||||
(when (not= (:type shape) :frame)
|
||||
[:*
|
||||
[:& menu-separator]
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.create-component")
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
handle-return
|
||||
(mf/use-callback
|
||||
(fn [_ state]
|
||||
(let [style (ted/get-editor-current-inline-styles state)
|
||||
(let [style (ted/get-editor-current-block-data state)
|
||||
state (-> (ted/insert-text state "\n" style)
|
||||
(handle-change))]
|
||||
(st/emit! (dwt/update-editor-state shape state)))
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
|
||||
on-fold-group
|
||||
(mf/use-callback
|
||||
(mf/deps group-open?)
|
||||
(mf/deps file-id box path group-open?)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dwl/set-assets-group-open file-id
|
||||
|
|
|
@ -17,19 +17,22 @@
|
|||
(mf/defc booleans-options
|
||||
[]
|
||||
(let [selected (mf/deref refs/selected-objects)
|
||||
selected-with-children (mf/deref refs/selected-objects-with-children)
|
||||
|
||||
disabled-bool-btns
|
||||
(or (empty? selected)
|
||||
(and (<= (count selected) 1)
|
||||
(not (contains? #{:group :bool} (:type (first selected))))))
|
||||
has-invalid-shapes? (->> selected-with-children
|
||||
(some (comp #{:frame :text} :type)))
|
||||
|
||||
disabled-flatten
|
||||
(empty? selected)
|
||||
first-not-group-like?
|
||||
(and (= (count selected) 1)
|
||||
(not (contains? #{:group :bool} (:type (first selected)))))
|
||||
|
||||
disabled-bool-btns (or (empty? selected) has-invalid-shapes? first-not-group-like?)
|
||||
disabled-flatten (or (empty? selected) has-invalid-shapes?)
|
||||
|
||||
head (first selected)
|
||||
is-group? (and (some? head) (= :group (:type head)))
|
||||
is-bool? (and (some? head) (= :bool (:type head)))
|
||||
head-bool-type (and (some? head) (:bool-type head))
|
||||
head-bool-type (and (some? head) is-bool? (:bool-type head))
|
||||
|
||||
set-bool
|
||||
(fn [bool-type]
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
:group (tr "workspace.options.group-stroke")
|
||||
(tr "workspace.options.stroke"))
|
||||
|
||||
show-options (not= (:stroke-style values :none) :none)
|
||||
show-options (not= (or (:stroke-style values) :none) :none)
|
||||
show-caps (and show-caps
|
||||
(not (#{:inner :outer} (:stroke-alignment values))))
|
||||
|
||||
|
@ -141,7 +141,10 @@
|
|||
target (dom/get-current-target event)
|
||||
rect (dom/get-bounding-rect target)
|
||||
|
||||
top (+ (:bottom rect) 5)
|
||||
top (if (< (+ (:bottom rect) 320) (:height window-size))
|
||||
(+ (:bottom rect) 5)
|
||||
(- (:height window-size) 325))
|
||||
|
||||
left (if (< (+ (:left rect) 200) (:width window-size))
|
||||
(:left rect)
|
||||
(- (:width window-size) 205))]
|
||||
|
|
|
@ -74,7 +74,8 @@
|
|||
|
||||
(defn filter-fonts
|
||||
[{:keys [term backends]} fonts]
|
||||
(let [xform (cond-> (map identity)
|
||||
(let [term (str/lower term)
|
||||
xform (cond-> (map identity)
|
||||
(seq term)
|
||||
(comp (filter #(str/includes? (str/lower (:name %)) term)))
|
||||
|
||||
|
@ -175,7 +176,7 @@
|
|||
[:div.font-selector
|
||||
[:div.font-selector-dropdown
|
||||
[:header
|
||||
[:input {:placeholder "Search font"
|
||||
[:input {:placeholder (tr "workspace.options.search-font")
|
||||
:value (:term @state)
|
||||
:ref input
|
||||
:spell-check false
|
||||
|
|
|
@ -197,7 +197,7 @@
|
|||
|
||||
[:& use/export-page {:options options}]
|
||||
|
||||
[:& (mf/provider use/include-metadata-ctx) {:value true}
|
||||
[:& (mf/provider use/include-metadata-ctx) {:value false}
|
||||
[:& (mf/provider embed/context) {:value true}
|
||||
;; Render root shape
|
||||
[:& shapes/root-shape {:key page-id
|
||||
|
|
|
@ -163,6 +163,12 @@ msgstr ""
|
|||
"When creating a new account, you agree to our terms of service and privacy "
|
||||
"policy."
|
||||
|
||||
msgid "auth.terms-of-service"
|
||||
msgstr "Terms of service"
|
||||
|
||||
msgid "auth.privacy-policy"
|
||||
msgstr "Privacy policy"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs
|
||||
msgid "auth.verification-email-sent"
|
||||
msgstr "We've sent a verification email to"
|
||||
|
@ -3177,3 +3183,6 @@ msgstr "Flatten"
|
|||
|
||||
msgid "workspace.shape.menu.transform-to-path"
|
||||
msgstr "Transform to path"
|
||||
|
||||
msgid "workspace.options.search-font"
|
||||
msgstr "Search font"
|
||||
|
|
|
@ -167,6 +167,12 @@ msgstr ""
|
|||
"Al crear una nueva cuenta, aceptas nuestros términos de servicio y política "
|
||||
"de privacidad."
|
||||
|
||||
msgid "auth.terms-of-service"
|
||||
msgstr "Terminos de servicio"
|
||||
|
||||
msgid "auth.privacy-policy"
|
||||
msgstr "Política de privacidad"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs
|
||||
msgid "auth.verification-email-sent"
|
||||
msgstr "Hemos enviado un email de verificación a"
|
||||
|
@ -3065,3 +3071,6 @@ msgstr "Aplanar"
|
|||
|
||||
msgid "workspace.shape.menu.transform-to-path"
|
||||
msgstr "Convertir en vector"
|
||||
|
||||
msgid "workspace.options.search-font"
|
||||
msgstr "Buscar fuente"
|
||||
|
|
|
@ -161,6 +161,12 @@ msgstr ""
|
|||
"En créant un compte, vous acceptez nos conditions générales d'utilisation "
|
||||
"et notre politique de confidentialité."
|
||||
|
||||
msgid "auth.terms-of-service"
|
||||
msgstr "Conditions générales d'utilisation"
|
||||
|
||||
msgid "auth.privacy-policy"
|
||||
msgstr "Politique de confidentialité"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs
|
||||
msgid "auth.verification-email-sent"
|
||||
msgstr "Nous avons envoyé un e-mail de vérification à"
|
||||
|
|
Loading…
Add table
Reference in a new issue