mirror of
https://github.com/penpot/penpot.git
synced 2026-05-18 06:23:49 +00:00
Merge pull request #2000 from penpot/alotor-frames
Nested/Rotated Boards
This commit is contained in:
commit
408f73396f
@ -3,6 +3,9 @@
|
||||
## :rocket: Next
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
- Allow for nested and rotated boards inside other boards and groups [Taiga #2874](https://tree.taiga.io/project/penpot/us/2874?milestone=319982)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
### :arrow_up: Deps updates
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
@ -289,7 +290,7 @@
|
||||
frame (-> page :objects cph/get-frames)]
|
||||
(assoc frame :page-id (:id page)))))
|
||||
|
||||
;; function responsible to filter objects data strucuture of
|
||||
;; function responsible to filter objects data structure of
|
||||
;; all unneded shapes if a concrete frame is provided. If no
|
||||
;; frame, the objects is returned untouched.
|
||||
(filter-objects [objects frame-id]
|
||||
@ -307,10 +308,24 @@
|
||||
object-id (str page-id frame-id)
|
||||
frame (if-let [thumb (get thumbnails object-id)]
|
||||
(assoc frame :thumbnail thumb :shapes [])
|
||||
(dissoc frame :thumbnail))]
|
||||
(dissoc frame :thumbnail))
|
||||
|
||||
children-ids
|
||||
(cph/get-children-ids objects frame-id)
|
||||
|
||||
bounds
|
||||
(when (:show-content frame)
|
||||
(gsh/selection-rect (concat [frame] (->> children-ids (map (d/getf objects))))))
|
||||
|
||||
frame
|
||||
(cond-> frame
|
||||
(some? bounds)
|
||||
(assoc :children-bounds bounds))]
|
||||
|
||||
(if (:thumbnail frame)
|
||||
(recur (-> (assoc objects frame-id frame)
|
||||
(d/without-keys (cph/get-children-ids objects frame-id)))
|
||||
(recur (-> objects
|
||||
(assoc frame-id frame)
|
||||
(d/without-keys children-ids))
|
||||
(rest frames))
|
||||
(recur (assoc objects frame-id frame)
|
||||
(rest frames))))
|
||||
|
||||
@ -53,8 +53,10 @@
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
|
||||
(p/let [slink (slnk/retrieve-share-link pool file-id share-id)
|
||||
perms (files/get-permissions pool profile-id file-id share-id)
|
||||
thumbs (files/retrieve-object-thumbnails cfg file-id)
|
||||
bundle (p/-> (retrieve-bundle cfg file-id)
|
||||
(assoc :permissions perms))]
|
||||
(assoc :permissions perms)
|
||||
(assoc-in [:file :thumbnails] thumbs))]
|
||||
|
||||
;; When we have neither profile nor share, we just return a not
|
||||
;; found response to the user.
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
|
||||
funcool/promesa {:mvn/version "8.0.450"}
|
||||
funcool/cuerdas {:mvn/version "2022.06.13-401"}
|
||||
funcool/cuerdas {:mvn/version "2022.06.16-403"}
|
||||
|
||||
lambdaisland/uri {:mvn/version "1.13.95"
|
||||
:exclusions [org.clojure/data.json]}
|
||||
|
||||
@ -222,9 +222,13 @@
|
||||
|
||||
(defn close-artboard [file]
|
||||
(assert (nil? (:current-component-id file)))
|
||||
(-> file
|
||||
(assoc :current-frame-id root-frame)
|
||||
(update :parent-stack pop)))
|
||||
|
||||
(let [parent-id (-> file :parent-id peek)
|
||||
parent (lookup-shape file parent-id)
|
||||
current-frame-id (or (:frame-id parent) root-frame)]
|
||||
(-> file
|
||||
(assoc :current-frame-id current-frame-id)
|
||||
(update :parent-stack pop))))
|
||||
|
||||
(defn add-group [file data]
|
||||
(let [frame-id (:current-frame-id file)
|
||||
|
||||
@ -159,6 +159,7 @@
|
||||
(dm/export gtr/move)
|
||||
(dm/export gtr/absolute-move)
|
||||
(dm/export gtr/transform-matrix)
|
||||
(dm/export gtr/transform-str)
|
||||
(dm/export gtr/inverse-transform-matrix)
|
||||
(dm/export gtr/transform-point-center)
|
||||
(dm/export gtr/transform-rect)
|
||||
@ -171,6 +172,7 @@
|
||||
(dm/export gtr/merge-modifiers)
|
||||
(dm/export gtr/transform-shape)
|
||||
(dm/export gtr/transform-selrect)
|
||||
(dm/export gtr/transform-selrect-matrix)
|
||||
(dm/export gtr/transform-bounds)
|
||||
(dm/export gtr/modifiers->transform)
|
||||
(dm/export gtr/empty-modifiers?)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
(ns app.common.geom.shapes.transforms
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
@ -143,16 +144,30 @@
|
||||
([shape params]
|
||||
(transform-matrix shape params (or (gco/center-shape shape) (gpt/point 0 0))))
|
||||
|
||||
([{:keys [flip-x flip-y] :as shape} {:keys [no-flip]} shape-center]
|
||||
([{:keys [flip-x flip-y transform] :as shape} {:keys [no-flip]} shape-center]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
|
||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||
(cond-> (some? transform)
|
||||
(gmt/multiply transform))
|
||||
|
||||
(cond->
|
||||
(and (not no-flip) flip-x) (gmt/scale (gpt/point -1 1))
|
||||
(and (not no-flip) flip-y) (gmt/scale (gpt/point 1 -1)))
|
||||
(gmt/translate (gpt/negate shape-center)))))
|
||||
|
||||
(defn transform-str
|
||||
([shape]
|
||||
(transform-str shape nil))
|
||||
|
||||
([{:keys [transform flip-x flip-y] :as shape} {:keys [no-flip]}]
|
||||
(if (and (some? shape)
|
||||
(or (some? transform)
|
||||
(and (not no-flip) flip-x)
|
||||
(and (not no-flip) flip-y)))
|
||||
(dm/str (transform-matrix shape))
|
||||
"")))
|
||||
|
||||
(defn inverse-transform-matrix
|
||||
([shape]
|
||||
(let [shape-center (or (gco/center-shape shape)
|
||||
@ -630,6 +645,13 @@
|
||||
(transform-bounds center modifiers)
|
||||
(gpr/points->selrect))))
|
||||
|
||||
(defn transform-selrect-matrix
|
||||
[selrect mtx]
|
||||
(-> selrect
|
||||
(gpr/rect->points)
|
||||
(gco/transform-points mtx)
|
||||
(gpr/points->selrect)))
|
||||
|
||||
(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."
|
||||
|
||||
@ -26,8 +26,8 @@
|
||||
(dm/export focus/is-in-focus?)
|
||||
|
||||
;; Indices
|
||||
(dm/export indices/calculate-z-index)
|
||||
(dm/export indices/update-z-index)
|
||||
#_(dm/export indices/calculate-z-index)
|
||||
#_(dm/export indices/update-z-index)
|
||||
(dm/export indices/generate-child-all-parents-index)
|
||||
(dm/export indices/generate-child-parent-index)
|
||||
(dm/export indices/create-clip-index)
|
||||
|
||||
@ -211,7 +211,7 @@
|
||||
(let [invalid-targets (calculate-invalid-targets objects shape-id)]
|
||||
(and (contains? objects shape-id)
|
||||
(not (invalid-targets parent-id))
|
||||
(cph/valid-frame-target? objects parent-id shape-id))))
|
||||
#_(cph/valid-frame-target? objects parent-id shape-id))))
|
||||
|
||||
(insert-items [prev-shapes index shapes]
|
||||
(let [prev-shapes (or prev-shapes [])]
|
||||
|
||||
@ -80,8 +80,11 @@
|
||||
:x :y
|
||||
:rx :ry
|
||||
:r1 :r2 :r3 :r4
|
||||
:rotation
|
||||
:selrect
|
||||
:points
|
||||
:show-content
|
||||
:hide-in-viewer
|
||||
|
||||
:opacity
|
||||
:blend-mode
|
||||
|
||||
@ -8,26 +8,20 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.pages.indices :as cpi]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn focus-objects
|
||||
[objects focus]
|
||||
(let [[ids-with-children z-index]
|
||||
(let [ids-with-children
|
||||
(when (d/not-empty? focus)
|
||||
[(into (conj focus uuid/zero)
|
||||
(mapcat (partial cph/get-children-ids objects))
|
||||
focus)
|
||||
(cpi/calculate-z-index objects)])
|
||||
|
||||
sort-by-z-index
|
||||
(fn [coll]
|
||||
(->> coll (sort-by (fn [a b] (- (get z-index a) (get z-index b))))))]
|
||||
(into (conj focus uuid/zero)
|
||||
(mapcat (partial cph/get-children-ids objects))
|
||||
focus))]
|
||||
|
||||
(cond-> objects
|
||||
(some? ids-with-children)
|
||||
(-> (select-keys ids-with-children)
|
||||
(assoc-in [uuid/zero :shapes] (sort-by-z-index focus))))))
|
||||
(assoc-in [uuid/zero :shapes] (cph/sort-z-index objects focus))))))
|
||||
|
||||
(defn filter-not-focus
|
||||
[objects focus ids]
|
||||
|
||||
@ -9,23 +9,36 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.spec :as us]
|
||||
[app.common.spec.page :as spec.page]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(declare reduce-objects)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GENERIC SHAPE SELECTORS AND PREDICATES
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn root-frame?
|
||||
(defn root?
|
||||
[{:keys [id type]}]
|
||||
(and (= type :frame)
|
||||
(= id uuid/zero)))
|
||||
(and (= type :frame) (= id uuid/zero)))
|
||||
|
||||
(defn root-frame?
|
||||
([objects id]
|
||||
(root-frame? (get objects id)))
|
||||
|
||||
([{:keys [frame-id type]}]
|
||||
(and (= type :frame)
|
||||
(= frame-id uuid/zero))))
|
||||
|
||||
(defn frame-shape?
|
||||
[{:keys [type]}]
|
||||
(= type :frame))
|
||||
([objects id]
|
||||
(frame-shape? (get objects id)))
|
||||
|
||||
([{:keys [type]}]
|
||||
(= type :frame)))
|
||||
|
||||
(defn group-shape?
|
||||
[{:keys [type]}]
|
||||
@ -39,6 +52,10 @@
|
||||
[{:keys [type]}]
|
||||
(= type :image))
|
||||
|
||||
(defn svg-raw-shape?
|
||||
[{:keys [type]}]
|
||||
(= type :svg-raw))
|
||||
|
||||
(defn unframed-shape?
|
||||
"Checks if it's a non-frame shape in the top level."
|
||||
[shape]
|
||||
@ -93,9 +110,10 @@
|
||||
"Returns a vector of parents of the specified shape."
|
||||
[objects shape-id]
|
||||
(loop [result [] id shape-id]
|
||||
(if-let [parent-id (dm/get-in objects [id :parent-id])]
|
||||
(recur (conj result parent-id) parent-id)
|
||||
result)))
|
||||
(let [parent-id (dm/get-in objects [id :parent-id])]
|
||||
(if (and (some? parent-id) (not= parent-id id))
|
||||
(recur (conj result parent-id) parent-id)
|
||||
result))))
|
||||
|
||||
(defn get-frame
|
||||
"Get the frame that contains the shape. If the shape is already a
|
||||
@ -140,38 +158,146 @@
|
||||
(:shapes)
|
||||
(keep lookup)))))
|
||||
|
||||
(defn get-frames-ids
|
||||
"Retrieves all frame objects as vector. It is not implemented in
|
||||
function of `get-immediate-children` for performance reasons. This
|
||||
function is executed in the render hot path."
|
||||
[objects]
|
||||
(let [lookup (d/getf objects)
|
||||
xform (comp (keep lookup)
|
||||
(filter frame-shape?)
|
||||
(map :id))]
|
||||
(->> (:shapes (lookup uuid/zero))
|
||||
(into [] xform))))
|
||||
|
||||
(defn get-frames
|
||||
"Retrieves all frame objects as vector"
|
||||
[objects]
|
||||
(if (contains? (meta objects) ::index-frames)
|
||||
(::index-frames (meta objects))
|
||||
(let [lookup (d/getf objects)
|
||||
xform (comp (remove #(= uuid/zero %))
|
||||
(keep lookup)
|
||||
(filter frame-shape?))]
|
||||
(->> (keys objects)
|
||||
(into [] xform)))))
|
||||
|
||||
(defn get-frames-ids
|
||||
"Retrieves all frame ids as vector"
|
||||
[objects]
|
||||
(->> (get-frames objects)
|
||||
(mapv :id)))
|
||||
|
||||
(defn get-nested-frames
|
||||
[objects frame-id]
|
||||
(into #{}
|
||||
(comp (filter frame-shape?)
|
||||
(map :id))
|
||||
(get-children objects frame-id)))
|
||||
|
||||
(defn get-root-frames-ids
|
||||
"Retrieves all frame objects as vector. It is not implemented in
|
||||
function of `get-immediate-children` for performance reasons. This
|
||||
function is executed in the render hot path."
|
||||
[objects]
|
||||
(let [lookup (d/getf objects)
|
||||
xform (comp (keep lookup)
|
||||
(filter frame-shape?))]
|
||||
(->> (:shapes (lookup uuid/zero))
|
||||
(into [] xform))))
|
||||
(let [add-frame
|
||||
(fn [result shape]
|
||||
(cond-> result
|
||||
(frame-shape? shape)
|
||||
(conj (:id shape))))]
|
||||
(reduce-objects objects (complement frame-shape?) add-frame [])))
|
||||
|
||||
(defn get-root-objects
|
||||
"Get all the objects under the root object"
|
||||
[objects]
|
||||
(let [add-shape
|
||||
(fn [result shape]
|
||||
(conj result shape))]
|
||||
(reduce-objects objects (complement frame-shape?) add-shape [])))
|
||||
|
||||
(defn get-root-shapes
|
||||
"Get all shapes that are not frames"
|
||||
[objects]
|
||||
(let [add-shape
|
||||
(fn [result shape]
|
||||
(cond-> result
|
||||
(not (frame-shape? shape))
|
||||
(conj shape)))]
|
||||
(reduce-objects objects (complement frame-shape?) add-shape [])))
|
||||
|
||||
(defn get-root-shapes-ids
|
||||
[objects]
|
||||
(->> (get-root-shapes objects)
|
||||
(mapv :id)))
|
||||
|
||||
(defn- get-base
|
||||
[objects id-a id-b]
|
||||
|
||||
(let [parents-a (reverse (get-parents-seq objects id-a))
|
||||
parents-b (reverse (get-parents-seq objects id-b))
|
||||
|
||||
[base base-child-a base-child-b]
|
||||
(loop [parents-a (rest parents-a)
|
||||
parents-b (rest parents-b)
|
||||
base uuid/zero]
|
||||
(cond
|
||||
(not= (first parents-a) (first parents-b))
|
||||
[base (first parents-a) (first parents-b)]
|
||||
|
||||
(or (empty? parents-a) (empty? parents-b))
|
||||
[uuid/zero (first parents-a) (first parents-b)]
|
||||
|
||||
:else
|
||||
(recur (rest parents-a) (rest parents-b) (first parents-a))))
|
||||
|
||||
index-base-a (when base-child-a (get-position-on-parent objects base-child-a))
|
||||
index-base-b (when base-child-b (get-position-on-parent objects base-child-b))]
|
||||
|
||||
[base index-base-a index-base-b]))
|
||||
|
||||
(defn is-shape-over-shape?
|
||||
[objects base-shape-id over-shape-id {:keys [top-frames?]}]
|
||||
|
||||
(let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)]
|
||||
(cond
|
||||
(= base base-shape-id)
|
||||
(and (not top-frames?)
|
||||
(frame-shape? objects base-shape-id)
|
||||
(root-frame? objects base-shape-id))
|
||||
|
||||
(= base over-shape-id)
|
||||
(or top-frames?
|
||||
(not (frame-shape? objects over-shape-id))
|
||||
(not (root-frame? objects over-shape-id)))
|
||||
|
||||
:else
|
||||
(< index-a index-b))))
|
||||
|
||||
(defn sort-z-index
|
||||
([objects ids]
|
||||
(sort-z-index objects ids nil))
|
||||
|
||||
([objects ids {:keys [bottom-frames?] :as options}]
|
||||
(letfn [(comp [id-a id-b]
|
||||
(let [type-a (dm/get-in objects [id-a :type])
|
||||
type-b (dm/get-in objects [id-b :type])]
|
||||
(cond
|
||||
(and bottom-frames? (= :frame type-a) (not= :frame type-b))
|
||||
1
|
||||
|
||||
(and bottom-frames? (not= :frame type-a) (= :frame type-b))
|
||||
-1
|
||||
|
||||
(= id-a id-b)
|
||||
0
|
||||
|
||||
(is-shape-over-shape? objects id-a id-b options)
|
||||
1
|
||||
|
||||
:else
|
||||
-1)))]
|
||||
(sort comp ids))))
|
||||
|
||||
(defn frame-id-by-position
|
||||
[objects position]
|
||||
(let [frames (get-frames objects)]
|
||||
(or
|
||||
(->> frames
|
||||
(reverse)
|
||||
(d/seek #(and position (gsh/has-point? % position)))
|
||||
:id)
|
||||
uuid/zero)))
|
||||
(let [top-frame
|
||||
(->> (get-frames-ids objects)
|
||||
(sort-z-index objects)
|
||||
(d/seek #(and position (gsh/has-point? (get objects %) position))))]
|
||||
(or top-frame uuid/zero)))
|
||||
|
||||
(defn frame-by-position
|
||||
[objects position]
|
||||
(let [frame-id (frame-id-by-position objects position)]
|
||||
(get objects frame-id)))
|
||||
|
||||
(declare indexed-shapes)
|
||||
|
||||
@ -520,3 +646,84 @@
|
||||
|
||||
(-> (select-keys objects selected+parents)
|
||||
(d/update-vals remove-children))))
|
||||
|
||||
(defn is-child?
|
||||
[objects parent-id candidate-child-id]
|
||||
(let [parents (get-parents-seq objects candidate-child-id)]
|
||||
(some? (d/seek #(= % parent-id) parents))))
|
||||
|
||||
(defn reduce-objects
|
||||
([objects reducer-fn init-val]
|
||||
(reduce-objects objects nil reducer-fn init-val))
|
||||
|
||||
([objects check-children? reducer-fn init-val]
|
||||
(let [root-children (get-in objects [uuid/zero :shapes])]
|
||||
(if (empty? root-children)
|
||||
init-val
|
||||
|
||||
(loop [current-val init-val
|
||||
current-id (first root-children)
|
||||
pending-ids (rest root-children)]
|
||||
|
||||
|
||||
(let [current-shape (get objects current-id)
|
||||
next-val (reducer-fn current-val current-shape)
|
||||
next-pending-ids
|
||||
(if (or (nil? check-children?) (check-children? current-shape))
|
||||
(concat (or (:shapes current-shape) []) pending-ids)
|
||||
pending-ids)]
|
||||
|
||||
(if (empty? next-pending-ids)
|
||||
next-val
|
||||
(recur next-val (first next-pending-ids) (rest next-pending-ids)))))))))
|
||||
|
||||
(defn selected-with-children
|
||||
[objects selected]
|
||||
|
||||
(into selected
|
||||
(mapcat #(get-children-ids objects %))
|
||||
selected))
|
||||
|
||||
(defn get-shape-id-root-frame
|
||||
[objects shape-id]
|
||||
(->> (get-parents-seq objects shape-id)
|
||||
(map (d/getf objects))
|
||||
(d/seek #(and (= :frame (:type %))
|
||||
(= uuid/zero (:frame-id %))))
|
||||
|
||||
:id))
|
||||
|
||||
(defn get-viewer-frames
|
||||
([objects]
|
||||
(get-viewer-frames objects nil))
|
||||
|
||||
([objects {:keys [all-frames?]}]
|
||||
(into []
|
||||
(comp (map (d/getf objects))
|
||||
(if all-frames?
|
||||
identity
|
||||
(remove :hide-in-viewer)))
|
||||
(sort-z-index objects (get-frames-ids objects) {:top-frames? true}))))
|
||||
|
||||
|
||||
(defn start-page-index
|
||||
[objects]
|
||||
(with-meta objects {::index-frames (get-frames (with-meta objects nil))}))
|
||||
|
||||
(defn update-page-index
|
||||
[objects]
|
||||
(with-meta objects {::index-frames (get-frames (with-meta objects nil))}))
|
||||
|
||||
(defn start-object-indices
|
||||
[file]
|
||||
(letfn [(process-index [page-index page-id]
|
||||
(update-in page-index [page-id :objects] start-page-index))]
|
||||
(update file :pages-index #(reduce process-index % (keys %)))))
|
||||
|
||||
(defn update-object-indices
|
||||
[file page-id]
|
||||
(update-in file [:pages-index page-id :objects] update-page-index))
|
||||
|
||||
(defn rotated-frame?
|
||||
[frame]
|
||||
(not (mth/almost-zero? (:rotation frame 0))))
|
||||
|
||||
@ -8,76 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn calculate-frame-z-index
|
||||
[z-index frame-id base-idx objects]
|
||||
|
||||
(let [is-frame? (fn [id] (= :frame (get-in objects [id :type])))
|
||||
children (or (get-in objects [frame-id :shapes]) [])]
|
||||
|
||||
(if (empty? children)
|
||||
z-index
|
||||
(loop [current (peek children)
|
||||
pending (pop children)
|
||||
current-idx base-idx
|
||||
z-index z-index]
|
||||
|
||||
(let [children (get-in objects [current :shapes])
|
||||
is-frame? (is-frame? current)
|
||||
pending (if (not is-frame?)
|
||||
(d/concat-vec pending children)
|
||||
pending)]
|
||||
|
||||
(if (empty? pending)
|
||||
(assoc z-index current current-idx)
|
||||
(recur (peek pending)
|
||||
(pop pending)
|
||||
(dec current-idx)
|
||||
(assoc z-index current current-idx))))))))
|
||||
|
||||
;; The z-index is really calculated per-frame. Every frame will have its own
|
||||
;; internal z-index. To calculate the "final" z-index we add the shape z-index with
|
||||
;; the z-index of its frame. This way we can update the z-index per frame without
|
||||
;; the need of recalculate all the frames
|
||||
(defn calculate-z-index
|
||||
"Given a collection of shapes calculates their z-index. Greater index
|
||||
means is displayed over other shapes with less index."
|
||||
[objects]
|
||||
|
||||
(let [frames (cph/get-frames objects)
|
||||
|
||||
by-frame (cph/objects-by-frame objects)
|
||||
frame-base-idx (d/update-vals by-frame count)
|
||||
|
||||
z-index (calculate-frame-z-index {} uuid/zero (get frame-base-idx uuid/zero) objects)]
|
||||
(->> frames
|
||||
(reduce
|
||||
(fn [z-index {:keys [id]}]
|
||||
(calculate-frame-z-index z-index id (get frame-base-idx id) objects)) z-index))))
|
||||
|
||||
(defn update-z-index
|
||||
"Updates the z-index given a set of ids to change and the old and new objects
|
||||
representations"
|
||||
[z-index changed-ids old-objects new-objects]
|
||||
|
||||
(let [old-frames (into #{} (map #(get-in old-objects [% :frame-id])) changed-ids)
|
||||
new-frames (into #{} (map #(get-in new-objects [% :frame-id])) changed-ids)
|
||||
|
||||
changed-frames (set/union old-frames new-frames)
|
||||
|
||||
frames (->> (cph/get-frames new-objects)
|
||||
(map :id)
|
||||
(filter #(contains? changed-frames %)))
|
||||
|
||||
by-frame (cph/objects-by-frame new-objects)
|
||||
frame-base-idx (d/update-vals by-frame count)
|
||||
z-index (calculate-frame-z-index z-index uuid/zero (get frame-base-idx uuid/zero) new-objects)]
|
||||
|
||||
(->> frames
|
||||
(reduce (fn [z-index id]
|
||||
(calculate-frame-z-index z-index id (get frame-base-idx id) new-objects)) z-index))))
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn generate-child-parent-index
|
||||
[objects]
|
||||
@ -102,11 +33,16 @@
|
||||
"Retrieves the mask information for an object"
|
||||
[objects parents-index]
|
||||
(let [retrieve-clips
|
||||
(fn [_ parents]
|
||||
(fn [parents]
|
||||
(let [lookup-object (fn [id] (get objects id))
|
||||
get-clip-parents
|
||||
(fn [shape]
|
||||
(cond-> []
|
||||
(and (= :frame (:type shape))
|
||||
(not (:show-content shape))
|
||||
(not= uuid/zero (:id shape)))
|
||||
(conj shape)
|
||||
|
||||
(:masked-group? shape)
|
||||
(conj (get objects (->> shape :shapes first)))
|
||||
|
||||
@ -117,5 +53,5 @@
|
||||
(comp (map lookup-object)
|
||||
(mapcat get-clip-parents))
|
||||
parents)))]
|
||||
(->> parents-index
|
||||
(d/mapm retrieve-clips))))
|
||||
(-> parents-index
|
||||
(d/update-vals retrieve-clips))))
|
||||
|
||||
@ -52,6 +52,8 @@
|
||||
(s/def ::fill-color-ref-id (s/nilable uuid?))
|
||||
|
||||
(s/def ::hide-fill-on-export boolean?)
|
||||
(s/def ::show-content boolean?)
|
||||
(s/def ::hide-in-viewer boolean?)
|
||||
|
||||
(s/def ::file-thumbnail boolean?)
|
||||
(s/def ::masked-group? boolean?)
|
||||
@ -254,8 +256,7 @@
|
||||
:internal.shape.text.position-data/rtl
|
||||
:internal.shape.text.position-data/text
|
||||
:internal.shape.text.position-data/text-decoration
|
||||
:internal.shape.text.position-data/text-transform]
|
||||
))
|
||||
:internal.shape.text.position-data/text-transform]))
|
||||
|
||||
(s/def :internal.shape.text.position-data/x ::us/safe-number)
|
||||
(s/def :internal.shape.text.position-data/y ::us/safe-number)
|
||||
@ -303,7 +304,9 @@
|
||||
(defmethod shape-spec :frame [_]
|
||||
(s/and ::shape-attrs
|
||||
(s/keys :opt-un [::file-thumbnail
|
||||
::hide-fill-on-export])))
|
||||
::hide-fill-on-export
|
||||
::show-content
|
||||
::hide-in-viewer])))
|
||||
|
||||
(s/def ::shape
|
||||
(s/and (s/multi-spec shape-spec :type)
|
||||
|
||||
@ -25,3 +25,170 @@
|
||||
|
||||
(def has-layout-item false)
|
||||
|
||||
(def size-presets
|
||||
[{:name "APPLE"}
|
||||
{:name "iPhone 12/12 Pro"
|
||||
:width 390
|
||||
:height 844}
|
||||
{:name "iPhone 12 Mini"
|
||||
:width 360
|
||||
:height 780}
|
||||
{:name "iPhone 12 Pro Max"
|
||||
:width 428
|
||||
:height 926}
|
||||
{:name "iPhone X/XS/11 Pro"
|
||||
:width 375
|
||||
:height 812}
|
||||
{:name "iPhone XS Max/XR/11"
|
||||
:width 414
|
||||
:height 896}
|
||||
{:name "iPhone 6/7/8 Plus"
|
||||
:width 414
|
||||
:height 736}
|
||||
{:name "iPhone 6/7/8/SE2"
|
||||
:width 375
|
||||
:height 667}
|
||||
{:name "iPhone 5/SE"
|
||||
:width 320
|
||||
:height 568}
|
||||
{:name "iPad"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "iPad Pro 10.5in"
|
||||
:width 834
|
||||
:height 1112}
|
||||
{:name "iPad Pro 12.9in"
|
||||
:width 1024
|
||||
:height 1366}
|
||||
{:name "Watch 44mm"
|
||||
:width 368
|
||||
:height 448}
|
||||
{:name "Watch 42mm"
|
||||
:width 312
|
||||
:height 390}
|
||||
{:name "Watch 40mm"
|
||||
:width 324
|
||||
:height 394}
|
||||
{:name "Watch 38mm"
|
||||
:width 272
|
||||
:height 340}
|
||||
|
||||
{:name "ANDROID"}
|
||||
{:name "Mobile"
|
||||
:width 360
|
||||
:height 640}
|
||||
{:name "Tablet"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "Google Pixel 4a/5"
|
||||
:width 393
|
||||
:height 851}
|
||||
{:name "Samsung Galaxy S20+"
|
||||
:width 384
|
||||
:height 854}
|
||||
{:name "Samsung Galaxy A71/A51"
|
||||
:width 412
|
||||
:height 914}
|
||||
|
||||
{:name "MICROSOFT"}
|
||||
{:name "Surface Pro 3"
|
||||
:width 1440
|
||||
:height 960}
|
||||
{:name "Surface Pro 4/5/6/7"
|
||||
:width 1368
|
||||
:height 912}
|
||||
|
||||
{:name "ReMarkable"}
|
||||
{:name "Remarkable 2"
|
||||
:width 840
|
||||
:height 1120}
|
||||
|
||||
{:name "WEB"}
|
||||
{:name "Web 1280"
|
||||
:width 1280
|
||||
:height 800}
|
||||
{:name "Web 1366"
|
||||
:width 1366
|
||||
:height 768}
|
||||
{:name "Web 1024"
|
||||
:width 1024
|
||||
:height 768}
|
||||
{:name "Web 1920"
|
||||
:width 1920
|
||||
:height 1080}
|
||||
|
||||
{:name "PRINT (96dpi)"}
|
||||
{:name "A0"
|
||||
:width 3179
|
||||
:height 4494}
|
||||
{:name "A1"
|
||||
:width 2245
|
||||
:height 3179}
|
||||
{:name "A2"
|
||||
:width 1587
|
||||
:height 2245}
|
||||
{:name "A3"
|
||||
:width 1123
|
||||
:height 1587}
|
||||
{:name "A4"
|
||||
:width 794
|
||||
:height 1123}
|
||||
{:name "A5"
|
||||
:width 559
|
||||
:height 794}
|
||||
{:name "A6"
|
||||
:width 397
|
||||
:height 559}
|
||||
{:name "Letter"
|
||||
:width 816
|
||||
:height 1054}
|
||||
{:name "DIN Lang"
|
||||
:width 835
|
||||
:height 413}
|
||||
|
||||
{:name "SOCIAL MEDIA"}
|
||||
{:name "Instagram profile"
|
||||
:width 320
|
||||
:height 320}
|
||||
{:name "Instagram post"
|
||||
:width 1080
|
||||
:height 1080}
|
||||
{:name "Instagram story"
|
||||
:width 1080
|
||||
:height 1920}
|
||||
{:name "Facebook profile"
|
||||
:width 720
|
||||
:height 720}
|
||||
{:name "Facebook cover"
|
||||
:width 820
|
||||
:height 312}
|
||||
{:name "Facebook post"
|
||||
:width 1200
|
||||
:height 630}
|
||||
{:name "LinkedIn profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "LinkedIn cover"
|
||||
:width 1584
|
||||
:height 396}
|
||||
{:name "LinkedIn post"
|
||||
:width 1200
|
||||
:height 627}
|
||||
{:name "Twitter profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "Twitter header"
|
||||
:width 1500
|
||||
:height 500}
|
||||
{:name "Twitter post"
|
||||
:width 1024
|
||||
:height 512}
|
||||
{:name "YouTube profile"
|
||||
:width 800
|
||||
:height 800}
|
||||
{:name "YouTube banner"
|
||||
:width 2560
|
||||
:height 1440}
|
||||
{:name "YouTube thumb"
|
||||
:width 1280
|
||||
:height 720}])
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.spec.interactions :as cti]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.repo :as rp]
|
||||
@ -86,13 +85,6 @@
|
||||
(update [_ state]
|
||||
(dissoc state :viewer))))
|
||||
|
||||
(defn select-frames
|
||||
[{:keys [objects] :as page}]
|
||||
(let [root (get objects uuid/zero)]
|
||||
(into [] (comp (map #(get objects %))
|
||||
(filter #(= :frame (:type %))))
|
||||
(reverse (:shapes root)))))
|
||||
|
||||
;; --- Data Fetching
|
||||
|
||||
(s/def ::fetch-bundle-params
|
||||
@ -120,7 +112,9 @@
|
||||
(let [pages (->> (get-in file [:data :pages])
|
||||
(map (fn [page-id]
|
||||
(let [data (get-in file [:data :pages-index page-id])]
|
||||
[page-id (assoc data :frames (select-frames data))])))
|
||||
[page-id (assoc data
|
||||
:frames (cph/get-viewer-frames (:objects data))
|
||||
:all-frames (cph/get-viewer-frames (:objects data) {:all-frames? true}))])))
|
||||
(into {}))]
|
||||
|
||||
(ptk/reify ::bundle-fetched
|
||||
@ -390,7 +384,9 @@
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :index index)))))))
|
||||
|
||||
(defn go-to-frame
|
||||
([frame-id] (go-to-frame frame-id nil))
|
||||
([frame-id]
|
||||
(go-to-frame frame-id nil))
|
||||
|
||||
([frame-id animation]
|
||||
(us/verify ::us/uuid frame-id)
|
||||
(us/verify (s/nilable ::cti/animation) animation)
|
||||
@ -420,8 +416,7 @@
|
||||
page-id (:page-id qparams)
|
||||
frames (get-in state [:viewer :pages page-id :frames])
|
||||
index (d/index-of-pred frames #(= (:id %) frame-id))]
|
||||
(when index
|
||||
(rx/of (go-to-frame-by-index index))))))))
|
||||
(rx/of (go-to-frame-by-index (or index 0))))))))
|
||||
|
||||
(defn go-to-frame-auto
|
||||
[]
|
||||
@ -492,7 +487,7 @@
|
||||
(let [route (:route state)
|
||||
qparams (:query-params route)
|
||||
page-id (:page-id qparams)
|
||||
frames (get-in state [:viewer :pages page-id :frames])
|
||||
frames (get-in state [:viewer :pages page-id :all-frames])
|
||||
frame (d/seek #(= (:id %) frame-id) frames)
|
||||
overlays (get-in state [:viewer-local :overlays])]
|
||||
(if-not (some #(= (:frame %) frame) overlays)
|
||||
@ -517,7 +512,7 @@
|
||||
(let [route (:route state)
|
||||
qparams (:query-params route)
|
||||
page-id (:page-id qparams)
|
||||
frames (get-in state [:viewer :pages page-id :frames])
|
||||
frames (get-in state [:viewer :pages page-id :all-frames])
|
||||
frame (d/seek #(= (:id %) frame-id) frames)
|
||||
overlays (get-in state [:viewer-local :overlays])]
|
||||
(if-not (some #(= (:frame %) frame) overlays)
|
||||
|
||||
@ -128,7 +128,6 @@
|
||||
(rx/merge
|
||||
(rx/of (dwn/initialize team-id file-id)
|
||||
(dwp/initialize-file-persistence file-id))
|
||||
|
||||
(->> stream
|
||||
(rx/filter #(= ::dwc/index-initialized %))
|
||||
(rx/take 1)
|
||||
@ -151,6 +150,7 @@
|
||||
:workspace-project project
|
||||
:workspace-file (assoc file :initialized true)
|
||||
:workspace-data (-> (:data file)
|
||||
(cph/start-object-indices)
|
||||
;; DEBUG: Uncomment this to try out migrations in local without changing
|
||||
;; the version number
|
||||
#_(assoc :version 17)
|
||||
@ -1214,16 +1214,8 @@
|
||||
;; selected and its parents
|
||||
objects (cph/selected-subtree objects selected)
|
||||
|
||||
z-index (cp/calculate-z-index objects)
|
||||
z-values (->> selected
|
||||
(map #(vector %
|
||||
(+ (get z-index %)
|
||||
(get z-index (get-in objects [% :frame-id]))))))
|
||||
selected
|
||||
(->> z-values
|
||||
(sort-by second)
|
||||
(map first)
|
||||
(into (d/ordered-set)))]
|
||||
selected (->> (cph/sort-z-index objects selected)
|
||||
(into (d/ordered-set)))]
|
||||
|
||||
(assoc data :selected selected)))
|
||||
|
||||
@ -1240,8 +1232,8 @@
|
||||
;; Prepare the shape object. Mainly needed for image shapes
|
||||
;; for retrieve the image data and convert it to the
|
||||
;; data-url.
|
||||
(prepare-object [objects selected {:keys [type] :as obj}]
|
||||
(let [obj (maybe-translate obj objects selected)]
|
||||
(prepare-object [objects selected+children {:keys [type] :as obj}]
|
||||
(let [obj (maybe-translate obj objects selected+children)]
|
||||
(if (= type :image)
|
||||
(let [url (cfg/resolve-file-media (:metadata obj))]
|
||||
(->> (http/send! {:method :get
|
||||
@ -1264,9 +1256,9 @@
|
||||
(update res :images conj img-part))
|
||||
res)))
|
||||
|
||||
(maybe-translate [shape objects selected]
|
||||
(maybe-translate [shape objects selected+children]
|
||||
(if (and (not= (:type shape) :frame)
|
||||
(not (contains? selected (:frame-id shape))))
|
||||
(not (contains? selected+children (:frame-id shape))))
|
||||
;; When the parent frame is not selected we change to relative
|
||||
;; coordinates
|
||||
(let [frame (get objects (:frame-id shape))]
|
||||
@ -1283,6 +1275,8 @@
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
selected (->> (wsh/lookup-selected state)
|
||||
(cph/clean-loops objects))
|
||||
|
||||
selected+children (cph/selected-with-children objects selected)
|
||||
pdata (reduce (partial collect-object-ids objects) {} selected)
|
||||
initial {:type :copied-shapes
|
||||
:file-id (:current-file-id state)
|
||||
@ -1292,7 +1286,7 @@
|
||||
|
||||
|
||||
(->> (rx/from (seq (vals pdata)))
|
||||
(rx/merge-map (partial prepare-object objects selected))
|
||||
(rx/merge-map (partial prepare-object objects selected+children))
|
||||
(rx/reduce collect-data initial)
|
||||
(rx/map (partial sort-selected state))
|
||||
(rx/map t/encode-str)
|
||||
@ -1672,20 +1666,22 @@
|
||||
(watch [_ state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
shapes (cph/get-immediate-children objects)
|
||||
selected (wsh/lookup-selected state)
|
||||
selected-objs (map #(get objects %) selected)
|
||||
has-frame? (some #(= (:type %) :frame) selected-objs)]
|
||||
(when (not (or (empty? selected) has-frame?))
|
||||
selected-objs (map #(get objects %) selected)]
|
||||
(when (d/not-empty? selected)
|
||||
(let [srect (gsh/selection-rect selected-objs)
|
||||
frame-id (:frame-id (first shapes))
|
||||
frame-id (get-in objects [(first selected) :frame-id])
|
||||
parent-id (get-in objects [(first selected) :parent-id])
|
||||
shape (-> (cp/make-minimal-shape :frame)
|
||||
(merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :frame-id frame-id :parent-id parent-id)
|
||||
(cond-> (not= frame-id uuid/zero)
|
||||
(assoc :fills [] :hide-in-viewer true))
|
||||
(cp/setup-rect-selrect))]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction)
|
||||
(dwc/add-shape shape)
|
||||
|
||||
(dwc/move-shapes-into-frame (:id shape) selected)
|
||||
(dwu/commit-undo-transaction))))))))
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.spec.change :as spec.change]
|
||||
[app.common.uuid :as uuid]
|
||||
@ -126,9 +127,7 @@
|
||||
[]))]
|
||||
(into #{}
|
||||
(comp (mapcat change->ids)
|
||||
(keep #(if (= :frame (get-in objects [% :type]))
|
||||
%
|
||||
(get-in objects [% :frame-id])))
|
||||
(keep #(cph/get-shape-id-root-frame objects %))
|
||||
(remove #(= uuid/zero %)))
|
||||
changes)))
|
||||
|
||||
@ -163,7 +162,10 @@
|
||||
(us/assert ::spec.change/changes redo-changes)
|
||||
(us/assert ::spec.change/changes undo-changes)
|
||||
|
||||
(update-in state path cp/process-changes redo-changes false)
|
||||
(update-in state path (fn [file]
|
||||
(-> file
|
||||
(cp/process-changes redo-changes false)
|
||||
(cph/update-object-indices page-id))))
|
||||
|
||||
(catch :default err
|
||||
(log/error :js/error err)
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.proportions :as gpr]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
@ -59,13 +58,6 @@
|
||||
|
||||
;; --- Common Helpers & Events
|
||||
|
||||
;; TODO: looks duplicate
|
||||
|
||||
(defn get-frame-at-point
|
||||
[objects point]
|
||||
(let [frames (cph/get-frames objects)]
|
||||
(d/seek #(gsh/has-point? % point) frames)))
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
[basename]
|
||||
(if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||
@ -261,25 +253,21 @@
|
||||
(defn get-shape-layer-position
|
||||
[objects selected attrs]
|
||||
|
||||
(if (= :frame (:type attrs))
|
||||
;; Frames are always positioned on the root frame
|
||||
[uuid/zero uuid/zero nil]
|
||||
;; Calculate the frame over which we're drawing
|
||||
(let [position @ms/mouse-position
|
||||
frame-id (:frame-id attrs (cph/frame-id-by-position objects position))
|
||||
shape (when-not (empty? selected)
|
||||
(cph/get-base-shape objects selected))]
|
||||
|
||||
;; Calculate the frame over which we're drawing
|
||||
(let [position @ms/mouse-position
|
||||
frame-id (:frame-id attrs (cph/frame-id-by-position objects position))
|
||||
shape (when-not (empty? selected)
|
||||
(cph/get-base-shape objects selected))]
|
||||
;; When no shapes has been selected or we're over a different frame
|
||||
;; we add it as the latest shape of that frame
|
||||
(if (or (not shape) (not= (:frame-id shape) frame-id))
|
||||
[frame-id frame-id nil]
|
||||
|
||||
;; When no shapes has been selected or we're over a different frame
|
||||
;; we add it as the latest shape of that frame
|
||||
(if (or (not shape) (not= (:frame-id shape) frame-id))
|
||||
[frame-id frame-id nil]
|
||||
|
||||
;; Otherwise, we add it to next to the selected shape
|
||||
(let [index (cph/get-position-on-parent objects (:id shape))
|
||||
{:keys [frame-id parent-id]} shape]
|
||||
[frame-id parent-id (inc index)])))))
|
||||
;; Otherwise, we add it to next to the selected shape
|
||||
(let [index (cph/get-position-on-parent objects (:id shape))
|
||||
{:keys [frame-id parent-id]} shape]
|
||||
[frame-id parent-id (inc index)]))))
|
||||
|
||||
(defn make-new-shape
|
||||
[attrs objects selected]
|
||||
@ -325,7 +313,10 @@
|
||||
selected)
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/add-object shape {:index (when (= :frame (:type shape)) 0)}))]
|
||||
(pcb/with-objects objects)
|
||||
(pcb/add-object shape)
|
||||
(cond-> (some? (:parent-id attrs))
|
||||
(pcb/change-parent (:parent-id attrs) [shape])))]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dch/commit-changes changes)
|
||||
@ -342,17 +333,20 @@
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
to-move-shapes (->> (cph/get-immediate-children objects)
|
||||
(remove cph/frame-shape?)
|
||||
(d/enumerate)
|
||||
(filterv (comp shapes :id second))
|
||||
(mapv second))
|
||||
to-move-shapes
|
||||
(into []
|
||||
(map (d/getf objects))
|
||||
(reverse (cph/sort-z-index objects shapes)))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/change-parent frame-id to-move-shapes 0))]
|
||||
changes
|
||||
(when (d/not-empty? to-move-shapes)
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/change-parent frame-id to-move-shapes 0)))]
|
||||
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
(if (some? changes)
|
||||
(rx/of (dch/commit-changes changes))
|
||||
(rx/empty))))))
|
||||
|
||||
(s/def ::set-of-uuid
|
||||
(s/every ::us/uuid :kind set?))
|
||||
|
||||
@ -65,22 +65,21 @@
|
||||
focus (:workspace-focus-selected state)
|
||||
zoom (get-in state [:workspace-local :zoom] 1)
|
||||
|
||||
frames (cph/get-frames objects)
|
||||
fid (or (->> frames
|
||||
(filter #(gsh/has-point? % initial))
|
||||
first
|
||||
:id)
|
||||
uuid/zero)
|
||||
fid (cph/frame-id-by-position objects initial)
|
||||
|
||||
shape (-> state
|
||||
(get-in [:workspace-drawing :object])
|
||||
(cp/setup-shape {:x (:x initial)
|
||||
:y (:y initial)
|
||||
:width 0.01
|
||||
:height 0.01})
|
||||
(assoc :frame-id fid)
|
||||
(assoc :initialized? true)
|
||||
(assoc :click-draw? true))]
|
||||
shape (get-in state [:workspace-drawing :object])
|
||||
shape (-> shape
|
||||
(cp/setup-shape {:x (:x initial)
|
||||
:y (:y initial)
|
||||
:width 0.01
|
||||
:height 0.01})
|
||||
(cond-> (and (cph/frame-shape? shape)
|
||||
(not= fid uuid/zero))
|
||||
(assoc :fills [] :hide-in-viewer true))
|
||||
|
||||
(assoc :frame-id fid)
|
||||
(assoc :initialized? true)
|
||||
(assoc :click-draw? true))]
|
||||
(rx/concat
|
||||
;; Add shape to drawing state
|
||||
(rx/of #(assoc-in state [:workspace-drawing :object] shape))
|
||||
|
||||
@ -10,7 +10,9 @@
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.worker :as uw]
|
||||
[beicon.core :as rx]
|
||||
@ -29,7 +31,8 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [tool (get-in state [:workspace-drawing :tool])
|
||||
shape (get-in state [:workspace-drawing :object])]
|
||||
shape (get-in state [:workspace-drawing :object])
|
||||
objects (wsh/lookup-page-objects state)]
|
||||
(rx/concat
|
||||
(when (:initialized? shape)
|
||||
(let [page-id (:current-page-id state)
|
||||
@ -68,7 +71,10 @@
|
||||
(if (= :frame (:type shape))
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
:page-id page-id
|
||||
:rect (:selrect shape)})
|
||||
:rect (:selrect shape)
|
||||
:include-frames? true
|
||||
:full-frame? true})
|
||||
(rx/map #(cph/clean-loops objects %))
|
||||
(rx/map #(dwc/move-shapes-into-frame (:id shape) %)))
|
||||
(rx/empty)))))
|
||||
|
||||
|
||||
@ -21,7 +21,6 @@
|
||||
[objects selected]
|
||||
(->> selected
|
||||
(map #(get objects %))
|
||||
(filter #(not= :frame (:type %)))
|
||||
(map #(assoc % ::index (cph/get-position-on-parent objects (:id %))))
|
||||
(sort-by ::index)))
|
||||
|
||||
|
||||
@ -171,21 +171,33 @@
|
||||
(rx/map #(move-edit-interaction initial-pos %)))
|
||||
(rx/of (finish-edit-interaction index initial-pos))))))))
|
||||
|
||||
|
||||
(defn get-target-frame
|
||||
[state position]
|
||||
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
from-id (-> state wsh/lookup-selected first)
|
||||
from-shape (wsh/lookup-shape state from-id)
|
||||
|
||||
from-frame-id (if (cph/frame-shape? from-shape)
|
||||
from-id (:frame-id from-shape))
|
||||
|
||||
target-frame (cph/frame-by-position objects position)]
|
||||
|
||||
(when (and (not= (:id target-frame) uuid/zero)
|
||||
(not= (:id target-frame) from-frame-id)
|
||||
(not (:hide-in-viewer target-frame)))
|
||||
target-frame)))
|
||||
|
||||
(defn move-edit-interaction
|
||||
[initial-pos position]
|
||||
[_initial-pos position]
|
||||
(ptk/reify ::move-edit-interaction
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
selected-shape-id (-> state wsh/lookup-selected first)
|
||||
selected-shape (get objects selected-shape-id)
|
||||
selected-shape-frame-id (:frame-id selected-shape)
|
||||
start-frame (get objects selected-shape-frame-id)
|
||||
end-frame (dwc/get-frame-at-point objects position)]
|
||||
(cond-> state
|
||||
(not= position initial-pos) (assoc-in [:workspace-local :draw-interaction-to] position)
|
||||
(not= start-frame end-frame) (assoc-in [:workspace-local :draw-interaction-to-frame] end-frame))))))
|
||||
(let [end-frame (get-target-frame state position)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :draw-interaction-to] position)
|
||||
(assoc-in [:workspace-local :draw-interaction-to-frame] end-frame))))))
|
||||
|
||||
(defn finish-edit-interaction
|
||||
[index initial-pos]
|
||||
@ -199,32 +211,40 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [position @ms/mouse-position
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
frame (dwc/get-frame-at-point objects position)
|
||||
(let [position @ms/mouse-position
|
||||
target-frame (get-target-frame state position)
|
||||
shape-id (-> state wsh/lookup-selected first)
|
||||
shape (wsh/lookup-shape state shape-id)
|
||||
|
||||
shape-id (-> state wsh/lookup-selected first)
|
||||
shape (get objects shape-id)]
|
||||
change-interaction
|
||||
(fn [interaction]
|
||||
(cond-> interaction
|
||||
(not (csi/has-destination interaction))
|
||||
(csi/set-action-type :navigate)
|
||||
|
||||
(when (and shape (not (= position initial-pos)))
|
||||
(if (nil? frame)
|
||||
(when index
|
||||
(rx/of (remove-interaction shape index)))
|
||||
(let [frame (if (or (= (:id frame) (:id shape))
|
||||
(= (:id frame) (:frame-id shape)))
|
||||
nil ;; Drop onto self frame -> set destination to none
|
||||
frame)]
|
||||
(if (nil? index)
|
||||
(rx/of (add-new-interaction shape (:id frame)))
|
||||
(rx/of (update-interaction shape index
|
||||
(fn [interaction]
|
||||
(cond-> interaction
|
||||
(not (csi/has-destination interaction))
|
||||
(csi/set-action-type :navigate)
|
||||
:always
|
||||
(csi/set-destination (:id target-frame))))]
|
||||
|
||||
(cond
|
||||
(or (nil? shape)
|
||||
|
||||
;; Didn't changed the position for the interaction
|
||||
(= position initial-pos)
|
||||
|
||||
;; New interaction but invalid target
|
||||
(and (nil? index) (nil? target-frame)))
|
||||
nil
|
||||
|
||||
;; Dropped interaction in an invalid target. We remove it
|
||||
(and (some? index) (nil? target-frame))
|
||||
(rx/of (remove-interaction shape index))
|
||||
|
||||
(nil? index)
|
||||
(rx/of (add-new-interaction shape (:id target-frame)))
|
||||
|
||||
:else
|
||||
(rx/of (update-interaction shape index change-interaction)))))))
|
||||
|
||||
:always
|
||||
(csi/set-destination (:id frame))))))))))))))
|
||||
;; --- Overlays
|
||||
|
||||
(declare move-overlay-pos)
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
@ -158,7 +158,7 @@
|
||||
(cond-> new-shape
|
||||
true
|
||||
(as-> $
|
||||
(geom/move $ delta)
|
||||
(gsh/move $ delta)
|
||||
(assoc $ :frame-id frame-id)
|
||||
(assoc $ :parent-id
|
||||
(or (:parent-id $) (:frame-id $)))
|
||||
@ -1150,7 +1150,7 @@
|
||||
origin-root-pos (shape-pos origin-root)
|
||||
dest-root-pos (shape-pos dest-root)
|
||||
delta (gpt/subtract dest-root-pos origin-root-pos)]
|
||||
(geom/move shape delta)))
|
||||
(gsh/move shape delta)))
|
||||
|
||||
(defn- make-change
|
||||
[container change]
|
||||
|
||||
@ -157,7 +157,7 @@
|
||||
(->> (rx/from frame-updates)
|
||||
(rx/flat-map (fn [[page-id frames]]
|
||||
(->> frames (map #(vector page-id %)))))
|
||||
(rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail page-id frame-id))))
|
||||
(rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail (:id file) page-id frame-id))))
|
||||
(->> (rx/of lagged)
|
||||
(rx/mapcat seq)
|
||||
(rx/map #(shapes-changes-persisted file-id %)))))))
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
@ -264,7 +264,7 @@
|
||||
;; in the later vector position
|
||||
selected (->> children
|
||||
reverse
|
||||
(d/seek #(geom/has-point? % position)))]
|
||||
(d/seek #(gsh/has-point? % position)))]
|
||||
(when selected
|
||||
(rx/of (select-shape (:id selected))))))))
|
||||
|
||||
@ -325,7 +325,7 @@
|
||||
:frame-id uuid/zero
|
||||
:shapes [])
|
||||
(dissoc :use-for-thumbnail?)
|
||||
(geom/move delta)
|
||||
(gsh/move delta)
|
||||
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
|
||||
|
||||
changes (-> (pcb/add-object changes new-frame)
|
||||
@ -360,7 +360,7 @@
|
||||
:parent-id parent-id
|
||||
:frame-id frame-id)
|
||||
(dissoc :shapes)
|
||||
(geom/move delta)
|
||||
(gsh/move delta)
|
||||
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
|
||||
|
||||
changes (-> (pcb/add-object changes new-obj {:ignore-touched true})
|
||||
@ -412,7 +412,7 @@
|
||||
(fn [g frame]
|
||||
(let [new-id (ids-map (:id frame))
|
||||
new-frame (-> frame
|
||||
(geom/move delta))
|
||||
(gsh/move delta))
|
||||
new-guides (->> guides
|
||||
(vals)
|
||||
(filter #(= (:frame-id %) (:id frame)))
|
||||
|
||||
@ -208,8 +208,8 @@
|
||||
|
||||
;; TOOLS
|
||||
|
||||
:draw-frame {:tooltip "A"
|
||||
:command "a"
|
||||
:draw-frame {:tooltip "B"
|
||||
:command ["b" "a"]
|
||||
:subsections [:tools :basics]
|
||||
:fn #(st/emit! (dwd/select-for-drawing :frame))}
|
||||
|
||||
|
||||
@ -70,6 +70,14 @@
|
||||
selected (dm/get-in state [:workspace-local :selected])]
|
||||
(process-selected-shapes objects selected options))))
|
||||
|
||||
(defn lookup-shape
|
||||
([state id]
|
||||
(lookup-shape state (:current-page-id state) id))
|
||||
|
||||
([state page-id id]
|
||||
(let [objects (lookup-page-objects state page-id)]
|
||||
(get objects id))))
|
||||
|
||||
(defn lookup-shapes
|
||||
([state ids]
|
||||
(lookup-shapes state (:current-page-id state) ids))
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
(fn [subs]
|
||||
;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty
|
||||
;; will be empty on first rendering before drawing the thumbnail and we don't want to store that
|
||||
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%']:not([data-empty])" object-id))]
|
||||
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-empty='false']" object-id))]
|
||||
(if (some? node)
|
||||
(-> node
|
||||
(.toBlob (fn [blob]
|
||||
@ -56,29 +56,35 @@
|
||||
|
||||
(defn update-thumbnail
|
||||
"Updates the thumbnail information for the given frame `id`"
|
||||
[page-id frame-id]
|
||||
(ptk/reify ::update-thumbnail
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [object-id (dm/str page-id frame-id)
|
||||
file-id (:current-file-id state)
|
||||
blob-result (thumbnail-stream object-id)]
|
||||
([page-id frame-id]
|
||||
(update-thumbnail nil page-id frame-id))
|
||||
|
||||
(->> blob-result
|
||||
(rx/merge-map
|
||||
(fn [blob]
|
||||
(if (some? blob)
|
||||
(wapi/read-file-as-data-url blob)
|
||||
(rx/of nil))))
|
||||
([file-id page-id frame-id]
|
||||
(ptk/reify ::update-thumbnail
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [object-id (dm/str page-id frame-id)
|
||||
file-id (or file-id (:current-file-id state))
|
||||
blob-result (thumbnail-stream object-id)]
|
||||
|
||||
(rx/merge-map
|
||||
(fn [data]
|
||||
(let [params {:file-id file-id :object-id object-id :data data}]
|
||||
(rx/merge
|
||||
;; Update the local copy of the thumbnails so we don't need to request it again
|
||||
(rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data))
|
||||
(->> (rp/mutation! :upsert-file-object-thumbnail params)
|
||||
(rx/ignore)))))))))))
|
||||
(->> blob-result
|
||||
(rx/merge-map
|
||||
(fn [blob]
|
||||
(if (some? blob)
|
||||
(wapi/read-file-as-data-url blob)
|
||||
(rx/of nil))))
|
||||
|
||||
(rx/merge-map
|
||||
(fn [data]
|
||||
(if (some? file-id)
|
||||
(let [params {:file-id file-id :object-id object-id :data data}]
|
||||
(rx/merge
|
||||
;; Update the local copy of the thumbnails so we don't need to request it again
|
||||
(rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data))
|
||||
(->> (rp/mutation! :upsert-file-object-thumbnail params)
|
||||
(rx/ignore))))
|
||||
|
||||
(rx/empty))))))))))
|
||||
|
||||
(defn- extract-frame-changes
|
||||
"Process a changes set in a commit to extract the frames that are changing"
|
||||
|
||||
@ -181,50 +181,58 @@
|
||||
(assoc :grow-type :fixed))))
|
||||
|
||||
(defn- apply-modifiers
|
||||
[ids]
|
||||
(us/verify (s/coll-of uuid?) ids)
|
||||
(ptk/reify ::apply-modifiers
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids)
|
||||
object-modifiers (get state :workspace-modifiers)
|
||||
shapes (map (d/getf objects) ids)
|
||||
ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes)
|
||||
(reduce merge {}))]
|
||||
([ids]
|
||||
(apply-modifiers ids nil))
|
||||
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dwg/move-frame-guides ids-with-children)
|
||||
(dch/update-shapes
|
||||
ids-with-children
|
||||
(fn [shape]
|
||||
(let [modif (get object-modifiers (:id shape))
|
||||
text-shape? (cph/text-shape? shape)]
|
||||
(-> shape
|
||||
(merge modif)
|
||||
(gsh/transform-shape)
|
||||
(cond-> text-shape?
|
||||
(update-grow-type shape)))))
|
||||
{:reg-objects? true
|
||||
:ignore-tree ignore-tree
|
||||
;; Attributes that can change in the transform. This way we don't have to check
|
||||
;; all the attributes
|
||||
:attrs [:selrect
|
||||
:points
|
||||
:x
|
||||
:y
|
||||
:width
|
||||
:height
|
||||
:content
|
||||
:transform
|
||||
:transform-inverse
|
||||
:rotation
|
||||
:position-data
|
||||
:flip-x
|
||||
:flip-y
|
||||
:grow-type]})
|
||||
(clear-local-transform)
|
||||
(dwu/commit-undo-transaction))))))
|
||||
([ids {:keys [undo-transation?] :or {undo-transation? true}}]
|
||||
(us/verify (s/coll-of uuid?) ids)
|
||||
(ptk/reify ::apply-modifiers
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids)
|
||||
object-modifiers (get state :workspace-modifiers)
|
||||
shapes (map (d/getf objects) ids)
|
||||
ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes)
|
||||
(reduce merge {}))]
|
||||
|
||||
(rx/concat
|
||||
(if undo-transation?
|
||||
(rx/of (dwu/start-undo-transaction))
|
||||
(rx/empty))
|
||||
(rx/of (dwg/move-frame-guides ids-with-children)
|
||||
(dch/update-shapes
|
||||
ids-with-children
|
||||
(fn [shape]
|
||||
(let [modif (get object-modifiers (:id shape))
|
||||
text-shape? (cph/text-shape? shape)]
|
||||
(-> shape
|
||||
(merge modif)
|
||||
(gsh/transform-shape)
|
||||
(cond-> text-shape?
|
||||
(update-grow-type shape)))))
|
||||
{:reg-objects? true
|
||||
:ignore-tree ignore-tree
|
||||
;; Attributes that can change in the transform. This way we don't have to check
|
||||
;; all the attributes
|
||||
:attrs [:selrect
|
||||
:points
|
||||
:x
|
||||
:y
|
||||
:width
|
||||
:height
|
||||
:content
|
||||
:transform
|
||||
:transform-inverse
|
||||
:rotation
|
||||
:position-data
|
||||
:flip-x
|
||||
:flip-y
|
||||
:grow-type]})
|
||||
(clear-local-transform))
|
||||
(if undo-transation?
|
||||
(rx/of (dwu/commit-undo-transaction))
|
||||
(rx/empty))))))))
|
||||
|
||||
(defn- check-delta
|
||||
"If the shape is a component instance, check its relative position respect the
|
||||
@ -762,9 +770,11 @@
|
||||
(rx/map (partial set-modifiers ids))
|
||||
(rx/take-until stopper))
|
||||
|
||||
(rx/of (apply-modifiers ids)
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(calculate-frame-for-move ids)
|
||||
(finish-transform)))))))))
|
||||
(apply-modifiers ids {:undo-transation? false})
|
||||
(finish-transform)
|
||||
(dwu/commit-undo-transaction)))))))))
|
||||
|
||||
(s/def ::direction #{:up :down :right :left})
|
||||
|
||||
@ -842,6 +852,14 @@
|
||||
(rx/of (set-modifiers [id] {:displacement displ} false true)
|
||||
(apply-modifiers [id]))))))
|
||||
|
||||
(defn check-frame-move?
|
||||
[target-frame-id objects position shape]
|
||||
|
||||
(let [current-frame (get objects (:frame-id shape))]
|
||||
;; If the current frame contains the point and it's a child of the target
|
||||
(and (gsh/has-point? current-frame position)
|
||||
(cph/is-child? objects target-frame-id (:id current-frame)))))
|
||||
|
||||
(defn- calculate-frame-for-move
|
||||
[ids]
|
||||
(ptk/reify ::calculate-frame-for-move
|
||||
@ -854,18 +872,15 @@
|
||||
|
||||
moving-shapes (->> ids
|
||||
(cph/clean-loops objects)
|
||||
(map #(get objects %))
|
||||
(remove #(or (nil? %)
|
||||
(= (:frame-id %) frame-id))))
|
||||
(keep #(get objects %))
|
||||
(remove (partial check-frame-move? frame-id objects position)))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/change-parent frame-id moving-shapes))]
|
||||
|
||||
(when-not (empty? changes)
|
||||
(rx/of dwu/pop-undo-into-transaction
|
||||
(dch/commit-changes changes)
|
||||
(dwu/commit-undo-transaction)
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(dwc/expand-collapse frame-id)))))))
|
||||
|
||||
(defn- get-displacement
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as ts]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(defn on-error
|
||||
@ -200,9 +201,12 @@
|
||||
|
||||
(defonce uncaught-error-handler
|
||||
(letfn [(on-error [event]
|
||||
(.preventDefault ^js event)
|
||||
(some-> (unchecked-get event "error")
|
||||
(on-unhandled-error)))]
|
||||
;; EvalError is a debug error that happens for unknown reason
|
||||
(when-not (str/includes? (.-message event) "EvalError")
|
||||
(.error js/console event)
|
||||
(.preventDefault ^js event)
|
||||
(some-> (unchecked-get event "error")
|
||||
(on-unhandled-error))))]
|
||||
(.addEventListener glob/window "error" on-error)
|
||||
(fn []
|
||||
(.removeEventListener glob/window "error" on-error))))
|
||||
|
||||
@ -270,6 +270,14 @@
|
||||
(into [] (keep (d/getf objects)) children-ids)))
|
||||
workspace-page-objects =))
|
||||
|
||||
(defn all-children-objects
|
||||
[id]
|
||||
(l/derived
|
||||
(fn [objects]
|
||||
(let [children-ids (cph/get-children-ids objects id)]
|
||||
(into [] (keep (d/getf objects)) children-ids)))
|
||||
workspace-page-objects =))
|
||||
|
||||
(def workspace-page-options
|
||||
(l/derived :options workspace-page))
|
||||
|
||||
@ -306,8 +314,11 @@
|
||||
(fn [{:keys [modifiers objects]}]
|
||||
(let [keys (->> modifiers
|
||||
(keys)
|
||||
(filter #(or (= frame-id %)
|
||||
(= frame-id (get-in objects [% :frame-id])))))]
|
||||
(filter (fn [id]
|
||||
(let [shape (get objects id)]
|
||||
(or (= frame-id id)
|
||||
(and (= frame-id (:frame-id shape))
|
||||
(not (= :frame (:type shape)))))))))]
|
||||
(select-keys modifiers keys)))
|
||||
workspace-modifiers-with-objects
|
||||
=))
|
||||
@ -349,15 +360,18 @@
|
||||
|
||||
;; ---- Viewer refs
|
||||
|
||||
(def viewer-file
|
||||
(l/derived :viewer-file st/state))
|
||||
|
||||
(def viewer-project
|
||||
(l/derived :viewer-file st/state))
|
||||
|
||||
(def viewer-data
|
||||
(l/derived :viewer st/state))
|
||||
|
||||
(def viewer-file
|
||||
(l/derived :file viewer-data))
|
||||
|
||||
(def viewer-thumbnails
|
||||
(l/derived :thumbnails viewer-file))
|
||||
|
||||
(def viewer-project
|
||||
(l/derived :project viewer-data))
|
||||
|
||||
(def viewer-state
|
||||
(l/derived :viewer st/state))
|
||||
|
||||
|
||||
@ -61,8 +61,12 @@
|
||||
|
||||
(defn- calculate-dimensions
|
||||
[objects]
|
||||
(let [shapes (cph/get-immediate-children objects)
|
||||
rect (gsh/selection-rect shapes)]
|
||||
(let [rect
|
||||
(->> (cph/get-root-objects objects)
|
||||
(map #(if (some? (:children-bounds %))
|
||||
(:children-bounds %)
|
||||
(gsh/points->selrect (:points %))))
|
||||
(gsh/join-selrects))]
|
||||
(-> rect
|
||||
(update :x mth/finite 0)
|
||||
(update :y mth/finite 0)
|
||||
@ -77,10 +81,13 @@
|
||||
frame-shape (frame/frame-shape shape-wrapper)]
|
||||
(mf/fnc frame-wrapper
|
||||
[{:keys [shape] :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes shape))
|
||||
|
||||
(let [render-thumbnails? (mf/use-ctx muc/render-thumbnails)
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
shape (gsh/transform-shape shape)]
|
||||
[:> shape-container {:shape shape}
|
||||
[:& frame-shape {:shape shape :childs childs}]]))))
|
||||
(if (and render-thumbnails? (some? (:thumbnail shape)))
|
||||
[:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}]
|
||||
[:& frame-shape {:shape shape :childs childs}])))))
|
||||
|
||||
(defn group-wrapper-factory
|
||||
[objects]
|
||||
@ -169,7 +176,7 @@
|
||||
[objects object-id]
|
||||
(let [object (get objects object-id)
|
||||
object (cond->> object
|
||||
(cph/root-frame? object)
|
||||
(cph/root? object)
|
||||
(adapt-root-frame objects))
|
||||
|
||||
;; Replace the previous object with the new one
|
||||
@ -196,12 +203,17 @@
|
||||
(update :width + (* 2 (:horizontal padding)))
|
||||
(update :height + (* 2 (:vertical padding))))]
|
||||
|
||||
(if (cph/group-shape? object)
|
||||
(if (:masked-group? object)
|
||||
(get-object-bounds objects (-> object :shapes first))
|
||||
(->> (:shapes object)
|
||||
(into [bounds] (map (partial get-object-bounds objects)))
|
||||
(gsh/join-rects)))
|
||||
(cond
|
||||
(and (cph/group-shape? object) (:masked-group? object))
|
||||
(get-object-bounds objects (-> object :shapes first))
|
||||
|
||||
(or (cph/group-shape? object)
|
||||
(and (cph/frame-shape? object) (:show-content object)))
|
||||
(->> (:shapes object)
|
||||
(into [bounds] (map (partial get-object-bounds objects)))
|
||||
(gsh/join-rects))
|
||||
|
||||
:else
|
||||
bounds)))
|
||||
|
||||
(mf/defc page-svg
|
||||
@ -215,51 +227,37 @@
|
||||
vbox (format-viewbox dim)
|
||||
bgcolor (dm/get-in data [:options :background] default-color)
|
||||
|
||||
frame-wrapper
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(frame-wrapper-factory objects))
|
||||
|
||||
shape-wrapper
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(shape-wrapper-factory objects))]
|
||||
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
|
||||
[:svg {:view-box vbox
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
|
||||
:style {:width "100%"
|
||||
:height "100%"
|
||||
:background bgcolor}
|
||||
:fill "none"}
|
||||
[:& (mf/provider muc/render-thumbnails) {:value thumbnails?}
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
|
||||
[:svg {:view-box vbox
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
|
||||
:style {:width "100%"
|
||||
:height "100%"
|
||||
:background bgcolor}
|
||||
:fill "none"}
|
||||
|
||||
(when include-metadata?
|
||||
[:& export/export-page {:options (:options data)}])
|
||||
(when include-metadata?
|
||||
[:& export/export-page {:options (:options data)}])
|
||||
|
||||
|
||||
(let [shapes (->> shapes
|
||||
(remove cph/frame-shape?)
|
||||
(mapcat #(cph/get-children-with-self objects (:id %))))
|
||||
fonts (ff/shapes->fonts shapes)]
|
||||
[:& ff/fontfaces-style {:fonts fonts}])
|
||||
(let [shapes (->> shapes
|
||||
(remove cph/frame-shape?)
|
||||
(mapcat #(cph/get-children-with-self objects (:id %))))
|
||||
fonts (ff/shapes->fonts shapes)]
|
||||
[:& ff/fontfaces-style {:fonts fonts}])
|
||||
|
||||
(for [item shapes]
|
||||
(let [frame? (= (:type item) :frame)]
|
||||
(cond
|
||||
(and frame? thumbnails? (some? (:thumbnail item)))
|
||||
[:> shape-container {:shape item}
|
||||
[:& frame/frame-thumbnail {:shape item}]]
|
||||
|
||||
frame?
|
||||
[:& frame-wrapper {:shape item
|
||||
:key (:id item)}]
|
||||
:else
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])))]]]))
|
||||
(for [item shapes]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])]]]]))
|
||||
|
||||
|
||||
;; Component that serves for render frame thumbnails, mainly used in
|
||||
@ -271,46 +269,60 @@
|
||||
(let [frame-id (:id frame)
|
||||
include-metadata? (mf/use-ctx export/include-metadata-ctx)
|
||||
|
||||
bounds (or (:children-bounds frame) (gsh/points->rect (:points frame)))
|
||||
|
||||
modifier
|
||||
(mf/with-memo [(:x frame) (:y frame)]
|
||||
(-> (gpt/point (:x frame) (:y frame))
|
||||
(mf/with-memo [(:x bounds) (:y bounds)]
|
||||
(-> (gpt/point (:x bounds) (:y bounds))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix)))
|
||||
|
||||
children-ids
|
||||
(cph/get-children-ids objects frame-id)
|
||||
|
||||
objects
|
||||
(mf/with-memo [frame-id objects modifier]
|
||||
(let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
|
||||
(->> (cph/get-children-ids objects frame-id)
|
||||
(->> children-ids
|
||||
(into [frame-id])
|
||||
(reduce update-fn objects))))
|
||||
|
||||
frame
|
||||
(mf/with-memo [modifier]
|
||||
(assoc-in frame [:modifiers :displacement] modifier))
|
||||
(-> frame
|
||||
(assoc-in [:modifiers :displacement] modifier)
|
||||
(gsh/transform-shape)))
|
||||
|
||||
wrapper
|
||||
bounds
|
||||
(if (:show-content frame)
|
||||
(gsh/selection-rect (concat [frame] (->> children-ids (map (d/getf objects)))))
|
||||
(-> frame :points gsh/points->rect))
|
||||
|
||||
frame
|
||||
(cond-> frame
|
||||
(and (some? bounds) (nil? (:children-bounds bounds)))
|
||||
(assoc :children-bounds bounds))
|
||||
|
||||
frame-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(frame-wrapper-factory objects))
|
||||
|
||||
width (* (:width frame) zoom)
|
||||
height (* (:height frame) zoom)
|
||||
vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)})]
|
||||
width (* (:width bounds) zoom)
|
||||
height (* (:height bounds) zoom)
|
||||
vbox (format-viewbox {:width (:width bounds 0) :height (:height bounds 0)})]
|
||||
|
||||
[:svg {:view-box vbox
|
||||
:width (ust/format-precision width viewbox-decimal-precision)
|
||||
:height (ust/format-precision height viewbox-decimal-precision)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
|
||||
:fill "none"}
|
||||
(if (or (not show-thumbnails?) (nil? (:thumbnail frame)))
|
||||
[:& wrapper {:shape frame :view-box vbox}]
|
||||
[:& (mf/provider muc/render-thumbnails) {:value show-thumbnails?}
|
||||
[:svg {:view-box vbox
|
||||
:width (ust/format-precision width viewbox-decimal-precision)
|
||||
:height (ust/format-precision height viewbox-decimal-precision)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
|
||||
:fill "none"}
|
||||
|
||||
;; Render the frame thumbnail
|
||||
(let [frame (gsh/transform-shape frame)]
|
||||
[:> shape-container {:shape frame}
|
||||
[:& frame/frame-thumbnail {:shape frame}]]))]))
|
||||
[:> shape-container {:shape frame}
|
||||
[:& frame-wrapper {:shape frame :view-box vbox}]]]]))
|
||||
|
||||
|
||||
;; Component for rendering a thumbnail of a single componenent. Mainly
|
||||
@ -389,42 +401,44 @@
|
||||
text-shapes (sequence (filter cph/text-shape?) (vals objects))
|
||||
render-texts? (and render-texts? (d/seek (comp nil? :position-data) text-shapes))]
|
||||
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:svg {:id (dm/str "screenshot-" object-id)
|
||||
:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
;; Fix Chromium bug about color of html texts
|
||||
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
|
||||
:style {:-webkit-print-color-adjust :exact}
|
||||
:fill "none"}
|
||||
[:& (mf/provider export/include-metadata-ctx) {:value false}
|
||||
[:& (mf/provider embed/context) {:value render-embed?}
|
||||
[:svg {:id (dm/str "screenshot-" object-id)
|
||||
:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
;; Fix Chromium bug about color of html texts
|
||||
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
|
||||
:style {:-webkit-print-color-adjust :exact}
|
||||
:fill "none"}
|
||||
|
||||
(let [fonts (ff/shape->fonts object objects)]
|
||||
[:& ff/fontfaces-style {:fonts fonts}])
|
||||
(let [fonts (ff/shape->fonts object objects)]
|
||||
[:& ff/fontfaces-style {:fonts fonts}])
|
||||
|
||||
(case (:type object)
|
||||
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
||||
:group [:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object}]]
|
||||
[:& shape-wrapper {:shape object}])]
|
||||
(case (:type object)
|
||||
:frame [:> shape-container {:shape object}
|
||||
[:& frame-wrapper {:shape object :view-box vbox}]]
|
||||
:group [:> shape-container {:shape object}
|
||||
[:& group-wrapper {:shape object}]]
|
||||
[:& shape-wrapper {:shape object}])]
|
||||
|
||||
;; Auxiliary SVG for rendering text-shapes
|
||||
(when render-texts?
|
||||
(for [object text-shapes]
|
||||
[:& (mf/provider muc/text-plain-colors-ctx) {:value true}
|
||||
[:svg
|
||||
{:id (dm/str "screenshot-text-" (:id object))
|
||||
:view-box (dm/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"
|
||||
:fill "none"}
|
||||
[:& shape-wrapper {:shape (assoc object :x 0 :y 0)}]]]))]))
|
||||
;; Auxiliary SVG for rendering text-shapes
|
||||
(when render-texts?
|
||||
(for [object text-shapes]
|
||||
[:& (mf/provider muc/text-plain-colors-ctx) {:value true}
|
||||
[:svg
|
||||
{:id (dm/str "screenshot-text-" (:id object))
|
||||
:view-box (dm/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"
|
||||
:fill "none"}
|
||||
[:& shape-wrapper {:shape (assoc object :x 0 :y 0)}]]]))]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SPRITES (DEBUG)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
(ns app.main.ui.components.context-menu
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.components.dropdown :refer [dropdown']]
|
||||
[app.main.ui.icons :as i]
|
||||
@ -110,10 +111,10 @@
|
||||
(for [[index [option-name option-handler sub-options data-test]] (d/enumerate (:options level))]
|
||||
(when option-name
|
||||
(if (= option-name :separator)
|
||||
[:li.separator]
|
||||
[:li.separator {:key (dm/str "context-item-" index)}]
|
||||
[:li.context-menu-item
|
||||
{:class (dom/classnames :is-selected (and selected (= option-name selected)))
|
||||
:key index}
|
||||
:key (dm/str "context-item-" index)}
|
||||
(if-not sub-options
|
||||
[:a.context-menu-action {:on-click #(do (dom/stop-propagation %)
|
||||
(on-close)
|
||||
|
||||
@ -21,4 +21,6 @@
|
||||
(def current-project-id (mf/create-context nil))
|
||||
(def current-page-id (mf/create-context nil))
|
||||
(def current-file-id (mf/create-context nil))
|
||||
(def scroll-ctx (mf/create-context nil))
|
||||
(def scroll-ctx (mf/create-context nil))
|
||||
(def active-frames-ctx (mf/create-context nil))
|
||||
(def render-thumbnails (mf/create-context nil))
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as log]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.storage :refer [storage]]
|
||||
[rumext.alpha :as mf]))
|
||||
@ -77,26 +78,31 @@
|
||||
(let [prev-val-ref (mf/use-ref nil)
|
||||
current-observer-ref (mf/use-ref nil)
|
||||
|
||||
callback-ref (hooks/use-update-var {:callback callback})
|
||||
|
||||
;; We use the ref as a callback when the dom node is ready (or change)
|
||||
node-ref
|
||||
(mf/use-callback
|
||||
(mf/deps callback)
|
||||
(fn [^js node]
|
||||
(let [^js current-observer (mf/ref-val current-observer-ref)
|
||||
^js prev-val (mf/ref-val prev-val-ref)]
|
||||
(when (some? node)
|
||||
(let [^js current-observer (mf/ref-val current-observer-ref)
|
||||
^js prev-val (mf/ref-val prev-val-ref)]
|
||||
|
||||
(when (and (not= prev-val node) (some? current-observer))
|
||||
(log/debug :action "disconnect" :js/prev-val prev-val :js/node node)
|
||||
(.disconnect current-observer)
|
||||
(mf/set-ref-val! current-observer-ref nil))
|
||||
(when (and (not= prev-val node) (some? current-observer))
|
||||
(log/debug :action "disconnect" :js/prev-val prev-val :js/node node)
|
||||
(.disconnect current-observer)
|
||||
(mf/set-ref-val! current-observer-ref nil))
|
||||
|
||||
(when (and (not= prev-val node) (some? node))
|
||||
(let [^js observer
|
||||
(js/ResizeObserver. #(callback last-resize-type (dom/get-client-size node)))]
|
||||
(mf/set-ref-val! current-observer-ref observer)
|
||||
(log/debug :action "observe" :js/node node :js/observer observer)
|
||||
(.observe observer node))))
|
||||
(mf/set-ref-val! prev-val-ref node)))]
|
||||
(when (and (not= prev-val node) (some? node))
|
||||
(let [^js observer
|
||||
(js/ResizeObserver.
|
||||
#(let [callback (get @callback-ref :callback)]
|
||||
(callback last-resize-type (dom/get-client-size node))))]
|
||||
(mf/set-ref-val! current-observer-ref observer)
|
||||
(log/debug :action "observe" :js/node node :js/observer observer)
|
||||
(.observe observer node))))
|
||||
|
||||
(mf/set-ref-val! prev-val-ref node))))]
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
|
||||
@ -240,16 +240,20 @@
|
||||
|
||||
(when (seq selected-shapes)
|
||||
[:g.measurement-feedback {:pointer-events "none"}
|
||||
[:& selection-guides {:selrect selected-selrect :bounds bounds :zoom zoom}]
|
||||
[:& selection-guides {:selrect selected-selrect
|
||||
:bounds bounds
|
||||
:zoom zoom}]
|
||||
[:& size-display {:selrect selected-selrect :zoom zoom}]
|
||||
|
||||
(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
|
||||
:zoom zoom
|
||||
:bounds bounds-selrect}]])
|
||||
(let [frame-bb (-> (:points frame) (gsh/points->selrect))]
|
||||
[:g.hover-shapes
|
||||
[:& selection-rect {:type :hover :selrect frame-bb :zoom zoom}]
|
||||
[:& distance-display {:from frame-bb
|
||||
:to selected-selrect
|
||||
:zoom zoom
|
||||
:bounds bounds-selrect}]]))
|
||||
|
||||
[:g.hover-shapes
|
||||
[:& selection-rect {:type :hover :selrect hover-selrect :zoom zoom}]
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.main.ui.shapes.circle
|
||||
(:require
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]]
|
||||
[app.util.object :as obj]
|
||||
@ -17,7 +17,7 @@
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
transform (geom/transform-matrix shape)
|
||||
transform (gsh/transform-str shape)
|
||||
|
||||
cx (+ x (/ width 2))
|
||||
cy (+ y (/ height 2))
|
||||
|
||||
@ -95,6 +95,10 @@
|
||||
(add! :constraints-v)
|
||||
(add! :fixed-scroll)
|
||||
|
||||
(cond-> frame?
|
||||
(-> (add! :show-content)
|
||||
(add! :hide-in-viewer)))
|
||||
|
||||
(cond-> (and (or rect? image? frame?) (some? (:r1 shape)))
|
||||
(-> (add! :r1)
|
||||
(add! :r2)
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
(cfg/resolve-file-media (:fill-image shape)))
|
||||
|
||||
embed (embed/use-data-uris [uri])
|
||||
transform (gsh/transform-matrix shape)
|
||||
transform (gsh/transform-str shape)
|
||||
|
||||
;; When true the image has not loaded yet
|
||||
loading? (and (some? uri) (not (contains? embed uri)))
|
||||
|
||||
@ -28,95 +28,95 @@
|
||||
[{:keys [shape render-id]}]
|
||||
(when (= :frame (:type shape))
|
||||
(let [{:keys [x y width height]} shape
|
||||
transform (gsh/transform-str shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height}))
|
||||
:height height
|
||||
:transform transform}))
|
||||
path? (some? (.-d props))]
|
||||
[:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])])))
|
||||
|
||||
;; Wrapper around the frame that will handle things such as strokes and other properties
|
||||
;; we wrap the proper frames and also the thumbnails
|
||||
(mf/defc frame-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [shape (obj/get props "shape")
|
||||
children (obj/get props "children")
|
||||
|
||||
{:keys [x y width height show-content]} shape
|
||||
transform (gsh/transform-str shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))
|
||||
path? (some? (.-d props))
|
||||
render-id (mf/use-ctx muc/render-ctx)]
|
||||
|
||||
[:*
|
||||
[:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))}
|
||||
(when (not show-content)
|
||||
[:& frame-clip-def {:shape shape :render-id render-id}])
|
||||
|
||||
[:& shape-fills {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]
|
||||
|
||||
children]
|
||||
|
||||
[:& shape-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]]))
|
||||
|
||||
|
||||
(mf/defc frame-thumbnail-image
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [shape (obj/get props "shape")
|
||||
bounds (or (obj/get props "bounds") (gsh/points->selrect (:points shape)))]
|
||||
|
||||
(when (:thumbnail shape)
|
||||
[:image.frame-thumbnail
|
||||
{:id (dm/str "thumbnail-" (:id shape))
|
||||
:href (:thumbnail shape)
|
||||
:x (:x bounds)
|
||||
:y (:y bounds)
|
||||
:width (:width bounds)
|
||||
:height (:height bounds)
|
||||
;; DEBUG
|
||||
:style {:filter (when (debug? :thumbnails) "sepia(1)")}}])))
|
||||
|
||||
(mf/defc frame-thumbnail
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (obj/get props "shape")]
|
||||
(when (:thumbnail shape)
|
||||
(let [{:keys [x y width height]} shape
|
||||
transform (gsh/transform-matrix shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:transform (str transform)
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))
|
||||
path? (some? (.-d props))
|
||||
render-id (mf/use-ctx muc/render-ctx)]
|
||||
|
||||
[:*
|
||||
[:g {:clip-path (frame-clip-url shape render-id)}
|
||||
[:& frame-clip-def {:shape shape :render-id render-id}]
|
||||
[:& shape-fills {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]
|
||||
|
||||
[:image.frame-thumbnail
|
||||
{:id (dm/str "thumbnail-" (:id shape))
|
||||
:href (:thumbnail shape)
|
||||
:x (:x shape)
|
||||
:y (:y shape)
|
||||
:width (:width shape)
|
||||
:height (:height shape)
|
||||
;; DEBUG
|
||||
:style {:filter (when (debug? :thumbnails) "sepia(1)")}}]]
|
||||
|
||||
[:& shape-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]]))))
|
||||
[:> frame-container props
|
||||
[:> frame-thumbnail-image props]])))
|
||||
|
||||
(defn frame-shape
|
||||
[shape-wrapper]
|
||||
(mf/fnc frame-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
|
||||
transform (gsh/transform-matrix shape)
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:transform (str transform)
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))
|
||||
path? (some? (.-d props))
|
||||
render-id (mf/use-ctx muc/render-ctx)]
|
||||
|
||||
[:*
|
||||
[:g {:clip-path (frame-clip-url shape render-id)}
|
||||
[:& shape-fills {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]
|
||||
|
||||
[:g.frame-children
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])]]
|
||||
|
||||
[:& shape-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]])))
|
||||
(let [childs (unchecked-get props "childs")]
|
||||
[:> frame-container props
|
||||
[:g.frame-children
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}])]])))
|
||||
|
||||
|
||||
@ -27,13 +27,15 @@
|
||||
(obj/set! "penpot:width" (:width gradient))))
|
||||
|
||||
(mf/defc linear-gradient [{:keys [id gradient shape]}]
|
||||
(let [transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))
|
||||
(let [transform (when (= :path (:type shape))
|
||||
(gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))
|
||||
|
||||
base-props #js {:id id
|
||||
:x1 (:start-x gradient)
|
||||
:y1 (:start-y gradient)
|
||||
:x2 (:end-x gradient)
|
||||
:y2 (:end-y gradient)
|
||||
:gradientTransform transform}
|
||||
:gradientTransform (dm/str transform)}
|
||||
|
||||
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
transform (gsh/transform-matrix shape)
|
||||
transform (gsh/transform-str shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge! (attrs/extract-border-radius-attrs shape))
|
||||
(obj/merge!
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
transform (gsh/transform-matrix shape)
|
||||
transform (gsh/transform-str shape)
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
@ -48,9 +49,10 @@
|
||||
{::mf/forward-ref true
|
||||
::mf/wrap-props false}
|
||||
[props ref]
|
||||
(let [shape (obj/get props "shape")
|
||||
children (obj/get props "children")
|
||||
pointer-events (obj/get props "pointer-events")
|
||||
(let [shape (obj/get props "shape")
|
||||
children (obj/get props "children")
|
||||
pointer-events (obj/get props "pointer-events")
|
||||
disable-shadows? (obj/get props "disable-shadows?")
|
||||
|
||||
type (:type shape)
|
||||
render-id (mf/use-memo #(str (uuid/next)))
|
||||
@ -72,15 +74,16 @@
|
||||
(obj/set! "id" (dm/fmt "shape-%" (:id shape)))
|
||||
(obj/set! "style" styles))
|
||||
|
||||
wrapper-props
|
||||
(cond-> wrapper-props
|
||||
(some #(= (:type shape) %) [:group :svg-raw :frame])
|
||||
(obj/set! "filter" (filters/filter-str filter-id shape)))
|
||||
|
||||
wrapper-props
|
||||
(cond-> wrapper-props
|
||||
(= :group type)
|
||||
(attrs/add-style-attrs shape render-id))
|
||||
(attrs/add-style-attrs shape render-id)
|
||||
|
||||
(and (or (cph/group-shape? shape)
|
||||
(cph/frame-shape? shape)
|
||||
(cph/svg-raw-shape? shape))
|
||||
(not disable-shadows?))
|
||||
(obj/set! "filter" (filters/filter-str filter-id shape)))
|
||||
|
||||
svg-group? (and (contains? shape :svg-attrs) (= :group type))
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
(obj/set! "preserveAspectRatio" "none"))]
|
||||
|
||||
[:& (mf/provider svg-ids-ctx) {:value ids-mapping}
|
||||
[:g.svg-raw {:transform (dm/str (gsh/transform-matrix shape))}
|
||||
[:g.svg-raw {:transform (gsh/transform-str shape)}
|
||||
[:> "svg" attrs children]]]))
|
||||
|
||||
(mf/defc svg-element
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.text.styles :as sts]
|
||||
@ -192,7 +192,7 @@
|
||||
::mf/forward-ref true}
|
||||
[props ref]
|
||||
(let [shape (obj/get props "shape")
|
||||
transform (str (geom/transform-matrix shape))
|
||||
transform (gsh/transform-str shape)
|
||||
|
||||
{:keys [id x y width height content]} shape
|
||||
grow-type (obj/get props "grow-type") ;; This is only needed in workspace
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
|
||||
{:keys [x y width height position-data]} shape
|
||||
|
||||
transform (str (gsh/transform-matrix shape {:no-flip true}))
|
||||
transform (gsh/transform-str shape {:no-flip true})
|
||||
|
||||
;; These position attributes are not really necesary but they are convenient for for the export
|
||||
group-props (-> #js {:transform transform
|
||||
|
||||
@ -8,8 +8,10 @@
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.comments :as dcm]
|
||||
@ -37,18 +39,23 @@
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn- calculate-size
|
||||
[frame zoom]
|
||||
(let [{:keys [_ _ width height]} (filters/get-filters-bounds frame)
|
||||
[frame zoom bounds]
|
||||
(let [frame-bounds (filters/get-filters-bounds frame)
|
||||
{:keys [x y width height]} (if (:show-content frame)
|
||||
(gsh/join-rects [bounds frame-bounds])
|
||||
frame-bounds)
|
||||
padding (filters/calculate-padding frame)
|
||||
x (- (:horizontal padding))
|
||||
y (- (:vertical padding))
|
||||
x (- x (:horizontal padding))
|
||||
y (- y (:vertical padding))
|
||||
width (+ width (* 2 (:horizontal padding)))
|
||||
height (+ height (* 2 (:vertical padding)))]
|
||||
{:base-width width
|
||||
:base-height height
|
||||
:x x
|
||||
:y y
|
||||
:width (* width zoom)
|
||||
:height (* height zoom)
|
||||
:vbox (str x " " y " " width " " height)}))
|
||||
:vbox (dm/fmt "% % % %" 0 0 width height)}))
|
||||
|
||||
(defn- calculate-wrapper
|
||||
[size1 size2 zoom]
|
||||
@ -79,7 +86,7 @@
|
||||
|
||||
(mf/defc viewer-wrapper
|
||||
[{:keys [wrapper-size scroll orig-frame orig-viewport-ref orig-size page file users current-viewport-ref
|
||||
size frame interactions-mode overlays zoom close-overlay section index] :as props}]
|
||||
size frame interactions-mode overlays zoom close-overlay section index children-bounds] :as props}]
|
||||
(let [{clist :list} (mf/deref refs/comments-local)
|
||||
show-comments-list (and (= section :comments) (= :show clist))]
|
||||
[:*
|
||||
@ -128,7 +135,7 @@
|
||||
:interactions-mode interactions-mode}]
|
||||
|
||||
(for [overlay overlays]
|
||||
(let [size-over (calculate-size (:frame overlay) zoom)]
|
||||
(let [size-over (calculate-size (:frame overlay) zoom children-bounds)]
|
||||
[:*
|
||||
(when (or (:close-click-outside overlay)
|
||||
(:background-overlay overlay))
|
||||
@ -166,8 +173,6 @@
|
||||
:page page
|
||||
:zoom zoom}])]]]]))
|
||||
|
||||
|
||||
|
||||
(mf/defc viewer
|
||||
[{:keys [params data]}]
|
||||
|
||||
@ -198,6 +203,13 @@
|
||||
zoom (:zoom local)
|
||||
frames (:frames page)
|
||||
frame (get frames index)
|
||||
|
||||
children-bounds
|
||||
(mf/use-memo
|
||||
(mf/deps page (:id frame))
|
||||
#(-> (cph/get-children (:objects page) (:id frame))
|
||||
(gsh/selection-rect)))
|
||||
|
||||
fullscreen? (mf/deref refs/viewer-fullscreen?)
|
||||
overlays (:overlays local)
|
||||
scroll (mf/use-state nil)
|
||||
@ -207,12 +219,13 @@
|
||||
(d/seek #(= (:id %) (:orig-frame-id current-animation)) frames))
|
||||
|
||||
size (mf/use-memo
|
||||
(mf/deps frame zoom)
|
||||
(fn [] (calculate-size frame zoom)))
|
||||
(mf/deps frame zoom children-bounds)
|
||||
(fn [] (calculate-size frame zoom children-bounds)))
|
||||
|
||||
orig-size (mf/use-memo
|
||||
(mf/deps orig-frame zoom)
|
||||
(fn [] (when orig-frame (calculate-size orig-frame zoom))))
|
||||
(fn [] (when orig-frame
|
||||
(calculate-size orig-frame zoom children-bounds))))
|
||||
|
||||
wrapper-size (mf/use-memo
|
||||
(mf/deps size orig-size zoom)
|
||||
@ -305,7 +318,7 @@
|
||||
wrapper-size)))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps current-animation)
|
||||
(mf/deps current-animation children-bounds)
|
||||
(fn []
|
||||
;; Overlay animations may be started when needed.
|
||||
(when current-animation
|
||||
@ -315,7 +328,7 @@
|
||||
(let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation))))
|
||||
overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation))
|
||||
overlays)
|
||||
overlay-size (calculate-size (:frame overlay) zoom)
|
||||
overlay-size (calculate-size (:frame overlay) zoom children-bounds)
|
||||
overlay-position {:x (* (:x (:position overlay)) zoom)
|
||||
:y (* (:y (:position overlay)) zoom)}]
|
||||
(interactions/animate-open-overlay
|
||||
@ -329,7 +342,7 @@
|
||||
(let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation))))
|
||||
overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation))
|
||||
overlays)
|
||||
overlay-size (calculate-size (:frame overlay) zoom)
|
||||
overlay-size (calculate-size (:frame overlay) zoom children-bounds)
|
||||
overlay-position {:x (* (:x (:position overlay)) zoom)
|
||||
:y (* (:y (:position overlay)) zoom)}]
|
||||
(interactions/animate-close-overlay
|
||||
@ -370,7 +383,8 @@
|
||||
[:& thumbnails-panel {:frames frames
|
||||
:show? (:show-thumbnails local false)
|
||||
:page page
|
||||
:index index}]
|
||||
:index index
|
||||
:thumbnail-data (:thumbnails file)}]
|
||||
[:section.viewer-section {:id "viewer-section"
|
||||
:ref viewer-section-ref
|
||||
:class (if fullscreen? "fullscreen" "")}
|
||||
@ -392,6 +406,7 @@
|
||||
:file file
|
||||
:section section
|
||||
:local local
|
||||
:size size
|
||||
:index index
|
||||
:viewer-pagination viewer-pagination}]
|
||||
|
||||
@ -412,7 +427,8 @@
|
||||
:overlays overlays
|
||||
:zoom zoom
|
||||
:section section
|
||||
:index index}]))]]]))
|
||||
:index index
|
||||
:children-bounds children-bounds}]))]]]))
|
||||
|
||||
;; --- Component: Viewer Page
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.refs :as refs]
|
||||
@ -75,13 +76,6 @@
|
||||
[:span.label (tr "labels.show-comments-list")]]]]]))
|
||||
|
||||
|
||||
(defn- frame-contains?
|
||||
[{:keys [x y width height]} {px :x py :y}]
|
||||
(let [x2 (+ x width)
|
||||
y2 (+ y height)]
|
||||
(and (<= x px x2)
|
||||
(<= y py y2))))
|
||||
|
||||
(def threads-ref
|
||||
(l/derived :comment-threads st/state))
|
||||
|
||||
@ -93,11 +87,11 @@
|
||||
(let [profile (mf/deref refs/profile)
|
||||
threads-map (mf/deref threads-ref)
|
||||
|
||||
modifier1 (-> (gpt/point (:x frame) (:y frame))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
frame-corner (-> frame :points gsh/points->selrect gpt/point)
|
||||
modifier1 (-> (gmt/matrix)
|
||||
(gmt/translate (gpt/negate frame-corner)))
|
||||
|
||||
modifier2 (-> (gpt/point (:x frame) (:y frame))
|
||||
modifier2 (-> (gpt/point frame-corner)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
cstate (mf/deref refs/comments-local)
|
||||
@ -105,7 +99,7 @@
|
||||
threads (->> (vals threads-map)
|
||||
(dcm/apply-filters cstate profile)
|
||||
(filter (fn [{:keys [position]}]
|
||||
(frame-contains? frame position))))
|
||||
(gsh/has-point? frame position))))
|
||||
|
||||
on-bubble-click
|
||||
(mf/use-callback
|
||||
@ -170,7 +164,6 @@
|
||||
:on-submit on-draft-submit
|
||||
:zoom zoom}])]]]))
|
||||
|
||||
|
||||
(mf/defc comments-sidebar
|
||||
[{:keys [users frame page]}]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
@ -179,7 +172,7 @@
|
||||
threads (->> (vals threads-map)
|
||||
(dcm/apply-filters cstate profile)
|
||||
(filter (fn [{:keys [position]}]
|
||||
(frame-contains? frame position))))]
|
||||
(gsh/has-point? frame position))))]
|
||||
[:aside.settings-bar.settings-bar-right.comments-right-sidebar
|
||||
[:div.settings-bar-inside
|
||||
[:& wc/comments-sidebar {:users users :threads threads :page-id (:id page)}]]]))
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
(st/emit! (dv/select-shape (:id frame)))))
|
||||
|
||||
(mf/defc viewport
|
||||
[{:keys [local file page frame index viewer-pagination]}]
|
||||
[{:keys [local file page frame index viewer-pagination size]}]
|
||||
(let [on-mouse-wheel
|
||||
(fn [event]
|
||||
(when (kbd/mod? event)
|
||||
@ -60,7 +60,7 @@
|
||||
[:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)}
|
||||
[:& viewer-pagination {:index index :num-frames (count (:frames page)) :left-bar true :right-bar true}]
|
||||
[:div.handoff-svg-container
|
||||
[:& render-frame-svg {:frame frame :page page :local local}]]]
|
||||
[:& render-frame-svg {:frame frame :page page :local local :size size}]]]
|
||||
|
||||
[:& right-sidebar {:frame frame
|
||||
:selected (:selected local)
|
||||
|
||||
@ -7,13 +7,12 @@
|
||||
(ns app.main.ui.viewer.handoff.render
|
||||
"The main container for a frame in handoff mode"
|
||||
(:require
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.shapes.bool :as bool]
|
||||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
@ -31,24 +30,26 @@
|
||||
(declare shape-container-factory)
|
||||
|
||||
(defn handle-hover-shape
|
||||
[{:keys [type id]} hover?]
|
||||
[shape hover?]
|
||||
(fn [event]
|
||||
(when-not (#{:group :frame} type)
|
||||
(when-not (or (cph/group-shape? shape)
|
||||
(cph/root-frame? shape))
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dv/hover-shape id hover?)))))
|
||||
(st/emit! (dv/hover-shape (:id shape) hover?)))))
|
||||
|
||||
(defn select-shape [{:keys [type id]}]
|
||||
(defn select-shape [shape]
|
||||
(fn [event]
|
||||
(when-not (#{:group :frame} type)
|
||||
(when-not (or (cph/group-shape? shape)
|
||||
(cph/root-frame? shape))
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(cond
|
||||
(.-shiftKey ^js event)
|
||||
(st/emit! (dv/toggle-selection id))
|
||||
(st/emit! (dv/toggle-selection (:id shape)))
|
||||
|
||||
:else
|
||||
(st/emit! (dv/select-shape id))))))
|
||||
(st/emit! (dv/select-shape (:id shape)))))))
|
||||
|
||||
(defn shape-wrapper-factory
|
||||
[component]
|
||||
@ -87,7 +88,7 @@
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
shape (geom/transform-shape shape)
|
||||
shape (gsh/transform-shape shape)
|
||||
|
||||
props (-> (obj/create)
|
||||
(obj/merge! props)
|
||||
@ -154,6 +155,10 @@
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
|
||||
frame-container
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(frame-container-factory objects))
|
||||
|
||||
group-container
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(group-container-factory objects))
|
||||
@ -166,11 +171,12 @@
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(svg-raw-container-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame))
|
||||
(let [shape (-> (gsh/transform-shape shape)
|
||||
(gsh/translate-to-frame frame))
|
||||
opts #js {:shape shape
|
||||
:frame frame}]
|
||||
(case (:type shape)
|
||||
:frame [:> frame-container opts]
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
@ -181,46 +187,31 @@
|
||||
:svg-raw [:> svg-raw-container opts])))))))
|
||||
|
||||
(mf/defc render-frame-svg
|
||||
[{:keys [page frame local]}]
|
||||
[{:keys [page frame local size]}]
|
||||
(let [objects (mf/use-memo
|
||||
(mf/deps page frame)
|
||||
(prepare-objects page frame))
|
||||
|
||||
(prepare-objects page frame size))
|
||||
|
||||
;; Retrieve frame again with correct modifier
|
||||
frame (get objects (:id frame))
|
||||
|
||||
zoom (:zoom local 1)
|
||||
|
||||
{:keys [_ _ width height]} (filters/get-filters-bounds frame)
|
||||
padding (filters/calculate-padding frame)
|
||||
x (- (:horizontal padding))
|
||||
y (- (:vertical padding))
|
||||
width (+ width (* 2 (:horizontal padding)))
|
||||
height (+ height (* 2 (:vertical padding)))
|
||||
|
||||
vbox (str x " " y " " width " " height)
|
||||
|
||||
width (* width zoom)
|
||||
height (* height zoom)
|
||||
|
||||
render (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(frame-container-factory objects))]
|
||||
|
||||
[:svg
|
||||
{:id "svg-frame"
|
||||
:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:view-box (:vbox size)
|
||||
:width (:width size)
|
||||
:height (:height size)
|
||||
:version "1.1"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:fill "none"}
|
||||
|
||||
[:& render {:shape frame :view-box vbox}]
|
||||
[:& render {:shape frame :view-box (:vbox size)}]
|
||||
[:& selection-feedback
|
||||
{:frame frame
|
||||
:objects objects
|
||||
:local local}]]))
|
||||
:local local
|
||||
:size size}]]))
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.measurements :refer [selection-guides size-display measurement]]
|
||||
[app.main.ui.measurements :refer [size-display measurement]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; ------------------------------------------------
|
||||
@ -52,24 +52,23 @@
|
||||
:stroke-width selection-rect-width}}]]))
|
||||
|
||||
(mf/defc selection-feedback
|
||||
[{:keys [frame local objects]}]
|
||||
[{:keys [frame local objects size]}]
|
||||
(let [{:keys [hover selected zoom]} local
|
||||
hover-shape (-> (or (first (resolve-shapes objects [hover])) frame)
|
||||
(gsh/translate-to-frame frame))
|
||||
selected-shapes (->> (resolve-shapes objects selected))
|
||||
|
||||
selrect (gsh/selection-rect selected-shapes)
|
||||
bounds (frame->bounds frame)]
|
||||
shapes (resolve-shapes objects [hover])
|
||||
hover-shape (or (first shapes) frame)
|
||||
hover-shape (gsh/translate-to-frame hover-shape size)
|
||||
|
||||
selected-shapes (resolve-shapes objects selected)
|
||||
selrect (gsh/selection-rect selected-shapes)]
|
||||
|
||||
(when (seq selected-shapes)
|
||||
(when (d/not-empty? selected-shapes)
|
||||
[:g.selection-feedback {:pointer-events "none"}
|
||||
[:g.selected-shapes
|
||||
[:& selection-guides {:bounds bounds :selrect selrect :zoom zoom}]
|
||||
[:& selection-rect {:selrect selrect :zoom zoom}]
|
||||
[:& size-display {:selrect selrect :zoom zoom}]]
|
||||
|
||||
[:& measurement {:bounds bounds
|
||||
[:& measurement {:bounds (assoc size :x 0 :y 0)
|
||||
:selected-shapes selected-shapes
|
||||
:hover-shape hover-shape
|
||||
:zoom zoom}]])))
|
||||
|
||||
@ -157,6 +157,7 @@
|
||||
[:ul.dropdown
|
||||
(for [id (get-in file [:data :pages])]
|
||||
[:li {:id (str id)
|
||||
:key (str id)
|
||||
:on-click (partial navigate-to id)}
|
||||
(get-in file [:data :pages-index id :name])])]]]
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
(ns app.main.ui.viewer.interactions
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages.helpers :as cph]
|
||||
@ -25,11 +26,11 @@
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn prepare-objects
|
||||
[page frame]
|
||||
[page frame size]
|
||||
(fn []
|
||||
(let [objects (:objects page)
|
||||
frame-id (:id frame)
|
||||
modifier (-> (gpt/point (:x frame) (:y frame))
|
||||
modifier (-> (gpt/point (:x size) (:y size))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
@ -43,8 +44,8 @@
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [page interactions-mode frame base-frame frame-offset size]}]
|
||||
(let [objects (mf/use-memo
|
||||
(mf/deps page frame)
|
||||
(prepare-objects page frame))
|
||||
(mf/deps page frame size)
|
||||
(prepare-objects page frame size))
|
||||
|
||||
wrapper (mf/use-memo
|
||||
(mf/deps objects)
|
||||
@ -127,8 +128,9 @@
|
||||
[:& dropdown {:show @show-dropdown?
|
||||
:on-close hide-dropdown}
|
||||
[:ul.dropdown.with-check
|
||||
(for [flow flows]
|
||||
[:li {:class (dom/classnames :selected (= (:id flow) (:id @current-flow)))
|
||||
(for [[index flow] (d/enumerate flows)]
|
||||
[:li {:key (dm/str "flow-" (:id flow) "-" index)
|
||||
:class (dom/classnames :selected (= (:id flow) (:id @current-flow)))
|
||||
:on-click #(select-flow flow)}
|
||||
[:span.icon i/tick]
|
||||
[:span.label (:name flow)]])]]])))
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"The main container for a frame in viewer mode"
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec.interactions :as cti]
|
||||
[app.main.data.viewer :as dv]
|
||||
@ -204,7 +204,7 @@
|
||||
:stroke-width (if interactions-show? 1 0)
|
||||
:fill-opacity (if interactions-show? 0.2 0)
|
||||
:style {:pointer-events (when frame? "none")}
|
||||
:transform (geom/transform-matrix shape)}])))
|
||||
:transform (gsh/transform-str shape)}])))
|
||||
|
||||
(defn generic-wrapper-factory
|
||||
"Wrap some svg shape and add interaction controls"
|
||||
@ -306,7 +306,7 @@
|
||||
[props]
|
||||
(let [shape (obj/get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
shape (geom/transform-shape shape)
|
||||
shape (gsh/transform-shape shape)
|
||||
props (obj/merge! #js {} props
|
||||
#js {:shape shape
|
||||
:childs childs
|
||||
@ -376,6 +376,10 @@
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(group-container-factory objects))
|
||||
|
||||
frame-container
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(frame-container-factory objects))
|
||||
|
||||
bool-container
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(bool-container-factory objects))
|
||||
@ -384,14 +388,14 @@
|
||||
(mf/use-memo (mf/deps objects)
|
||||
#(svg-raw-container-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame)
|
||||
(cond-> fixed? (geom/move delta)))
|
||||
(let [shape (-> (gsh/transform-shape shape)
|
||||
(gsh/translate-to-frame frame)
|
||||
(cond-> fixed? (gsh/move delta)))
|
||||
|
||||
opts #js {:shape shape
|
||||
:objects objects}]
|
||||
(case (:type shape)
|
||||
:frame [:g.empty]
|
||||
:frame [:> frame-container opts]
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
(ns app.main.ui.viewer.thumbnails
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.render :as render]
|
||||
[app.main.store :as st]
|
||||
@ -74,21 +77,27 @@
|
||||
(mf/defc thumbnail-item
|
||||
{::mf/wrap [mf/memo
|
||||
#(mf/deferred % ts/idle-then-raf)]}
|
||||
[{:keys [selected? frame on-click index objects]}]
|
||||
[:div.thumbnail-item {:on-click #(on-click % index)}
|
||||
[:div.thumbnail-preview
|
||||
{:class (dom/classnames :selected selected?)}
|
||||
[:& render/frame-svg {:frame frame :objects objects :show-thumbnails? true}]]
|
||||
[:div.thumbnail-info
|
||||
[:span.name {:title (:name frame)} (:name frame)]]])
|
||||
[{:keys [selected? frame on-click index objects page-id thumbnail-data]}]
|
||||
|
||||
(let [children-ids (cph/get-children-ids objects (:id frame))
|
||||
children-bounds (gsh/selection-rect (concat [frame] (->> children-ids (keep (d/getf objects)))))]
|
||||
[:div.thumbnail-item {:on-click #(on-click % index)}
|
||||
[:div.thumbnail-preview
|
||||
{:class (dom/classnames :selected selected?)}
|
||||
[:& render/frame-svg {:frame (-> frame
|
||||
(assoc :thumbnail (get thumbnail-data (dm/str page-id (:id frame))))
|
||||
(assoc :children-bounds children-bounds))
|
||||
:objects objects
|
||||
:show-thumbnails? true}]]
|
||||
[:div.thumbnail-info
|
||||
[:span.name {:title (:name frame)} (:name frame)]]]))
|
||||
|
||||
(mf/defc thumbnails-panel
|
||||
[{:keys [frames page index show?] :as props}]
|
||||
[{:keys [frames page index show? thumbnail-data] :as props}]
|
||||
(let [expanded? (mf/use-state false)
|
||||
container (mf/use-ref)
|
||||
|
||||
objects (:objects page)
|
||||
|
||||
on-close #(st/emit! dv/toggle-thumbnails-panel)
|
||||
selected (mf/use-var false)
|
||||
|
||||
@ -114,7 +123,10 @@
|
||||
:total (count frames)}
|
||||
(for [[i frame] (d/enumerate frames)]
|
||||
[:& thumbnail-item {:index i
|
||||
:key (dm/str (:id frame) "-" i)
|
||||
:frame frame
|
||||
:page-id (:id page)
|
||||
:objects objects
|
||||
:on-click on-item-click
|
||||
:selected? (= i index)}])]]))
|
||||
:selected? (= i index)
|
||||
:thumbnail-data thumbnail-data}])]]))
|
||||
|
||||
@ -191,7 +191,6 @@
|
||||
has-group? (->> shapes (d/seek #(= :group (:type %))))
|
||||
has-bool? (->> shapes (d/seek #(= :bool (:type %))))
|
||||
has-mask? (->> shapes (d/seek :masked-group?))
|
||||
has-frame? (->> shapes (d/seek #(= :frame (:type %))))
|
||||
|
||||
is-group? (and single? has-group?)
|
||||
is-bool? (and single? has-bool?)
|
||||
@ -207,10 +206,9 @@
|
||||
:shortcut (sc/get-tooltip :ungroup)
|
||||
:on-click do-remove-group}])
|
||||
|
||||
(when (not has-frame?)
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.group")
|
||||
:shortcut (sc/get-tooltip :group)
|
||||
:on-click do-create-group}])
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.group")
|
||||
:shortcut (sc/get-tooltip :group)
|
||||
:on-click do-create-group}]
|
||||
|
||||
(when (or multiple? (and is-group? (not has-mask?)) is-bool?)
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.mask")
|
||||
@ -222,12 +220,10 @@
|
||||
:shortcut (sc/get-tooltip :unmask)
|
||||
:on-click do-unmask-group}])
|
||||
|
||||
(when (not has-frame?)
|
||||
[:*
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection")
|
||||
:shortcut (sc/get-tooltip :artboard-selection)
|
||||
:on-click do-create-artboard-from-selection}]
|
||||
[:& menu-separator]])]))
|
||||
[:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection")
|
||||
:shortcut (sc/get-tooltip :artboard-selection)
|
||||
:on-click do-create-artboard-from-selection}]
|
||||
[:& menu-separator]]))
|
||||
|
||||
(mf/defc context-focus-mode-menu
|
||||
[{:keys []}]
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
common."
|
||||
(:require
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
[app.main.ui.shapes.rect :as rect]
|
||||
@ -52,7 +53,8 @@
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(cph/objects-by-frame objects))]
|
||||
[:*
|
||||
|
||||
[:& (mf/provider ctx/active-frames-ctx) {:value active-frames}
|
||||
;; Render font faces only for shapes that are part of the root
|
||||
;; frame but don't belongs to any other frame.
|
||||
(let [xform (comp
|
||||
@ -75,23 +77,30 @@
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (obj/get props "shape")
|
||||
opts #js {:shape shape}]
|
||||
|
||||
active-frames
|
||||
(when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames-ctx))
|
||||
|
||||
thumbnail?
|
||||
(and (some? active-frames)
|
||||
(not (contains? active-frames (:id shape))))
|
||||
|
||||
opts #js {:shape shape :thumbnail? thumbnail?}]
|
||||
(when (and (some? shape) (not (:hidden shape)))
|
||||
[:*
|
||||
(case (:type shape)
|
||||
:path [:> path/path-wrapper opts]
|
||||
:text [:> text/text-wrapper opts]
|
||||
:group [:> group-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:svg-raw [:> svg-raw-wrapper opts]
|
||||
:bool [:> bool-wrapper opts]
|
||||
(case (:type shape)
|
||||
:path [:> path/path-wrapper opts]
|
||||
:text [:> text/text-wrapper opts]
|
||||
:group [:> group-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:svg-raw [:> svg-raw-wrapper opts]
|
||||
:bool [:> bool-wrapper opts]
|
||||
|
||||
;; Only used when drawing a new frame.
|
||||
:frame [:> frame-wrapper opts]
|
||||
;; Only used when drawing a new frame.
|
||||
:frame [:> frame-wrapper opts]
|
||||
|
||||
nil)])))
|
||||
nil))))
|
||||
|
||||
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
||||
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
||||
|
||||
@ -8,10 +8,12 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.shapes.embed :as embed]
|
||||
@ -38,15 +40,14 @@
|
||||
childs (mf/deref childs-ref)]
|
||||
|
||||
[:& (mf/provider embed/context) {:value true}
|
||||
[:& shape-container {:shape shape :ref ref}
|
||||
[:& shape-container {:shape shape :ref ref :disable-shadows? true}
|
||||
[:& frame-shape {:shape shape :childs childs} ]]]))))
|
||||
|
||||
(defn check-props
|
||||
[new-props old-props]
|
||||
(and
|
||||
(= (unchecked-get new-props "thumbnail?") (unchecked-get old-props "thumbnail?"))
|
||||
(= (unchecked-get new-props "shape") (unchecked-get old-props "shape"))
|
||||
(= (unchecked-get new-props "objects") (unchecked-get old-props "objects"))))
|
||||
(= (unchecked-get new-props "shape") (unchecked-get old-props "shape"))))
|
||||
|
||||
(defn frame-wrapper-factory
|
||||
[shape-wrapper]
|
||||
@ -57,77 +58,81 @@
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [shape (unchecked-get props "shape")
|
||||
thumbnail? (unchecked-get props "thumbnail?")
|
||||
objects (unchecked-get props "objects")
|
||||
|
||||
render-id (mf/use-memo #(str (uuid/next)))
|
||||
fonts (mf/use-memo (mf/deps shape objects) #(ff/shape->fonts shape objects))
|
||||
fonts (-> fonts (hooks/use-equal-memo))
|
||||
|
||||
force-render (mf/use-state false)
|
||||
|
||||
;; Thumbnail data
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame-id (:id shape)
|
||||
page-id (mf/use-ctx ctx/current-page-id)
|
||||
|
||||
;; References to the current rendered node and the its parentn
|
||||
node-ref (mf/use-var nil)
|
||||
|
||||
;; when `true` we've called the mount for the frame
|
||||
rendered? (mf/use-var false)
|
||||
objects (wsh/lookup-page-objects @st/state)
|
||||
|
||||
;; Modifiers
|
||||
modifiers-ref (mf/use-memo (mf/deps frame-id) #(refs/workspace-modifiers-by-frame-id frame-id))
|
||||
modifiers (mf/deref modifiers-ref)
|
||||
|
||||
disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers]))
|
||||
|
||||
[on-load-frame-dom render-frame? thumbnail-renderer]
|
||||
(ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail? @force-render)
|
||||
|
||||
on-frame-load
|
||||
(fns/use-node-store thumbnail? node-ref rendered? render-frame?)]
|
||||
modifiers (mf/deref modifiers-ref)]
|
||||
|
||||
(fdm/use-dynamic-modifiers objects @node-ref modifiers)
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps fonts)
|
||||
(fn []
|
||||
(->> (rx/from fonts)
|
||||
(rx/merge-map fonts/fetch-font-css)
|
||||
(rx/ignore))))
|
||||
(if-not (cph/root-frame? shape)
|
||||
[:& frame-shape {:shape shape :ref node-ref}]
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
;; When a change in the data is received a "force-render" event is emited
|
||||
;; that will force the component to be mounted in memory
|
||||
(let [sub
|
||||
(->> (dwt/force-render-stream (:id shape))
|
||||
(rx/take-while #(not @rendered?))
|
||||
(rx/subs #(reset! force-render true)))]
|
||||
#(when sub
|
||||
(rx/dispose! sub)))))
|
||||
;; If the current shape is root we handle its thumbnail and the dynamic modifiers
|
||||
(let [thumbnail? (unchecked-get props "thumbnail?")
|
||||
fonts (mf/use-memo (mf/deps shape objects) #(ff/shape->fonts shape objects))
|
||||
fonts (-> fonts (hooks/use-equal-memo))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps shape fonts thumbnail? on-load-frame-dom @force-render render-frame?)
|
||||
(fn []
|
||||
(when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render render-frame?))
|
||||
(mf/mount
|
||||
(mf/element frame-shape
|
||||
#js {:ref on-load-frame-dom :shape shape :fonts fonts})
|
||||
force-render (mf/use-state false)
|
||||
|
||||
@node-ref)
|
||||
(when (not @rendered?) (reset! rendered? true)))))
|
||||
;; Thumbnail data
|
||||
page-id (mf/use-ctx ctx/current-page-id)
|
||||
|
||||
[:& (mf/provider ctx/render-ctx) {:value render-id}
|
||||
[:g.frame-container {:id (dm/str "frame-container-" (:id shape))
|
||||
:key "frame-container"
|
||||
:ref on-frame-load
|
||||
:opacity (when (:hidden shape) 0)}
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
[:g.frame-thumbnail-wrapper
|
||||
{:id (dm/str "thumbnail-container-" (:id shape))
|
||||
;; Hide the thumbnail when not displaying
|
||||
:opacity (when (and @rendered? (not thumbnail?) (not render-frame?)) 0)}
|
||||
thumbnail-renderer]]]))))
|
||||
;; when `true` we've called the mount for the frame
|
||||
rendered? (mf/use-var false)
|
||||
|
||||
disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers]))
|
||||
|
||||
[on-load-frame-dom render-frame? thumbnail-renderer]
|
||||
(ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail? @force-render)
|
||||
|
||||
on-frame-load
|
||||
(fns/use-node-store thumbnail? node-ref rendered? render-frame?)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps fonts)
|
||||
(fn []
|
||||
(->> (rx/from fonts)
|
||||
(rx/merge-map fonts/fetch-font-css)
|
||||
(rx/ignore))))
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
;; When a change in the data is received a "force-render" event is emited
|
||||
;; that will force the component to be mounted in memory
|
||||
(let [sub
|
||||
(->> (dwt/force-render-stream (:id shape))
|
||||
(rx/take-while #(not @rendered?))
|
||||
(rx/subs #(reset! force-render true)))]
|
||||
#(when sub
|
||||
(rx/dispose! sub)))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps shape fonts thumbnail? on-load-frame-dom @force-render render-frame?)
|
||||
(fn []
|
||||
(when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render render-frame?))
|
||||
(mf/mount
|
||||
(mf/element frame-shape
|
||||
#js {:ref on-load-frame-dom :shape shape :fonts fonts})
|
||||
|
||||
@node-ref)
|
||||
(when (not @rendered?) (reset! rendered? true)))))
|
||||
|
||||
[:& shape-container {:shape shape}
|
||||
[:g.frame-container {:id (dm/str "frame-container-" (:id shape))
|
||||
:key "frame-container"
|
||||
:ref on-frame-load
|
||||
:opacity (when (:hidden shape) 0)}
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
[:g.frame-thumbnail-wrapper
|
||||
{:id (dm/str "thumbnail-container-" (:id shape))
|
||||
;; Hide the thumbnail when not displaying
|
||||
:opacity (when (and @rendered? (not thumbnail?) (not render-frame?)) 0)}
|
||||
thumbnail-renderer]]]))))))
|
||||
|
||||
@ -77,39 +77,42 @@
|
||||
|
||||
(defn get-nodes
|
||||
"Retrieve the DOM nodes to apply the matrix transformation"
|
||||
[base-node {:keys [id type masked-group?]}]
|
||||
(let [shape-node (dom/query base-node (str "#shape-" id))
|
||||
[base-node {:keys [id type masked-group?] :as shape}]
|
||||
(when (some? base-node)
|
||||
(let [shape-node (if (= (.-id base-node) (dm/str "shape-" id))
|
||||
base-node
|
||||
(dom/query base-node (dm/str "#shape-" id)))
|
||||
|
||||
frame? (= :frame type)
|
||||
group? (= :group type)
|
||||
text? (= :text type)
|
||||
mask? (and group? masked-group?)]
|
||||
frame? (= :frame type)
|
||||
group? (= :group type)
|
||||
text? (= :text type)
|
||||
mask? (and group? masked-group?)]
|
||||
(cond
|
||||
frame?
|
||||
[shape-node
|
||||
(dom/query shape-node ".frame-children")
|
||||
(dom/query (dm/str "#thumbnail-container-" id))
|
||||
(dom/query (dm/str "#thumbnail-" id))
|
||||
(dom/query (dm/str "#frame-title-" id))]
|
||||
|
||||
(cond
|
||||
frame?
|
||||
[shape-node
|
||||
(dom/query shape-node ".frame-children")
|
||||
(dom/query (str "#thumbnail-container-" id))
|
||||
(dom/query (str "#thumbnail-" id))]
|
||||
;; For groups we don't want to transform the whole group but only
|
||||
;; its filters/masks
|
||||
mask?
|
||||
[(dom/query shape-node ".mask-clip-path")
|
||||
(dom/query shape-node ".mask-shape")]
|
||||
|
||||
;; For groups we don't want to transform the whole group but only
|
||||
;; its filters/masks
|
||||
mask?
|
||||
[(dom/query shape-node ".mask-clip-path")
|
||||
(dom/query shape-node ".mask-shape")]
|
||||
group?
|
||||
(let [shape-defs (dom/query shape-node "defs")]
|
||||
(d/concat-vec
|
||||
(dom/query-all shape-defs ".svg-def")
|
||||
(dom/query-all shape-defs ".svg-mask-wrapper")))
|
||||
|
||||
group?
|
||||
(let [shape-defs (dom/query shape-node "defs")]
|
||||
(d/concat-vec
|
||||
(dom/query-all shape-defs ".svg-def")
|
||||
(dom/query-all shape-defs ".svg-mask-wrapper")))
|
||||
text?
|
||||
[shape-node
|
||||
(dom/query shape-node ".text-container")]
|
||||
|
||||
text?
|
||||
[shape-node
|
||||
(dom/query shape-node ".text-container")]
|
||||
|
||||
:else
|
||||
[shape-node])))
|
||||
:else
|
||||
[shape-node]))))
|
||||
|
||||
(defn transform-region!
|
||||
[node modifiers]
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
[thumbnail? node-ref rendered? render-frame?]
|
||||
|
||||
(let [;; when `true` the node is in memory
|
||||
in-memory? (mf/use-var true)
|
||||
in-memory? (mf/use-state true)
|
||||
|
||||
;; State just for re-rendering
|
||||
re-render (mf/use-state 0)
|
||||
@ -32,7 +32,7 @@
|
||||
(reset! parent-ref node)
|
||||
(swap! re-render inc)))))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/use-layout-effect
|
||||
(mf/deps thumbnail? render-frame?)
|
||||
(fn []
|
||||
(when (and (some? @parent-ref) (some? @node-ref) @rendered? (and thumbnail? (not render-frame?)))
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.refs :as refs]
|
||||
@ -32,7 +33,6 @@
|
||||
|
||||
(.clearRect canvas-context 0 0 canvas-width canvas-height)
|
||||
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
|
||||
(.removeAttribute canvas-node "data-empty")
|
||||
true))
|
||||
(catch :default err
|
||||
(.error js/console err)
|
||||
@ -54,7 +54,7 @@
|
||||
|
||||
(defn use-render-thumbnail
|
||||
"Hook that will create the thumbnail thata"
|
||||
[page-id {:keys [id x y width height] :as shape} node-ref rendered? disable? force-render]
|
||||
[page-id {:keys [id] :as shape} node-ref rendered? disable? force-render]
|
||||
|
||||
(let [frame-canvas-ref (mf/use-ref nil)
|
||||
frame-image-ref (mf/use-ref nil)
|
||||
@ -63,13 +63,21 @@
|
||||
|
||||
regenerate-thumbnail (mf/use-var false)
|
||||
|
||||
fixed-width (mth/clamp (:width shape) 250 2000)
|
||||
fixed-height (/ (* (:height shape) fixed-width) (:width shape))
|
||||
all-children-ref (mf/use-memo (mf/deps id) #(refs/all-children-objects id))
|
||||
all-children (mf/deref all-children-ref)
|
||||
|
||||
{:keys [x y width height] :as shape-bb}
|
||||
(if (:show-content shape)
|
||||
(gsh/selection-rect (concat [shape] all-children))
|
||||
(-> shape :points gsh/points->selrect))
|
||||
|
||||
fixed-width (mth/clamp width 250 2000)
|
||||
fixed-height (/ (* height fixed-width) width)
|
||||
|
||||
image-url (mf/use-state nil)
|
||||
observer-ref (mf/use-var nil)
|
||||
|
||||
shape-ref (hooks/use-update-var shape)
|
||||
shape-bb-ref (hooks/use-update-var shape-bb)
|
||||
|
||||
updates-str (mf/use-memo #(rx/subject))
|
||||
|
||||
@ -78,8 +86,12 @@
|
||||
|
||||
prev-thumbnail-data (hooks/use-previous thumbnail-data)
|
||||
|
||||
;; State to indicate to the parent that should render the frame
|
||||
render-frame? (mf/use-state (not thumbnail-data))
|
||||
|
||||
;; State variable to select whether we show the image thumbnail or the canvas thumbnail
|
||||
show-frame-thumbnail (mf/use-state (some? thumbnail-data))
|
||||
|
||||
on-image-load
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
@ -89,6 +101,8 @@
|
||||
(when (draw-thumbnail-canvas! canvas-node img-node)
|
||||
(reset! image-url nil)
|
||||
|
||||
(when @show-frame-thumbnail
|
||||
(reset! show-frame-thumbnail false))
|
||||
;; If we don't have the thumbnail data saved (normaly the first load) we update the data
|
||||
;; when available
|
||||
(when (not @thumbnail-data-ref)
|
||||
@ -101,7 +115,8 @@
|
||||
(fn []
|
||||
(let [node @node-ref
|
||||
frame-html (dom/node->xml node)
|
||||
{:keys [x y width height]} @shape-ref
|
||||
|
||||
{:keys [x y width height]} @shape-bb-ref
|
||||
|
||||
style-node (dom/query (dm/str "#frame-container-" (:id shape) " style"))
|
||||
style-str (or (-> style-node dom/node->xml) "")
|
||||
@ -199,18 +214,26 @@
|
||||
[on-load-frame-dom
|
||||
@render-frame?
|
||||
(mf/html
|
||||
[:*
|
||||
[:> frame/frame-thumbnail {:key (dm/str (:id shape))
|
||||
:shape (cond-> shape
|
||||
(some? thumbnail-data)
|
||||
(assoc :thumbnail thumbnail-data))}]
|
||||
[:& frame/frame-container {:bounds shape-bb
|
||||
:shape (cond-> shape
|
||||
(some? thumbnail-data)
|
||||
(assoc :thumbnail thumbnail-data))}
|
||||
|
||||
(when @show-frame-thumbnail
|
||||
[:> frame/frame-thumbnail-image
|
||||
{:key (dm/str (:id shape))
|
||||
:bounds shape-bb
|
||||
:shape (cond-> shape
|
||||
(some? thumbnail-data)
|
||||
(assoc :thumbnail thumbnail-data))}])
|
||||
|
||||
|
||||
[:foreignObject {:x x :y y :width width :height height}
|
||||
[:canvas.thumbnail-canvas
|
||||
{:key (dm/str "thumbnail-canvas-" (:id shape))
|
||||
:ref frame-canvas-ref
|
||||
:data-object-id (dm/str page-id (:id shape))
|
||||
:data-empty true
|
||||
:data-empty @show-frame-thumbnail
|
||||
:width fixed-width
|
||||
:height fixed-height
|
||||
;; DEBUG
|
||||
@ -220,9 +243,9 @@
|
||||
|
||||
(when (some? @image-url)
|
||||
[:image {:ref frame-image-ref
|
||||
:x (:x shape)
|
||||
:y (:y shape)
|
||||
:x x
|
||||
:y y
|
||||
:href @image-url
|
||||
:width (:width shape)
|
||||
:height (:height shape)
|
||||
:width width
|
||||
:height height
|
||||
:on-load on-image-load}])])]))
|
||||
|
||||
@ -14,6 +14,12 @@
|
||||
[app.main.ui.workspace.shapes.path.common :as pc]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn apply-content-modifiers
|
||||
[shape content-modifiers]
|
||||
(let [shape (update shape :content upc/apply-content-modifiers content-modifiers)
|
||||
[_ new-selrect] (helpers/content->points+selrect shape (:content shape))]
|
||||
(assoc shape :selrect new-selrect)))
|
||||
|
||||
(mf/defc path-wrapper
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
@ -22,11 +28,7 @@
|
||||
content-modifiers (mf/deref content-modifiers-ref)
|
||||
editing-id (mf/deref refs/selected-edition)
|
||||
editing? (= editing-id (:id shape))
|
||||
shape (update shape :content upc/apply-content-modifiers content-modifiers)
|
||||
|
||||
[_ new-selrect]
|
||||
(helpers/content->points+selrect shape (:content shape))
|
||||
shape (assoc shape :selrect new-selrect)]
|
||||
shape (mf/use-memo (mf/deps shape content-modifiers) #(apply-content-modifiers shape content-modifiers))]
|
||||
|
||||
[:> shape-container {:shape shape
|
||||
:pointer-events (when editing? "none")}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
(some? text-modifier)
|
||||
(dwt/apply-text-modifier text-modifier))
|
||||
|
||||
transform (gsh/transform-matrix shape {:no-flip true})
|
||||
transform (gsh/transform-str shape {:no-flip true})
|
||||
{:keys [x y width height]} shape]
|
||||
|
||||
[:rect.main.viewport-selrect
|
||||
@ -31,7 +31,7 @@
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:transform (str transform)
|
||||
:transform transform
|
||||
:style {:stroke "var(--color-select)"
|
||||
:stroke-width (/ 1 zoom)
|
||||
:fill "none"}}]))
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
|
||||
(defn strip-modifier
|
||||
[modifier]
|
||||
(if (or (some? (get-in modifier [:modifiers :resize-vector]))
|
||||
(some? (get-in modifier [:modifiers :resize-vector-2])))
|
||||
(if (or (some? (dm/get-in modifier [:modifiers :resize-vector]))
|
||||
(some? (dm/get-in modifier [:modifiers :resize-vector-2])))
|
||||
modifier
|
||||
(d/update-when modifier :modifiers dissoc :displacement :rotation)))
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.interactions
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec.interactions :as csi]
|
||||
[app.common.spec.page :as csp]
|
||||
@ -178,10 +179,10 @@
|
||||
|
||||
(mf/defc interaction-entry
|
||||
[{:keys [index shape interaction update-interaction remove-interaction]}]
|
||||
(let [objects (deref refs/workspace-page-objects)
|
||||
destination (get objects (:destination interaction))
|
||||
frames (mf/with-memo [objects]
|
||||
(cph/get-frames objects))
|
||||
(let [objects (deref refs/workspace-page-objects)
|
||||
destination (get objects (:destination interaction))
|
||||
|
||||
frames (mf/with-memo [objects] (cph/get-viewer-frames objects {:all-frames? (not= :navigate (:action-type interaction))}))
|
||||
|
||||
overlay-pos-type (:overlay-pos-type interaction)
|
||||
close-click-outside? (:close-click-outside interaction false)
|
||||
@ -313,7 +314,8 @@
|
||||
(for [[value name] (event-type-names)]
|
||||
(when-not (and (= value :after-delay)
|
||||
(not= (:type shape) :frame))
|
||||
[:option {:value (str value)} name]))]]
|
||||
[:option {:key (dm/str value)
|
||||
:value (dm/str value)} name]))]]
|
||||
|
||||
; Delay
|
||||
(when (csi/has-delay interaction)
|
||||
@ -334,7 +336,8 @@
|
||||
{:value (str (:action-type interaction))
|
||||
:on-change change-action-type}
|
||||
(for [[value name] (action-type-names)]
|
||||
[:option {:value (str value)} name])]]
|
||||
[:option {:key (dm/str "action-" value)
|
||||
:value (str value)} name])]]
|
||||
|
||||
; Destination
|
||||
(when (csi/has-destination interaction)
|
||||
@ -349,7 +352,8 @@
|
||||
(for [frame frames]
|
||||
(when (and (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
|
||||
(not= (:id frame) (:frame-id shape))) ; nor a shape to its container frame
|
||||
[:option {:value (str (:id frame))} (:name frame)]))]])
|
||||
[:option {:key (dm/str "destination-" (:id frame))
|
||||
:value (str (:id frame))} (:name frame)]))]])
|
||||
|
||||
; Preserve scroll
|
||||
(when (csi/has-preserve-scroll interaction)
|
||||
@ -568,7 +572,8 @@
|
||||
[:div.interactions-help (tr "workspace.options.use-play-button")]])]
|
||||
[:div.groups
|
||||
(for [[index interaction] (d/enumerate interactions)]
|
||||
[:& interaction-entry {:index index
|
||||
[:& interaction-entry {:key (dm/str (:id shape) "-" index)
|
||||
:index index
|
||||
:shape shape
|
||||
:interaction interaction
|
||||
:update-interaction update-interaction
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.spec.radius :as ctr]
|
||||
[app.main.constants :refer [size-presets]]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.refs :as refs]
|
||||
@ -30,12 +31,14 @@
|
||||
:rx :ry
|
||||
:r1 :r2 :r3 :r4
|
||||
:selrect
|
||||
:points])
|
||||
:points
|
||||
:show-content
|
||||
:hide-in-viewer])
|
||||
|
||||
(def ^:private type->options
|
||||
{:bool #{:size :position :rotation}
|
||||
:circle #{:size :position :rotation}
|
||||
:frame #{:presets :size :position :radius}
|
||||
:frame #{:presets :size :position :rotation :radius :clip-content :show-in-viewer}
|
||||
:group #{:size :position :rotation}
|
||||
:image #{:size :position :rotation :radius}
|
||||
:path #{:size :position :rotation}
|
||||
@ -43,8 +46,6 @@
|
||||
:svg-raw #{:size :position :rotation}
|
||||
:text #{:size :position :rotation}})
|
||||
|
||||
(declare +size-presets+)
|
||||
|
||||
;; -- User/drawing coords
|
||||
(mf/defc measures-menu
|
||||
[{:keys [ids ids-with-children values type all-types shape] :as props}]
|
||||
@ -103,6 +104,9 @@
|
||||
radius-multi? (mf/use-state nil)
|
||||
radius-input-ref (mf/use-ref nil)
|
||||
|
||||
clip-content-ref (mf/use-ref nil)
|
||||
show-in-viewer-ref (mf/use-ref nil)
|
||||
|
||||
on-preset-selected
|
||||
(fn [width height]
|
||||
(st/emit! (udw/update-dimensions ids :width width)
|
||||
@ -146,13 +150,13 @@
|
||||
|
||||
change-radius
|
||||
(mf/use-callback
|
||||
(mf/deps ids-with-children)
|
||||
(fn [update-fn]
|
||||
(dch/update-shapes ids-with-children
|
||||
(fn [shape]
|
||||
(if (ctr/has-radius? shape)
|
||||
(update-fn shape)
|
||||
shape)))))
|
||||
(mf/deps ids-with-children)
|
||||
(fn [update-fn]
|
||||
(dch/update-shapes ids-with-children
|
||||
(fn [shape]
|
||||
(if (ctr/has-radius? shape)
|
||||
(update-fn shape)
|
||||
shape)))))
|
||||
|
||||
on-switch-to-radius-1
|
||||
(mf/use-callback
|
||||
@ -200,17 +204,31 @@
|
||||
on-radius-r3-change #(on-radius-4-change % :r3)
|
||||
on-radius-r4-change #(on-radius-4-change % :r4)
|
||||
|
||||
on-change-clip-content
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/checked?)]
|
||||
(st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :show-content (not value))))))))
|
||||
|
||||
on-change-show-in-viewer
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/checked?)]
|
||||
(st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value))))))))
|
||||
|
||||
select-all #(-> % (dom/get-target) (.select))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps radius-mode @radius-multi?)
|
||||
(fn []
|
||||
(when (and (= radius-mode :radius-1)
|
||||
(= @radius-multi? false))
|
||||
;; when going back from radius-multi to normal radius-1,
|
||||
;; restore focus to the newly created numeric-input
|
||||
(let [radius-input (mf/ref-val radius-input-ref)]
|
||||
(dom/focus! radius-input)))))
|
||||
(mf/use-layout-effect
|
||||
(mf/deps radius-mode @radius-multi?)
|
||||
(fn []
|
||||
(when (and (= radius-mode :radius-1)
|
||||
(= @radius-multi? false))
|
||||
;; when going back from radius-multi to normal radius-1,
|
||||
;; restore focus to the newly created numeric-input
|
||||
(let [radius-input (mf/ref-val radius-input-ref)]
|
||||
(dom/focus! radius-input)))))
|
||||
|
||||
[:*
|
||||
[:div.element-set
|
||||
@ -226,7 +244,7 @@
|
||||
[:& dropdown {:show @show-presets-dropdown?
|
||||
:on-close #(reset! show-presets-dropdown? false)}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [size-preset +size-presets+]
|
||||
(for [size-preset size-presets]
|
||||
(if-not (:width size-preset)
|
||||
[:li.dropdown-label {:key (:name size-preset)}
|
||||
[:span (:name size-preset)]]
|
||||
@ -367,172 +385,30 @@
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r4-change
|
||||
:value (:r4 values)}]]])])]]]))
|
||||
:value (:r4 values)}]]])])
|
||||
|
||||
(def +size-presets+
|
||||
[{:name "APPLE"}
|
||||
{:name "iPhone 12/12 Pro"
|
||||
:width 390
|
||||
:height 844}
|
||||
{:name "iPhone 12 Mini"
|
||||
:width 360
|
||||
:height 780}
|
||||
{:name "iPhone 12 Pro Max"
|
||||
:width 428
|
||||
:height 926}
|
||||
{:name "iPhone X/XS/11 Pro"
|
||||
:width 375
|
||||
:height 812}
|
||||
{:name "iPhone XS Max/XR/11"
|
||||
:width 414
|
||||
:height 896}
|
||||
{:name "iPhone 6/7/8 Plus"
|
||||
:width 414
|
||||
:height 736}
|
||||
{:name "iPhone 6/7/8/SE2"
|
||||
:width 375
|
||||
:height 667}
|
||||
{:name "iPhone 5/SE"
|
||||
:width 320
|
||||
:height 568}
|
||||
{:name "iPad"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "iPad Pro 10.5in"
|
||||
:width 834
|
||||
:height 1112}
|
||||
{:name "iPad Pro 12.9in"
|
||||
:width 1024
|
||||
:height 1366}
|
||||
{:name "Watch 44mm"
|
||||
:width 368
|
||||
:height 448}
|
||||
{:name "Watch 42mm"
|
||||
:width 312
|
||||
:height 390}
|
||||
{:name "Watch 40mm"
|
||||
:width 324
|
||||
:height 394}
|
||||
{:name "Watch 38mm"
|
||||
:width 272
|
||||
:height 340}
|
||||
(when (options :clip-content)
|
||||
[:div.input-checkbox
|
||||
[:input {:type "checkbox"
|
||||
:id "clip-content"
|
||||
:ref clip-content-ref
|
||||
:checked (not (:show-content values))
|
||||
:on-change on-change-clip-content}]
|
||||
|
||||
{:name "ANDROID"}
|
||||
{:name "Mobile"
|
||||
:width 360
|
||||
:height 640}
|
||||
{:name "Tablet"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "Google Pixel 4a/5"
|
||||
:width 393
|
||||
:height 851}
|
||||
{:name "Samsung Galaxy S20+"
|
||||
:width 384
|
||||
:height 854}
|
||||
{:name "Samsung Galaxy A71/A51"
|
||||
:width 412
|
||||
:height 914}
|
||||
[:label {:for "clip-content"}
|
||||
(tr "workspace.options.clip-content")]])
|
||||
|
||||
{:name "MICROSOFT"}
|
||||
{:name "Surface Pro 3"
|
||||
:width 1440
|
||||
:height 960}
|
||||
{:name "Surface Pro 4/5/6/7"
|
||||
:width 1368
|
||||
:height 912}
|
||||
(when (options :show-in-viewer)
|
||||
[:div.input-checkbox
|
||||
[:input {:type "checkbox"
|
||||
:id "show-in-viewer"
|
||||
:ref show-in-viewer-ref
|
||||
:checked (not (:hide-in-viewer values))
|
||||
:on-change on-change-show-in-viewer}]
|
||||
|
||||
{:name "ReMarkable"}
|
||||
{:name "Remarkable 2"
|
||||
:width 840
|
||||
:height 1120}
|
||||
[:label {:for "show-in-viewer"}
|
||||
(tr "workspace.options.show-in-viewer")]])
|
||||
|
||||
{:name "WEB"}
|
||||
{:name "Web 1280"
|
||||
:width 1280
|
||||
:height 800}
|
||||
{:name "Web 1366"
|
||||
:width 1366
|
||||
:height 768}
|
||||
{:name "Web 1024"
|
||||
:width 1024
|
||||
:height 768}
|
||||
{:name "Web 1920"
|
||||
:width 1920
|
||||
:height 1080}
|
||||
]]]))
|
||||
|
||||
{:name "PRINT (96dpi)"}
|
||||
{:name "A0"
|
||||
:width 3179
|
||||
:height 4494}
|
||||
{:name "A1"
|
||||
:width 2245
|
||||
:height 3179}
|
||||
{:name "A2"
|
||||
:width 1587
|
||||
:height 2245}
|
||||
{:name "A3"
|
||||
:width 1123
|
||||
:height 1587}
|
||||
{:name "A4"
|
||||
:width 794
|
||||
:height 1123}
|
||||
{:name "A5"
|
||||
:width 559
|
||||
:height 794}
|
||||
{:name "A6"
|
||||
:width 397
|
||||
:height 559}
|
||||
{:name "Letter"
|
||||
:width 816
|
||||
:height 1054}
|
||||
{:name "DIN Lang"
|
||||
:width 835
|
||||
:height 413}
|
||||
|
||||
{:name "SOCIAL MEDIA"}
|
||||
{:name "Instagram profile"
|
||||
:width 320
|
||||
:height 320}
|
||||
{:name "Instagram post"
|
||||
:width 1080
|
||||
:height 1080}
|
||||
{:name "Instagram story"
|
||||
:width 1080
|
||||
:height 1920}
|
||||
{:name "Facebook profile"
|
||||
:width 720
|
||||
:height 720}
|
||||
{:name "Facebook cover"
|
||||
:width 820
|
||||
:height 312}
|
||||
{:name "Facebook post"
|
||||
:width 1200
|
||||
:height 630}
|
||||
{:name "LinkedIn profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "LinkedIn cover"
|
||||
:width 1584
|
||||
:height 396}
|
||||
{:name "LinkedIn post"
|
||||
:width 1200
|
||||
:height 627}
|
||||
{:name "Twitter profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "Twitter header"
|
||||
:width 1500
|
||||
:height 500}
|
||||
{:name "Twitter post"
|
||||
:width 1024
|
||||
:height 512}
|
||||
{:name "YouTube profile"
|
||||
:width 800
|
||||
:height 800}
|
||||
{:name "YouTube banner"
|
||||
:width 2560
|
||||
:height 1440}
|
||||
{:name "YouTube thumb"
|
||||
:width 1280
|
||||
:height 720}])
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.main.constants :refer [has-layout-item]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
|
||||
@ -25,6 +26,7 @@
|
||||
stroke-values (select-keys shape stroke-attrs)
|
||||
layer-values (select-keys shape layer-attrs)
|
||||
measure-values (select-keys shape measure-attrs)
|
||||
constraint-values (select-keys shape constraint-attrs)
|
||||
layout-values (select-keys shape layout-attrs)
|
||||
layout-item-values (select-keys shape layout-item-attrs)]
|
||||
[:*
|
||||
@ -32,6 +34,8 @@
|
||||
:values measure-values
|
||||
:type type
|
||||
:shape shape}]
|
||||
[:& constraints-menu {:ids ids
|
||||
:values constraint-values}]
|
||||
(when has-layout-item
|
||||
[:& layout-menu {:type type :ids [(:id shape)] :values layout-values}])
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as ui-hooks]
|
||||
@ -155,7 +156,13 @@
|
||||
show-draw-area? drawing-obj
|
||||
show-gradient-handlers? (= (count selected) 1)
|
||||
show-grids? (contains? layout :display-grid)
|
||||
show-outlines? (and (nil? transform) (not edition) (not drawing-obj) (not (#{:comments :path :curve} drawing-tool)))
|
||||
|
||||
show-frame-outline? (= transform :move)
|
||||
show-outlines? (and (nil? transform)
|
||||
(not edition)
|
||||
(not drawing-obj)
|
||||
(not (#{:comments :path :curve} drawing-tool)))
|
||||
|
||||
show-pixel-grid? (and (contains? layout :show-pixel-grid)
|
||||
(>= zoom 8))
|
||||
show-text-editor? (and editing-shape (= :text (:type editing-shape)))
|
||||
@ -275,16 +282,20 @@
|
||||
(when show-text-editor?
|
||||
[:& editor/text-editor-svg {:shape editing-shape}])
|
||||
|
||||
(when show-frame-outline?
|
||||
[:& outline/shape-outlines
|
||||
{:objects base-objects
|
||||
:hover #{(->> @hover-ids
|
||||
(filter #(cph/frame-shape? base-objects %))
|
||||
(remove selected)
|
||||
(first))}
|
||||
:zoom zoom}])
|
||||
|
||||
(when show-outlines?
|
||||
[:& outline/shape-outlines
|
||||
{:objects base-objects
|
||||
:selected selected
|
||||
:hover (cond
|
||||
(and @hover (or @mod? (not= :frame (:type @hover))))
|
||||
#{(:id @hover)}
|
||||
|
||||
@frame-hover
|
||||
#{@frame-hover})
|
||||
:hover #{(:id @hover) @frame-hover}
|
||||
:edition edition
|
||||
:zoom zoom}])
|
||||
|
||||
@ -311,10 +322,9 @@
|
||||
:zoom zoom}])
|
||||
|
||||
[:& widgets/frame-titles
|
||||
{:objects objects-modified
|
||||
{:objects base-objects
|
||||
:selected selected
|
||||
:zoom zoom
|
||||
:modifiers modifiers
|
||||
:show-artboard-names? show-artboard-names?
|
||||
:on-frame-enter on-frame-enter
|
||||
:on-frame-leave on-frame-leave
|
||||
@ -391,14 +401,6 @@
|
||||
|
||||
[:& widgets/viewport-actions]
|
||||
|
||||
(when show-prototypes?
|
||||
[:& interactions/interactions
|
||||
{:selected selected
|
||||
:zoom zoom
|
||||
:objects objects-modified
|
||||
:current-transform transform
|
||||
:hover-disabled? hover-disabled?}])
|
||||
|
||||
[:& scroll-bars/viewport-scrollbars
|
||||
{:objects base-objects
|
||||
:zoom zoom
|
||||
@ -435,6 +437,12 @@
|
||||
:shapes selected-shapes
|
||||
:zoom zoom
|
||||
:edition edition
|
||||
:disable-handlers (or drawing-tool edition @space?)}]])
|
||||
:disable-handlers (or drawing-tool edition @space?)}]
|
||||
|
||||
]]]))
|
||||
(when show-prototypes?
|
||||
[:& interactions/interactions
|
||||
{:selected selected
|
||||
:zoom zoom
|
||||
:objects objects-modified
|
||||
:current-transform transform
|
||||
:hover-disabled? hover-disabled?}])])]]]))
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.workspace :as dw]
|
||||
@ -50,10 +51,7 @@
|
||||
mod? (kbd/mod? event)
|
||||
|
||||
left-click? (and (not panning) (= 1 (.-which event)))
|
||||
middle-click? (and (not panning) (= 2 (.-which event)))
|
||||
|
||||
frame? (= :frame type)
|
||||
selected? (contains? selected id)]
|
||||
middle-click? (and (not panning) (= 2 (.-which event)))]
|
||||
|
||||
(cond
|
||||
middle-click?
|
||||
@ -96,7 +94,7 @@
|
||||
drawing-tool
|
||||
(st/emit! (dd/start-drawing drawing-tool))
|
||||
|
||||
(or (not id) (and frame? (not selected?)) mod?)
|
||||
(or (not id) mod?)
|
||||
(st/emit! (dw/handle-area-selection shift? mod?))
|
||||
|
||||
(not drawing-tool)
|
||||
@ -156,13 +154,10 @@
|
||||
shift? (kbd/shift? event)
|
||||
alt? (kbd/alt? event)
|
||||
meta? (kbd/meta? event)
|
||||
mod? (kbd/mod? event)
|
||||
hovering? (some? @hover)
|
||||
frame? (= :frame (:type @hover))]
|
||||
hovering? (some? @hover)]
|
||||
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt? meta?))
|
||||
|
||||
(when (and hovering?
|
||||
(or (not frame?) mod?)
|
||||
(not @space?)
|
||||
(not edition)
|
||||
(not drawing-path?)
|
||||
@ -171,6 +166,7 @@
|
||||
|
||||
(defn on-double-click
|
||||
[hover hover-ids drawing-path? objects edition]
|
||||
|
||||
(mf/use-callback
|
||||
(mf/deps @hover @hover-ids drawing-path? edition)
|
||||
(fn [event]
|
||||
@ -180,30 +176,28 @@
|
||||
alt? (kbd/alt? event)
|
||||
meta? (kbd/meta? event)
|
||||
|
||||
{:keys [id type] :as shape} @hover
|
||||
{:keys [id type] :as shape} (or @hover (get objects (first @hover-ids)))
|
||||
|
||||
frame? (= :frame type)
|
||||
group? (= :group type)]
|
||||
editable? (contains? #{:text :rect :path :image :circle} type)]
|
||||
|
||||
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?))
|
||||
|
||||
;; Emit asynchronously so the double click to exit shapes won't break
|
||||
(timers/schedule
|
||||
#(when (and (not drawing-path?) shape)
|
||||
(cond
|
||||
frame?
|
||||
(st/emit! (dw/select-shape id shift?))
|
||||
(fn []
|
||||
(when (and (not drawing-path?) shape)
|
||||
(cond
|
||||
(and editable? (not= id edition))
|
||||
(st/emit! (dw/select-shape id)
|
||||
(dw/start-editing-selected))
|
||||
|
||||
(and group? (> (count @hover-ids) 1))
|
||||
(let [selected (get objects (second @hover-ids))]
|
||||
(reset! hover selected)
|
||||
(reset! hover-ids (into [] (rest @hover-ids)))
|
||||
|
||||
(st/emit! (dw/select-shape (:id selected))))
|
||||
|
||||
(not= id edition)
|
||||
(st/emit! (dw/select-shape id)
|
||||
(dw/start-editing-selected)))))))))
|
||||
:else
|
||||
(let [;; We only get inside childrens of the hovering shape
|
||||
hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id)))
|
||||
selected (get objects (if (> (count hover-ids) 1) (second hover-ids) (first hover-ids)))]
|
||||
(when (some? selected)
|
||||
(reset! hover selected)
|
||||
(st/emit! (dw/select-shape (:id selected)))))))))))))
|
||||
|
||||
(defn on-context-menu
|
||||
[hover hover-ids]
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.refs :as refs]
|
||||
[app.util.geom.grid :as gg]
|
||||
@ -126,13 +127,15 @@
|
||||
(mf/defc frame-grid
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [zoom transform selected focus]}]
|
||||
(let [frames (mf/deref refs/workspace-frames)
|
||||
moving (when (= :move transform) selected)
|
||||
is-moving? #(contains? moving (:id %))]
|
||||
(let [frames (mf/deref refs/workspace-frames)
|
||||
transforming (when (some? transform) selected)
|
||||
is-transform? #(contains? transforming (:id %))]
|
||||
|
||||
[:g.grid-display {:style {:pointer-events "none"}}
|
||||
(for [frame (remove is-moving? frames)]
|
||||
(when (or (empty? focus) (contains? focus (:id frame)))
|
||||
(for [frame frames]
|
||||
(when (and (not (is-transform? frame))
|
||||
(not (cph/rotated-frame? frame))
|
||||
(or (empty? focus) (contains? focus (:id frame))))
|
||||
[:& grid-display-frame {:key (str "grid-" (:id frame))
|
||||
:zoom zoom
|
||||
:frame (gsh/transform-shape frame)}]))]))
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
@ -286,8 +287,9 @@
|
||||
guide-pill-corner-radius (/ guide-pill-corner-radius zoom)]
|
||||
|
||||
(when (or (nil? frame)
|
||||
(is-guide-inside-frame? (assoc guide :position pos) frame)
|
||||
(:hover @state true))
|
||||
(and (is-guide-inside-frame? (assoc guide :position pos) frame)
|
||||
(cph/root-frame? frame)
|
||||
(not (cph/rotated-frame? frame))))
|
||||
[:g.guide-area
|
||||
(when-not disabled-guides?
|
||||
(let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)]
|
||||
|
||||
@ -6,10 +6,10 @@
|
||||
|
||||
(ns app.main.ui.workspace.viewport.hooks
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.path.shortcuts :as psc]
|
||||
@ -112,6 +112,9 @@
|
||||
hover-disabled-ref (mf/use-ref hover-disabled?)
|
||||
focus-ref (mf/use-ref focus)
|
||||
|
||||
last-point-ref (mf/use-var nil)
|
||||
mod-str (mf/use-memo #(rx/subject))
|
||||
|
||||
query-point
|
||||
(mf/use-callback
|
||||
(mf/deps page-id)
|
||||
@ -126,22 +129,22 @@
|
||||
:page-id page-id
|
||||
:rect rect
|
||||
:include-frames? true
|
||||
:clip-children? (not mod?)
|
||||
:reverse? true}))))) ;; we want the topmost shape to be selected first
|
||||
:clip-children? (not mod?)})))))
|
||||
|
||||
over-shapes-stream
|
||||
(mf/use-memo
|
||||
(fn []
|
||||
(rx/merge
|
||||
(->> move-stream
|
||||
;; When transforming shapes we stop querying the worker
|
||||
(rx/filter #(not (some? (mf/ref-val transform-ref))))
|
||||
(rx/merge-map query-point))
|
||||
;; This stream works to "refresh" the outlines when the control is pressed
|
||||
;; but the mouse has not been moved from its position.
|
||||
(->> mod-str
|
||||
(rx/observe-on :async)
|
||||
(rx/map #(deref last-point-ref)))
|
||||
|
||||
(->> move-stream
|
||||
;; When transforming shapes we stop querying the worker
|
||||
(rx/filter #(some? (mf/ref-val transform-ref)))
|
||||
(rx/map (constantly nil))))))]
|
||||
(rx/merge-map query-point)
|
||||
(rx/tap #(reset! last-point-ref %))))))]
|
||||
|
||||
;; Refresh the refs on a value change
|
||||
(mf/use-effect
|
||||
@ -154,7 +157,9 @@
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps @mod?)
|
||||
#(mf/set-ref-val! mod-ref @mod?))
|
||||
(fn []
|
||||
(rx/push! mod-str :update)
|
||||
(mf/set-ref-val! mod-ref @mod?)))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps selected)
|
||||
@ -172,29 +177,39 @@
|
||||
over-shapes-stream
|
||||
(mf/deps page-id objects)
|
||||
(fn [ids]
|
||||
(let [is-group?
|
||||
(fn [id]
|
||||
(contains? #{:group :bool} (get-in objects [id :type])))
|
||||
|
||||
selected (mf/ref-val selected-ref)
|
||||
(let [selected (mf/ref-val selected-ref)
|
||||
focus (mf/ref-val focus-ref)
|
||||
|
||||
mod? (mf/ref-val mod-ref)
|
||||
|
||||
remove-xfm (mapcat #(cph/get-parent-ids objects %))
|
||||
remove-id? (cond-> (into #{} remove-xfm selected)
|
||||
(not mod?)
|
||||
(into (filter #(group-empty-space? % objects ids)) ids)
|
||||
ids (into
|
||||
(d/ordered-set)
|
||||
(cph/sort-z-index objects ids {:bottom-frames? mod?}))
|
||||
|
||||
mod?
|
||||
(into (filter is-group?) ids))
|
||||
grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type])))
|
||||
|
||||
hover-shape (->> ids
|
||||
(filter (comp not remove-id?))
|
||||
(filter #(or (empty? focus)
|
||||
(cp/is-in-focus? objects focus %)))
|
||||
(first)
|
||||
(get objects))]
|
||||
|
||||
selected-with-parents
|
||||
(into #{} (mapcat #(cph/get-parent-ids objects %)) selected)
|
||||
|
||||
root-frame-with-data? #(and (cph/root-frame? objects %) (d/not-empty? (get-in objects [% :shapes])))
|
||||
|
||||
;; Set with the elements to remove from the hover list
|
||||
remove-id?
|
||||
(cond-> selected-with-parents
|
||||
(not mod?)
|
||||
(into (filter #(or (root-frame-with-data? %)
|
||||
(group-empty-space? % objects ids)))
|
||||
ids)
|
||||
|
||||
mod?
|
||||
(into (filter grouped?) ids))
|
||||
|
||||
hover-shape
|
||||
(->> ids
|
||||
(remove remove-id?)
|
||||
(filter #(or (empty? focus) (cp/is-in-focus? objects focus %)))
|
||||
(first)
|
||||
(get objects))]
|
||||
(reset! hover hover-shape)
|
||||
(reset! hover-ids ids))))))
|
||||
|
||||
@ -203,13 +218,7 @@
|
||||
(let [root-frame-ids
|
||||
(mf/use-memo
|
||||
(mf/deps objects)
|
||||
(fn []
|
||||
(let [frame? (into #{} (cph/get-frames-ids objects))
|
||||
;; Removes from zero/shapes attribute all the frames so we can ask only for
|
||||
;; the non-frame children
|
||||
objects (-> objects
|
||||
(update-in [uuid/zero :shapes] #(filterv (comp not frame?) %)))]
|
||||
(cph/get-children-ids objects uuid/zero))))
|
||||
#(cph/get-root-shapes-ids objects))
|
||||
modifiers (select-keys modifiers root-frame-ids)]
|
||||
(sfd/use-dynamic-modifiers objects globals/document modifiers)))
|
||||
|
||||
@ -221,20 +230,19 @@
|
||||
[objects hover-ids selected active-frames zoom transform vbox]
|
||||
|
||||
(let [frame? #(= :frame (get-in objects [% :type]))
|
||||
all-frames (mf/use-memo (mf/deps objects) #(cph/get-frames-ids objects))
|
||||
all-frames (mf/use-memo (mf/deps objects) #(cph/get-root-frames-ids objects))
|
||||
selected-frames (mf/use-memo (mf/deps selected) #(->> all-frames (filter selected)))
|
||||
xf-selected-frame (comp (remove frame?) (map #(get-in objects [% :frame-id])))
|
||||
selected-shapes-frames (mf/use-memo (mf/deps selected) #(into #{} xf-selected-frame selected))
|
||||
|
||||
active-selection (when (and (not= transform :move) (= (count selected-frames) 1)) (first selected-frames))
|
||||
hover-frame (last @hover-ids)
|
||||
last-hover-frame (mf/use-var nil)]
|
||||
last-hover-ids (mf/use-var nil)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps hover-frame)
|
||||
(mf/deps @hover-ids)
|
||||
(fn []
|
||||
(when (some? hover-frame)
|
||||
(reset! last-hover-frame hover-frame))))
|
||||
(when (d/not-empty? @hover-ids)
|
||||
(reset! last-hover-ids (set @hover-ids)))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps objects @hover-ids selected zoom transform vbox)
|
||||
@ -247,7 +255,9 @@
|
||||
;; - If no hovering over any frames we keep the previous active one
|
||||
;; - Check always that the active frames are inside the vbox
|
||||
|
||||
(let [is-active-frame?
|
||||
(let [hover-ids? (set @hover-ids)
|
||||
|
||||
is-active-frame?
|
||||
(fn [id]
|
||||
(or
|
||||
;; Zoom > 130% shows every frame
|
||||
@ -256,7 +266,7 @@
|
||||
;; Zoom >= 25% will show frames hovering
|
||||
(and
|
||||
(>= zoom 0.25)
|
||||
(or (= id hover-frame) (= id @last-hover-frame)))
|
||||
(or (contains? hover-ids? id) (contains? @last-hover-ids id)))
|
||||
|
||||
;; Otherwise, if it's a selected frame
|
||||
(= id active-selection)
|
||||
|
||||
@ -280,7 +280,7 @@
|
||||
selected? (contains? selected (:id shape))
|
||||
level (calc-level index (:interactions shape))]
|
||||
(when-not selected?
|
||||
[:& interaction-path {:key (dm/str (:id shape) "-" index)
|
||||
[:& interaction-path {:key (dm/str "non-selected-" (:id shape) "-" index)
|
||||
:index index
|
||||
:level level
|
||||
:orig-shape shape
|
||||
@ -307,35 +307,32 @@
|
||||
(let [dest-shape (when (cti/destination? interaction)
|
||||
(get objects (:destination interaction)))
|
||||
level (calc-level index (:interactions shape))]
|
||||
[:*
|
||||
[:& interaction-path {:key (dm/str (:id shape) "-" index)
|
||||
:index index
|
||||
:level level
|
||||
:orig-shape shape
|
||||
:dest-shape dest-shape
|
||||
:selected selected
|
||||
:selected? true
|
||||
:action-type (:action-type interaction)
|
||||
:zoom zoom}]
|
||||
(when (and (or (= (:action-type interaction) :open-overlay)
|
||||
(= (:action-type interaction) :toggle-overlay))
|
||||
(= (:overlay-pos-type interaction) :manual))
|
||||
(if (and (some? move-overlay-to)
|
||||
(= move-overlay-index index))
|
||||
[:& overlay-marker {:key (dm/str "pos" (:id shape) "-" index)
|
||||
:index index
|
||||
:orig-shape shape
|
||||
:dest-shape dest-shape
|
||||
:position move-overlay-to
|
||||
:objects objects
|
||||
:hover-disabled? hover-disabled?}]
|
||||
[:& overlay-marker {:key (dm/str "pos" (:id shape) "-" index)
|
||||
:index index
|
||||
:orig-shape shape
|
||||
:dest-shape dest-shape
|
||||
:position (:overlay-position interaction)
|
||||
:objects objects
|
||||
:hover-disabled? hover-disabled?}]))])))
|
||||
[:g {:key (dm/str "interaction-path-" (:id shape) "-" index)}
|
||||
[:& interaction-path {:index index
|
||||
:level level
|
||||
:orig-shape shape
|
||||
:dest-shape dest-shape
|
||||
:selected selected
|
||||
:selected? true
|
||||
:action-type (:action-type interaction)
|
||||
:zoom zoom}]
|
||||
(when (and (or (= (:action-type interaction) :open-overlay)
|
||||
(= (:action-type interaction) :toggle-overlay))
|
||||
(= (:overlay-pos-type interaction) :manual))
|
||||
(if (and (some? move-overlay-to)
|
||||
(= move-overlay-index index))
|
||||
[:& overlay-marker {:index index
|
||||
:orig-shape shape
|
||||
:dest-shape dest-shape
|
||||
:position move-overlay-to
|
||||
:objects objects
|
||||
:hover-disabled? hover-disabled?}]
|
||||
[:& overlay-marker {:index index
|
||||
:orig-shape shape
|
||||
:dest-shape dest-shape
|
||||
:position (:overlay-position interaction)
|
||||
:objects objects
|
||||
:hover-disabled? hover-disabled?}]))])))
|
||||
(when (and shape
|
||||
(not (cph/unframed-shape? shape))
|
||||
(not (#{:move :rotate} current-transform)))
|
||||
|
||||
@ -8,8 +8,6 @@
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.refs :as refs]
|
||||
[app.util.object :as obj]
|
||||
[app.util.path.format :as upf]
|
||||
[clojure.set :as set]
|
||||
@ -23,7 +21,7 @@
|
||||
zoom (obj/get props "zoom" 1)
|
||||
|
||||
color (unchecked-get props "color")
|
||||
transform (gsh/transform-matrix shape)
|
||||
transform (gsh/transform-str shape)
|
||||
path? (= :path (:type shape))
|
||||
path-data
|
||||
(mf/use-memo
|
||||
@ -41,7 +39,7 @@
|
||||
|
||||
common {:fill "none"
|
||||
:stroke color
|
||||
:strokeWidth (/ 1 zoom)
|
||||
:strokeWidth (/ 2 zoom)
|
||||
:pointerEvents "none"
|
||||
:transform transform}
|
||||
|
||||
@ -82,15 +80,12 @@
|
||||
[props]
|
||||
(let [selected (or (obj/get props "selected") #{})
|
||||
hover (or (obj/get props "hover") #{})
|
||||
|
||||
objects (obj/get props "objects")
|
||||
edition (obj/get props "edition")
|
||||
zoom (obj/get props "zoom")
|
||||
|
||||
transform (mf/deref refs/current-transform)
|
||||
|
||||
outlines-ids (->> (set/union selected hover)
|
||||
(cph/clean-loops objects))
|
||||
|
||||
outlines-ids (set/union selected hover)
|
||||
show-outline? (fn [shape] (and (not (:hidden shape))
|
||||
(not (:blocked shape))))
|
||||
|
||||
@ -100,6 +95,6 @@
|
||||
(filterv show-outline?)
|
||||
(filter some?))]
|
||||
|
||||
[:g.outlines {:display (when (some? transform) "none")}
|
||||
[:g.outlines
|
||||
[:& shape-outlines-render {:shapes shapes
|
||||
:zoom zoom}]]))
|
||||
|
||||
@ -170,7 +170,7 @@
|
||||
:height size
|
||||
:fill (if (debug? :handlers) "blue" "none")
|
||||
:stroke-width 0
|
||||
:transform (str transform)
|
||||
:transform (dm/str transform)
|
||||
:on-mouse-down on-rotate}]))
|
||||
|
||||
(mf/defc resize-point-handler
|
||||
@ -224,7 +224,7 @@
|
||||
height (/ resize-side-height zoom)
|
||||
offset-y (if (= align :outside) (- height) (- (/ height 2)))
|
||||
target-y (+ y offset-y)
|
||||
transform-str (str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))]
|
||||
transform-str (dm/str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))]
|
||||
[:g.resize-handler
|
||||
(when show-handler?
|
||||
[:circle {:r (/ resize-point-radius zoom)
|
||||
@ -271,13 +271,13 @@
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
|
||||
selrect (:selrect shape)
|
||||
transform (gsh/transform-matrix shape {:no-flip true})]
|
||||
transform (gsh/transform-str shape {:no-flip true})]
|
||||
|
||||
(when (not (#{:move :rotate} current-transform))
|
||||
[:g.controls {:pointer-events (if disable-handlers "none" "visible")}
|
||||
;; Selection rect
|
||||
[:& selection-rect {:rect selrect
|
||||
:transform (str transform)
|
||||
:transform transform
|
||||
:zoom zoom
|
||||
:color color
|
||||
:on-move-selected on-move-selected
|
||||
@ -316,7 +316,7 @@
|
||||
:color color}
|
||||
props (map->obj (merge common-props props))]
|
||||
(case type
|
||||
:rotation (when (not= :frame (:type shape)) [:> rotation-handler props])
|
||||
:rotation [:> rotation-handler props]
|
||||
:resize-point [:> resize-point-handler props]
|
||||
:resize-side [:> resize-side-handler props])))])))
|
||||
|
||||
@ -327,7 +327,7 @@
|
||||
(let [{:keys [x y width height]} shape]
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y
|
||||
:transform (str (gsh/transform-matrix shape))
|
||||
:transform (gsh/transform-str shape)
|
||||
:width width
|
||||
:height height
|
||||
:pointer-events "visible"
|
||||
|
||||
@ -6,10 +6,12 @@
|
||||
|
||||
(ns app.main.ui.workspace.viewport.widgets
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
[app.main.refs :as refs]
|
||||
@ -92,10 +94,12 @@
|
||||
|
||||
(mf/defc frame-title
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [frame modifiers selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}]
|
||||
(let [{:keys [width x y]} (gsh/transform-shape frame)
|
||||
[{:keys [frame selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}]
|
||||
(let [{:keys [width x y]} frame
|
||||
label-pos (gpt/point x (- y (/ 10 zoom)))
|
||||
|
||||
frame-transform (gsh/transform-str frame)
|
||||
|
||||
on-mouse-down
|
||||
(mf/use-callback
|
||||
(mf/deps (:id frame) on-frame-select)
|
||||
@ -136,11 +140,10 @@
|
||||
text-pos-x (if (:use-for-thumbnail? frame) 15 0)]
|
||||
|
||||
(when (not (:hidden frame))
|
||||
[:*
|
||||
[:g {:id (dm/str "frame-title-" (:id frame))
|
||||
}
|
||||
(when (:use-for-thumbnail? frame)
|
||||
[:g {:transform (str (when (and selected? modifiers)
|
||||
(str (:displacement modifiers) " "))
|
||||
(text-transform label-pos zoom))}
|
||||
[:g {:transform (dm/str frame-transform " " (text-transform label-pos zoom))}
|
||||
[:svg {:x 0
|
||||
:y -9
|
||||
:width 12
|
||||
@ -154,9 +157,7 @@
|
||||
:width width
|
||||
:height 20
|
||||
:class "workspace-frame-label"
|
||||
:transform (str (when (and selected? modifiers)
|
||||
(str (:displacement modifiers) " "))
|
||||
(text-transform label-pos zoom))
|
||||
:transform (dm/str frame-transform " " (text-transform label-pos zoom))
|
||||
:style {:fill (when selected? "var(--color-primary-dark)")}
|
||||
:visibility (if show-artboard-names? "visible" "hidden")
|
||||
:on-mouse-down on-mouse-down
|
||||
@ -182,15 +183,16 @@
|
||||
|
||||
[:g.frame-titles
|
||||
(for [frame frames]
|
||||
[:& frame-title {:key (dm/str "frame-title-" (:id frame))
|
||||
:frame frame
|
||||
:selected? (contains? selected (:id frame))
|
||||
:zoom zoom
|
||||
:show-artboard-names? show-artboard-names?
|
||||
:modifiers modifiers
|
||||
:on-frame-enter on-frame-enter
|
||||
:on-frame-leave on-frame-leave
|
||||
:on-frame-select on-frame-select}])]))
|
||||
(when (= (:frame-id frame) uuid/zero)
|
||||
[:& frame-title {:key (dm/str "frame-title-" (:id frame))
|
||||
:frame frame
|
||||
:selected? (contains? selected (:id frame))
|
||||
:zoom zoom
|
||||
:show-artboard-names? show-artboard-names?
|
||||
:modifiers modifiers
|
||||
:on-frame-enter on-frame-enter
|
||||
:on-frame-leave on-frame-leave
|
||||
:on-frame-select on-frame-select}]))]))
|
||||
|
||||
(mf/defc frame-flow
|
||||
[{:keys [flow frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
|
||||
@ -252,9 +254,10 @@
|
||||
on-frame-leave (unchecked-get props "on-frame-leave")
|
||||
on-frame-select (unchecked-get props "on-frame-select")]
|
||||
[:g.frame-flows
|
||||
(for [flow flows]
|
||||
(for [[index flow] (d/enumerate flows)]
|
||||
(let [frame (get objects (:starting-frame flow))]
|
||||
[:& frame-flow {:flow flow
|
||||
[:& frame-flow {:key (dm/str (:id frame) "-" index)
|
||||
:flow flow
|
||||
:frame frame
|
||||
:selected? (contains? selected (:id frame))
|
||||
:zoom zoom
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
(ns app.util.geom.snap-points
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]))
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]))
|
||||
|
||||
(defn selrect-snap-points [{:keys [x y width height] :as selrect}]
|
||||
#{(gpt/point x y)
|
||||
@ -29,11 +30,20 @@
|
||||
(when (and (not blocked) (not hidden))
|
||||
(let [shape (gsh/transform-shape shape)]
|
||||
(case (:type shape)
|
||||
:frame (-> shape :selrect frame-snap-points)
|
||||
:frame (-> shape :points gsh/points->selrect frame-snap-points)
|
||||
(into #{(gsh/center-shape shape)} (:points shape))))))
|
||||
|
||||
(defn guide-snap-points
|
||||
[guide]
|
||||
(if (= :x (:axis guide))
|
||||
[guide frame]
|
||||
|
||||
(cond
|
||||
(and (some? frame)
|
||||
(not (cph/rotated-frame? frame))
|
||||
(not (cph/root-frame? frame)))
|
||||
#{}
|
||||
|
||||
(= :x (:axis guide))
|
||||
#{(gpt/point (:position guide) 0)}
|
||||
|
||||
:else
|
||||
#{(gpt/point 0 (:position guide))}))
|
||||
|
||||
@ -789,10 +789,14 @@
|
||||
:content node-content}))))
|
||||
|
||||
(defn add-frame-data [props node]
|
||||
(let [grids (parse-grids node)]
|
||||
(cond-> props
|
||||
(d/not-empty? grids)
|
||||
(assoc :grids grids))))
|
||||
(let [grids (parse-grids node)
|
||||
show-content (get-meta node :show-content str->bool)
|
||||
hide-in-viewer (get-meta node :hide-in-viewer str->bool)]
|
||||
(-> props
|
||||
(assoc :show-content show-content)
|
||||
(assoc :hide-in-viewer hide-in-viewer)
|
||||
(cond-> (d/not-empty? grids)
|
||||
(assoc :grids grids)))))
|
||||
|
||||
(defn has-image?
|
||||
[node]
|
||||
|
||||
@ -55,16 +55,18 @@
|
||||
|
||||
(defn get-grids-snap-points
|
||||
[frame coord]
|
||||
(let [grid->snap (fn [[grid-type position]]
|
||||
{:type :layout
|
||||
:id (:id frame)
|
||||
:grid grid-type
|
||||
:pt position})]
|
||||
(->> (:grids frame)
|
||||
(mapcat (fn [grid]
|
||||
(->> (gg/grid-snap-points frame grid coord)
|
||||
(mapv #(vector (:type grid) %)))))
|
||||
(mapv grid->snap))))
|
||||
(if (not (cph/rotated-frame? frame))
|
||||
[]
|
||||
(let [grid->snap (fn [[grid-type position]]
|
||||
{:type :layout
|
||||
:id (:id frame)
|
||||
:grid grid-type
|
||||
:pt position})]
|
||||
(->> (:grids frame)
|
||||
(mapcat (fn [grid]
|
||||
(->> (gg/grid-snap-points frame grid coord)
|
||||
(mapv #(vector (:type grid) %)))))
|
||||
(mapv grid->snap)))))
|
||||
|
||||
(defn- add-frame
|
||||
[page-data frame]
|
||||
@ -105,9 +107,10 @@
|
||||
|
||||
|
||||
(defn- add-guide
|
||||
[page-data guide]
|
||||
[objects page-data guide]
|
||||
|
||||
(let [guide-data (->> (snap/guide-snap-points guide)
|
||||
(let [frame (get objects (:frame-id guide))
|
||||
guide-data (->> (snap/guide-snap-points guide frame)
|
||||
(mapv #(array-map
|
||||
:type :guide
|
||||
:id (:id guide)
|
||||
@ -178,10 +181,10 @@
|
||||
(add-shape new-shape)))
|
||||
|
||||
(defn- update-guide
|
||||
[page-data [old-guide new-guide]]
|
||||
(-> page-data
|
||||
(remove-guide old-guide)
|
||||
(add-guide new-guide)))
|
||||
[objects page-data [old-guide new-guide]]
|
||||
(as-> page-data $
|
||||
(remove-guide $ old-guide)
|
||||
(add-guide objects $ new-guide)))
|
||||
|
||||
;; PUBLIC API
|
||||
|
||||
@ -203,7 +206,7 @@
|
||||
(add-root-frame $)
|
||||
(reduce add-frame $ frames)
|
||||
(reduce add-shape $ shapes)
|
||||
(reduce add-guide $ guides))]
|
||||
(reduce (partial add-guide objects) $ guides))]
|
||||
(assoc snap-data (:id page) page-data)))
|
||||
|
||||
(defn update-page
|
||||
@ -214,7 +217,8 @@
|
||||
;; Update page
|
||||
(update snap-data (:id page)
|
||||
(fn [page-data]
|
||||
(let [{:keys [change-frame-shapes
|
||||
(let [{:keys [objects]} page
|
||||
{:keys [change-frame-shapes
|
||||
change-frame-guides
|
||||
removed-frames
|
||||
removed-shapes
|
||||
@ -235,10 +239,12 @@
|
||||
(reduce update-shape $ updated-shapes)
|
||||
(reduce add-frame $ new-frames)
|
||||
(reduce add-shape $ new-shapes)
|
||||
(reduce update-guide $ change-frame-guides)
|
||||
(reduce remove-guide $ removed-guides)
|
||||
(reduce update-guide $ updated-guides)
|
||||
(reduce add-guide $ new-guides)))))
|
||||
|
||||
;; Guides functions. Need objects to get its frame data
|
||||
(reduce (partial update-guide objects) $ change-frame-guides)
|
||||
(reduce (partial update-guide objects) $ updated-guides)
|
||||
(reduce (partial add-guide objects) $ new-guides)))))
|
||||
|
||||
;; Page doesn't exist, we create a new entry
|
||||
(add-page snap-data page)))
|
||||
|
||||
@ -84,14 +84,12 @@
|
||||
index-shape (make-index-shape objects parents-index clip-parents-index)
|
||||
initial-quadtree (qdt/create (clj->js bounds))
|
||||
|
||||
index (reduce index-shape initial-quadtree shapes)
|
||||
index (reduce index-shape initial-quadtree shapes)]
|
||||
|
||||
z-index (cp/calculate-z-index objects)]
|
||||
|
||||
{:index index :z-index z-index :bounds bounds}))
|
||||
{:index index :bounds bounds}))
|
||||
|
||||
(defn- update-index
|
||||
[{index :index z-index :z-index :as data} old-objects new-objects]
|
||||
[{index :index :as data} old-objects new-objects]
|
||||
(let [changes? (fn [id]
|
||||
(not= (get old-objects id)
|
||||
(get new-objects id)))
|
||||
@ -110,14 +108,12 @@
|
||||
new-index (qdt/remove-all index changed-ids)
|
||||
|
||||
index-shape (make-index-shape new-objects parents-index clip-parents-index)
|
||||
index (reduce index-shape new-index shapes)
|
||||
index (reduce index-shape new-index shapes)]
|
||||
|
||||
z-index (cp/update-z-index z-index changed-ids old-objects new-objects)]
|
||||
|
||||
(assoc data :index index :z-index z-index)))
|
||||
(assoc data :index index)))
|
||||
|
||||
(defn- query-index
|
||||
[{index :index z-index :z-index} rect frame-id full-frame? include-frames? ignore-groups? clip-children? reverse?]
|
||||
[{index :index} rect frame-id full-frame? include-frames? ignore-groups? clip-children?]
|
||||
(let [result (-> (qdt/search index (clj->js rect))
|
||||
(es6-iterator-seq))
|
||||
|
||||
@ -143,32 +139,18 @@
|
||||
|
||||
overlaps-parent?
|
||||
(fn [clip-parents]
|
||||
(->> clip-parents (some (comp not overlaps?)) not))
|
||||
|
||||
add-z-index
|
||||
(fn [{:keys [id frame-id] :as shape}]
|
||||
(assoc shape :z (+ (get z-index id)
|
||||
(get z-index frame-id 0))))
|
||||
|
||||
;; Shapes after filters of overlapping and criteria
|
||||
matching-shapes
|
||||
(into []
|
||||
(comp (map #(unchecked-get % "data"))
|
||||
(filter match-criteria?)
|
||||
(filter overlaps?)
|
||||
(filter (comp overlaps? :frame))
|
||||
(filter (if clip-children?
|
||||
(comp overlaps-parent? :clip-parents)
|
||||
(constantly true)))
|
||||
(map add-z-index))
|
||||
result)
|
||||
|
||||
keyfn (if reverse? (comp - :z) :z)]
|
||||
(->> clip-parents (some (comp not overlaps?)) not))]
|
||||
|
||||
;; Shapes after filters of overlapping and criteria
|
||||
(into (d/ordered-set)
|
||||
(->> matching-shapes
|
||||
(sort-by keyfn)
|
||||
(map :id)))))
|
||||
(comp (map #(unchecked-get % "data"))
|
||||
(filter match-criteria?)
|
||||
(filter overlaps?)
|
||||
(filter (if clip-children?
|
||||
(comp overlaps-parent? :clip-parents)
|
||||
(constantly true)))
|
||||
(map :id))
|
||||
result)))
|
||||
|
||||
|
||||
(defmethod impl/handler :selection/initialize-index
|
||||
@ -203,13 +185,8 @@
|
||||
nil)
|
||||
|
||||
(defmethod impl/handler :selection/query
|
||||
[{:keys [page-id rect frame-id reverse? full-frame? include-frames? ignore-groups? clip-children?]
|
||||
:or {reverse? false full-frame? false include-frames? false clip-children? true} :as message}]
|
||||
[{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children?]
|
||||
:or {full-frame? false include-frames? false clip-children? true} :as message}]
|
||||
(when-let [index (get @state page-id)]
|
||||
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? reverse?)))
|
||||
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children?)))
|
||||
|
||||
(defmethod impl/handler :selection/query-z-index
|
||||
[{:keys [page-id objects ids]}]
|
||||
(when-let [{z-index :z-index} (get @state page-id)]
|
||||
(->> ids (map #(+ (get z-index %)
|
||||
(get z-index (get-in objects [% :frame-id])))))))
|
||||
|
||||
@ -257,7 +257,7 @@ msgstr ""
|
||||
"[Libraries & templates](https://penpot.app/libraries-templates.html)"
|
||||
|
||||
msgid "dashboard.export-frames"
|
||||
msgstr "Export artboards to PDF..."
|
||||
msgstr "Export boards to PDF..."
|
||||
|
||||
#: src/app/main/ui/export.cljs
|
||||
msgid "dashboard.export-frames.title"
|
||||
@ -976,7 +976,7 @@ msgid "handoff.tabs.code.selected.curve"
|
||||
msgstr "Curve"
|
||||
|
||||
msgid "handoff.tabs.code.selected.frame"
|
||||
msgstr "Artboard"
|
||||
msgstr "Board"
|
||||
|
||||
msgid "handoff.tabs.code.selected.group"
|
||||
msgstr "Group"
|
||||
@ -1277,8 +1277,8 @@ msgstr[1] "%s files"
|
||||
|
||||
msgid "labels.num-of-frames"
|
||||
msgid_plural "labels.num-of-frames"
|
||||
msgstr[0] "1 artboard"
|
||||
msgstr[1] "%s artboards"
|
||||
msgstr[0] "1 board"
|
||||
msgstr[1] "%s boards"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
msgid "labels.num-of-projects"
|
||||
@ -2021,7 +2021,7 @@ msgid "shortcuts.align-vcenter"
|
||||
msgstr "Align center vertically"
|
||||
|
||||
msgid "shortcuts.artboard-selection"
|
||||
msgstr "Create artboard from selection"
|
||||
msgstr "Create board from selection"
|
||||
|
||||
msgid "shortcuts.bool-difference"
|
||||
msgstr "Boolean difference"
|
||||
@ -2081,7 +2081,7 @@ msgid "shortcuts.draw-ellipse"
|
||||
msgstr "Ellipse"
|
||||
|
||||
msgid "shortcuts.draw-frame"
|
||||
msgstr "Artboard"
|
||||
msgstr "Board"
|
||||
|
||||
msgid "shortcuts.draw-nodes"
|
||||
msgstr "Draw path"
|
||||
@ -2183,7 +2183,7 @@ msgid "shortcuts.move-unit-up"
|
||||
msgstr "Move up"
|
||||
|
||||
msgid "shortcuts.next-frame"
|
||||
msgstr "Next artboard"
|
||||
msgstr "Next board"
|
||||
|
||||
msgid "shortcuts.opacity-0"
|
||||
msgstr "Set opacity to 100%"
|
||||
@ -2240,7 +2240,7 @@ msgid "shortcuts.paste"
|
||||
msgstr "Paste"
|
||||
|
||||
msgid "shortcuts.prev-frame"
|
||||
msgstr "Previous artboard"
|
||||
msgstr "Previous board"
|
||||
|
||||
msgid "shortcuts.redo"
|
||||
msgstr "Redo"
|
||||
@ -2425,11 +2425,11 @@ msgstr "Sorry!"
|
||||
|
||||
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
|
||||
msgid "viewer.empty-state"
|
||||
msgstr "No artboards found on the page."
|
||||
msgstr "No boards found on the page."
|
||||
|
||||
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
|
||||
msgid "viewer.frame-not-found"
|
||||
msgstr "Artboard not found."
|
||||
msgstr "Board not found."
|
||||
|
||||
msgid "viewer.header.comments-section"
|
||||
msgstr "Comments (%s)"
|
||||
@ -2701,7 +2701,7 @@ msgstr "Enable snap to pixel"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs
|
||||
msgid "workspace.header.menu.hide-artboard-names"
|
||||
msgstr "Hide artboard names"
|
||||
msgstr "Hide board names"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs
|
||||
msgid "workspace.header.menu.hide-grid"
|
||||
@ -2748,7 +2748,7 @@ msgstr "Select all"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs
|
||||
msgid "workspace.header.menu.show-artboard-names"
|
||||
msgstr "Show artboards names"
|
||||
msgstr "Show boards names"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs
|
||||
msgid "workspace.header.menu.show-grid"
|
||||
@ -3629,11 +3629,11 @@ msgstr "Search font"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
|
||||
msgid "workspace.options.select-a-shape"
|
||||
msgstr "Select a shape, artboard or group to drag a connection to other artboard."
|
||||
msgstr "Select a shape, board or group to drag a connection to other board."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
|
||||
msgid "workspace.options.select-artboard"
|
||||
msgstr "Select artboard"
|
||||
msgstr "Select board"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.selection-color"
|
||||
@ -3890,6 +3890,12 @@ msgstr "X"
|
||||
msgid "workspace.options.y"
|
||||
msgstr "Y"
|
||||
|
||||
msgid "workspace.options.clip-content"
|
||||
msgstr "Clip content"
|
||||
|
||||
msgid"workspace.options.show-in-viewer"
|
||||
msgstr "Show in view mode"
|
||||
|
||||
msgid "workspace.path.actions.add-node"
|
||||
msgstr "Add node (%s)"
|
||||
|
||||
@ -3934,7 +3940,7 @@ msgstr "Copy"
|
||||
|
||||
#: src/app/main/ui/workspace/context_menu.cljs
|
||||
msgid "workspace.shape.menu.create-artboard-from-selection"
|
||||
msgstr "Selection to artboard"
|
||||
msgstr "Selection to board"
|
||||
|
||||
#: src/app/main/ui/workspace/context_menu.cljs
|
||||
msgid "workspace.shape.menu.create-component"
|
||||
@ -4090,7 +4096,7 @@ msgid "workspace.sidebar.layers.components"
|
||||
msgstr "Components"
|
||||
|
||||
msgid "workspace.sidebar.layers.frames"
|
||||
msgstr "Artboards"
|
||||
msgstr "Boards"
|
||||
|
||||
msgid "workspace.sidebar.layers.groups"
|
||||
msgstr "Groups"
|
||||
@ -4144,7 +4150,7 @@ msgstr "Ellipse (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||
msgid "workspace.toolbar.frame"
|
||||
msgstr "Artboard (%s)"
|
||||
msgstr "Board (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||
msgid "workspace.toolbar.image"
|
||||
@ -4203,7 +4209,7 @@ msgid "workspace.undo.entry.multiple.curve"
|
||||
msgstr "curves"
|
||||
|
||||
msgid "workspace.undo.entry.multiple.frame"
|
||||
msgstr "artboard"
|
||||
msgstr "board"
|
||||
|
||||
msgid "workspace.undo.entry.multiple.group"
|
||||
msgstr "groups"
|
||||
@ -4249,7 +4255,7 @@ msgid "workspace.undo.entry.single.curve"
|
||||
msgstr "curve"
|
||||
|
||||
msgid "workspace.undo.entry.single.frame"
|
||||
msgstr "artboard"
|
||||
msgstr "board"
|
||||
|
||||
msgid "workspace.undo.entry.single.group"
|
||||
msgstr "group"
|
||||
@ -4311,4 +4317,4 @@ msgid "shortcuts.or"
|
||||
msgstr " or "
|
||||
|
||||
msgid "shortcuts.not-found"
|
||||
msgstr "No shortcuts found"
|
||||
msgstr "No shortcuts found"
|
||||
|
||||
@ -4058,6 +4058,12 @@ msgstr "X"
|
||||
msgid "workspace.options.y"
|
||||
msgstr "Y"
|
||||
|
||||
msgid "workspace.options.clip-content"
|
||||
msgstr "Truncar contenido"
|
||||
|
||||
msgid"workspace.options.show-in-viewer"
|
||||
msgstr "Mostrar en modo visualización"
|
||||
|
||||
msgid "workspace.path.actions.add-node"
|
||||
msgstr "Añadir nodo (%s)"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user