mirror of
https://github.com/penpot/penpot.git
synced 2026-01-06 05:18:52 -05:00
Compare commits
18 Commits
1.4.0-alph
...
1.4.1-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba0f9360f9 | ||
|
|
a8565dc2c2 | ||
|
|
14359d9acf | ||
|
|
6161911ff1 | ||
|
|
162b0cfa6c | ||
|
|
94ccc013d7 | ||
|
|
239ec12529 | ||
|
|
99bcf0484a | ||
|
|
464a686c04 | ||
|
|
a92820e910 | ||
|
|
080dd88509 | ||
|
|
4e1d85a5f4 | ||
|
|
09aa28a943 | ||
|
|
faff32203c | ||
|
|
5f7f88d299 | ||
|
|
166fdbd406 | ||
|
|
e677692594 | ||
|
|
459c9a3bb1 |
18
CHANGES.md
18
CHANGES.md
@@ -11,6 +11,18 @@
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
|
||||
## 1.4.1-alpha
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix typography unlinking.
|
||||
- Fix incorrect measures on shapes outside artboard.
|
||||
- Fix issues on svg parsing related to numbers with exponents.
|
||||
- Fix some race conditions on removing shape from workspace.
|
||||
- Fix incorrect state management of user lang selection.
|
||||
- Fix email validation usability issue on team invitation lightbox.
|
||||
|
||||
|
||||
## 1.4.0-alpha
|
||||
|
||||
### :sparkles: New features
|
||||
@@ -70,6 +82,12 @@
|
||||
- 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)
|
||||
|
||||
### :boom: Breaking changes
|
||||
|
||||
- The LDAP configuration variables interpolation starts using `:`
|
||||
(example `:username`) instead of `$`. The main reason is avoid
|
||||
unnecesary conflict with bash interpolation.
|
||||
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
|
||||
|
||||
@@ -163,18 +163,20 @@
|
||||
;; Subscribe to corresponding topics
|
||||
(a/<! (msgbus :sub {:topics [file-id team-id] :chan sub-ch}))
|
||||
(a/<! (handle-connect cfg))
|
||||
|
||||
;; when connection is closed
|
||||
(mtx-aconn :dec)
|
||||
(mtx-sessions :observe (/ (inst-ms (dt/duration-between created-at (dt/now))) 1000.0))
|
||||
|
||||
;; close subscription
|
||||
(a/close! sub-ch))))
|
||||
|
||||
(on-error [_conn e]
|
||||
(mtx-aconn :dec)
|
||||
(mtx-sessions :observe (/ (inst-ms (dt/duration-between created-at (dt/now))) 1000.0))
|
||||
(log/tracef "on-error %s (%s)" (:session-id cfg) (ex-message e))
|
||||
(a/close! out-ch)
|
||||
(a/close! rcv-ch))
|
||||
|
||||
(on-close [_conn _status _reason]
|
||||
(mtx-aconn :dec)
|
||||
(mtx-sessions :observe (/ (inst-ms (dt/duration-between created-at (dt/now))) 1000.0))
|
||||
(log/tracef "on-close %s" (:session-id cfg))
|
||||
(a/close! out-ch)
|
||||
(a/close! rcv-ch))
|
||||
|
||||
@@ -289,13 +289,13 @@
|
||||
"Updates the viewbox for groups imported from SVG's"
|
||||
[{:keys [selrect svg-viewbox] :as group} new-selrect]
|
||||
(let [;; Gets deltas for the selrect to update the svg-viewbox (for svg-imports)
|
||||
deltas {:x (- (:x new-selrect) (:x selrect))
|
||||
:y (- (:y new-selrect) (:y selrect))
|
||||
:width (- (:width new-selrect) (:width selrect))
|
||||
:height (- (:height new-selrect) (:height selrect))}]
|
||||
deltas {:x (- (:x new-selrect 0) (:x selrect 0))
|
||||
:y (- (:y new-selrect 0) (:y selrect 0))
|
||||
:width (- (:width new-selrect 1) (:width selrect 1))
|
||||
:height (- (:height new-selrect 1) (:height selrect 1))}]
|
||||
|
||||
(cond-> group
|
||||
svg-viewbox
|
||||
(and (some? svg-viewbox) (some? selrect) (some? new-selrect))
|
||||
(update :svg-viewbox
|
||||
#(-> %
|
||||
(update :x + (:x deltas))
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
|
||||
;; --- Constants
|
||||
|
||||
(def email-rx
|
||||
#"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
|
||||
|
||||
(def uuid-rx
|
||||
#"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
|
||||
|
||||
@@ -86,12 +83,6 @@
|
||||
v
|
||||
::s/invalid))
|
||||
|
||||
(defn- email-conformer
|
||||
[v]
|
||||
(if (and (string? v) (re-matches email-rx v))
|
||||
v
|
||||
::s/invalid))
|
||||
|
||||
(defn keyword-conformer
|
||||
[v]
|
||||
(cond
|
||||
@@ -109,7 +100,6 @@
|
||||
(s/def ::keyword (s/conformer keyword-conformer name))
|
||||
(s/def ::inst inst?)
|
||||
(s/def ::string string?)
|
||||
(s/def ::email (s/conformer email-conformer str))
|
||||
(s/def ::color (s/conformer color-conformer str))
|
||||
(s/def ::uuid (s/conformer uuid-conformer str))
|
||||
(s/def ::boolean (s/conformer boolean-conformer boolean-unformer))
|
||||
@@ -134,6 +124,18 @@
|
||||
(>= % min-safe-int)
|
||||
(<= % max-safe-int)))
|
||||
|
||||
|
||||
;; --- SPEC: email
|
||||
|
||||
(let [re #"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
|
||||
cfn (fn [v]
|
||||
(if (string? v)
|
||||
(if-let [matches (re-seq re v)]
|
||||
(first matches)
|
||||
(do ::s/invalid))
|
||||
::s/invalid))]
|
||||
(s/def ::email (s/conformer cfn str)))
|
||||
|
||||
;; --- Macros
|
||||
|
||||
(defn spec-assert*
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
(defn ^boolean empty-path-element?
|
||||
[item]
|
||||
(and (= (get item "name") "path")
|
||||
(let [d (get item ["attributes" "d"])]
|
||||
(let [d (get-in item ["attributes" "d"])]
|
||||
(or (str/blank? d)
|
||||
(nil? d)
|
||||
(str/empty? d)))))
|
||||
@@ -93,16 +93,7 @@
|
||||
item))
|
||||
|
||||
(process-element [item xform]
|
||||
(let [item (d/update-when item "elements" #(into [] xform %))]
|
||||
(if (shape-element? item)
|
||||
(update item "elements"
|
||||
(fn [elements]
|
||||
;; flatten content of a shape element
|
||||
(into [] (mapcat (fn [item]
|
||||
(if (group-element? item)
|
||||
(get item "elements")
|
||||
[item]))) elements)))
|
||||
item)))]
|
||||
(d/update-when item "elements" #(into [] xform %)))]
|
||||
|
||||
(let [xform (comp (remove empty-defs-element?)
|
||||
(remove empty-path-element?)
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
svg {
|
||||
fill: #2C233E;
|
||||
max-width: 11vw;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[app.config :as cfg]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.users :as udu]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui :as ui]
|
||||
@@ -79,20 +79,19 @@
|
||||
|
||||
(defn init-ui
|
||||
[]
|
||||
(st/emit! (rt/initialize-router ui/routes)
|
||||
(rt/initialize-history on-navigate))
|
||||
|
||||
(st/emit! (udu/fetch-profile)
|
||||
(udu/fetch-user-teams))
|
||||
(mf/mount (mf/element ui/app) (dom/get-element "app"))
|
||||
(mf/mount (mf/element modal) (dom/get-element "modal")))
|
||||
(mf/mount (mf/element modal) (dom/get-element "modal")))
|
||||
|
||||
(defn ^:export init
|
||||
[]
|
||||
(i18n/init! cfg/translations)
|
||||
(theme/init! cfg/themes)
|
||||
(st/init)
|
||||
(init-ui))
|
||||
(init-ui)
|
||||
|
||||
(st/emit! (rt/initialize-router ui/routes)
|
||||
(rt/initialize-history on-navigate)
|
||||
(du/fetch-profile-and-teams)))
|
||||
|
||||
(defn reinit
|
||||
[]
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
(watch [this state stream]
|
||||
(let [team-id (current-team-id profile)
|
||||
props (:props profile)]
|
||||
(rx/merge
|
||||
(rx/of (du/profile-fetched profile)
|
||||
(rt/nav' :dashboard-projects {:team-id team-id}))
|
||||
|
||||
(rx/concat
|
||||
(rx/of (du/profile-fetched profile))
|
||||
(rx/of (du/fetch-teams))
|
||||
(rx/of (rt/nav' :dashboard-projects {:team-id team-id}))
|
||||
(when-not (:onboarding-viewed props)
|
||||
(->> (rx/of (modal/show {:type :onboarding}))
|
||||
(rx/delay 1000))))))))
|
||||
|
||||
@@ -65,8 +65,6 @@
|
||||
;; Data Fetching
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- Fetch Team
|
||||
|
||||
(defn fetch-team
|
||||
[{:keys [id] :as params}]
|
||||
(letfn [(fetched [team state]
|
||||
@@ -117,7 +115,7 @@
|
||||
(defn fetch-bundle
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::fetch-team
|
||||
(ptk/reify ::fetch-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [profile (:profile state)]
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.config :as cf]
|
||||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.repo :as rp]
|
||||
@@ -47,19 +48,29 @@
|
||||
::lang
|
||||
::theme]))
|
||||
|
||||
;; --- Profile Fetched
|
||||
(defn fetch-teams
|
||||
[]
|
||||
(letfn [(on-fetched [state data]
|
||||
(let [teams (d/index-by :id data)]
|
||||
(assoc state :teams teams)))]
|
||||
(ptk/reify ::fetch-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map (fn [data] #(on-fetched % data))))))))
|
||||
|
||||
(defn profile-fetched
|
||||
[{:keys [fullname id] :as data}]
|
||||
(us/verify ::profile data)
|
||||
(ptk/reify ::profile-fetched
|
||||
IDeref
|
||||
(-deref [_] data)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :profile-id id)
|
||||
(assoc :profile data)
|
||||
;; Safeguard if the profile is loaded after teams
|
||||
(assoc-in [:profile :teams] (get-in state [:profile :teams]))))
|
||||
(assoc :profile data)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
@@ -73,12 +84,31 @@
|
||||
|
||||
(defn fetch-profile
|
||||
[]
|
||||
(reify
|
||||
(ptk/reify ::fetch-profile
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query! :profile)
|
||||
(rx/map profile-fetched)))))
|
||||
|
||||
(defn fetch-profile-and-teams
|
||||
"Event used mainly on application bootstrap; it fetches the profile
|
||||
and if and only if the fetched profile corresponds to an
|
||||
authenticated user; proceed to fetch teams."
|
||||
[]
|
||||
(ptk/reify ::fetch-profile-and-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/merge
|
||||
(rx/of (fetch-profile))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::profile-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat (fn [profile]
|
||||
(if (= uuid/zero (:id profile))
|
||||
(rx/empty)
|
||||
(rx/of (fetch-teams))))))))))
|
||||
|
||||
;; --- Update Profile
|
||||
|
||||
(defn update-profile
|
||||
@@ -204,24 +234,3 @@
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :team-users {:team-id team-id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
(defn user-teams-fetched [data]
|
||||
(ptk/reify ::user-teams-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [teams (->> data
|
||||
(group-by :id)
|
||||
(d/mapm #(first %2)))]
|
||||
(assoc-in state [:profile :teams] teams)))))
|
||||
|
||||
(defn fetch-user-teams []
|
||||
(ptk/reify ::fetch-user-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map user-teams-fetched)
|
||||
(rx/catch (fn [error]
|
||||
(if (= (:type error) :not-found)
|
||||
(rx/of (rt/nav :auth-login))
|
||||
(rx/empty))))))))
|
||||
|
||||
|
||||
@@ -105,6 +105,10 @@
|
||||
%)))]
|
||||
(attrs/get-attrs-multi nodes attrs)))
|
||||
|
||||
(defn current-root-values
|
||||
[{:keys [attrs shape]}]
|
||||
(shape-current-values shape txt/is-root-node? attrs))
|
||||
|
||||
(defn current-paragraph-values
|
||||
[{:keys [editor-state attrs shape]}]
|
||||
(if editor-state
|
||||
@@ -172,22 +176,21 @@
|
||||
|
||||
(defn update-text-attrs
|
||||
[{:keys [id attrs]}]
|
||||
(let [attrs (d/without-nils attrs)]
|
||||
(ptk/reify ::update-text-attrs
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(d/update-in-when state [:workspace-editor-state id] ted/update-editor-current-inline-styles attrs))
|
||||
(ptk/reify ::update-text-attrs
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(d/update-in-when state [:workspace-editor-state id] ted/update-editor-current-inline-styles attrs))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(when-not (some? (get-in state [:workspace-editor-state id]))
|
||||
(let [objects (dwc/lookup-page-objects state)
|
||||
shape (get objects id)
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(when-not (some? (get-in state [:workspace-editor-state id]))
|
||||
(let [objects (dwc/lookup-page-objects state)
|
||||
shape (get objects id)
|
||||
|
||||
update-fn #(update-shape % txt/is-text-node? attrs/merge attrs)
|
||||
shape-ids (cond (= (:type shape) :text) [id]
|
||||
(= (:type shape) :group) (cp/get-children id objects))]
|
||||
(rx/of (dwc/update-shapes shape-ids update-fn))))))))
|
||||
update-fn #(update-shape % txt/is-text-node? attrs/merge attrs)
|
||||
shape-ids (cond (= (:type shape) :text) [id]
|
||||
(= (:type shape) :group) (cp/get-children id objects))]
|
||||
(rx/of (dwc/update-shapes shape-ids update-fn)))))))
|
||||
|
||||
;; --- RESIZE UTILS
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
(def profile
|
||||
(l/derived :profile st/state))
|
||||
|
||||
(def teams
|
||||
(l/derived :teams st/state))
|
||||
|
||||
(def exception
|
||||
(l/derived :exception st/state))
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
(mf/defc teams-selector-dropdown
|
||||
[{:keys [team profile locale] :as props}]
|
||||
(let [show-dropdown? (mf/use-state false)
|
||||
teams (mf/use-state [])
|
||||
teams (mf/deref refs/teams)
|
||||
|
||||
on-create-clicked
|
||||
(mf/use-callback
|
||||
@@ -217,12 +217,6 @@
|
||||
(da/set-current-team! team-id)
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps (:id team))
|
||||
(fn []
|
||||
(->> (rp/query! :teams)
|
||||
(rx/subs #(reset! teams %)))))
|
||||
|
||||
[:ul.dropdown.teams-dropdown
|
||||
[:li.title (t locale "dashboard.switch-team")]
|
||||
[:hr]
|
||||
@@ -230,7 +224,7 @@
|
||||
[:span.team-icon i/logo-icon]
|
||||
[:span.team-text (t locale "dashboard.your-penpot")]]
|
||||
|
||||
(for [team (remove :is-default @teams)]
|
||||
(for [team (remove :is-default (vals teams))]
|
||||
[:* {:key (:id team)}
|
||||
[:li.team-name {:on-click (partial team-selected (:id team))}
|
||||
[:span.team-icon
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
|
||||
(ns app.main.ui.measurements
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.store :as st]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as mth]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.main.store :as st]))
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; ------------------------------------------------
|
||||
;; CONSTANTS
|
||||
@@ -233,19 +234,21 @@
|
||||
:stroke-width (/ select-guide-width zoom)
|
||||
:stroke-dasharray (/ select-guide-dasharray zoom)}}])])
|
||||
|
||||
(mf/defc measurement [{:keys [bounds frame selected-shapes hover-shape zoom]}]
|
||||
(let [selected-ids (into #{} (map :id) selected-shapes)
|
||||
selected-selrect (gsh/selection-rect selected-shapes)
|
||||
hover-selrect (:selrect hover-shape)
|
||||
bounds-selrect (bound->selrect bounds)]
|
||||
(mf/defc measurement
|
||||
[{:keys [bounds frame selected-shapes hover-shape zoom]}]
|
||||
(let [selected-ids (into #{} (map :id) selected-shapes)
|
||||
selected-selrect (gsh/selection-rect selected-shapes)
|
||||
hover-selrect (:selrect hover-shape)
|
||||
bounds-selrect (bound->selrect bounds)
|
||||
hover-selected-shape? (not (contains? selected-ids (:id hover-shape)))]
|
||||
|
||||
(when (and (seq selected-shapes) (not (contains? selected-ids (:id hover-shape))))
|
||||
(when (seq selected-shapes)
|
||||
[:g.measurement-feedback {:pointer-events "none"}
|
||||
[:& selection-guides {:selrect selected-selrect :bounds bounds :zoom zoom}]
|
||||
[:& size-display {:selrect selected-selrect :zoom zoom}]
|
||||
|
||||
(if (not hover-shape)
|
||||
(when frame
|
||||
|
||||
(if (or (not hover-shape) (not hover-selected-shape?))
|
||||
(when (and frame (not= uuid/zero (:id frame)))
|
||||
[:g.hover-shapes
|
||||
[:& distance-display {:from (:selrect frame)
|
||||
:to selected-selrect
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
[:hr]
|
||||
|
||||
[:li {:on-click show-release-notes}
|
||||
i/msg-info
|
||||
i/pencil
|
||||
[:span.element-title (tr "labels.release-notes")]]
|
||||
|
||||
(when cf/feedback-enabled
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
")
|
||||
|
||||
;; -- Embed fonts into styles
|
||||
|
||||
(defn get-node-fonts
|
||||
[node]
|
||||
(let [current-font (if (not (nil? (:font-id node)))
|
||||
@@ -39,35 +40,39 @@
|
||||
children-font (map get-node-fonts (:children node))]
|
||||
(reduce set/union (conj children-font current-font))))
|
||||
|
||||
(defn get-local-font-css
|
||||
[font-id font-variant-id]
|
||||
(let [{:keys [family variants] :as font} (get @fonts/fontsdb font-id)
|
||||
{:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants)]
|
||||
(-> (str/format font-face-template {:family family :suffix (or suffix font-variant-id) :width weight})
|
||||
(p/resolved))))
|
||||
(defn get-font-css
|
||||
"Given a font and the variant-id, retrieves the style CSS for it."
|
||||
[{:keys [id backend family variants] :as font} font-variant-id]
|
||||
(if (= :google backend)
|
||||
(-> (fonts/gfont-url family [{:id font-variant-id}])
|
||||
(js/fetch)
|
||||
(p/then (fn [res] (.text res))))
|
||||
|
||||
(defn fetch-font-css
|
||||
[font-id font-variant-id]
|
||||
(let [{:keys [backend family] :as entry} (get @fonts/fontsdb font-id)]
|
||||
(if (= :google backend)
|
||||
(-> (fonts/gfont-url family [{:id font-variant-id}])
|
||||
(js/fetch)
|
||||
(p/then (fn [res] (.text res))))
|
||||
(get-local-font-css font-id font-variant-id))))
|
||||
(let [{:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants)
|
||||
result (str/fmt font-face-template {:family family
|
||||
:style style
|
||||
:suffix (or suffix font-variant-id)
|
||||
:weight weight})]
|
||||
(p/resolved result))))
|
||||
|
||||
(defn get-text-font-data [text]
|
||||
(->> text
|
||||
(defn get-font-data
|
||||
"Parses the CSS and retrieves the font data as DataURI."
|
||||
[^string css]
|
||||
(->> css
|
||||
(re-seq #"url\(([^)]+)\)")
|
||||
(map second)
|
||||
(map df/fetch-as-data-uri)
|
||||
(p/all)))
|
||||
|
||||
(defn embed-font [{:keys [font-id font-variant-id] :or {font-variant-id "regular"}}]
|
||||
(let [{:keys [backend family]} (get @fonts/fontsdb font-id)]
|
||||
(p/let [font-text (fetch-font-css font-id font-variant-id)
|
||||
url-to-data (get-text-font-data font-text)
|
||||
(defn embed-font
|
||||
"Given a font-id and font-variant-id, retrieves the CSS for it and
|
||||
convert all external urls to embedded data URI's."
|
||||
[{:keys [font-id font-variant-id] :or {font-variant-id "regular"}}]
|
||||
(let [{:keys [backend family] :as font} (get @fonts/fontsdb font-id)]
|
||||
(p/let [css (get-font-css font font-variant-id)
|
||||
url-to-data (get-font-data css)
|
||||
replace-text (fn [text [url data]] (str/replace text url data))]
|
||||
(reduce replace-text font-text url-to-data))))
|
||||
(reduce replace-text css url-to-data))))
|
||||
|
||||
(mf/defc embed-fontfaces-style
|
||||
{::mf/wrap-props false}
|
||||
@@ -81,7 +86,9 @@
|
||||
font-to-embed (if (empty? font-to-embed) #{txt/default-text-attrs} font-to-embed)
|
||||
embeded (map embed-font font-to-embed)]
|
||||
(-> (p/all embeded)
|
||||
(p/then (fn [result] (reset! style (str/join "\n" result))))))))
|
||||
(p/then (fn [result]
|
||||
(reset! style (str/join "\n" result))))))))
|
||||
|
||||
|
||||
(when (some? @style)
|
||||
[:style @style])))
|
||||
|
||||
@@ -265,19 +265,18 @@
|
||||
|
||||
(mf/defc viewer-page
|
||||
[{:keys [file-id page-id index token section] :as props}]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps file-id page-id token)
|
||||
(st/emitf (dv/initialize props)))
|
||||
|
||||
(let [data (mf/deref refs/viewer-data)
|
||||
state (mf/deref refs/viewer-local)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps file-id page-id token)
|
||||
(fn []
|
||||
(st/emit! (dv/initialize props))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps (:file data))
|
||||
#(when (:file data)
|
||||
(dom/set-html-title (tr "title.viewer"
|
||||
(get-in data [:file :name])))))
|
||||
#(when-let [name (get-in data [:file :name])]
|
||||
(dom/set-html-title (tr "title.viewer" name))))
|
||||
|
||||
(when (and data state)
|
||||
[:& viewer-content
|
||||
|
||||
@@ -188,12 +188,12 @@
|
||||
total (count frames)
|
||||
locale (mf/deref i18n/locale)
|
||||
profile (mf/deref refs/profile)
|
||||
anonymous? (= uuid/zero (:id profile))
|
||||
teams (mf/deref refs/teams)
|
||||
|
||||
team-id (get-in data [:project :team-id])
|
||||
|
||||
has-permission? (and (not anonymous?)
|
||||
(contains? (:teams profile) team-id))
|
||||
has-permission? (and (not= uuid/zero (:id profile))
|
||||
(contains? teams team-id))
|
||||
|
||||
project-id (get-in data [:project :id])
|
||||
file-id (get-in data [:file :id])
|
||||
|
||||
@@ -67,8 +67,7 @@
|
||||
(def shape-attrs
|
||||
[:grow-type])
|
||||
|
||||
(def root-attrs
|
||||
(d/concat text-valign-attrs text-align-attrs))
|
||||
(def root-attrs text-valign-attrs)
|
||||
|
||||
(def paragraph-attrs
|
||||
(d/concat text-align-attrs
|
||||
@@ -241,18 +240,19 @@
|
||||
(when-not (empty? attrs)
|
||||
(st/emit! (dwt/update-text-attrs {:id id :attrs attrs})))))
|
||||
|
||||
typography (cond
|
||||
(and (:typography-ref-id values)
|
||||
(not= (:typography-ref-id values) :multiple)
|
||||
(not= (:typography-ref-file values) file-id))
|
||||
(-> shared-libs
|
||||
(get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)])
|
||||
(assoc :file-id (:typography-ref-file values)))
|
||||
typography
|
||||
(cond
|
||||
(and (:typography-ref-id values)
|
||||
(not= (:typography-ref-id values) :multiple)
|
||||
(not= (:typography-ref-file values) file-id))
|
||||
(-> shared-libs
|
||||
(get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)])
|
||||
(assoc :file-id (:typography-ref-file values)))
|
||||
|
||||
(and (:typography-ref-id values)
|
||||
(not= (:typography-ref-id values) :multiple)
|
||||
(= (:typography-ref-file values) file-id))
|
||||
(get typographies (:typography-ref-id values)))
|
||||
(and (:typography-ref-id values)
|
||||
(not= (:typography-ref-id values) :multiple)
|
||||
(= (:typography-ref-file values) file-id))
|
||||
(get typographies (:typography-ref-id values)))
|
||||
|
||||
on-convert-to-typography
|
||||
(mf/use-callback
|
||||
|
||||
@@ -42,12 +42,11 @@
|
||||
(:fill fill-values) (assoc :fill-color (:fill fill-values))
|
||||
(:opacity fill-values) (assoc :fill-opacity (:fill fill-values)))
|
||||
|
||||
text-values (merge
|
||||
(select-keys shape [:grow-type :vertical-align :text-align])
|
||||
#_(dwt/current-root-values
|
||||
{:editor-state editor-state
|
||||
:shape shape
|
||||
:attrs root-attrs})
|
||||
text-values (d/merge
|
||||
(select-keys shape [:grow-type])
|
||||
(dwt/current-root-values
|
||||
{:shape shape
|
||||
:attrs root-attrs})
|
||||
(dwt/current-paragraph-values
|
||||
{:editor-state editor-state
|
||||
:shape shape
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.measurements :as msr]
|
||||
[app.main.ui.workspace.shapes :as shapes]
|
||||
[app.main.ui.workspace.shapes.text.editor :as editor]
|
||||
[app.main.ui.workspace.viewport.actions :as actions]
|
||||
@@ -88,6 +89,15 @@
|
||||
drawing-tool (:tool drawing)
|
||||
drawing-obj (:object drawing)
|
||||
|
||||
selected-shapes (into []
|
||||
(comp (map #(get objects %))
|
||||
(filter some?))
|
||||
selected)
|
||||
selected-frames (into #{} (map :frame-id) selected-shapes)
|
||||
|
||||
;; Only when we have all the selected shapes in one frame
|
||||
selected-frame (when (= (count selected-frames) 1) (get objects (first selected-frames)))
|
||||
|
||||
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
||||
(and (some? drawing-obj) (= :path (:type drawing-obj))))
|
||||
text-editing? (and edition (= :text (get-in objects [edition :type])))
|
||||
@@ -213,10 +223,17 @@
|
||||
{:selected selected
|
||||
:zoom zoom
|
||||
:edition edition
|
||||
:show-distances (and (not transform) show-distances?)
|
||||
:disable-handlers (or drawing-tool edition)
|
||||
:on-move-selected on-move-selected}])
|
||||
|
||||
(when (and (not transform) show-distances?)
|
||||
[:& msr/measurement
|
||||
{:bounds vbox
|
||||
:selected-shapes selected-shapes
|
||||
:frame selected-frame
|
||||
:hover-shape @hover
|
||||
:zoom zoom}])
|
||||
|
||||
(when text-editing?
|
||||
[:& editor/text-shape-edit {:shape (get objects edition)}])
|
||||
|
||||
@@ -279,7 +296,7 @@
|
||||
{:page-id page-id}])
|
||||
|
||||
[:& widgets/viewport-actions]
|
||||
|
||||
|
||||
(when show-prototypes?
|
||||
[:& interactions/interactions
|
||||
{:selected selected}])
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "zoom"]))]}
|
||||
[props]
|
||||
(let [shapes (obj/get props "shapes")
|
||||
zoom (obj/get props "zoom")
|
||||
color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes))))
|
||||
"#31EFB8" "#00E0FF")]
|
||||
zoom (obj/get props "zoom")
|
||||
color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes))))
|
||||
"#31EFB8" "#00E0FF")]
|
||||
(for [shape shapes]
|
||||
[:& outline {:key (str "outline-" (:id shape))
|
||||
:shape (gsh/transform-shape shape)
|
||||
@@ -97,7 +97,8 @@
|
||||
shapes (->> outlines-ids
|
||||
(filter #(not= edition %))
|
||||
(map #(get objects %))
|
||||
(filterv show-outline?))]
|
||||
(filterv show-outline?)
|
||||
(filter some?))]
|
||||
|
||||
[:g.outlines {:display (when (some? transform) "none")}
|
||||
[:& shape-outlines-render {:shapes shapes
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
[app.main.streams :as ms]
|
||||
[app.main.ui.cursors :as cur]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.measurements :as msr]
|
||||
[app.main.ui.workspace.viewport.outline :refer [outline]]
|
||||
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
|
||||
[app.util.data :as d]
|
||||
@@ -295,7 +294,7 @@
|
||||
:fill "transparent"}}]]))
|
||||
|
||||
(mf/defc multiple-selection-handlers
|
||||
[{:keys [shapes selected zoom color show-distances disable-handlers on-move-selected] :as props}]
|
||||
[{:keys [shapes selected zoom color disable-handlers on-move-selected] :as props}]
|
||||
(let [shape (mf/use-memo
|
||||
(mf/deps shapes)
|
||||
#(->> shapes
|
||||
@@ -327,17 +326,11 @@
|
||||
:on-resize on-resize
|
||||
:on-rotate on-rotate}]
|
||||
|
||||
(when show-distances
|
||||
[:& msr/measurement {:bounds vbox
|
||||
:selected-shapes shapes
|
||||
:hover-shape hover-shape
|
||||
:zoom zoom}])
|
||||
|
||||
(when (debug? :selection-center)
|
||||
[:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])]))
|
||||
|
||||
(mf/defc single-selection-handlers
|
||||
[{:keys [shape zoom color show-distances disable-handlers on-move-selected] :as props}]
|
||||
[{:keys [shape zoom color disable-handlers on-move-selected] :as props}]
|
||||
(let [shape-id (:id shape)
|
||||
shape (geom/transform-shape shape)
|
||||
|
||||
@@ -357,25 +350,17 @@
|
||||
on-rotate
|
||||
#(do (dom/stop-propagation %)
|
||||
(st/emit! (dw/start-rotate [shape])))]
|
||||
[:*
|
||||
[:& controls {:shape shape'
|
||||
:zoom zoom
|
||||
:color color
|
||||
:on-rotate on-rotate
|
||||
:on-resize on-resize
|
||||
:disable-handlers disable-handlers
|
||||
:on-move-selected on-move-selected}]
|
||||
|
||||
(when show-distances
|
||||
[:& msr/measurement {:bounds vbox
|
||||
:frame frame
|
||||
:selected-shapes [shape]
|
||||
:hover-shape hover-shape
|
||||
:zoom zoom}])]))
|
||||
[:& controls {:shape shape'
|
||||
:zoom zoom
|
||||
:color color
|
||||
:on-rotate on-rotate
|
||||
:on-resize on-resize
|
||||
:disable-handlers disable-handlers
|
||||
:on-move-selected on-move-selected}]))
|
||||
|
||||
(mf/defc selection-handlers
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [selected edition zoom show-distances disable-handlers on-move-selected] :as props}]
|
||||
[{:keys [selected edition zoom disable-handlers on-move-selected] :as props}]
|
||||
(let [;; We need remove posible nil values because on shape
|
||||
;; deletion many shape will reamin selected and deleted
|
||||
;; in the same time for small instant of time
|
||||
@@ -396,7 +381,6 @@
|
||||
:selected selected
|
||||
:zoom zoom
|
||||
:color color
|
||||
:show-distances show-distances
|
||||
:disable-handlers disable-handlers
|
||||
:on-move-selected on-move-selected}]
|
||||
|
||||
@@ -415,6 +399,5 @@
|
||||
[:& single-selection-handlers {:shape shape
|
||||
:zoom zoom
|
||||
:color color
|
||||
:show-distances show-distances
|
||||
:disable-handlers disable-handlers
|
||||
:on-move-selected on-move-selected}])))
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
(into [] (impl-simplify/simplify points tolerance true)))))
|
||||
|
||||
;;
|
||||
(def commands-regex #"(?i)[a-z][^a-z]*")
|
||||
(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*")
|
||||
|
||||
;; Matches numbers for path values allows values like... -.01, 10, +12.22
|
||||
;; 0 and 1 are special because can refer to flags
|
||||
(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)")
|
||||
(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?")
|
||||
|
||||
(def flag-regex #"[01]")
|
||||
|
||||
@@ -373,14 +373,15 @@
|
||||
(reduce simplify-command [[start] start-pos start-pos start-pos start-pos])
|
||||
(first))))
|
||||
|
||||
(defn path->content [string]
|
||||
(let [clean-string (-> string
|
||||
(str/trim)
|
||||
;; Change "commas" for spaces
|
||||
(str/replace #"," " ")
|
||||
;; Remove all consecutive spaces
|
||||
(str/replace #"\s+" " "))
|
||||
commands (re-seq commands-regex clean-string)]
|
||||
(defn path->content [path-str]
|
||||
(let [clean-path-str
|
||||
(-> path-str
|
||||
(str/trim)
|
||||
;; Change "commas" for spaces
|
||||
(str/replace #"," " ")
|
||||
;; Remove all consecutive spaces
|
||||
(str/replace #"\s+" " "))
|
||||
commands (re-seq commands-regex clean-path-str)]
|
||||
(-> (mapcat parse-command commands)
|
||||
(simplify-commands))))
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
(swap! storage assoc ::locale lang)
|
||||
(reset! locale lang))
|
||||
(do
|
||||
(swap! storage dissoc ::locale)
|
||||
(reset! locale (autodetect)))))
|
||||
|
||||
(defn reset-locale
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
(ns app.util.text-editor
|
||||
"Draft related abstraction functions."
|
||||
(:require
|
||||
["draft-js" :as draft]
|
||||
["./text_editor_impl.js" :as impl]
|
||||
["draft-js" :as draft]
|
||||
[app.common.attrs :as attrs]
|
||||
[app.common.text :as txt]
|
||||
[app.common.data :as d]
|
||||
[app.util.transit :as t]
|
||||
[app.common.text :as txt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.array :as arr]
|
||||
[app.util.object :as obj]
|
||||
[app.util.transit :as t]
|
||||
[clojure.walk :as walk]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
@@ -26,6 +27,7 @@
|
||||
(defn encode-style-value
|
||||
[v]
|
||||
(cond
|
||||
(uuid? v) (str "u:" v)
|
||||
(string? v) (str "s:" v)
|
||||
(number? v) (str "n:" v)
|
||||
(keyword? v) (str "k:" (name v))
|
||||
@@ -41,6 +43,7 @@
|
||||
"n:" (js/Number (subs v 2))
|
||||
"k:" (keyword (subs v 2))
|
||||
"m:" (t/decode (subs v 2))
|
||||
"u:" (uuid/uuid (subs v 2))
|
||||
"z:" nil
|
||||
"o:" (subs v 2)
|
||||
v)))
|
||||
|
||||
@@ -109,12 +109,18 @@ export function applyInlineStyle(state, styles) {
|
||||
let content = null;
|
||||
|
||||
for (let style of styles) {
|
||||
const [p, k, _] = style.split("$$$");
|
||||
console.log("applyInlineStyle", style);
|
||||
|
||||
const [p, k, v] = style.split("$$$");
|
||||
const prefix = [p, k, ""].join("$$$");
|
||||
|
||||
content = state.getCurrentContent();
|
||||
content = removeInlineStylePrefix(content, selection, prefix);
|
||||
content = Modifier.applyInlineStyle(content, selection, style);
|
||||
|
||||
if (v !== "z:null") {
|
||||
content = Modifier.applyInlineStyle(content, selection, style);
|
||||
}
|
||||
|
||||
state = EditorState.push(state, content, "change-inline-style");
|
||||
}
|
||||
|
||||
|
||||
@@ -90,9 +90,7 @@
|
||||
(let [target (.-target ^js event)]
|
||||
(when (and (not (.-isContentEditable target)) ;; ignore when pasting into
|
||||
(not= (.-tagName target) "INPUT")) ;; an editable control
|
||||
(-> ^js event
|
||||
(.getBrowserEvent)
|
||||
(.-clipboardData)))))
|
||||
(.. ^js event getBrowserEvent -clipboardData))))
|
||||
|
||||
(defn extract-text
|
||||
[clipboard-data]
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.4.0-alpha
|
||||
1.4.1-alpha
|
||||
|
||||
Reference in New Issue
Block a user