mirror of
https://github.com/penpot/penpot.git
synced 2025-06-06 09:21:39 +02:00
commit
0f5ce3b836
24 changed files with 1133 additions and 235 deletions
|
@ -22,12 +22,13 @@
|
|||
[app.config :as cfg]
|
||||
[app.main.constants :as c]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.notifications :as dwn]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.texts :as dwtxt]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.data.colors :as dwl]
|
||||
[app.main.data.colors :as mdc]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
|
@ -47,10 +48,6 @@
|
|||
(s/def ::set-of-string
|
||||
(s/every string? :kind set?))
|
||||
|
||||
;; --- Expose inner functions
|
||||
|
||||
(defn interrupt? [e] (= e :interrupt))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Workspace Initialization
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -949,7 +946,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> stream
|
||||
(rx/filter interrupt?)
|
||||
(rx/filter dwc/interrupt?)
|
||||
(rx/take 1)
|
||||
(rx/map (constantly clear-edition-mode))))))
|
||||
|
||||
|
@ -978,7 +975,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [cancel-event? (fn [event]
|
||||
(interrupt? event))
|
||||
(dwc/interrupt? event))
|
||||
stoper (rx/filter (ptk/type? ::clear-drawing) stream)]
|
||||
(->> (rx/filter cancel-event? stream)
|
||||
(rx/take 1)
|
||||
|
@ -1127,8 +1124,14 @@
|
|||
(ptk/reify ::show-context-menu
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [mdata {:position position
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-id (cph/get-root-component (:id shape) objects)
|
||||
root-shape (get objects root-id)
|
||||
|
||||
mdata {:position position
|
||||
:shape shape
|
||||
:root-shape root-shape
|
||||
:selected (get-in state [:workspace-local :selected])}]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :context-menu] mdata))))
|
||||
|
@ -1260,70 +1263,19 @@
|
|||
;; GROUPS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn group-shape
|
||||
[id frame-id selected selection-rect]
|
||||
{:id id
|
||||
:type :group
|
||||
:name (name (gensym "Group-"))
|
||||
:shapes []
|
||||
:frame-id frame-id
|
||||
:x (:x selection-rect)
|
||||
:y (:y selection-rect)
|
||||
:width (:width selection-rect)
|
||||
:height (:height selection-rect)})
|
||||
|
||||
(def group-selected
|
||||
(ptk/reify ::group-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (uuid/next)
|
||||
page-id (:current-page-id state)
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
items (->> selected
|
||||
(map #(get objects %))
|
||||
(filter #(not= :frame (:type %)))
|
||||
(map #(assoc % ::index (cph/position-on-parent (:id %) objects)))
|
||||
(sort-by ::index))]
|
||||
|
||||
(when (not-empty items)
|
||||
(let [selrect (geom/selection-rect items)
|
||||
frame-id (-> items first :frame-id)
|
||||
parent-id (-> items first :parent-id)
|
||||
group (-> (group-shape id frame-id selected selrect)
|
||||
(geom/setup selrect))
|
||||
|
||||
index (::index (first items))
|
||||
|
||||
rchanges [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:obj group
|
||||
:index index}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id id
|
||||
:shapes (->> items
|
||||
(map :id)
|
||||
(into #{})
|
||||
(vec))}]
|
||||
|
||||
uchanges
|
||||
(reduce (fn [res obj]
|
||||
(conj res {:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:parent-id obj)
|
||||
:index (::index obj)
|
||||
:shapes [(:id obj)]}))
|
||||
[]
|
||||
items)
|
||||
|
||||
uchanges (conj uchanges {:type :del-obj :id id :page-id page-id})]
|
||||
|
||||
shapes (dws/shapes-for-grouping objects selected)]
|
||||
(when-not (empty? shapes)
|
||||
(let [[group rchanges uchanges]
|
||||
(dws/prepare-create-group page-id shapes "Group-" false)]
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set id)))))))))
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
(def ungroup-selected
|
||||
(ptk/reify ::ungroup-selected
|
||||
|
@ -1336,34 +1288,11 @@
|
|||
group (get objects group-id)]
|
||||
(when (and (= 1 (count selected))
|
||||
(= (:type group) :group))
|
||||
(let [shapes (:shapes group)
|
||||
parent-id (cph/get-parent group-id objects)
|
||||
parent (get objects parent-id)
|
||||
index-in-parent (->> (:shapes parent)
|
||||
(map-indexed vector)
|
||||
(filter #(#{group-id} (second %)))
|
||||
(ffirst))
|
||||
rchanges [{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id parent-id
|
||||
:shapes shapes
|
||||
:index index-in-parent}]
|
||||
uchanges [{:type :add-obj
|
||||
:page-id page-id
|
||||
:id group-id
|
||||
:frame-id (:frame-id group)
|
||||
:obj (assoc group :shapes [])}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id group-id
|
||||
:shapes shapes}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id parent-id
|
||||
:shapes [group-id]
|
||||
:index index-in-parent}]]
|
||||
(let [[rchanges uchanges]
|
||||
(dws/prepare-remove-group page-id group objects)]
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Interactions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1506,6 +1435,7 @@
|
|||
"+" #(st/emit! (increase-zoom nil))
|
||||
"-" #(st/emit! (decrease-zoom nil))
|
||||
"ctrl+g" #(st/emit! group-selected)
|
||||
"ctrl+k" #(st/emit! dwl/add-component)
|
||||
"shift+g" #(st/emit! ungroup-selected)
|
||||
"shift+0" #(st/emit! reset-zoom)
|
||||
"shift+1" #(st/emit! zoom-to-fit-all)
|
||||
|
@ -1537,5 +1467,5 @@
|
|||
"right" #(st/emit! (dwt/move-selected :right false))
|
||||
"left" #(st/emit! (dwt/move-selected :left false))
|
||||
|
||||
"i" #(st/emit! (dwl/picker-for-selected-shape ))})
|
||||
"i" #(st/emit! (mdc/picker-for-selected-shape ))})
|
||||
|
||||
|
|
|
@ -44,6 +44,11 @@
|
|||
([state page-id]
|
||||
(get-in state [:workspace-data :pages-index page-id :options])))
|
||||
|
||||
(defn interrupt? [e] (= e :interrupt))
|
||||
|
||||
(defn lookup-component-objects
|
||||
([state component-id]
|
||||
(get-in state [:workspace-data :components component-id :objects])))
|
||||
|
||||
|
||||
;; --- Changes Handling
|
||||
|
@ -454,3 +459,4 @@
|
|||
objects (lookup-page-objects state page-id)
|
||||
[rchanges uchanges] (impl-gen-changes objects page-id (seq ids))]
|
||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))))))
|
||||
|
||||
|
|
|
@ -12,12 +12,18 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.common.pages :as cp]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.color :as color]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
|
@ -68,7 +74,7 @@
|
|||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
(defn delete-color
|
||||
[{:keys [id] :as color}]
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::delete-color
|
||||
ptk/WatchEvent
|
||||
|
@ -94,7 +100,7 @@
|
|||
|
||||
|
||||
(defn delete-media
|
||||
[{:keys [id] :as media}]
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::delete-media
|
||||
ptk/WatchEvent
|
||||
|
@ -106,3 +112,502 @@
|
|||
:object prev}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
(declare make-component-shape)
|
||||
|
||||
(def add-component
|
||||
(ptk/reify ::add-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
shapes (dws/shapes-for-grouping objects selected)]
|
||||
(when-not (empty? shapes)
|
||||
(let [;; If the selected shape is a group, we can use it. If not,
|
||||
;; we need to create a group before creating the component.
|
||||
[group rchanges uchanges]
|
||||
(if (and (= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
[(first shapes) [] []]
|
||||
(dws/prepare-create-group page-id shapes "Component-" true))
|
||||
|
||||
[new-shape new-shapes updated-shapes]
|
||||
(make-component-shape group nil objects)
|
||||
|
||||
rchanges (conj rchanges
|
||||
{:type :add-component
|
||||
:id (:id new-shape)
|
||||
:name (:name new-shape)
|
||||
:shapes new-shapes})
|
||||
|
||||
rchanges (into rchanges
|
||||
(map (fn [updated-shape]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id updated-shape)
|
||||
:operations [{:type :set
|
||||
:attr :component-id
|
||||
:val (:component-id updated-shape)}
|
||||
{:type :set
|
||||
:attr :component-file
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref updated-shape)}]})
|
||||
updated-shapes))
|
||||
|
||||
uchanges (conj uchanges
|
||||
{:type :del-component
|
||||
:id (:id new-shape)})
|
||||
|
||||
uchanges (into uchanges
|
||||
(map (fn [updated-shape]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id updated-shape)
|
||||
:operations [{:type :set
|
||||
:attr :component-id
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :component-file
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val nil}]})
|
||||
updated-shapes))]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
(defn- make-component-shape
|
||||
"Clone the shape and all children. Generate new ids and detach
|
||||
from parent and frame. Update the original shapes to have links
|
||||
to the new ones."
|
||||
[shape parent-id objects]
|
||||
(let [update-new-shape (fn [new-shape original-shape]
|
||||
(assoc new-shape :frame-id nil))
|
||||
|
||||
update-original-shape (fn [original-shape new-shape]
|
||||
(cond-> original-shape
|
||||
true
|
||||
(assoc :shape-ref (:id new-shape))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(assoc :component-id (:id new-shape))))]
|
||||
|
||||
(cph/clone-object shape parent-id objects update-new-shape update-original-shape)))
|
||||
|
||||
(defn delete-component
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::delete-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [component (get-in state [:workspace-data :components id])
|
||||
|
||||
rchanges [{:type :del-component
|
||||
:id id}]
|
||||
|
||||
uchanges [{:type :add-component
|
||||
:id id
|
||||
:name (:name component)
|
||||
:shapes (vals (:objects component))}]]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(defn instantiate-component
|
||||
[file-id component-id]
|
||||
(us/assert (s/nilable ::us/uuid) file-id)
|
||||
(us/assert ::us/uuid component-id)
|
||||
(ptk/reify ::instantiate-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [component (if (nil? file-id)
|
||||
(get-in state [:workspace-data :components component-id])
|
||||
(get-in state [:workspace-libraries file-id :data :components component-id]))
|
||||
component-shape (get-in component [:objects (:id component)])
|
||||
|
||||
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||
mouse-pos @ms/mouse-position
|
||||
delta (gpt/subtract mouse-pos orig-pos)
|
||||
|
||||
_ (js/console.log "orig-pos" (clj->js orig-pos))
|
||||
_ (js/console.log "mouse-pos" (clj->js mouse-pos))
|
||||
_ (js/console.log "delta" (clj->js delta))
|
||||
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
unames (atom (dwc/retrieve-used-names objects))
|
||||
|
||||
all-frames (cph/select-frames objects)
|
||||
|
||||
update-new-shape
|
||||
(fn [new-shape original-shape]
|
||||
(let [new-name
|
||||
(dwc/generate-unique-name @unames (:name new-shape))]
|
||||
|
||||
(swap! unames conj new-name)
|
||||
|
||||
(cond-> new-shape
|
||||
true
|
||||
(as-> $
|
||||
(assoc $ :name new-name)
|
||||
(geom/move $ delta)
|
||||
(assoc $ :frame-id
|
||||
(dwc/calculate-frame-overlap all-frames $))
|
||||
(assoc $ :parent-id
|
||||
(or (:parent-id $) (:frame-id $)))
|
||||
(assoc $ :shape-ref (:id original-shape)))
|
||||
|
||||
(nil? (:parent-id original-shape))
|
||||
(assoc :component-id (:id original-shape))
|
||||
|
||||
(and (nil? (:parent-id original-shape)) (some? file-id))
|
||||
(assoc :component-file file-id))))
|
||||
|
||||
[new-shape new-shapes _]
|
||||
(cph/clone-object component-shape
|
||||
nil
|
||||
(get component :objects)
|
||||
update-new-shape)
|
||||
|
||||
rchanges (map (fn [obj]
|
||||
{:type :add-obj
|
||||
:id (:id obj)
|
||||
:page-id page-id
|
||||
:frame-id (:frame-id obj)
|
||||
:parent-id (:parent-id obj)
|
||||
:obj obj})
|
||||
new-shapes)
|
||||
|
||||
uchanges (map (fn [obj]
|
||||
{:type :del-obj
|
||||
:id (:id obj)
|
||||
:page-id page-id})
|
||||
new-shapes)]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id new-shape))))))))
|
||||
|
||||
(defn detach-component
|
||||
[id]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::detach-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-id (cph/get-root-component id objects)
|
||||
|
||||
shapes (cph/get-object-with-children root-id objects)
|
||||
|
||||
rchanges (map (fn [obj]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id obj)
|
||||
:operations [{:type :set
|
||||
:attr :component-id
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :component-file
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val nil}]})
|
||||
shapes)
|
||||
|
||||
uchanges (map (fn [obj]
|
||||
{:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id obj)
|
||||
:operations [{:type :set
|
||||
:attr :component-id
|
||||
:val (:component-id obj)}
|
||||
{:type :set
|
||||
:attr :component-file
|
||||
:val (:component-file obj)}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref obj)}]})
|
||||
shapes)]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(defn nav-to-component-file
|
||||
[file-id]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(ptk/reify ::nav-to-component-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [file (get-in state [:workspace-libraries file-id])
|
||||
pparams {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
qparams {:page-id (first (get-in file [:data :pages]))}]
|
||||
(st/emit! (rt/nav-new-window :workspace pparams qparams))))))
|
||||
|
||||
(declare generate-sync-file)
|
||||
(declare generate-sync-page)
|
||||
(declare generate-sync-shape-and-children)
|
||||
(declare generate-sync-shape)
|
||||
(declare remove-component-and-ref)
|
||||
(declare remove-ref)
|
||||
(declare update-attrs)
|
||||
(declare sync-attrs)
|
||||
(declare calc-new-pos)
|
||||
|
||||
(defn reset-component
|
||||
[id]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::reset-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-id (cph/get-root-component id objects)
|
||||
root-shape (get objects id)
|
||||
file-id (get root-shape :component-file)
|
||||
|
||||
components
|
||||
(if (nil? file-id)
|
||||
(get-in state [:workspace-data :components])
|
||||
(get-in state [:workspace-libraries file-id :data :components]))
|
||||
|
||||
[rchanges uchanges]
|
||||
(generate-sync-shape-and-children root-shape page components)]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(defn update-component
|
||||
[id]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::update-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-id (cph/get-root-component id objects)
|
||||
root-shape (get objects id)
|
||||
|
||||
component-id (get root-shape :component-id)
|
||||
component-objs (dwc/lookup-component-objects state component-id)
|
||||
component-obj (get component-objs component-id)
|
||||
|
||||
;; Clone again the original shape and its children, maintaing
|
||||
;; the ids of the cloned shapes. If the original shape has some
|
||||
;; new child shapes, the cloned ones will have new generated ids.
|
||||
update-new-shape (fn [new-shape original-shape]
|
||||
(cond-> new-shape
|
||||
true
|
||||
(assoc :frame-id nil)
|
||||
|
||||
(some? (:shape-ref original-shape))
|
||||
(assoc :id (:shape-ref original-shape))))
|
||||
|
||||
[new-shape new-shapes _]
|
||||
(cph/clone-object root-shape nil objects update-new-shape)
|
||||
|
||||
rchanges [{:type :update-component
|
||||
:id component-id
|
||||
:name (:name new-shape)
|
||||
:shapes new-shapes}]
|
||||
|
||||
uchanges [{:type :update-component
|
||||
:id component-id
|
||||
:name (:name component-obj)
|
||||
:shapes (vals component-objs)}]]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(defn sync-file
|
||||
[{:keys [file-id] :as params}]
|
||||
(us/assert (s/nilable ::us/uuid) file-id)
|
||||
(ptk/reify ::sync-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [[rchanges uchanges] (generate-sync-file state file-id)]
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(defn- generate-sync-file
|
||||
[state file-id]
|
||||
(let [components
|
||||
(if (nil? file-id)
|
||||
(get-in state [:workspace-data :components])
|
||||
(get-in state [:workspace-libraries file-id :data :components]))]
|
||||
(loop [pages (seq (vals (get-in state [:workspace-data :pages-index])))
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [page (first pages)]
|
||||
(if (nil? page)
|
||||
[rchanges uchanges]
|
||||
(let [[page-rchanges page-uchanges]
|
||||
(generate-sync-page page components)]
|
||||
(recur (next pages)
|
||||
(concat rchanges page-rchanges)
|
||||
(concat uchanges page-uchanges))))))))
|
||||
|
||||
(defn- generate-sync-page
|
||||
[page components]
|
||||
(let [linked-shapes
|
||||
(cph/select-objects #(some? (:component-id %)) page)]
|
||||
(loop [shapes (seq linked-shapes)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [shape (first shapes)]
|
||||
(if (nil? shape)
|
||||
[rchanges uchanges]
|
||||
(let [[shape-rchanges shape-uchanges]
|
||||
(generate-sync-shape-and-children shape page components)]
|
||||
(recur (next shapes)
|
||||
(concat rchanges shape-rchanges)
|
||||
(concat uchanges shape-uchanges))))))))
|
||||
|
||||
(defn- generate-sync-shape-and-children
|
||||
[root-shape page components]
|
||||
(let [objects (get page :objects)
|
||||
all-shapes (cph/get-object-with-children (:id root-shape) objects)
|
||||
component (get components (:component-id root-shape))
|
||||
root-component (get-in component [:objects (:shape-ref root-shape)])]
|
||||
(loop [shapes (seq all-shapes)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [shape (first shapes)]
|
||||
(if (nil? shape)
|
||||
[rchanges uchanges]
|
||||
(let [[shape-rchanges shape-uchanges]
|
||||
(generate-sync-shape shape root-shape root-component page component)]
|
||||
(recur (next shapes)
|
||||
(concat rchanges shape-rchanges)
|
||||
(concat uchanges shape-uchanges))))))))
|
||||
|
||||
(defn- generate-sync-shape
|
||||
[shape root-shape root-component page component]
|
||||
(if (nil? component)
|
||||
(remove-component-and-ref shape page)
|
||||
(let [component-shape (get (:objects component) (:shape-ref shape))]
|
||||
(if (nil? component-shape)
|
||||
(remove-ref shape page)
|
||||
(update-attrs shape component-shape root-shape root-component page)))))
|
||||
|
||||
(defn- remove-component-and-ref
|
||||
[shape page]
|
||||
[[{:type :mod-obj
|
||||
:page-id (:id page)
|
||||
:id (:id shape)
|
||||
:operations [{:type :set
|
||||
:attr :component-id
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :component-file
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val nil}]}]
|
||||
[{:type :mod-obj
|
||||
:page-id (:id page)
|
||||
:id (:id shape)
|
||||
:operations [{:type :set
|
||||
:attr :component-id
|
||||
:val (:component-id shape)}
|
||||
{:type :set
|
||||
:attr :component-file
|
||||
:val (:component-file shape)}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref shape)}]}]])
|
||||
|
||||
(defn- remove-ref
|
||||
[shape page]
|
||||
[[{:type :mod-obj
|
||||
:page-id (:id page)
|
||||
:id (:id shape)
|
||||
:operations [{:type :set
|
||||
:attr :shape-ref
|
||||
:val nil}]}]
|
||||
[{:type :mod-obj
|
||||
:page-id (:id page)
|
||||
:id (:id shape)
|
||||
:operations [{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref shape)}]}]])
|
||||
|
||||
(defn- update-attrs
|
||||
[shape component-shape root-shape root-component page]
|
||||
(let [new-pos (calc-new-pos shape component-shape root-shape root-component)]
|
||||
(loop [attrs (seq sync-attrs)
|
||||
roperations [{:type :set
|
||||
:attr :x
|
||||
:val (:x new-pos)}
|
||||
{:type :set
|
||||
:attr :y
|
||||
:val (:y new-pos)}]
|
||||
uoperations [{:type :set
|
||||
:attr :x
|
||||
:val (:x shape)}
|
||||
{:type :set
|
||||
:attr :y
|
||||
:val (:y shape)}]]
|
||||
|
||||
(let [attr (first attrs)]
|
||||
(if (nil? attr)
|
||||
(let [rchanges [{:type :mod-obj
|
||||
:page-id (:id page)
|
||||
:id (:id shape)
|
||||
:operations roperations}]
|
||||
uchanges [{:type :mod-obj
|
||||
:page-id (:id page)
|
||||
:id (:id shape)
|
||||
:operations uoperations}]]
|
||||
[rchanges uchanges])
|
||||
(if-not (contains? shape attr)
|
||||
(recur (next attrs)
|
||||
roperations
|
||||
uoperations)
|
||||
(let [roperation {:type :set
|
||||
:attr attr
|
||||
:val (get component-shape attr)}
|
||||
uoperation {:type :set
|
||||
:attr attr
|
||||
:val (get shape attr)}]
|
||||
(recur (next attrs)
|
||||
(conj roperations roperation)
|
||||
(conj uoperations uoperation)))))))))
|
||||
|
||||
(def sync-attrs [:content
|
||||
:fill-color
|
||||
:fill-color-ref-file
|
||||
:fill-color-ref-id
|
||||
:fill-opacity
|
||||
:font-family
|
||||
:font-size
|
||||
:font-style
|
||||
:font-weight
|
||||
:letter-spacing
|
||||
:line-height
|
||||
:proportion
|
||||
:rx
|
||||
:ry
|
||||
:stroke-color
|
||||
:stroke-color-ref-file
|
||||
:stroke-color-ref-id
|
||||
:stroke-opacity
|
||||
:stroke-style
|
||||
:stroke-width
|
||||
:stroke-alignment
|
||||
:text-align
|
||||
:width
|
||||
:height
|
||||
:interactions
|
||||
:points
|
||||
:transform])
|
||||
|
||||
(defn- calc-new-pos
|
||||
[shape component-shape root-shape root-component]
|
||||
(let [root-pos (gpt/point (:x root-shape) (:y root-shape))
|
||||
root-component-pos (gpt/point (:x root-component) (:y root-component))
|
||||
component-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||
delta (gpt/subtract component-pos root-component-pos)
|
||||
shape-pos (gpt/point (:x shape) (:y shape))
|
||||
new-pos (gpt/add root-pos delta)]
|
||||
new-pos))
|
||||
|
||||
|
|
|
@ -33,33 +33,6 @@
|
|||
(s/def ::set-of-string
|
||||
(s/every string? :kind set?))
|
||||
|
||||
;; Duplicate from workspace.
|
||||
;; FIXME: Move these functions to a common place
|
||||
|
||||
(defn interrupt? [e] (= e :interrupt))
|
||||
|
||||
(defn- retrieve-used-names
|
||||
[objects]
|
||||
(into #{} (map :name) (vals objects)))
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
[basename]
|
||||
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||
[p1 (+ 1 (d/parse-integer p2))]
|
||||
[basename 1]))
|
||||
|
||||
(defn- generate-unique-name
|
||||
"A unique name generator"
|
||||
[used basename]
|
||||
(s/assert ::set-of-string used)
|
||||
(s/assert ::us/string basename)
|
||||
(let [[prefix initial] (extract-numeric-suffix basename)]
|
||||
(loop [counter initial]
|
||||
(let [candidate (str prefix "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate)))))
|
||||
|
||||
;; --- Selection Rect
|
||||
|
||||
(declare select-shapes-by-current-selrect)
|
||||
|
@ -88,7 +61,7 @@
|
|||
(ptk/reify ::handle-selection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter #(or (interrupt? %)
|
||||
(let [stoper (rx/filter #(or (dwc/interrupt? %)
|
||||
(ms/mouse-up? %))
|
||||
stream)]
|
||||
(rx/concat
|
||||
|
@ -183,6 +156,88 @@
|
|||
(rx/of deselect-all (select-shape (:id selected))))))))
|
||||
|
||||
|
||||
;; --- Group shapes
|
||||
|
||||
(defn shapes-for-grouping
|
||||
[objects selected]
|
||||
(->> selected
|
||||
(map #(get objects %))
|
||||
(filter #(not= :frame (:type %)))
|
||||
(map #(assoc % ::index (cph/position-on-parent (:id %) objects)))
|
||||
(sort-by ::index)))
|
||||
|
||||
(defn- make-group
|
||||
[shapes prefix keep-name]
|
||||
(let [selrect (geom/selection-rect shapes)
|
||||
frame-id (-> shapes first :frame-id)
|
||||
parent-id (-> shapes first :parent-id)
|
||||
group-name (if (and keep-name
|
||||
(= (count shapes) 1)
|
||||
(= (:type (first shapes)) :group))
|
||||
(:name (first shapes))
|
||||
(name (gensym prefix)))]
|
||||
(-> (cp/make-minimal-group frame-id selrect group-name)
|
||||
(geom/setup selrect)
|
||||
(assoc :shapes (map :id shapes)))))
|
||||
|
||||
(defn prepare-create-group
|
||||
[page-id shapes prefix keep-name]
|
||||
(let [group (make-group shapes prefix keep-name)
|
||||
rchanges [{:type :add-obj
|
||||
:id (:id group)
|
||||
:page-id page-id
|
||||
:frame-id (:frame-id (first shapes))
|
||||
:parent-id (:parent-id (first shapes))
|
||||
:obj group
|
||||
:index (::index (first shapes))}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:id group)
|
||||
:shapes (map :id shapes)}]
|
||||
|
||||
uchanges (conj
|
||||
(map (fn [obj] {:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:parent-id obj)
|
||||
:index (::index obj)
|
||||
:shapes [(:id obj)]})
|
||||
shapes)
|
||||
{:type :del-obj
|
||||
:id (:id group)
|
||||
:page-id page-id})]
|
||||
[group rchanges uchanges]))
|
||||
|
||||
(defn prepare-remove-group
|
||||
[page-id group objects]
|
||||
(let [shapes (:shapes group)
|
||||
parent-id (cph/get-parent (:id group) objects)
|
||||
parent (get objects parent-id)
|
||||
index-in-parent (->> (:shapes parent)
|
||||
(map-indexed vector)
|
||||
(filter #(#{(:id group)} (second %)))
|
||||
(ffirst))
|
||||
rchanges [{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id parent-id
|
||||
:shapes shapes
|
||||
:index index-in-parent}]
|
||||
uchanges [{:type :add-obj
|
||||
:page-id page-id
|
||||
:id (:id group)
|
||||
:frame-id (:frame-id group)
|
||||
:obj (assoc group :shapes [])}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:id group)
|
||||
:shapes shapes}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id parent-id
|
||||
:shapes [(:id group)]
|
||||
:index index-in-parent}]]
|
||||
[rchanges uchanges]))
|
||||
|
||||
|
||||
;; --- Duplicate Shapes
|
||||
(declare prepare-duplicate-change)
|
||||
(declare prepare-duplicate-frame-change)
|
||||
|
@ -218,7 +273,7 @@
|
|||
(defn- prepare-duplicate-shape-change
|
||||
[objects page-id names obj delta frame-id parent-id]
|
||||
(let [id (uuid/next)
|
||||
name (generate-unique-name names (:name obj))
|
||||
name (dwc/generate-unique-name names (:name obj))
|
||||
renamed-obj (assoc obj :id id :name name)
|
||||
moved-obj (geom/move renamed-obj delta)
|
||||
frames (cph/select-frames objects)
|
||||
|
@ -258,7 +313,7 @@
|
|||
(defn- prepare-duplicate-frame-change
|
||||
[objects page-id names obj delta]
|
||||
(let [frame-id (uuid/next)
|
||||
frame-name (generate-unique-name names (:name obj))
|
||||
frame-name (dwc/generate-unique-name names (:name obj))
|
||||
sch (->> (map #(get objects %) (:shapes obj))
|
||||
(mapcat #(prepare-duplicate-shape-change objects page-id names % delta frame-id frame-id)))
|
||||
|
||||
|
@ -287,7 +342,7 @@
|
|||
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
delta (gpt/point 0 0)
|
||||
unames (retrieve-used-names objects)
|
||||
unames (dwc/retrieve-used-names objects)
|
||||
|
||||
rchanges (prepare-duplicate-changes objects page-id unames selected delta)
|
||||
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
|
||||
|
|
|
@ -156,3 +156,34 @@
|
|||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns "http://www.w3.org/2000/svg"}
|
||||
[:& wrapper {:shape frame :view-box vbox}]]))
|
||||
|
||||
(mf/defc component-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects group zoom] :or {zoom 1} :as props}]
|
||||
(let [modifier (-> (gpt/point (:x group) (:y group))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
group-id (:id group)
|
||||
|
||||
modifier-ids (concat [group-id] (cph/get-children group-id objects))
|
||||
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
|
||||
objects (reduce update-fn objects modifier-ids)
|
||||
group (assoc-in group [:modifiers :displacement] modifier)
|
||||
|
||||
width (* (:width group) zoom)
|
||||
height (* (:height group) zoom)
|
||||
vbox (str "0 0 " (:width group 0)
|
||||
" " (:height group 0))
|
||||
wrapper (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(group-wrapper-factory objects))]
|
||||
|
||||
[:svg {:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns "http://www.w3.org/2000/svg"}
|
||||
[:& wrapper {:shape group :view-box vbox}]]))
|
||||
|
||||
|
|
|
@ -67,4 +67,4 @@
|
|||
|
||||
(defn ^:export dump-objects []
|
||||
(let [page-id (get @state :current-page-id)]
|
||||
(logjs "state" (get-in @state [:workspace-data page-id :objects]))))
|
||||
(logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects]))))
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
(def chat (icon-xref :chat))
|
||||
(def circle (icon-xref :circle))
|
||||
(def close (icon-xref :close))
|
||||
(def component (icon-xref :component))
|
||||
(def copy (icon-xref :copy))
|
||||
(def curve (icon-xref :curve))
|
||||
(def download (icon-xref :download))
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.ui.hooks :refer [use-rxsub]]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]))
|
||||
|
||||
|
@ -45,6 +46,7 @@
|
|||
[{:keys [mdata] :as props}]
|
||||
(let [{:keys [id] :as shape} (:shape mdata)
|
||||
selected (:selected mdata)
|
||||
root-shape (:root-shape mdata)
|
||||
|
||||
do-duplicate #(st/emit! dw/duplicate-selected)
|
||||
do-delete #(st/emit! dw/delete-selected)
|
||||
|
@ -59,7 +61,15 @@
|
|||
do-lock-shape #(st/emit! (dw/update-shape-flags id {:blocked true}))
|
||||
do-unlock-shape #(st/emit! (dw/update-shape-flags id {:blocked false}))
|
||||
do-create-group #(st/emit! dw/group-selected)
|
||||
do-remove-group #(st/emit! dw/ungroup-selected)]
|
||||
do-remove-group #(st/emit! dw/ungroup-selected)
|
||||
do-add-component #(st/emit! dwl/add-component)
|
||||
do-detach-component #(st/emit! (dwl/detach-component id))
|
||||
do-reset-component #(st/emit! (dwl/reset-component id))
|
||||
do-update-component #(do
|
||||
(st/emit! (dwl/update-component id))
|
||||
(st/emit! (dwl/sync-file {:file-id nil})))
|
||||
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file
|
||||
(:component-file root-shape)))]
|
||||
[:*
|
||||
[:& menu-entry {:title "Copy"
|
||||
:shortcut "Ctrl + c"
|
||||
|
@ -101,13 +111,29 @@
|
|||
[:& menu-entry {:title "Hide"
|
||||
:on-click do-hide-shape}])
|
||||
|
||||
|
||||
|
||||
(if (:blocked shape)
|
||||
[:& menu-entry {:title "Unlock"
|
||||
:on-click do-unlock-shape}]
|
||||
[:& menu-entry {:title "Lock"
|
||||
:on-click do-lock-shape}])
|
||||
|
||||
[:& menu-separator]
|
||||
|
||||
(if (nil? (:shape-ref shape))
|
||||
[:& menu-entry {:title "Create component"
|
||||
:shortcut "Ctrl + K"
|
||||
:on-click do-add-component}]
|
||||
[:*
|
||||
[:& menu-entry {:title "Detach instance"
|
||||
:on-click do-detach-component}]
|
||||
[:& menu-entry {:title "Reset overrides"
|
||||
:on-click do-reset-component}]
|
||||
(if (nil? (:component-file root-shape))
|
||||
[:& menu-entry {:title "Update master component"
|
||||
:on-click do-update-component}]
|
||||
[:& menu-entry {:title "Go to master component file"
|
||||
:on-click do-navigate-component-file}])])
|
||||
|
||||
[:& menu-separator]
|
||||
[:& menu-entry {:title "Delete"
|
||||
:shortcut "Supr"
|
||||
|
|
|
@ -34,10 +34,11 @@
|
|||
(def resize-point-circle-radius 10)
|
||||
(def resize-point-rect-size 8)
|
||||
(def resize-side-height 8)
|
||||
(def selection-rect-color "#1FDEA7")
|
||||
(def selection-rect-color-normal "#1FDEA7")
|
||||
(def selection-rect-color-component "#00E0FF")
|
||||
(def selection-rect-width 1)
|
||||
|
||||
(mf/defc selection-rect [{:keys [transform rect zoom]}]
|
||||
(mf/defc selection-rect [{:keys [transform rect zoom color]}]
|
||||
(let [{:keys [x y width height]} rect]
|
||||
[:rect.main
|
||||
{:x x
|
||||
|
@ -45,7 +46,7 @@
|
|||
:width width
|
||||
:height height
|
||||
:transform transform
|
||||
:style {:stroke selection-rect-color
|
||||
:style {:stroke color
|
||||
:stroke-width (/ selection-rect-width zoom)
|
||||
:fill "transparent"}}]))
|
||||
|
||||
|
@ -125,7 +126,7 @@
|
|||
:on-mouse-down on-rotate}]))
|
||||
|
||||
(mf/defc resize-point-handler
|
||||
[{:keys [cx cy zoom position on-resize transform rotation]}]
|
||||
[{:keys [cx cy zoom position on-resize transform rotation color]}]
|
||||
(let [{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)
|
||||
rot-square (case position
|
||||
:top-left 0
|
||||
|
@ -139,7 +140,7 @@
|
|||
:vectorEffect "non-scaling-stroke"
|
||||
}
|
||||
:fill "#FFFFFF"
|
||||
:stroke "#1FDEA7"
|
||||
:stroke color
|
||||
:cx cx'
|
||||
:cy cy'}]
|
||||
|
||||
|
@ -173,6 +174,7 @@
|
|||
[props]
|
||||
(let [shape (obj/get props "shape")
|
||||
zoom (obj/get props "zoom")
|
||||
color (obj/get props "color")
|
||||
on-resize (obj/get props "on-resize")
|
||||
on-rotate (obj/get props "on-rotate")
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
|
@ -186,8 +188,10 @@
|
|||
;; Selection rect
|
||||
[:& selection-rect {:rect selrect
|
||||
:transform transform
|
||||
:zoom zoom}]
|
||||
[:& outline {:shape (geom/transform-shape shape)}]
|
||||
:zoom zoom
|
||||
:color color}]
|
||||
[:& outline {:shape (geom/transform-shape shape)
|
||||
:color color}]
|
||||
|
||||
;; Handlers
|
||||
(for [{:keys [type position props]} (handlers-for-selection selrect)]
|
||||
|
@ -197,7 +201,8 @@
|
|||
:on-rotate on-rotate
|
||||
:on-resize (partial on-resize position)
|
||||
:transform transform
|
||||
:rotation (:rotation shape)}
|
||||
:rotation (:rotation shape)
|
||||
:color color}
|
||||
props (map->obj (merge common-props props))]
|
||||
(case type
|
||||
:rotation (when (not= :frame (:type shape)) [:> rotation-handler props])
|
||||
|
@ -206,7 +211,7 @@
|
|||
|
||||
;; --- Selection Handlers (Component)
|
||||
(mf/defc path-edition-selection-handlers
|
||||
[{:keys [shape modifiers zoom] :as props}]
|
||||
[{:keys [shape modifiers zoom color] :as props}]
|
||||
(letfn [(on-mouse-down [event index]
|
||||
(dom/stop-propagation event)
|
||||
;; TODO: this need code ux refactor
|
||||
|
@ -240,26 +245,26 @@
|
|||
:key index
|
||||
:on-mouse-down #(on-mouse-down % index)
|
||||
:fill "#ffffff"
|
||||
:stroke "#1FDEA7"
|
||||
:stroke color
|
||||
:style {:cursor cur/move-pointer}}]))])))
|
||||
|
||||
;; TODO: add specs for clarity
|
||||
|
||||
(mf/defc text-edition-selection-handlers
|
||||
[{:keys [shape zoom] :as props}]
|
||||
[{:keys [shape zoom color] :as props}]
|
||||
(let [{:keys [x y width height]} shape]
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y
|
||||
:transform (geom/transform-matrix shape)
|
||||
:width width
|
||||
:height height
|
||||
:style {:stroke "#1FDEA7"
|
||||
:style {:stroke color
|
||||
:stroke-width "0.5"
|
||||
:stroke-opacity "1"
|
||||
:fill "transparent"}}]]))
|
||||
|
||||
(mf/defc multiple-selection-handlers
|
||||
[{:keys [shapes selected zoom] :as props}]
|
||||
[{:keys [shapes selected zoom color] :as props}]
|
||||
(let [shape (geom/selection-rect shapes)
|
||||
shape-center (geom/center shape)
|
||||
on-resize (fn [current-position initial-position event]
|
||||
|
@ -272,13 +277,14 @@
|
|||
[:*
|
||||
[:& controls {:shape shape
|
||||
:zoom zoom
|
||||
:color color
|
||||
:on-resize on-resize
|
||||
:on-rotate on-rotate}]
|
||||
(when (debug? :selection-center)
|
||||
[:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])]))
|
||||
|
||||
(mf/defc single-selection-handlers
|
||||
[{:keys [shape zoom] :as props}]
|
||||
[{:keys [shape zoom color] :as props}]
|
||||
(let [shape-id (:id shape)
|
||||
shape (geom/transform-shape shape)
|
||||
shape' (if (debug? :simple-selection) (geom/selection-rect [shape]) shape)
|
||||
|
@ -293,6 +299,7 @@
|
|||
[:*
|
||||
[:& controls {:shape shape'
|
||||
:zoom zoom
|
||||
:color color
|
||||
:on-rotate on-rotate
|
||||
:on-resize on-resize}]]))
|
||||
|
||||
|
@ -304,7 +311,11 @@
|
|||
shapes (->> (mf/deref (refs/objects-by-id selected))
|
||||
(remove nil?))
|
||||
num (count shapes)
|
||||
{:keys [id type] :as shape} (first shapes)]
|
||||
{:keys [id type] :as shape} (first shapes)
|
||||
|
||||
color (if (or (> num 1) (nil? (:shape-ref shape)))
|
||||
selection-rect-color-normal
|
||||
selection-rect-color-component)]
|
||||
(cond
|
||||
(zero? num)
|
||||
nil
|
||||
|
@ -312,18 +323,22 @@
|
|||
(> num 1)
|
||||
[:& multiple-selection-handlers {:shapes shapes
|
||||
:selected selected
|
||||
:zoom zoom}]
|
||||
:zoom zoom
|
||||
:color color}]
|
||||
|
||||
(and (= type :text)
|
||||
(= edition (:id shape)))
|
||||
[:& text-edition-selection-handlers {:shape shape
|
||||
:zoom zoom}]
|
||||
:zoom zoom
|
||||
:color color}]
|
||||
(and (or (= type :path)
|
||||
(= type :curve))
|
||||
(= edition (:id shape)))
|
||||
[:& path-edition-selection-handlers {:shape shape
|
||||
:zoom zoom}]
|
||||
:zoom zoom
|
||||
:color color}]
|
||||
|
||||
:else
|
||||
[:& single-selection-handlers {:shape shape
|
||||
:zoom zoom}])))
|
||||
:zoom zoom
|
||||
:color color}])))
|
||||
|
|
|
@ -158,7 +158,8 @@
|
|||
:zoom zoom}]
|
||||
|
||||
(when dest-shape
|
||||
[:& outline {:shape dest-shape}])])))
|
||||
[:& outline {:shape dest-shape
|
||||
:color "#31EFB8"}])])))
|
||||
|
||||
|
||||
(mf/defc interaction-handle
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
[props]
|
||||
(let [zoom (mf/deref refs/selected-zoom)
|
||||
shape (unchecked-get props "shape")
|
||||
color (unchecked-get props "color")
|
||||
transform (gsh/transform-matrix shape)
|
||||
{:keys [id x y width height]} shape
|
||||
|
||||
|
@ -31,7 +32,7 @@
|
|||
"rect")
|
||||
|
||||
common {:fill "transparent"
|
||||
:stroke "#31EFB8"
|
||||
:stroke color
|
||||
:strokeWidth (/ 1 zoom)
|
||||
:pointerEvents "none"
|
||||
:transform transform}
|
||||
|
@ -42,10 +43,10 @@
|
|||
:cy (+ y (/ height 2))
|
||||
:rx (/ width 2)
|
||||
:ry (/ height 2)}
|
||||
|
||||
|
||||
(:curve :path)
|
||||
{:d (path/render-path shape)}
|
||||
|
||||
|
||||
{:x x
|
||||
:y y
|
||||
:width width
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[app.common.geom.shapes :as geom]
|
||||
[app.common.media :as cm]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.workspace :as dw]
|
||||
|
@ -21,6 +22,7 @@
|
|||
[app.main.data.colors :as dc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.exports :as exports]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||
|
@ -38,6 +40,63 @@
|
|||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc components-box
|
||||
[{:keys [file-id local? components] :as props}]
|
||||
(let [state (mf/use-state {:menu-open false
|
||||
:top nil
|
||||
:left nil
|
||||
:component-id nil})
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps state)
|
||||
(fn []
|
||||
(st/emit! (dwl/delete-component {:id (:component-id @state)}))
|
||||
(st/emit! (dwl/sync-file {:file-id nil}))))
|
||||
|
||||
on-context-menu
|
||||
(mf/use-callback
|
||||
(fn [component-id]
|
||||
(fn [event]
|
||||
(when local?
|
||||
(let [pos (dom/get-client-position event)
|
||||
top (:y pos)
|
||||
left (- (:x pos) 20)]
|
||||
(dom/prevent-default event)
|
||||
(swap! state assoc :menu-open true
|
||||
:top top
|
||||
:left left
|
||||
:component-id component-id))))))
|
||||
|
||||
on-drag-start
|
||||
(mf/use-callback
|
||||
(fn [component-id event]
|
||||
(dnd/set-data! event "app/component" {:file-id (if local? nil file-id)
|
||||
:component-id component-id})
|
||||
(dnd/set-allowed-effect! event "move")))]
|
||||
|
||||
[:div.asset-group
|
||||
[:div.group-title
|
||||
(tr "workspace.assets.components")
|
||||
[:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space
|
||||
[:div.group-grid.big
|
||||
(for [component components]
|
||||
[:div.grid-cell {:key (:id component)
|
||||
:draggable true
|
||||
:on-context-menu (on-context-menu (:id component))
|
||||
:on-drag-start (partial on-drag-start (:id component))}
|
||||
[:& exports/component-svg {:group (get-in component [:objects (:id component)])
|
||||
:objects (:objects component)}]
|
||||
[:div.cell-name (:name component)]])
|
||||
|
||||
(when local?
|
||||
[:& context-menu
|
||||
{:selectable false
|
||||
:show (:menu-open @state)
|
||||
:on-close #(swap! state assoc :menu-open false)
|
||||
:top (:top @state)
|
||||
:left (:left @state)
|
||||
:options [[(tr "workspace.assets.delete") on-delete]]}])]]))
|
||||
|
||||
(mf/defc graphics-box
|
||||
[{:keys [file-id local? objects open? on-open on-close] :as props}]
|
||||
(let [input-ref (mf/use-ref nil)
|
||||
|
@ -126,7 +185,6 @@
|
|||
:left (:left @state)
|
||||
:options [[(tr "workspace.assets.delete") on-delete]]}])])]))
|
||||
|
||||
|
||||
(mf/defc color-item
|
||||
[{:keys [color local? locale file-id] :as props}]
|
||||
(let [rename? (= (:color-for-rename @refs/workspace-local) (:id color))
|
||||
|
@ -287,32 +345,45 @@
|
|||
(vals (get-in state [:workspace-libraries id :data :media])))))
|
||||
st/state =))
|
||||
|
||||
(defn file-components-ref
|
||||
[id]
|
||||
(l/derived (fn [state]
|
||||
(let [wfile (:workspace-file state)]
|
||||
(if (= (:id wfile) id)
|
||||
(vals (get-in wfile [:data :components]))
|
||||
(vals (get-in state [:workspace-libraries id :data :components])))))
|
||||
st/state =))
|
||||
|
||||
(defn apply-filters
|
||||
[coll filters]
|
||||
(filter (fn [item]
|
||||
(or (matches-search (:name item "!$!") (:term filters))
|
||||
(matches-search (:value item "!$!") (:term filters))))
|
||||
coll))
|
||||
(->> coll
|
||||
(filter (fn [item]
|
||||
(or (matches-search (:name item "!$!") (:term filters))
|
||||
(matches-search (:value item "!$!") (:term filters)))))
|
||||
(sort-by #(str/lower (:name %)))))
|
||||
|
||||
(mf/defc file-library
|
||||
[{:keys [file local? open? filters locale] :as props}]
|
||||
(let [open? (mf/use-state open?)
|
||||
shared? (:is-shared file)
|
||||
router (mf/deref refs/router)
|
||||
toggle-open #(swap! open? not)
|
||||
(let [open? (mf/use-state open?)
|
||||
shared? (:is-shared file)
|
||||
router (mf/deref refs/router)
|
||||
toggle-open #(swap! open? not)
|
||||
|
||||
toggles (mf/use-state #{:graphics :colors})
|
||||
toggles (mf/use-state #{:graphics :colors})
|
||||
|
||||
url (rt/resolve router :workspace
|
||||
{:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
{:page-id (get-in file [:data :pages 0])})
|
||||
url (rt/resolve router :workspace
|
||||
{:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
{:page-id (get-in file [:data :pages 0])})
|
||||
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file)))
|
||||
colors (apply-filters (mf/deref colors-ref) filters)
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file)))
|
||||
colors (apply-filters (mf/deref colors-ref) filters)
|
||||
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file)))
|
||||
media (apply-filters (mf/deref media-ref) filters)]
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file)))
|
||||
media (apply-filters (mf/deref media-ref) filters)
|
||||
|
||||
components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file)))
|
||||
components (apply-filters (mf/deref components-ref) filters)]
|
||||
|
||||
[:div.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
@ -332,15 +403,23 @@
|
|||
[:a {:href (str "#" url) :target "_blank"} i/chain]]])]
|
||||
|
||||
(when @open?
|
||||
(let [show-graphics? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :graphics))
|
||||
(or (> (count media) 0)
|
||||
(str/empty? (:term filters))))
|
||||
show-colors? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :colors))
|
||||
(or (> (count colors) 0)
|
||||
(str/empty? (:term filters))))]
|
||||
(let [show-components? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :components))
|
||||
(or (> (count components) 0)
|
||||
(str/empty? (:term filters))))
|
||||
show-graphics? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :graphics))
|
||||
(or (> (count media) 0)
|
||||
(str/empty? (:term filters))))
|
||||
show-colors? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :colors))
|
||||
(or (> (count colors) 0)
|
||||
(str/empty? (:term filters))))]
|
||||
[:div.tool-window-content
|
||||
(when show-components?
|
||||
[:& components-box {:file-id (:id file)
|
||||
:local? local?
|
||||
:components components}])
|
||||
(when show-graphics?
|
||||
[:& graphics-box {:file-id (:id file)
|
||||
:local? local?
|
||||
|
@ -357,10 +436,11 @@
|
|||
:on-open #(swap! toggles conj :colors)
|
||||
:on-close #(swap! toggles disj :colors)}])
|
||||
|
||||
(when (and (not show-graphics?) (not show-colors?))
|
||||
(when (and (not show-components?) (not show-graphics?) (not show-colors?))
|
||||
[:div.asset-group
|
||||
[:div.group-title (t locale "workspace.assets.not-found")]])]))]))
|
||||
|
||||
|
||||
(mf/defc assets-toolbox
|
||||
[{:keys [team-id file] :as props}]
|
||||
(let [libraries (mf/deref refs/workspace-libraries)
|
||||
|
|
|
@ -43,7 +43,9 @@
|
|||
:rect i/box
|
||||
:curve i/curve
|
||||
:text i/text
|
||||
:group i/folder
|
||||
:group (if (nil? (:component-id shape))
|
||||
i/folder
|
||||
i/component)
|
||||
nil))
|
||||
|
||||
;; --- Layer Name
|
||||
|
@ -186,6 +188,7 @@
|
|||
[:li {:on-context-menu on-context-menu
|
||||
:ref dref
|
||||
:class (dom/classnames
|
||||
:component (not (nil? (:component-id item)))
|
||||
:dnd-over (= (:over dprops) :center)
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot)
|
||||
|
@ -285,7 +288,18 @@
|
|||
|
||||
(defn- strip-objects
|
||||
[objects]
|
||||
(let [strip-data #(select-keys % [:id :name :blocked :hidden :shapes :type :content :parent-id :metadata])]
|
||||
(let [strip-data #(select-keys % [:id
|
||||
:name
|
||||
:blocked
|
||||
:hidden
|
||||
:shapes
|
||||
:type
|
||||
:content
|
||||
:parent-id
|
||||
:component-id
|
||||
:component-file
|
||||
:shape-ref
|
||||
:metadata])]
|
||||
(persistent!
|
||||
(reduce-kv (fn [res id obj]
|
||||
(assoc! res id (strip-data obj)))
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.main.constants :as c]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.drawing :as dd]
|
||||
[app.main.data.colors :as dwc]
|
||||
[app.main.data.fetch :as mdf]
|
||||
|
@ -132,12 +133,16 @@
|
|||
hover (or (unchecked-get props "hover") #{})
|
||||
outline? (set/union selected hover)
|
||||
shapes (->> (vals objects) (filter (comp outline? :id)))
|
||||
transform (mf/deref refs/current-transform)]
|
||||
transform (mf/deref refs/current-transform)
|
||||
color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes))))
|
||||
"#31EFB8"
|
||||
"#00E0FF")]
|
||||
(when (nil? transform)
|
||||
[:g.outlines
|
||||
(for [shape shapes]
|
||||
[:& outline {:key (str "outline-" (:id shape))
|
||||
:shape (gsh/transform-shape shape)}])])))
|
||||
:shape (gsh/transform-shape shape)
|
||||
:color color}])])))
|
||||
|
||||
(mf/defc frames
|
||||
{::mf/wrap [mf/memo]
|
||||
|
@ -454,6 +459,7 @@
|
|||
on-drag-enter
|
||||
(fn [e]
|
||||
(when (or (dnd/has-type? e "app/shape")
|
||||
(dnd/has-type? e "app/component")
|
||||
(dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "text/uri-list"))
|
||||
(dom/prevent-default e)))
|
||||
|
@ -461,6 +467,7 @@
|
|||
on-drag-over
|
||||
(fn [e]
|
||||
(when (or (dnd/has-type? e "app/shape")
|
||||
(dnd/has-type? e "app/component")
|
||||
(dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "text/uri-list"))
|
||||
(dom/prevent-default e)))
|
||||
|
@ -491,6 +498,10 @@
|
|||
(assoc :x final-x)
|
||||
(assoc :y final-y)))))
|
||||
|
||||
(dnd/has-type? event "app/component")
|
||||
(let [{:keys [component-id file-id]} (dnd/get-data event "app/component")]
|
||||
(st/emit! (dwl/instantiate-component file-id component-id)))
|
||||
|
||||
(dnd/has-type? event "text/uri-list")
|
||||
(let [data (dnd/get-data event "text/uri-list")
|
||||
lines (str/lines data)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[potok.core :as ptk]
|
||||
[reitit.core :as r]
|
||||
[app.common.data :as d]
|
||||
[app.config :as cfg]
|
||||
[app.util.browser-history :as bhistory]
|
||||
[app.util.timers :as ts])
|
||||
(:import
|
||||
|
@ -112,6 +113,19 @@
|
|||
|
||||
(def navigate nav)
|
||||
|
||||
(deftype NavigateNewWindow [id params qparams]
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [router (:router state)
|
||||
path (resolve router id params qparams)
|
||||
uri (str cfg/public-uri "/#" path)]
|
||||
(js/window.open uri "_blank"))))
|
||||
|
||||
(defn nav-new-window
|
||||
([id] (nav-new-window id nil nil))
|
||||
([id params] (nav-new-window id params nil))
|
||||
([id params qparams] (NavigateNewWindow. id params qparams)))
|
||||
|
||||
;; --- History API
|
||||
|
||||
(defn initialize-history
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue