mirror of
https://github.com/penpot/penpot.git
synced 2025-08-06 12:38:33 +02:00
Merge branch 'staging' into develop
This commit is contained in:
commit
9950f464ce
18 changed files with 238 additions and 192 deletions
|
@ -61,6 +61,14 @@
|
||||||
- Update Work-Sans font [#744](https://github.com/penpot/penpot/issues/744)
|
- Update Work-Sans font [#744](https://github.com/penpot/penpot/issues/744)
|
||||||
- Fix issue with recent files not showing [Taiga #1493](https://tree.taiga.io/project/penpot/issue/1493)
|
- Fix issue with recent files not showing [Taiga #1493](https://tree.taiga.io/project/penpot/issue/1493)
|
||||||
- Fix issue when promoting to owner [Taiga #1494](https://tree.taiga.io/project/penpot/issue/1494)
|
- Fix issue when promoting to owner [Taiga #1494](https://tree.taiga.io/project/penpot/issue/1494)
|
||||||
|
- Fix cannot click on blocked elements in viewer [Taiga #1430](https://tree.taiga.io/project/penpot/issue/1430)
|
||||||
|
- Fix SVG not showing properties at code [Taiga #1437](https://tree.taiga.io/project/penpot/issue/1437)
|
||||||
|
- Fix shadows when exporting groups [Taiga #1495](https://tree.taiga.io/project/penpot/issue/1495)
|
||||||
|
- Fix drag-select when renaming layer text [Taiga #1307](https://tree.taiga.io/project/penpot/issue/1307)
|
||||||
|
- Fix layout problem for editable selects [Taiga #1488](https://tree.taiga.io/project/penpot/issue/1488)
|
||||||
|
- Fix artboard title wasn't move when resizing [Taiga #1479](https://tree.taiga.io/project/penpot/issue/1479)
|
||||||
|
- Fix titles in viewer thumbnails too long [Taiga #1474](https://tree.taiga.io/project/penpot/issue/1474)
|
||||||
|
- Fix when right click on a selected text shows artboard contextual menu [Taiga #1226](https://tree.taiga.io/project/penpot/issue/1226)
|
||||||
|
|
||||||
>>>>>>> origin/staging
|
>>>>>>> origin/staging
|
||||||
|
|
||||||
|
|
|
@ -420,6 +420,10 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-option {
|
&.input-option {
|
||||||
|
@ -438,6 +442,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-select-dropdown {
|
||||||
|
top: unset;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid $color-gray-40;
|
border: 1px solid $color-gray-40;
|
||||||
}
|
}
|
||||||
|
@ -449,10 +458,10 @@
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
.editable-select svg {
|
.editable-select svg {
|
||||||
fill: $color-gray-40;
|
fill: $color-gray-40;
|
||||||
}
|
}
|
||||||
.dropdown-button {
|
.dropdown-button {
|
||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
.editable-select.input-option .input-text {
|
.editable-select.input-option .input-text {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -778,8 +787,8 @@
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
|
|
||||||
& .custom-select-dropdown {
|
& .custom-select-dropdown {
|
||||||
width: 56px;
|
width: 5rem;
|
||||||
min-width: 56px;
|
min-width: 5rem;
|
||||||
max-height: 10rem;
|
max-height: 10rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1004,7 +1013,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
max-height: 16rem;
|
max-height: 16rem;
|
||||||
min-width: 6rem;
|
min-width: 6rem;
|
||||||
margin-top: 25px;
|
|
||||||
left: initial;
|
left: initial;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,10 @@
|
||||||
|
|
||||||
.thumbnail-info {
|
.thumbnail-info {
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: $fs13;
|
font-size: $fs13;
|
||||||
|
|
|
@ -77,11 +77,7 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(->> (rp/query! :profile)
|
(->> (rp/query! :profile)
|
||||||
(rx/map profile-fetched)
|
(rx/map profile-fetched)))))
|
||||||
(rx/catch (fn [error]
|
|
||||||
(if (= (:type error) :not-found)
|
|
||||||
(rx/of (rt/nav :auth-login))
|
|
||||||
(rx/empty))))))))
|
|
||||||
|
|
||||||
;; --- Update Profile
|
;; --- Update Profile
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,19 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
[app.util.timers :as timers]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.main.ui.components.dropdown :refer [dropdown]]))
|
[app.main.ui.components.dropdown :refer [dropdown]]))
|
||||||
|
|
||||||
(mf/defc editable-select [{:keys [value type options class on-change placeholder]}]
|
(mf/defc editable-select [{:keys [value type options class on-change placeholder]}]
|
||||||
(let [state (mf/use-state {:id (uuid/next)
|
(let [state (mf/use-state {:id (uuid/next)
|
||||||
:is-open? false
|
:is-open? false
|
||||||
:current-value value})
|
:current-value value
|
||||||
|
:top nil
|
||||||
|
:left nil
|
||||||
|
:bottom nil})
|
||||||
open-dropdown #(swap! state assoc :is-open? true)
|
open-dropdown #(swap! state assoc :is-open? true)
|
||||||
close-dropdown #(swap! state assoc :is-open? false)
|
close-dropdown #(swap! state assoc :is-open? false)
|
||||||
|
|
||||||
select-item (fn [value]
|
select-item (fn [value]
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(swap! state assoc :current-value value)
|
(swap! state assoc :current-value value)
|
||||||
|
@ -38,7 +41,23 @@
|
||||||
(let [value (-> event dom/get-target dom/get-value)
|
(let [value (-> event dom/get-target dom/get-value)
|
||||||
value (or (d/parse-integer value) value)]
|
value (or (d/parse-integer value) value)]
|
||||||
(swap! state assoc :current-value value)
|
(swap! state assoc :current-value value)
|
||||||
(when on-change (on-change value))))]
|
(when on-change (on-change value))))
|
||||||
|
|
||||||
|
on-node-load
|
||||||
|
(fn [node]
|
||||||
|
;; There is a problem when changing the state in this callback that
|
||||||
|
;; produces the dropdown to close in the same event
|
||||||
|
(timers/schedule
|
||||||
|
#(when-let [bounds (when node (dom/get-bounding-rect node))]
|
||||||
|
(let [{window-height :height} (dom/get-window-size)
|
||||||
|
{:keys [left top height]} bounds
|
||||||
|
bottom (when (< (- window-height top) 300) (- window-height top))
|
||||||
|
top (when (>= (- window-height top) 300) (+ top height))]
|
||||||
|
(swap! state
|
||||||
|
assoc
|
||||||
|
:left left
|
||||||
|
:top top
|
||||||
|
:bottom bottom)))))]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps value)
|
(mf/deps value)
|
||||||
|
@ -49,7 +68,8 @@
|
||||||
#(reset! state {:is-open? false
|
#(reset! state {:is-open? false
|
||||||
:current-value value}))
|
:current-value value}))
|
||||||
|
|
||||||
[:div.editable-select {:class class}
|
[:div.editable-select {:class class
|
||||||
|
:ref on-node-load}
|
||||||
[:input.input-text {:value (or (-> @state :current-value value->label) "")
|
[:input.input-text {:value (or (-> @state :current-value value->label) "")
|
||||||
:on-change handle-change-input
|
:on-change handle-change-input
|
||||||
:placeholder placeholder
|
:placeholder placeholder
|
||||||
|
@ -58,7 +78,10 @@
|
||||||
|
|
||||||
[:& dropdown {:show (get @state :is-open? false)
|
[:& dropdown {:show (get @state :is-open? false)
|
||||||
:on-close close-dropdown}
|
:on-close close-dropdown}
|
||||||
[:ul.custom-select-dropdown
|
[:ul.custom-select-dropdown {:style {:position "fixed"
|
||||||
|
:top (:top @state)
|
||||||
|
:left (:left @state)
|
||||||
|
:bottom (:bottom @state)}}
|
||||||
(for [[index item] (map-indexed vector options)]
|
(for [[index item] (map-indexed vector options)]
|
||||||
(cond
|
(cond
|
||||||
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
|
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
|
||||||
|
|
|
@ -5,27 +5,22 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.main.ui.dashboard.team-form
|
(ns app.main.ui.dashboard.team-form
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
|
||||||
[app.main.data.auth :as da]
|
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.repo :as rp]
|
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.forms :as fm]
|
[app.main.ui.components.forms :as fm]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [t tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.util.keyboard :as kbd]
|
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
[app.util.time :as dt]
|
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
|
@ -52,10 +47,8 @@
|
||||||
[form response]
|
[form response]
|
||||||
(let [id (get-in @form [:clean-data :id])]
|
(let [id (get-in @form [:clean-data :id])]
|
||||||
(if id
|
(if id
|
||||||
(st/emit! (dm/error "Error on updating team."))
|
(rx/of (dm/error "Error on updating team."))
|
||||||
(st/emit! (dm/error "Error on creating team.")))))
|
(rx/of (dm/error "Error on creating team.")))))
|
||||||
|
|
||||||
;; TODO: check global error handler
|
|
||||||
|
|
||||||
(defn- on-create-submit
|
(defn- on-create-submit
|
||||||
[form]
|
[form]
|
||||||
|
@ -76,8 +69,7 @@
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
::mf/register-as :team-form}
|
::mf/register-as :team-form}
|
||||||
[{:keys [team] :as props}]
|
[{:keys [team] :as props}]
|
||||||
(let [locale (mf/deref i18n/locale)
|
(let [form (fm/use-form :spec ::team-form
|
||||||
form (fm/use-form :spec ::team-form
|
|
||||||
:initial (or team {}))
|
:initial (or team {}))
|
||||||
|
|
||||||
on-submit
|
on-submit
|
||||||
|
@ -89,19 +81,20 @@
|
||||||
|
|
||||||
[:div.modal-overlay
|
[:div.modal-overlay
|
||||||
[:div.modal-container.team-form-modal
|
[:div.modal-container.team-form-modal
|
||||||
[:& fm/form {:form form
|
[:& fm/form {:form form :on-submit on-submit}
|
||||||
:on-submit on-submit}
|
|
||||||
|
|
||||||
[:div.modal-header
|
[:div.modal-header
|
||||||
[:div.modal-header-title
|
[:div.modal-header-title
|
||||||
(if team
|
(if team
|
||||||
[:h2 (tr "labels.rename-team")]
|
[:h2 (tr "labels.rename-team")]
|
||||||
[:h2 (tr "labels.create-team")])]
|
[:h2 (tr "labels.create-team")])]
|
||||||
|
|
||||||
[:div.modal-close-button
|
[:div.modal-close-button
|
||||||
{:on-click (st/emitf (modal/hide))} i/close]]
|
{:on-click (st/emitf (modal/hide))} i/close]]
|
||||||
|
|
||||||
[:div.modal-content.generic-form
|
[:div.modal-content.generic-form
|
||||||
[:& fm/input {:type "text"
|
[:& fm/input {:type "text"
|
||||||
|
:auto-focus true
|
||||||
:form form
|
:form form
|
||||||
:name :name
|
:name :name
|
||||||
:label "Enter new team name:"}]]
|
:label "Enter new team name:"}]]
|
||||||
|
|
|
@ -63,10 +63,10 @@
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
childs (unchecked-get props "childs")
|
childs (unchecked-get props "childs")
|
||||||
frame (unchecked-get props "frame")
|
frame (unchecked-get props "frame")
|
||||||
svg-element? (and (= :svg-raw (:type shape))
|
render-wrapper? (or (not= :svg-raw (:type shape))
|
||||||
(not= :svg (get-in shape [:content :tag])))]
|
(svg-raw/graphic-element? (get-in shape [:content :tag])))]
|
||||||
|
|
||||||
(if-not svg-element?
|
(if render-wrapper?
|
||||||
[:> shape-container {:shape shape
|
[:> shape-container {:shape shape
|
||||||
:on-mouse-enter (handle-hover-shape shape true)
|
:on-mouse-enter (handle-hover-shape shape true)
|
||||||
:on-mouse-leave (handle-hover-shape shape false)
|
:on-mouse-leave (handle-hover-shape shape false)
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.main.data.shortcuts :refer [bind-shortcuts]]
|
[app.main.data.shortcuts :refer [bind-shortcuts]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
[app.util.object :as obj]
|
||||||
[app.util.dom.dnd :as dnd]
|
[app.util.dom.dnd :as dnd]
|
||||||
[app.util.logging :as log]
|
[app.util.logging :as log]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
|
@ -97,7 +98,7 @@
|
||||||
;; things go weird.
|
;; things go weird.
|
||||||
|
|
||||||
(defn use-sortable
|
(defn use-sortable
|
||||||
[& {:keys [data-type data on-drop on-drag on-hold detect-center?] :as opts}]
|
[& {:keys [data-type data on-drop on-drag on-hold disabled detect-center?] :as opts}]
|
||||||
(let [ref (mf/use-ref)
|
(let [ref (mf/use-ref)
|
||||||
state (mf/use-state {:over nil
|
state (mf/use-state {:over nil
|
||||||
:timer nil
|
:timer nil
|
||||||
|
@ -125,13 +126,16 @@
|
||||||
|
|
||||||
on-drag-start
|
on-drag-start
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(if disabled
|
||||||
;; (dnd/trace event data "drag-start")
|
(dom/prevent-default event)
|
||||||
(dnd/set-data! event data-type data)
|
(let [target (dom/get-target event)]
|
||||||
(dnd/set-drag-image! event (invisible-image))
|
(dom/stop-propagation event)
|
||||||
(dnd/set-allowed-effect! event "move")
|
;; (dnd/trace event data "drag-start")
|
||||||
(when (fn? on-drag)
|
(dnd/set-data! event data-type data)
|
||||||
(on-drag data)))
|
(dnd/set-drag-image! event (invisible-image))
|
||||||
|
(dnd/set-allowed-effect! event "move")
|
||||||
|
(when (fn? on-drag)
|
||||||
|
(on-drag data)))))
|
||||||
|
|
||||||
on-drag-enter
|
on-drag-enter
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
|
|
@ -9,18 +9,21 @@
|
||||||
|
|
||||||
(ns app.main.ui.render
|
(ns app.main.ui.render
|
||||||
(:require
|
(:require
|
||||||
[cljs.spec.alpha :as s]
|
|
||||||
[beicon.core :as rx]
|
|
||||||
[rumext.alpha :as mf]
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.common.pages :as cp]
|
|
||||||
[app.common.math :as mth]
|
|
||||||
[app.common.geom.shapes :as geom]
|
|
||||||
[app.common.geom.point :as gpt]
|
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.main.ui.context :as muc]
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.common.pages :as cp]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.main.exports :as exports]
|
[app.main.exports :as exports]
|
||||||
[app.main.repo :as repo]))
|
[app.main.repo :as repo]
|
||||||
|
[app.main.ui.context :as muc]
|
||||||
|
[app.main.ui.shapes.filters :as filters]
|
||||||
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[cljs.spec.alpha :as s]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc object-svg
|
(mf/defc object-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
|
@ -37,18 +40,23 @@
|
||||||
mod-ids (cons frame-id (cp/get-children frame-id objects))
|
mod-ids (cons frame-id (cp/get-children frame-id objects))
|
||||||
updt-fn #(-> %1
|
updt-fn #(-> %1
|
||||||
(assoc-in [%2 :modifiers :displacement] modifier)
|
(assoc-in [%2 :modifiers :displacement] modifier)
|
||||||
(update %2 geom/transform-shape))
|
(update %2 gsh/transform-shape))
|
||||||
|
|
||||||
objects (reduce updt-fn objects mod-ids)
|
objects (reduce updt-fn objects mod-ids)
|
||||||
object (get objects object-id)
|
object (get objects object-id)
|
||||||
|
|
||||||
width (* (get-in object [:selrect :width]) zoom)
|
|
||||||
height (* (get-in object [:selrect :height]) zoom)
|
|
||||||
|
|
||||||
vbox (str (get-in object [:selrect :x]) " "
|
{:keys [width height]} (gsh/points->selrect (:points object))
|
||||||
(get-in object [:selrect :y]) " "
|
|
||||||
(get-in object [:selrect :width]) " "
|
;; We need to get the shadows/blurs paddings to create the viewbox properly
|
||||||
(get-in object [:selrect :height]))
|
{:keys [x y width height]} (filters/get-filters-bounds object)
|
||||||
|
|
||||||
|
x (* x zoom)
|
||||||
|
y (* y zoom)
|
||||||
|
width (* width zoom)
|
||||||
|
height (* height zoom)
|
||||||
|
|
||||||
|
vbox (str/join " " [x y width height])
|
||||||
|
|
||||||
frame-wrapper
|
frame-wrapper
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
|
@ -76,7 +84,8 @@
|
||||||
:xmlns "http://www.w3.org/2000/svg"}
|
:xmlns "http://www.w3.org/2000/svg"}
|
||||||
(case (:type object)
|
(case (:type object)
|
||||||
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
||||||
:group [:& group-wrapper {:shape object}]
|
:group [:> shape-container {:shape object}
|
||||||
|
[:& group-wrapper {:shape object}]]
|
||||||
[:& shape-wrapper {:shape object}])]]))
|
[:& shape-wrapper {:shape object}])]]))
|
||||||
|
|
||||||
(defn- adapt-root-frame
|
(defn- adapt-root-frame
|
||||||
|
@ -84,9 +93,9 @@
|
||||||
(if (uuid/zero? object-id)
|
(if (uuid/zero? object-id)
|
||||||
(let [object (get objects object-id)
|
(let [object (get objects object-id)
|
||||||
shapes (cp/select-toplevel-shapes objects {:include-frames? true})
|
shapes (cp/select-toplevel-shapes objects {:include-frames? true})
|
||||||
srect (geom/selection-rect shapes)
|
srect (gsh/selection-rect shapes)
|
||||||
object (merge object (select-keys srect [:x :y :width :height]))
|
object (merge object (select-keys srect [:x :y :width :height]))
|
||||||
object (geom/transform-shape object)
|
object (gsh/transform-shape object)
|
||||||
object (assoc object :fill-color "#f0f0f0")]
|
object (assoc object :fill-color "#f0f0f0")]
|
||||||
(assoc objects (:id object) object))
|
(assoc objects (:id object) object))
|
||||||
objects))
|
objects))
|
||||||
|
|
|
@ -122,40 +122,6 @@
|
||||||
:x2 (+ filter-x filter-width)
|
:x2 (+ filter-x filter-width)
|
||||||
:y2 (+ filter-y filter-height)}))
|
:y2 (+ filter-y filter-height)}))
|
||||||
|
|
||||||
(defn get-filters-bounds
|
|
||||||
[shape filters blur-value]
|
|
||||||
|
|
||||||
(let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag])))
|
|
||||||
frame? (= :frame (:type shape))
|
|
||||||
{:keys [x y width height]} (:selrect shape)]
|
|
||||||
(if svg-root?
|
|
||||||
;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum
|
|
||||||
;; we're allowed to display
|
|
||||||
{:x 0 :y 0 :width width :height height}
|
|
||||||
|
|
||||||
;; Otherwise we calculate the bound
|
|
||||||
(let [filter-bounds (->> filters
|
|
||||||
(filter #(= :drop-shadow (:type %)))
|
|
||||||
(map (partial filter-bounds shape) ))
|
|
||||||
;; We add the selrect so the minimum size will be the selrect
|
|
||||||
filter-bounds (conj filter-bounds (-> shape :points gsh/points->selrect))
|
|
||||||
x1 (apply min (map :x1 filter-bounds))
|
|
||||||
y1 (apply min (map :y1 filter-bounds))
|
|
||||||
x2 (apply max (map :x2 filter-bounds))
|
|
||||||
y2 (apply max (map :y2 filter-bounds))
|
|
||||||
|
|
||||||
x1 (- x1 (* blur-value 2))
|
|
||||||
x2 (+ x2 (* blur-value 2))
|
|
||||||
y1 (- y1 (* blur-value 2))
|
|
||||||
y2 (+ y2 (* blur-value 2))]
|
|
||||||
|
|
||||||
;; We should move the frame filter coordinates because they should be
|
|
||||||
;; relative with the frame. By default they come as absolute
|
|
||||||
{:x (if frame? (- x1 x) x1)
|
|
||||||
:y (if frame? (- y1 y) y1)
|
|
||||||
:width (- x2 x1)
|
|
||||||
:height (- y2 y1)}))))
|
|
||||||
|
|
||||||
(defn blur-filters [type value]
|
(defn blur-filters [type value]
|
||||||
(->> [value]
|
(->> [value]
|
||||||
(remove :hidden)
|
(remove :hidden)
|
||||||
|
@ -184,21 +150,64 @@
|
||||||
:image-fix [:> image-fix-filter props]
|
:image-fix [:> image-fix-filter props]
|
||||||
:blend-filters [:> blend-filters props])))
|
:blend-filters [:> blend-filters props])))
|
||||||
|
|
||||||
|
(defn shape->filters
|
||||||
|
[shape]
|
||||||
|
(d/concat
|
||||||
|
[]
|
||||||
|
[{:id "BackgroundImageFix" :type :image-fix}]
|
||||||
|
|
||||||
|
;; Background blur won't work in current SVG specification
|
||||||
|
;; We can revisit this in the future
|
||||||
|
#_(->> shape :blur (blur-filters :background-blur))
|
||||||
|
|
||||||
|
(->> shape :shadow (shadow-filters :drop-shadow))
|
||||||
|
[{:id "shape" :type :blend-filters}]
|
||||||
|
(->> shape :shadow (shadow-filters :inner-shadow))
|
||||||
|
(->> shape :blur (blur-filters :layer-blur))))
|
||||||
|
|
||||||
|
(defn get-filters-bounds
|
||||||
|
([shape]
|
||||||
|
(let [filters (shape->filters shape)
|
||||||
|
blur-value (or (-> shape :blur :value) 0)]
|
||||||
|
(get-filters-bounds shape filters blur-value)))
|
||||||
|
|
||||||
|
([shape filters blur-value]
|
||||||
|
|
||||||
|
(let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag])))
|
||||||
|
frame? (= :frame (:type shape))
|
||||||
|
{:keys [x y width height]} (:selrect shape)]
|
||||||
|
(if svg-root?
|
||||||
|
;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum
|
||||||
|
;; we're allowed to display
|
||||||
|
{:x 0 :y 0 :width width :height height}
|
||||||
|
|
||||||
|
;; Otherwise we calculate the bound
|
||||||
|
(let [filter-bounds (->> filters
|
||||||
|
(filter #(= :drop-shadow (:type %)))
|
||||||
|
(map (partial filter-bounds shape)))
|
||||||
|
;; We add the selrect so the minimum size will be the selrect
|
||||||
|
filter-bounds (conj filter-bounds (-> shape :points gsh/points->selrect))
|
||||||
|
x1 (apply min (map :x1 filter-bounds))
|
||||||
|
y1 (apply min (map :y1 filter-bounds))
|
||||||
|
x2 (apply max (map :x2 filter-bounds))
|
||||||
|
y2 (apply max (map :y2 filter-bounds))
|
||||||
|
|
||||||
|
x1 (- x1 (* blur-value 2))
|
||||||
|
x2 (+ x2 (* blur-value 2))
|
||||||
|
y1 (- y1 (* blur-value 2))
|
||||||
|
y2 (+ y2 (* blur-value 2))]
|
||||||
|
|
||||||
|
;; We should move the frame filter coordinates because they should be
|
||||||
|
;; relative with the frame. By default they come as absolute
|
||||||
|
{:x (if frame? (- x1 x) x1)
|
||||||
|
:y (if frame? (- y1 y) y1)
|
||||||
|
:width (- x2 x1)
|
||||||
|
:height (- y2 y1)})))))
|
||||||
|
|
||||||
(mf/defc filters
|
(mf/defc filters
|
||||||
[{:keys [filter-id shape]}]
|
[{:keys [filter-id shape]}]
|
||||||
|
|
||||||
(let [filters (d/concat
|
(let [filters (shape->filters shape)
|
||||||
[]
|
|
||||||
[{:id "BackgroundImageFix" :type :image-fix}]
|
|
||||||
|
|
||||||
;; Background blur won't work in current SVG specification
|
|
||||||
;; We can revisit this in the future
|
|
||||||
#_(->> shape :blur (blur-filters :background-blur))
|
|
||||||
|
|
||||||
(->> shape :shadow (shadow-filters :drop-shadow))
|
|
||||||
[{:id "shape" :type :blend-filters}]
|
|
||||||
(->> shape :shadow (shadow-filters :inner-shadow))
|
|
||||||
(->> shape :blur (blur-filters :layer-blur)))
|
|
||||||
|
|
||||||
;; Adds the previous filter as `filter-in` parameter
|
;; Adds the previous filter as `filter-in` parameter
|
||||||
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
|
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
|
||||||
|
|
|
@ -20,12 +20,13 @@
|
||||||
(mf/defc shape-container
|
(mf/defc shape-container
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
children (obj/get props "children")
|
children (obj/get props "children")
|
||||||
render-id (mf/use-memo #(str (uuid/next)))
|
pointer-events (obj/get props "pointer-events")
|
||||||
filter-id (str "filter_" render-id)
|
render-id (mf/use-memo #(str (uuid/next)))
|
||||||
styles (cond-> (obj/new)
|
filter-id (str "filter_" render-id)
|
||||||
(:blocked shape) (obj/set! "pointerEvents" "none"))
|
styles (-> (obj/new)
|
||||||
|
(obj/set! "pointerEvents" pointer-events))
|
||||||
|
|
||||||
{:keys [x y width height type]} shape
|
{:keys [x y width height type]} shape
|
||||||
frame? (= :frame type)
|
frame? (= :frame type)
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
;; Graphic tags
|
;; Graphic tags
|
||||||
(defonce graphic-element? #{:circle :ellipse :image :line :path :polygon :polyline :rect :text :use})
|
(defonce graphic-element?
|
||||||
|
#{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath :use})
|
||||||
|
|
||||||
;; Context to store a re-mapping of the ids
|
;; Context to store a re-mapping of the ids
|
||||||
(def svg-ids-ctx (mf/create-context nil))
|
(def svg-ids-ctx (mf/create-context nil))
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
{:class (classnames :selected selected?)}
|
{:class (classnames :selected selected?)}
|
||||||
[:& exports/frame-svg {:frame frame :objects objects}]]
|
[:& exports/frame-svg {:frame frame :objects objects}]]
|
||||||
[:div.thumbnail-info
|
[:div.thumbnail-info
|
||||||
[:span.name (:name frame)]]])
|
[:span.name {:title (:name frame)} (:name frame)]]])
|
||||||
|
|
||||||
(mf/defc thumbnails-panel
|
(mf/defc thumbnails-panel
|
||||||
[{:keys [data index screen] :as props}]
|
[{:keys [data index screen] :as props}]
|
||||||
|
|
|
@ -76,7 +76,8 @@
|
||||||
shape (-> (geom/transform-shape shape)
|
shape (-> (geom/transform-shape shape)
|
||||||
(geom/translate-to-frame frame))
|
(geom/translate-to-frame frame))
|
||||||
opts #js {:shape shape
|
opts #js {:shape shape
|
||||||
:frame frame}
|
:frame frame
|
||||||
|
:pointer-events (when (:blocked shape) "none")}
|
||||||
|
|
||||||
svg-element? (and (= (:type shape) :svg-raw)
|
svg-element? (and (= (:type shape) :svg-raw)
|
||||||
(not= :svg (get-in shape [:content :tag])))]
|
(not= :svg (get-in shape [:content :tag])))]
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.main.ui.context :as muc]))
|
[app.main.ui.context :as muc]))
|
||||||
|
|
||||||
;; This is a list of svg tags that can be grouped in shape-container
|
|
||||||
;; this allows them to have gradients, shadows and masks
|
|
||||||
(def svg-elements #{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath :use})
|
|
||||||
|
|
||||||
(defn svg-raw-wrapper-factory
|
(defn svg-raw-wrapper-factory
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||||
|
@ -43,21 +39,12 @@
|
||||||
def-ctx? (mf/use-ctx muc/def-ctx)]
|
def-ctx? (mf/use-ctx muc/def-ctx)]
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
(and (contains? svg-elements tag) (not def-ctx?))
|
(and (svg-raw/graphic-element? tag) (not def-ctx?))
|
||||||
[:> shape-container { :shape shape }
|
[:> shape-container { :shape shape }
|
||||||
[:& svg-raw-shape
|
[:& svg-raw-shape
|
||||||
{:frame frame
|
{:frame frame
|
||||||
:shape shape
|
:shape shape
|
||||||
:childs childs}]
|
:childs childs}]]
|
||||||
|
|
||||||
[:rect.actions
|
|
||||||
{:x x
|
|
||||||
:y y
|
|
||||||
:transform transform
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:fill "transparent"
|
|
||||||
:stroke "none"}]]
|
|
||||||
|
|
||||||
;; We cannot wrap inside groups the shapes that go inside the defs tag
|
;; We cannot wrap inside groups the shapes that go inside the defs tag
|
||||||
;; we use the context so we know when we should not render the container
|
;; we use the context so we know when we should not render the container
|
||||||
|
|
|
@ -54,31 +54,25 @@
|
||||||
(l/derived (l/in [:workspace-local :shape-for-rename]) st/state))
|
(l/derived (l/in [:workspace-local :shape-for-rename]) st/state))
|
||||||
|
|
||||||
(mf/defc layer-name
|
(mf/defc layer-name
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape on-start-edit on-stop-edit] :as props}]
|
||||||
(let [local (mf/use-state {})
|
(let [local (mf/use-state {})
|
||||||
shape-for-rename (mf/deref shape-for-rename-ref)
|
shape-for-rename (mf/deref shape-for-rename-ref)
|
||||||
name-ref (mf/use-ref)
|
name-ref (mf/use-ref)
|
||||||
|
|
||||||
set-draggable (fn [value]
|
|
||||||
(let [parent (.. (mf/ref-val name-ref)
|
|
||||||
-parentNode
|
|
||||||
-parentNode)]
|
|
||||||
(set! (.-draggable parent) value)))
|
|
||||||
|
|
||||||
start-edit (fn []
|
start-edit (fn []
|
||||||
(set-draggable false)
|
(on-start-edit)
|
||||||
(swap! local assoc :edition true))
|
(swap! local assoc :edition true))
|
||||||
|
|
||||||
accept-edit (fn []
|
accept-edit (fn []
|
||||||
(let [name-input (mf/ref-val name-ref)
|
(let [name-input (mf/ref-val name-ref)
|
||||||
name (dom/get-value name-input)]
|
name (dom/get-value name-input)]
|
||||||
(set-draggable true)
|
(on-stop-edit)
|
||||||
(swap! local assoc :edition false)
|
(swap! local assoc :edition false)
|
||||||
(st/emit! (dw/end-rename-shape)
|
(st/emit! (dw/end-rename-shape)
|
||||||
(dw/update-shape (:id shape) {:name name}))))
|
(dw/update-shape (:id shape) {:name name}))))
|
||||||
|
|
||||||
cancel-edit (fn []
|
cancel-edit (fn []
|
||||||
(set-draggable true)
|
(on-stop-edit)
|
||||||
(swap! local assoc :edition false)
|
(swap! local assoc :edition false)
|
||||||
(st/emit! (dw/end-rename-shape)))
|
(st/emit! (dw/end-rename-shape)))
|
||||||
|
|
||||||
|
@ -124,6 +118,8 @@
|
||||||
selected? (contains? selected id)
|
selected? (contains? selected id)
|
||||||
container? (or (= (:type item) :frame) (= (:type item) :group))
|
container? (or (= (:type item) :frame) (= (:type item) :group))
|
||||||
|
|
||||||
|
disable-drag (mf/use-state false)
|
||||||
|
|
||||||
expanded-iref (mf/use-memo
|
expanded-iref (mf/use-memo
|
||||||
(mf/deps id)
|
(mf/deps id)
|
||||||
(make-collapsed-iref id))
|
(make-collapsed-iref id))
|
||||||
|
@ -203,6 +199,7 @@
|
||||||
:on-drop on-drop
|
:on-drop on-drop
|
||||||
:on-drag on-drag
|
:on-drag on-drag
|
||||||
:on-hold on-hold
|
:on-hold on-hold
|
||||||
|
:disabled @disable-drag
|
||||||
:detect-center? container?
|
:detect-center? container?
|
||||||
:data {:id (:id item)
|
:data {:id (:id item)
|
||||||
:index index
|
:index index
|
||||||
|
@ -229,7 +226,9 @@
|
||||||
:on-click select-shape
|
:on-click select-shape
|
||||||
:on-double-click #(dom/stop-propagation %)}
|
:on-double-click #(dom/stop-propagation %)}
|
||||||
[:& element-icon {:shape item}]
|
[:& element-icon {:shape item}]
|
||||||
[:& layer-name {:shape item}]
|
[:& layer-name {:shape item
|
||||||
|
:on-start-edit #(reset! disable-drag true)
|
||||||
|
:on-stop-edit #(reset! disable-drag false)}]
|
||||||
|
|
||||||
[:div.element-actions
|
[:div.element-actions
|
||||||
[:div.toggle-element {:class (when (:hidden item) "selected")
|
[:div.toggle-element {:class (when (:hidden item) "selected")
|
||||||
|
|
|
@ -34,46 +34,47 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps id blocked hidden type drawing-tool text-editing? edition selected)
|
(mf/deps id blocked hidden type drawing-tool text-editing? edition selected)
|
||||||
(fn [bevent]
|
(fn [bevent]
|
||||||
(dom/stop-propagation bevent)
|
(when (dom/class? (dom/get-target bevent) "viewport-controls")
|
||||||
|
(dom/stop-propagation bevent)
|
||||||
|
|
||||||
(let [event (.-nativeEvent bevent)
|
(let [event (.-nativeEvent bevent)
|
||||||
ctrl? (kbd/ctrl? event)
|
ctrl? (kbd/ctrl? event)
|
||||||
shift? (kbd/shift? event)
|
shift? (kbd/shift? event)
|
||||||
alt? (kbd/alt? event)
|
alt? (kbd/alt? event)
|
||||||
|
|
||||||
left-click? (= 1 (.-which event))
|
left-click? (= 1 (.-which event))
|
||||||
middle-click? (= 2 (.-which event))
|
middle-click? (= 2 (.-which event))
|
||||||
|
|
||||||
frame? (= :frame type)
|
|
||||||
selected? (contains? selected id)]
|
|
||||||
|
|
||||||
(when middle-click?
|
frame? (= :frame type)
|
||||||
(dom/prevent-default bevent)
|
selected? (contains? selected id)]
|
||||||
(st/emit! (dw/start-panning)))
|
|
||||||
|
|
||||||
(when left-click?
|
(when middle-click?
|
||||||
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt?))
|
(dom/prevent-default bevent)
|
||||||
|
(st/emit! (dw/start-panning)))
|
||||||
|
|
||||||
(when (and (not= edition id) text-editing?)
|
(when left-click?
|
||||||
(st/emit! dw/clear-edition-mode))
|
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt?))
|
||||||
|
|
||||||
(when (and (or (not edition) (not= edition id)) (not blocked) (not hidden))
|
(when (and (not= edition id) text-editing?)
|
||||||
(cond
|
(st/emit! dw/clear-edition-mode))
|
||||||
(and drawing-tool (not (#{:comments :path} drawing-tool)))
|
|
||||||
(st/emit! (dd/start-drawing drawing-tool))
|
|
||||||
|
|
||||||
edit-path
|
(when (and (or (not edition) (not= edition id)) (not blocked) (not hidden))
|
||||||
;; Handle node select-drawing. NOP at the moment
|
(cond
|
||||||
nil
|
(and drawing-tool (not (#{:comments :path} drawing-tool)))
|
||||||
|
(st/emit! (dd/start-drawing drawing-tool))
|
||||||
|
|
||||||
(or (not id) (and frame? (not selected?)))
|
edit-path
|
||||||
(st/emit! (dw/handle-selection shift?))
|
;; Handle node select-drawing. NOP at the moment
|
||||||
|
nil
|
||||||
|
|
||||||
:else
|
(or (not id) (and frame? (not selected?)))
|
||||||
(st/emit! (when (or shift? (not selected?))
|
(st/emit! (dw/handle-selection shift?))
|
||||||
(dw/select-shape id shift?))
|
|
||||||
(when (not shift?)
|
:else
|
||||||
(dw/start-move-selected))))))))))
|
(st/emit! (when (or shift? (not selected?))
|
||||||
|
(dw/select-shape id shift?))
|
||||||
|
(when (not shift?)
|
||||||
|
(dw/start-move-selected)))))))))))
|
||||||
|
|
||||||
(defn on-move-selected
|
(defn on-move-selected
|
||||||
[hover hover-ids selected]
|
[hover hover-ids selected]
|
||||||
|
@ -122,17 +123,18 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps @hover selected)
|
(mf/deps @hover selected)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
(when (dom/class? (dom/get-target event) "viewport-controls")
|
||||||
shift? (kbd/shift? event)
|
(let [ctrl? (kbd/ctrl? event)
|
||||||
alt? (kbd/alt? event)
|
shift? (kbd/shift? event)
|
||||||
|
alt? (kbd/alt? event)
|
||||||
|
|
||||||
hovering? (some? @hover)
|
hovering? (some? @hover)
|
||||||
frame? (= :frame (:type @hover))
|
frame? (= :frame (:type @hover))
|
||||||
selected? (contains? selected (:id @hover))]
|
selected? (contains? selected (:id @hover))]
|
||||||
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt?))
|
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt?))
|
||||||
|
|
||||||
(when (and hovering? (not shift?) (not frame?) (not selected?))
|
(when (and hovering? (not shift?) (not frame?) (not selected?))
|
||||||
(st/emit! (dw/select-shape (:id @hover))))))))
|
(st/emit! (dw/select-shape (:id @hover)))))))))
|
||||||
|
|
||||||
(defn on-double-click
|
(defn on-double-click
|
||||||
[hover hover-ids drawing-path? objects]
|
[hover hover-ids drawing-path? objects]
|
||||||
|
@ -176,16 +178,17 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps @hover)
|
(mf/deps @hover)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/prevent-default event)
|
(when (dom/class? (dom/get-target event) "viewport-controls")
|
||||||
|
(dom/prevent-default event)
|
||||||
|
|
||||||
(let [position (dom/get-client-position event)]
|
(let [position (dom/get-client-position event)]
|
||||||
;; Delayed callback because we need to wait to the previous context menu to be closed
|
;; Delayed callback because we need to wait to the previous context menu to be closed
|
||||||
(timers/schedule
|
(timers/schedule
|
||||||
#(st/emit!
|
#(st/emit!
|
||||||
(if (some? @hover)
|
(if (some? @hover)
|
||||||
(dw/show-shape-context-menu {:position position
|
(dw/show-shape-context-menu {:position position
|
||||||
:shape @hover})
|
:shape @hover})
|
||||||
(dw/show-context-menu {:position position}))))))))
|
(dw/show-context-menu {:position position})))))))))
|
||||||
|
|
||||||
(defn on-mouse-up
|
(defn on-mouse-up
|
||||||
[disable-paste]
|
[disable-paste]
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
|
|
||||||
(mf/defc frame-title
|
(mf/defc frame-title
|
||||||
[{:keys [frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
|
[{:keys [frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
|
||||||
(let [{:keys [width x y]} frame
|
(let [{:keys [width x y]} (gsh/transform-shape frame)
|
||||||
label-pos (gpt/point x (- y (/ 10 zoom)))
|
label-pos (gpt/point x (- y (/ 10 zoom)))
|
||||||
|
|
||||||
on-mouse-down
|
on-mouse-down
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue