mirror of
https://github.com/penpot/penpot.git
synced 2025-01-08 16:00:19 -05:00
♻️ Major refactor of page data structure.
In preparation to future collaborative edition.
This commit is contained in:
parent
8c4bdc3f31
commit
af62d949d8
33 changed files with 1025 additions and 1124 deletions
|
@ -72,14 +72,16 @@
|
|||
(defn create-page
|
||||
[conn [pjid paid uid]]
|
||||
(println "create page" paid "(for project=" pjid ", user=" uid ")")
|
||||
(let [data {:shapes [{:id (mk-uuid "canvas" 1)
|
||||
:name "Canvas-1"
|
||||
:type :canvas
|
||||
:page (mk-uuid "page" pjid paid uid)
|
||||
:x1 200
|
||||
:y1 200
|
||||
:x2 1224
|
||||
:y2 968}]}
|
||||
(let [canvas {:id (mk-uuid "canvas" 1)
|
||||
:name "Canvas-1"
|
||||
:type :canvas
|
||||
:x1 200
|
||||
:y1 200
|
||||
:x2 1224
|
||||
:y2 968}
|
||||
data {:shapes []
|
||||
:canvas [(:id canvas)]
|
||||
:shapes-by-id {(:id canvas) canvas}}
|
||||
data (blob/encode data)
|
||||
mdata (blob/encode {})]
|
||||
(p/do!
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
(let [data (:body-params req)
|
||||
user-agent (get-in req [:headers "user-agent"])]
|
||||
(-> (sm/handle (assoc data ::sm/type :login))
|
||||
(p/then #(session/create % user-agent))
|
||||
(p/then #(session/create (:id %) user-agent))
|
||||
(p/then' (fn [token]
|
||||
{:status 204
|
||||
:cookies {"auth-token" {:value token}}
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
:opt-un [::scope]))
|
||||
|
||||
(sm/defmutation ::login
|
||||
{:doc "User login"
|
||||
:spec ::login-params}
|
||||
[{:keys [username password scope] :as params}]
|
||||
(letfn [(check-password [user password]
|
||||
(hashers/check password (:password user)))
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::metadata any?)
|
||||
(s/def ::ordering ::us/number)
|
||||
|
||||
;; --- Mutation: Create Page
|
||||
|
||||
|
@ -41,14 +42,15 @@
|
|||
(create-page db/pool params))
|
||||
|
||||
(defn create-page
|
||||
[conn {:keys [id user project-id name data metadata] :as params}]
|
||||
(let [sql "insert into pages (id, user_id, project_id, name, data, metadata, version)
|
||||
values ($1, $2, $3, $4, $5, $6, 0)
|
||||
[conn {:keys [id user project-id name ordering data metadata] :as params}]
|
||||
(let [sql "insert into pages (id, user_id, project_id, name,
|
||||
ordering, data, metadata, version)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, 0)
|
||||
returning *"
|
||||
id (or id (uuid/next))
|
||||
data (blob/encode data)
|
||||
mdata (blob/encode metadata)]
|
||||
(-> (db/query-one db/pool [sql id user project-id name data mdata])
|
||||
(-> (db/query-one db/pool [sql id user project-id name ordering data mdata])
|
||||
(p/then' decode-row))))
|
||||
|
||||
;; --- Mutation: Update Page
|
||||
|
@ -99,6 +101,21 @@
|
|||
(update-history conn params)
|
||||
(select-keys params [:id :version])))))))))
|
||||
|
||||
;; --- Mutation: Rename Page
|
||||
|
||||
(s/def ::rename-page
|
||||
(s/keys :req-un [::id ::name ::user]))
|
||||
|
||||
(sm/defmutation ::rename-page
|
||||
[{:keys [id name user]}]
|
||||
(let [sql "update pages
|
||||
set name = $3
|
||||
where id = $1
|
||||
and user_id = $2
|
||||
and deleted_at is null"]
|
||||
(-> (db/query-one db/pool [sql id user name])
|
||||
(p/then su/constantly-nil))))
|
||||
|
||||
;; --- Mutation: Update Page Metadata
|
||||
|
||||
(s/def ::update-page-metadata
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
(s/keys :req-un [::user ::name]
|
||||
:opt-un [::id]))
|
||||
|
||||
;; TODO: create role on project creation (maybe in DB?)
|
||||
|
||||
(sm/defmutation ::create-project
|
||||
[{:keys [id user name] :as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
|
@ -51,9 +49,7 @@
|
|||
(s/def ::update-project
|
||||
(s/keys :req-un [::user ::name ::id]))
|
||||
|
||||
(sm/defmutation :update-project
|
||||
{:doc "Update project."
|
||||
:spec ::update-project}
|
||||
(sm/defmutation ::update-project
|
||||
[{:keys [id name user] :as params}]
|
||||
(let [sql "update projects
|
||||
set name = $3
|
||||
|
@ -68,9 +64,7 @@
|
|||
(s/def ::delete-project
|
||||
(s/keys :req-un [::id ::user]))
|
||||
|
||||
(sm/defmutation :delete-project
|
||||
{:doc "Delete project"
|
||||
:spec ::delete-project}
|
||||
(sm/defmutation ::delete-project
|
||||
[{:keys [id user] :as params}]
|
||||
(let [sql "update projects
|
||||
set deleted_at = clock_timestamp()
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
funcool/beicon {:mvn/version "5.1.0"}
|
||||
funcool/cuerdas {:mvn/version "2.2.0"}
|
||||
funcool/lentes {:mvn/version "1.3.3"}
|
||||
funcool/potok {:mvn/version "2.6.0"}
|
||||
funcool/potok {:mvn/version "2.7.0"}
|
||||
funcool/promesa {:mvn/version "4.0.2"}
|
||||
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
|
||||
#_(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
|
||||
(->> stream
|
||||
(rx/filter udp/page-persisted?)
|
||||
(rx/debounce 1000)
|
||||
|
@ -186,7 +186,7 @@
|
|||
(ptk/reify ::select
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
#_(let [pid (get-in state [:workspace :current])
|
||||
item (get-in state [:workspace pid :history :byver version])
|
||||
page (-> (get-in state [:pages pid])
|
||||
(assoc :history true
|
||||
|
@ -208,7 +208,7 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
#_(let [pid (get-in state [:workspace :current])]
|
||||
(rx/of (udp/persist-page pid))))))
|
||||
|
||||
;; --- Deselect Page History
|
||||
|
@ -217,7 +217,7 @@
|
|||
(ptk/reify ::deselect
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
#_(let [pid (get-in state [:workspace :current])
|
||||
packed (get-in state [:packed-pages pid])]
|
||||
(-> (udp/unpack-page state packed)
|
||||
(assoc-in [:workspace pid :history :selected] nil))))))
|
||||
|
|
|
@ -31,22 +31,30 @@
|
|||
(s/def ::grid-x-axis ::us/number)
|
||||
(s/def ::grid-y-axis ::us/number)
|
||||
(s/def ::grid-color ::us/string)
|
||||
(s/def ::order ::us/number)
|
||||
(s/def ::ordering ::us/number)
|
||||
(s/def ::background ::us/string)
|
||||
(s/def ::background-opacity ::us/number)
|
||||
(s/def ::user ::us/uuid)
|
||||
|
||||
(s/def ::metadata
|
||||
(s/keys :req-un [::width ::height]
|
||||
:opt-un [::grid-y-axis
|
||||
(s/keys :opt-un [::grid-y-axis
|
||||
::grid-x-axis
|
||||
::grid-color
|
||||
::order
|
||||
::background
|
||||
::background-opacity]))
|
||||
|
||||
(s/def ::shapes
|
||||
(s/coll-of ::us/uuid :kind vector? :into []))
|
||||
(s/def ::minimal-shape
|
||||
(s/keys :req-un [::type ::name]
|
||||
:opt-un [::id]))
|
||||
|
||||
(s/def ::shapes (s/every ::us/uuid :kind vector? :into []))
|
||||
(s/def ::canvas (s/every ::us/uuid :kind vector? :into []))
|
||||
|
||||
(s/def ::shapes-by-id
|
||||
(s/map-of ::us/uuid ::minimal-shape))
|
||||
|
||||
(s/def ::data
|
||||
(s/keys :req-un [::shapes ::canvas ::shapes-by-id]))
|
||||
|
||||
(s/def ::page-entity
|
||||
(s/keys :req-un [::id
|
||||
|
@ -55,28 +63,9 @@
|
|||
::created-at
|
||||
::modified-at
|
||||
::user-id
|
||||
::ordering
|
||||
::metadata
|
||||
::shapes]))
|
||||
|
||||
(s/def ::minimal-shape
|
||||
(s/keys :req-un [::type ::name]
|
||||
:opt-un [::id]))
|
||||
|
||||
(s/def :uxbox.backend/shapes
|
||||
(s/coll-of ::minimal-shape :kind vector?))
|
||||
|
||||
(s/def :uxbox.backend/data
|
||||
(s/keys :req-un [:uxbox.backend/shapes]))
|
||||
|
||||
(s/def ::server-page
|
||||
(s/keys :req-un [::id ::name
|
||||
::project-id
|
||||
::version
|
||||
::created-at
|
||||
::modified-at
|
||||
::user
|
||||
::metadata
|
||||
:uxbox.backend/data]))
|
||||
::data]))
|
||||
|
||||
;; --- Protocols
|
||||
|
||||
|
@ -96,62 +85,34 @@
|
|||
|
||||
;; --- Helpers
|
||||
|
||||
(defn pack-page
|
||||
"Return a packed version of page object ready
|
||||
for send to remore storage service."
|
||||
[state id]
|
||||
(letfn [(pack-shapes [ids]
|
||||
(mapv #(get-in state [:shapes %]) ids))]
|
||||
(let [page (get-in state [:pages id])
|
||||
data {:shapes (pack-shapes (concatv (:canvas page)
|
||||
(:shapes page)))}]
|
||||
(-> page
|
||||
(assoc :data data)
|
||||
(dissoc :shapes)))))
|
||||
;; (defn pack-page
|
||||
;; "Return a packed version of page object ready
|
||||
;; for send to remore storage service."
|
||||
;; [state id]
|
||||
;; (letfn [(pack-shapes [ids]
|
||||
;; (mapv #(get-in state [:shapes %]) ids))]
|
||||
;; (let [page (get-in state [:pages id])
|
||||
;; data {:shapes (pack-shapes (concatv (:canvas page)
|
||||
;; (:shapes page)))}]
|
||||
;; (-> page
|
||||
;; (assoc :data data)
|
||||
;; (dissoc :shapes)))))
|
||||
|
||||
(defn unpack-page
|
||||
"Unpacks packed page object and assocs it to the
|
||||
provided state."
|
||||
[state {:keys [id data] :as page}]
|
||||
(let [shapes-list (:shapes data [])
|
||||
|
||||
shapes (->> shapes-list
|
||||
(filter #(not= :canvas (:type %)))
|
||||
(mapv :id))
|
||||
canvas (->> shapes-list
|
||||
(filter #(= :canvas (:type %)))
|
||||
(mapv :id))
|
||||
|
||||
shapes-map (index-by-id shapes-list)
|
||||
|
||||
page (-> page
|
||||
(dissoc :data)
|
||||
(assoc :shapes shapes :canvas canvas))]
|
||||
(-> state
|
||||
(update :shapes merge shapes-map)
|
||||
(update :pages assoc id page))))
|
||||
[state {:keys [id data metadata] :as page}]
|
||||
(-> state
|
||||
(update :pages assoc id (dissoc page :data))
|
||||
(update :pages-data assoc id data)))
|
||||
|
||||
(defn purge-page
|
||||
"Remove page and all related stuff from the state."
|
||||
[state id]
|
||||
(let [pid (get-in state [:pages id :project])]
|
||||
(if-let [project-id (get-in state [:pages id :project-id])]
|
||||
(-> state
|
||||
(update-in [:projects pid :pages] #(filterv (partial not= id) %))
|
||||
(update-in [:projects project-id :pages] #(filterv (partial not= id) %))
|
||||
(update :pages dissoc id)
|
||||
(update :packed-pages dissoc id)
|
||||
(update :shapes (fn [shapes] (->> shapes
|
||||
(map second)
|
||||
(filter #(= (:page %) id))
|
||||
(map :id)
|
||||
(apply dissoc shapes)))))))
|
||||
|
||||
(defn assoc-packed-page
|
||||
[state {:keys [id] :as page}]
|
||||
(assoc-in state [:packed-pages id] page))
|
||||
|
||||
(defn dissoc-packed-page
|
||||
[state id]
|
||||
(update state :packed-pages dissoc id))
|
||||
(update :pages-data dissoc id))
|
||||
state))
|
||||
|
||||
;; --- Pages Fetched
|
||||
|
||||
|
@ -165,13 +126,7 @@
|
|||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [get-order #(get-in % [:metadata :order])
|
||||
pages (sort-by get-order pages)
|
||||
page-ids (into [] (map :id) pages)]
|
||||
(as-> state $
|
||||
(assoc-in $ [:projects id :pages] page-ids)
|
||||
(reduce unpack-page $ pages)
|
||||
(reduce assoc-packed-page $ pages))))))
|
||||
(reduce unpack-page state pages))))
|
||||
|
||||
(defn pages-fetched?
|
||||
[v]
|
||||
|
@ -192,22 +147,19 @@
|
|||
|
||||
(defn page-fetched
|
||||
[data]
|
||||
(s/assert any? data) ;; TODO: minimal validate
|
||||
(s/assert ::page-entity data)
|
||||
(ptk/reify ::page-fetched
|
||||
IDeref
|
||||
(-deref [_] data)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(unpack-page data)
|
||||
(assoc-packed-page data)))))
|
||||
(unpack-page state data))))
|
||||
|
||||
(defn page-fetched?
|
||||
[v]
|
||||
(= ::page-fetched (ptk/type v)))
|
||||
|
||||
|
||||
;; --- Fetch Pages (by project id)
|
||||
|
||||
(defn fetch-page
|
||||
|
@ -222,115 +174,68 @@
|
|||
|
||||
;; --- Page Created
|
||||
|
||||
(declare rehash-pages)
|
||||
|
||||
(s/def ::page-created-params
|
||||
(s/keys :req-un [::id ::name ::project-id ::metadata]))
|
||||
|
||||
(defn page-created
|
||||
[data]
|
||||
(s/assert ::page-created-params data)
|
||||
(reify
|
||||
[{:keys [id project-id] :as page}]
|
||||
(s/assert ::page-entity page)
|
||||
(ptk/reify ::page-created
|
||||
cljs.core/IDeref
|
||||
(-deref [_] page)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (:project data)]
|
||||
(let [data (:data page)
|
||||
page (dissoc page :data)]
|
||||
(-> state
|
||||
(update-in [:projects pid :pages] (fnil conj []) (:id data))
|
||||
(unpack-page data)
|
||||
(assoc-packed-page data))))
|
||||
(update-in [:projects project-id :pages] (fnil conj []) (:id page))
|
||||
(update :pages assoc id page)
|
||||
(update :pages-data assoc id data))))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (rehash-pages (:project data))))))
|
||||
(defn page-created?
|
||||
[v]
|
||||
(= ::page-created (ptk/type v)))
|
||||
|
||||
;; --- Create Page Form
|
||||
|
||||
(s/def ::form-created-page-params
|
||||
(s/keys :req-un [::name ::project-id ::width ::height]))
|
||||
(s/def ::create-page
|
||||
(s/keys :req-un [::name ::project-id]))
|
||||
|
||||
(defn form->create-page
|
||||
[{:keys [name project width height layout] :as data}]
|
||||
(s/assert ::form-created-page-params data)
|
||||
(reify
|
||||
(defn create-page
|
||||
[{:keys [project-id name] :as data}]
|
||||
(s/assert ::create-page data)
|
||||
(ptk/reify ::create-page
|
||||
ptk/WatchEvent
|
||||
(watch [this state s]
|
||||
(let [canvas {:id (uuid/random)
|
||||
:name "Canvas 1"
|
||||
:type :canvas
|
||||
:x1 200
|
||||
:y1 200
|
||||
:x2 (+ 200 width)
|
||||
:y2 (+ 200 height)}
|
||||
metadata {:width width
|
||||
:height height
|
||||
:order -100}
|
||||
(let [ordering (count (get-in state [:projects project-id :pages]))
|
||||
params {:name name
|
||||
:project project
|
||||
:data {:shapes [canvas]}
|
||||
:metadata metadata}]
|
||||
(->> (rp/req :create/page params)
|
||||
(rx/map :payload)
|
||||
:project-id project-id
|
||||
:ordering ordering
|
||||
:data {:shapes []
|
||||
:canvas []
|
||||
:shapes-by-id {}}
|
||||
:metadata {}}]
|
||||
(->> (rp/mutation :create-page params)
|
||||
(rx/map page-created))))))
|
||||
|
||||
;; --- Update Page Form
|
||||
|
||||
(s/def ::form-update-page-params
|
||||
(s/keys :req-un [::id ::name ::width ::height]))
|
||||
(declare page-renamed)
|
||||
|
||||
(defn form->update-page
|
||||
[{:keys [id name width height] :as data}]
|
||||
(s/assert ::form-update-page-params data)
|
||||
(reify
|
||||
IPageUpdate
|
||||
(s/def ::rename-page
|
||||
(s/keys :req-un [::id ::name]))
|
||||
|
||||
(defn rename-page
|
||||
[{:keys [id name] :as data}]
|
||||
(s/assert ::rename-page data)
|
||||
(ptk/reify ::rename-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:pages id]
|
||||
(fn [page]
|
||||
(-> (assoc page :name name)
|
||||
(assoc-in [:name] name)
|
||||
(assoc-in [:metadata :width] width)
|
||||
(assoc-in [:metadata :height] height)))))))
|
||||
(update-in state [:pages id] assoc :name name))
|
||||
|
||||
;; --- Page Persisted
|
||||
|
||||
(defn page-persisted
|
||||
[data]
|
||||
(s/assert ::server-page data)
|
||||
(ptk/reify ::page-persisted
|
||||
cljs.core/IDeref
|
||||
(-deref [_] data)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [{:keys [id version]} data]
|
||||
(-> state
|
||||
(assoc-in [:pages id :version] version)
|
||||
(assoc-packed-page data))))))
|
||||
|
||||
(defn- page-persisted?
|
||||
[event]
|
||||
(= (ptk/type event) ::page-persisted))
|
||||
|
||||
;; --- Persist Page
|
||||
|
||||
(defn persist-page
|
||||
[id]
|
||||
(s/assert ::us/uuid id)
|
||||
(ptk/reify ::persist-page
|
||||
ptk/WatchEvent
|
||||
(watch [this state s]
|
||||
(let [page (get-in state [:pages id])]
|
||||
(if (:history page)
|
||||
(rx/empty)
|
||||
(let [page (pack-page state id)]
|
||||
(->> (rp/req :update/page page)
|
||||
(rx/map :payload)
|
||||
(rx/map page-persisted)
|
||||
(rx/catch (fn [err] (rx/of ::page-persist-error))))))))))
|
||||
|
||||
(defn persist-page?
|
||||
[v]
|
||||
(= ::persist-page (ptk/type v)))
|
||||
(watch [_ state stream]
|
||||
(let [params {:id id :name name}]
|
||||
(->> (rp/mutation :rename-page params)
|
||||
(rx/map #(ptk/data-event ::page-renamed data)))))))
|
||||
|
||||
;; --- Page Metadata Persisted
|
||||
|
||||
|
@ -349,7 +254,6 @@
|
|||
[v]
|
||||
(= ::metadata-persisted (ptk/type v)))
|
||||
|
||||
|
||||
;; --- Persist Page Metadata
|
||||
|
||||
;; This is a simplified version of `PersistPage` event
|
||||
|
@ -362,7 +266,7 @@
|
|||
(ptk/reify ::persist-metadata
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page (get-in state [:pages id])]
|
||||
#_(let [page (get-in state [:pages id])]
|
||||
(->> (rp/req :update/page-metadata page)
|
||||
(rx/map :payload)
|
||||
(rx/map metadata-persisted))))))
|
||||
|
@ -390,44 +294,6 @@
|
|||
(update [this state]
|
||||
(assoc-in state [:pages id :metadata] metadata))))
|
||||
|
||||
|
||||
;; --- Rehash Pages
|
||||
;;
|
||||
;; A post processing event that normalizes the
|
||||
;; page order numbers after a user sorting
|
||||
;; operation for a concrete project.
|
||||
|
||||
(defn rehash-pages
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(let [page-ids (get-in state [:projects id :pages])]
|
||||
(reduce (fn [state [index id]]
|
||||
(assoc-in state [:pages id :metadata :order] index))
|
||||
state
|
||||
(map-indexed vector page-ids))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-ids (get-in state [:projects id :pages])]
|
||||
(->> (rx/from-coll page-ids)
|
||||
(rx/map persist-metadata))))))
|
||||
|
||||
;; --- Move Page (Ordering)
|
||||
|
||||
(defn move-page
|
||||
[{:keys [page-id project-id index] :as params}]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pages (get-in state [:projects project-id :pages])
|
||||
pages (into [] (remove #(= % page-id)) pages)
|
||||
[before after] (split-at index pages)
|
||||
pages (vec (concat before [page-id] after))]
|
||||
(assoc-in state [:projects project-id :pages] pages)))))
|
||||
|
||||
;; --- Delete Page (by id)
|
||||
|
||||
(defn delete-page
|
||||
|
@ -440,34 +306,5 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/req :delete/page id)
|
||||
(->> (rp/mutation :delete-page {:id id})
|
||||
(rx/map (constantly ::delete-completed))))))
|
||||
|
||||
;; --- Watch Page Changes
|
||||
|
||||
(defn watch-page-changes
|
||||
[id]
|
||||
(s/assert ::us/uuid id)
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
|
||||
(->> (rx/merge
|
||||
(->> stream
|
||||
(rx/filter #(or (satisfies? IPageUpdate %)
|
||||
(= ::page-update %)))
|
||||
(rx/debounce 1000)
|
||||
(rx/mapcat #(rx/merge (rx/of (persist-page id))
|
||||
(->> (rx/filter page-persisted? stream)
|
||||
(rx/timeout 1000 (rx/empty))
|
||||
(rx/take 1)
|
||||
(rx/ignore)))))
|
||||
(->> stream
|
||||
(rx/filter #(satisfies? IMetadataUpdate %))
|
||||
(rx/debounce 1000)
|
||||
(rx/mapcat #(rx/merge (rx/of (persist-metadata id))
|
||||
(->> (rx/filter metadata-persisted? stream)
|
||||
(rx/take 1)
|
||||
(rx/ignore))))))
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [this state stream]
|
||||
(->> (rp/req :create/project {:name name})
|
||||
#_(->> (rp/req :create/project {:name name})
|
||||
(rx/map :payload)
|
||||
(rx/mapcat (fn [{:keys [id] :as project}]
|
||||
(rx/of #(assoc-project % project)
|
||||
|
|
|
@ -1,209 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.data.shapes
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.data :refer [index-of]]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::blocked boolean?)
|
||||
(s/def ::collapsed boolean?)
|
||||
(s/def ::content string?)
|
||||
(s/def ::fill-color string?)
|
||||
(s/def ::fill-opacity number?)
|
||||
(s/def ::font-family string?)
|
||||
(s/def ::font-size number?)
|
||||
(s/def ::font-style string?)
|
||||
(s/def ::font-weight string?)
|
||||
(s/def ::height number?)
|
||||
(s/def ::hidden boolean?)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::letter-spacing number?)
|
||||
(s/def ::line-height number?)
|
||||
(s/def ::locked boolean?)
|
||||
(s/def ::name string?)
|
||||
(s/def ::page uuid?)
|
||||
(s/def ::proportion number?)
|
||||
(s/def ::proportion-lock boolean?)
|
||||
(s/def ::rx number?)
|
||||
(s/def ::ry number?)
|
||||
(s/def ::stroke-color string?)
|
||||
(s/def ::stroke-opacity number?)
|
||||
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
|
||||
(s/def ::stroke-width number?)
|
||||
(s/def ::text-align #{"left" "right" "center" "justify"})
|
||||
(s/def ::type #{:rect :path :circle :image :text})
|
||||
(s/def ::width number?)
|
||||
(s/def ::x1 number?)
|
||||
(s/def ::x2 number?)
|
||||
(s/def ::y1 number?)
|
||||
(s/def ::y2 number?)
|
||||
|
||||
(s/def ::attributes
|
||||
(s/keys :opt-un [::blocked
|
||||
::collapsed
|
||||
::content
|
||||
::fill-color
|
||||
::fill-opacity
|
||||
::font-family
|
||||
::font-size
|
||||
::font-style
|
||||
::font-weight
|
||||
::hidden
|
||||
::letter-spacing
|
||||
::line-height
|
||||
::locked
|
||||
::proportion
|
||||
::proportion-lock
|
||||
::rx ::ry
|
||||
::stroke-color
|
||||
::stroke-opacity
|
||||
::stroke-style
|
||||
::stroke-width
|
||||
::text-align
|
||||
::x1 ::x2
|
||||
::y1 ::y2]))
|
||||
|
||||
(s/def ::minimal-shape
|
||||
(s/keys :req-un [::id ::page ::type ::name]))
|
||||
|
||||
(s/def ::shape
|
||||
(s/and ::minimal-shape ::attributes))
|
||||
|
||||
(s/def ::rect-like-shape
|
||||
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))
|
||||
|
||||
;; --- Shape Creation
|
||||
|
||||
(defn retrieve-used-names
|
||||
"Returns a set of already used names by shapes
|
||||
in the current page."
|
||||
[{:keys [shapes] :as state}]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
xf (comp (filter #(= pid (:page %)))
|
||||
(map :name))]
|
||||
(into #{} xf (vals shapes))))
|
||||
|
||||
(defn generate-unique-name
|
||||
"A unique name generator based on the previous
|
||||
state of the used names."
|
||||
[state basename]
|
||||
(let [used (retrieve-used-names state)]
|
||||
(loop [counter 1]
|
||||
(let [candidate (str basename "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate)))))
|
||||
|
||||
(defn assoc-shape-to-page
|
||||
[state shape page]
|
||||
(let [shape-id (uuid/random)
|
||||
shape-name (generate-unique-name state (:name shape))
|
||||
shape (assoc shape
|
||||
:page page
|
||||
:id shape-id
|
||||
:name shape-name)]
|
||||
(as-> state $
|
||||
(if (= :canvas (:type shape))
|
||||
(update-in $ [:pages page :canvas] conj shape-id)
|
||||
(update-in $ [:pages page :shapes] conj shape-id))
|
||||
(assoc-in $ [:shapes shape-id] shape))))
|
||||
|
||||
(defn- duplicate-shape
|
||||
[state shape page]
|
||||
(let [id (uuid/random)
|
||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||
shape (assoc shape :id id :page page :name name)]
|
||||
(-> state
|
||||
(update-in [:pages page :shapes] #(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))))
|
||||
|
||||
(defn duplicate-shapes
|
||||
([state shapes]
|
||||
(duplicate-shapes state shapes nil))
|
||||
([state shapes page]
|
||||
(let [shapes (reverse (map #(get-in state [:shapes %]) shapes))
|
||||
page (or page (:page (first shapes)))]
|
||||
(reduce #(duplicate-shape %1 %2 page) state shapes))))
|
||||
|
||||
;; --- Delete Shapes
|
||||
|
||||
(defn dissoc-from-index
|
||||
"A function that dissoc shape from the indexed
|
||||
data structure of shapes from the state."
|
||||
[state shape]
|
||||
(update state :shapes dissoc (:id shape)))
|
||||
|
||||
(defn dissoc-from-page
|
||||
"Given a shape, try to remove its reference from the
|
||||
corresponding page."
|
||||
[state {:keys [id page type] :as shape}]
|
||||
(if (= :canvas type)
|
||||
(update-in state [:pages page :canvas]
|
||||
(fn [items] (vec (remove #(= % id) items))))
|
||||
(update-in state [:pages page :shapes]
|
||||
(fn [items] (vec (remove #(= % id) items))))))
|
||||
|
||||
(defn dissoc-shape
|
||||
"Given a shape, removes it from the state."
|
||||
[state shape]
|
||||
(-> state
|
||||
(dissoc-from-page shape)
|
||||
(dissoc-from-index shape)))
|
||||
|
||||
;; --- Shape Vertical Ordering
|
||||
|
||||
(defn order-shape
|
||||
[state sid opt]
|
||||
(let [{:keys [page] :as shape} (get-in state [:shapes sid])
|
||||
shapes (get-in state [:pages page :shapes])
|
||||
index (case opt
|
||||
:top 0
|
||||
:down (min (- (count shapes) 1) (inc (index-of shapes sid)))
|
||||
:up (max 0 (- (index-of shapes sid) 1))
|
||||
:bottom (- (count shapes) 1))]
|
||||
(update-in state [:pages page :shapes]
|
||||
(fn [items]
|
||||
(let [[fst snd] (->> (remove #(= % sid) items)
|
||||
(split-at index))]
|
||||
(into [] (concat fst [sid] snd)))))))
|
||||
|
||||
;; --- Shape Selection
|
||||
|
||||
(defn- try-match-shape
|
||||
[xf selrect acc {:keys [type id items] :as shape}]
|
||||
(cond
|
||||
(geom/contained-in? shape selrect)
|
||||
(conj acc id)
|
||||
|
||||
(geom/overlaps? shape selrect)
|
||||
(conj acc id)
|
||||
|
||||
:else
|
||||
acc))
|
||||
|
||||
(defn match-by-selrect
|
||||
[state page-id selrect]
|
||||
(let [xf (comp (map #(get-in state [:shapes %]))
|
||||
(remove :hidden)
|
||||
(remove :blocked)
|
||||
(remove #(= :canvas (:type %)))
|
||||
(map geom/selection-rect))
|
||||
match (partial try-match-shape xf selrect)
|
||||
shapes (get-in state [:pages page-id :shapes])]
|
||||
(reduce match #{} (sequence xf shapes))))
|
||||
|
||||
(defn materialize-xfmt
|
||||
[state id xfmt]
|
||||
(let [{:keys [type items] :as shape} (get-in state [:shapes id])]
|
||||
(update-in state [:shapes id] geom/transform xfmt)))
|
|
@ -28,7 +28,7 @@
|
|||
(ptk/reify ::watch-page-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (rx/filter #(= % ::udp/stop-page-watcher) stream)]
|
||||
#_(let [stopper (rx/filter #(= % ::udp/stop-page-watcher) stream)]
|
||||
(->> stream
|
||||
(rx/filter udp/page-update?)
|
||||
(rx/filter #(not (undo? %)))
|
||||
|
@ -49,7 +49,7 @@
|
|||
(ptk/reify ::save-undo-entry
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page (udp/pack-page state id)
|
||||
#_(let [page (udp/pack-page state id)
|
||||
undo {:data (:data page)
|
||||
:metadata (:metadata page)}]
|
||||
(-> state
|
||||
|
@ -66,7 +66,7 @@
|
|||
(ptk/reify ::undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
#_(let [pid (get-in state [:workspace :current])
|
||||
{:keys [stack selected] :as ustate} (get-in state [:undo pid])]
|
||||
(if (>= selected (dec (count stack)))
|
||||
state
|
||||
|
@ -95,7 +95,7 @@
|
|||
(ptk/reify ::redo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
#_(let [pid (get-in state [:workspace :current])
|
||||
undo-state (get-in state [:undo pid])
|
||||
stack (:stack undo-state)
|
||||
selected (:selected undo-state)]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,39 +12,57 @@
|
|||
[uxbox.main.store :as st]))
|
||||
|
||||
(def workspace
|
||||
(letfn [(selector [state]
|
||||
(let [id (get-in state [:workspace :current])]
|
||||
(get-in state [:workspace id])))]
|
||||
(-> (l/lens selector)
|
||||
(l/derive st/state))))
|
||||
(-> (l/key :workspace-local)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-local
|
||||
(-> (l/key :workspace-local)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-layout
|
||||
(-> (l/key :workspace-layout)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-page
|
||||
(-> (l/key :workspace-page)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-data
|
||||
(-> (l/key :workspace-data)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def selected-shapes
|
||||
(-> (l/key :selected)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def selected-canvas
|
||||
(-> (l/key :selected-canvas)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def toolboxes
|
||||
(-> (l/key :toolboxes)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
;; DEPRECATED
|
||||
(def flags
|
||||
(-> (l/key :flags)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def selected-flags
|
||||
(-> (l/key :flags)
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def selected-zoom
|
||||
(-> (l/key :zoom)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def selected-tooltip
|
||||
(-> (l/key :tooltip)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def selected-drawing-shape
|
||||
(-> (l/key :drawing)
|
||||
(l/derive workspace)))
|
||||
(l/derive workspace-local)))
|
||||
|
||||
(def selected-drawing-tool
|
||||
(-> (l/key :drawing-tool)
|
||||
|
|
|
@ -86,9 +86,9 @@
|
|||
(defn send-mutation!
|
||||
[id params]
|
||||
(let [url (str url "/w/mutation/" (name id))]
|
||||
(send! {:method :post
|
||||
:url url
|
||||
:body params})))
|
||||
(->> (impl-send {:method :post :url url :body params})
|
||||
(rx/map conditional-decode)
|
||||
(rx/mapcat handle-response))))
|
||||
|
||||
(defn- dispatch
|
||||
[& args]
|
||||
|
|
|
@ -20,23 +20,25 @@
|
|||
(defonce store (ptk/store {:on-error #(*on-error* %)}))
|
||||
(defonce stream (ptk/input-stream store))
|
||||
|
||||
;; (defn repr-event
|
||||
;; [event]
|
||||
;; (cond
|
||||
;; (satisfies? ptk/Event event)
|
||||
;; (str "typ: " (pr-str (ptk/type event)))
|
||||
(defn repr-event
|
||||
[event]
|
||||
(cond
|
||||
(satisfies? ptk/Event event)
|
||||
(str "typ: " (pr-str (ptk/type event)))
|
||||
|
||||
;; (and (fn? event)
|
||||
;; (pos? (count (.-name event))))
|
||||
;; (str "fn: " (demunge (.-name event)))
|
||||
(and (fn? event)
|
||||
(pos? (count (.-name event))))
|
||||
(str "fn: " (demunge (.-name event)))
|
||||
|
||||
;; :else
|
||||
;; (str "unk: " (pr-str event))))
|
||||
:else
|
||||
(str "unk: " (pr-str event))))
|
||||
|
||||
;; (defonce debug (as-> stream $
|
||||
;; (rx/filter ptk/event? $)
|
||||
;; (rx/subscribe $ (fn [event]
|
||||
;; (println "[stream]: " (repr-event event))))))
|
||||
(defonce debug (as-> stream $
|
||||
(rx/filter ptk/event? $)
|
||||
;; Comment this line if you want full debug.
|
||||
(rx/ignore $)
|
||||
(rx/subscribe $ (fn [event]
|
||||
(println "[stream]: " (repr-event event))))))
|
||||
|
||||
(def auth-ref
|
||||
(-> (l/key :auth)
|
||||
|
@ -61,15 +63,17 @@
|
|||
:profile (:profile storage)
|
||||
:clipboard #queue []
|
||||
:undo {}
|
||||
:workspace nil
|
||||
:workspace-layout nil
|
||||
:workspace-local nil
|
||||
:workspace-pdata nil
|
||||
:images-collections nil
|
||||
:images nil
|
||||
:icons-collections nil
|
||||
:icons nil
|
||||
:colors-collections colors/collections
|
||||
:shapes nil
|
||||
:projects nil
|
||||
:pages nil})
|
||||
:pages nil
|
||||
:pages-data nil})
|
||||
|
||||
(defn init
|
||||
"Initialize the state materialization."
|
||||
|
|
|
@ -29,9 +29,8 @@
|
|||
(ptk/reify ::start-move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
flags (get-in state [:workspace pid :flags])
|
||||
selected (get-in state [:workspace pid :selected])
|
||||
(let [flags (get-in state [:workspace-local :flags])
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
stoper (rx/filter uws/mouse-up? stream)
|
||||
position @uws/mouse-position]
|
||||
(rx/concat
|
||||
|
@ -40,7 +39,8 @@
|
|||
(->> (uws/mouse-position-deltas position)
|
||||
(rx/map #(dw/apply-temporal-displacement-in-bulk selected %))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-current-modifier-in-bulk selected)))))))
|
||||
(rx/of (dw/materialize-current-modifier-in-bulk selected)
|
||||
::dw/page-data-update))))))
|
||||
|
||||
(defn on-mouse-down
|
||||
[event {:keys [id type] :as shape} selected]
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.undo :as udu]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.confirm]
|
||||
|
@ -22,9 +22,9 @@
|
|||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.workspace.viewport :refer [viewport]]
|
||||
[uxbox.main.ui.workspace.colorpalette :refer [colorpalette]]
|
||||
[uxbox.main.ui.workspace.download]
|
||||
;; [uxbox.main.ui.workspace.download]
|
||||
[uxbox.main.ui.workspace.header :refer [header]]
|
||||
[uxbox.main.ui.workspace.images]
|
||||
;; [uxbox.main.ui.workspace.images]
|
||||
[uxbox.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
||||
[uxbox.main.ui.workspace.scroll :as scroll]
|
||||
[uxbox.main.ui.workspace.shortcuts :as shortcuts]
|
||||
|
@ -55,47 +55,20 @@
|
|||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(if (pos? (.-deltaY event))
|
||||
(st/emit! (dw/decrease-zoom))
|
||||
(st/emit! (dw/increase-zoom)))
|
||||
(st/emit! (udw/decrease-zoom))
|
||||
(st/emit! (udw/increase-zoom)))
|
||||
(scroll/scroll-to-point dom mouse-point scroll-position))))
|
||||
|
||||
(defn- subscribe
|
||||
[canvas page]
|
||||
(st/emit! (udp/watch-page-changes (:id page))
|
||||
(udu/watch-page-changes (:id page))
|
||||
;; TODO: temporary commented
|
||||
;; (udh/initialize (:id page))
|
||||
;; (udh/watch-page-changes (:id page))
|
||||
(dw/start-shapes-watcher (:id page)))
|
||||
(let [sub (shortcuts/init)]
|
||||
#(do (st/emit! ::udp/stop-page-watcher
|
||||
::udh/stop-page-watcher
|
||||
::dw/stop-shapes-watcher)
|
||||
(rx/cancel! sub))))
|
||||
|
||||
(mf/defc workspace
|
||||
[{:keys [page] :as props}]
|
||||
(let [flags (or (mf/deref refs/flags) #{})
|
||||
canvas (mf/use-ref nil)
|
||||
left-sidebar? (not (empty? (keep flags [:layers :sitemap
|
||||
(mf/defc workspace-content
|
||||
[{:keys [layout page] :as params}]
|
||||
(let [canvas (mf/use-ref nil)
|
||||
left-sidebar? (not (empty? (keep layout [:layers :sitemap
|
||||
:document-history])))
|
||||
right-sidebar? (not (empty? (keep flags [:icons :drawtools
|
||||
right-sidebar? (not (empty? (keep layout [:icons :drawtools
|
||||
:element-options])))
|
||||
classes (classnames
|
||||
:no-tool-bar-right (not right-sidebar?)
|
||||
:no-tool-bar-left (not left-sidebar?)
|
||||
:scrolling (:viewport-positionig workspace))]
|
||||
|
||||
(mf/use-effect #(subscribe canvas page)
|
||||
#js [(:id page)])
|
||||
[:*
|
||||
[:& messages-widget]
|
||||
[:& header {:page page
|
||||
:flags flags
|
||||
:key (:id page)}]
|
||||
|
||||
(when (:colorpalette flags)
|
||||
[:& colorpalette])
|
||||
:no-tool-bar-left (not left-sidebar?))]
|
||||
|
||||
[:main.main-content
|
||||
[:section.workspace-content
|
||||
|
@ -106,32 +79,45 @@
|
|||
[:& history-dialog]
|
||||
|
||||
;; Rules
|
||||
(when (contains? flags :rules)
|
||||
[:& horizontal-rule])
|
||||
|
||||
(when (contains? flags :rules)
|
||||
[:& vertical-rule])
|
||||
(when (contains? layout :rules)
|
||||
[:*
|
||||
[:& horizontal-rule]
|
||||
[:& vertical-rule]])
|
||||
|
||||
[:section.workspace-viewport {:id "workspace-viewport" :ref canvas}
|
||||
[:& viewport {:page page :key (:id page)}]]]
|
||||
[:& viewport {:page page}]]]
|
||||
|
||||
;; Aside
|
||||
(when left-sidebar?
|
||||
[:& left-sidebar {:page page :flags flags}])
|
||||
[:& left-sidebar {:page page :layout layout}])
|
||||
(when right-sidebar?
|
||||
[:& right-sidebar {:page page :flags flags}])]]))
|
||||
[:& right-sidebar {:page page :layout layout}])]))
|
||||
|
||||
(mf/defc workspace
|
||||
[{:keys [page-id] :as props}]
|
||||
(let [layout (mf/deref refs/workspace-layout)
|
||||
flags (mf/deref refs/selected-flags)
|
||||
page (mf/deref refs/workspace-page)]
|
||||
|
||||
[:*
|
||||
[:& messages-widget]
|
||||
[:& header {:page page :flags flags}]
|
||||
|
||||
(when (:colorpalette flags)
|
||||
[:& colorpalette])
|
||||
|
||||
(when (and layout page)
|
||||
[:& workspace-content {:layout layout :page page}])]))
|
||||
|
||||
(mf/defc workspace-page
|
||||
[{:keys [project-id page-id] :as props}]
|
||||
(let [page-iref (mf/use-memo {:deps #js [project-id page-id]
|
||||
:fn #(-> (l/in [:pages page-id])
|
||||
(l/derive st/state))})
|
||||
page (mf/deref page-iref)]
|
||||
|
||||
(mf/use-effect
|
||||
{:deps #js [project-id page-id]
|
||||
:fn #(st/emit! (dw/initialize project-id page-id))})
|
||||
(mf/use-effect
|
||||
{:deps #js [page-id]
|
||||
:fn (fn []
|
||||
(let [sub (shortcuts/init)]
|
||||
(st/emit! (udw/initialize project-id page-id))
|
||||
#(rx/cancel! sub)))})
|
||||
|
||||
[:> rdnd/provider {:backend rdnd/html5}
|
||||
(when page
|
||||
[:& workspace {:page page}])]))
|
||||
[:> rdnd/provider {:backend rdnd/html5}
|
||||
[:& workspace {:page-id page-id :key page-id}]])
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as ds]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -73,17 +72,16 @@
|
|||
(ptk/reify ::start-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace :drawing-lock] #(if (nil? %) id %)))
|
||||
(update-in state [:workspace-local :drawing-lock] #(if (nil? %) id %)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
lock (get-in state [:workspace :drawing-lock])]
|
||||
(let [lock (get-in state [:workspace-local :drawing-lock])]
|
||||
(if (= lock id)
|
||||
(rx/merge
|
||||
(->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
||||
(rx/map (fn [_] #(update % :workspace-local dissoc :drawing-lock))))
|
||||
(rx/of (handle-drawing type)))
|
||||
(rx/empty)))))))
|
||||
|
||||
|
@ -98,9 +96,8 @@
|
|||
(ptk/reify ::handle-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
data (make-minimal-shape type)]
|
||||
(update-in state [:workspace pid :drawing] merge data)))
|
||||
(let [data (make-minimal-shape type)]
|
||||
(update-in state [:workspace-local :drawing] merge data)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -111,13 +108,12 @@
|
|||
|
||||
(def handle-drawing-generic
|
||||
(letfn [(initialize-drawing [state point]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
shape (get-in state [:workspace pid :drawing])
|
||||
(let [shape (get-in state [:workspace-local :drawing])
|
||||
shape (geom/setup shape {:x1 (:x point)
|
||||
:y1 (:y point)
|
||||
:x2 (+ (:x point) 2)
|
||||
:y2 (+ (:y point) 2)})]
|
||||
(assoc-in state [:workspace pid :drawing] (assoc shape ::initialized? true))))
|
||||
(assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true))))
|
||||
|
||||
(resize-shape [shape point lock?]
|
||||
(let [shape (-> (geom/shape->rect-shape shape)
|
||||
|
@ -128,14 +124,12 @@
|
|||
(assoc shape :modifier-mtx mtx)))
|
||||
|
||||
(update-drawing [state point lock?]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid :drawing] resize-shape point lock?)))]
|
||||
(update-in state [:workspace-local :drawing] resize-shape point lock?))]
|
||||
|
||||
(ptk/reify ::handle-drawing-generic
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||
(let [{:keys [zoom flags]} (:workspace-local state)
|
||||
align? (refs/alignment-activated? flags)
|
||||
|
||||
stoper? #(or (uws/mouse-up? %) (= % :interrupt))
|
||||
|
@ -165,30 +159,26 @@
|
|||
(= 13 (:key event)))))
|
||||
|
||||
(initialize-drawing [state point]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(-> state
|
||||
(assoc-in [:workspace pid :drawing :segments] [point point])
|
||||
(assoc-in [:workspace pid :drawing ::initialized?] true))))
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :drawing :segments] [point point])
|
||||
(assoc-in [:workspace-local :drawing ::initialized?] true)))
|
||||
|
||||
(insert-point-segment [state point]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid :drawing :segments] (fnil conj []) point)))
|
||||
(update-in state [:workspace-local :drawing :segments] (fnil conj []) point))
|
||||
|
||||
(update-point-segment [state index point]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
segments (count (get-in state [:workspace pid :drawing :segments]))
|
||||
(let [segments (count (get-in state [:workspace-local :drawing :segments]))
|
||||
exists? (< -1 index segments)]
|
||||
(cond-> state
|
||||
exists? (assoc-in [:workspace pid :drawing :segments index] point))))
|
||||
exists? (assoc-in [:workspace-local :drawing :segments index] point))))
|
||||
|
||||
(remove-dangling-segmnet [state]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid :drawing :segments] #(vec (butlast %)))))]
|
||||
(update-in state [:workspace-local :drawing :segments] #(vec (butlast %))))]
|
||||
|
||||
(ptk/reify ::handle-drawing-path
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||
(let [{:keys [zoom flags]} (:workspace-local state)
|
||||
|
||||
align? (refs/alignment-activated? flags)
|
||||
last-point (volatile! (gpt/divide @uws/mouse-position zoom))
|
||||
|
@ -252,29 +242,23 @@
|
|||
(and (uws/mouse-event? event) (= type :up))))
|
||||
|
||||
(initialize-drawing [state]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(assoc-in state [:workspace pid :drawing ::initialized?] true)))
|
||||
(assoc-in state [:workspace-local :drawing ::initialized?] true))
|
||||
|
||||
(insert-point-segment [state point]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid :drawing :segments] (fnil conj []) point)))
|
||||
(update-in state [:workspace-local :drawing :segments] (fnil conj []) point))
|
||||
|
||||
(simplify-drawing-path [state tolerance]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid :drawing :segments] path/simplify tolerance)))]
|
||||
(update-in state [:workspace-local :drawing :segments] path/simplify tolerance))]
|
||||
|
||||
(ptk/reify ::handle-drawing-curve
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||
|
||||
(let [{:keys [zoom flags]} (:workspace-local state)
|
||||
align? (refs/alignment-activated? flags)
|
||||
stoper (rx/filter stoper-event? stream)
|
||||
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/map #(gpt/divide % zoom)))]
|
||||
|
||||
(rx/concat
|
||||
(rx/of initialize-drawing)
|
||||
(->> mouse
|
||||
|
@ -287,8 +271,7 @@
|
|||
(ptk/reify ::handle-finish-drawing
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
shape (get-in state [:workspace pid :drawing])]
|
||||
(let [shape (get-in state [:workspace-local :drawing])]
|
||||
(rx/concat
|
||||
(rx/of dw/clear-drawing)
|
||||
(when (::initialized? shape)
|
||||
|
@ -305,8 +288,7 @@
|
|||
(ptk/reify ::close-drawing-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(assoc-in state [:workspace pid :drawing :close?] true)))))
|
||||
(assoc-in state [:workspace-local :drawing :close?] true))))
|
||||
|
||||
;; --- Components
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
[:ul.options-btn
|
||||
[:li.tooltip.tooltip-bottom.view-mode
|
||||
{:alt (tr "header.view-mode")
|
||||
:on-click #(st/emit! (dw/->OpenView (:id page)))
|
||||
;; :on-click #(st/emit! (dw/->OpenView (:id page)))
|
||||
}
|
||||
i/play]]
|
||||
[:& zoom-widget]]
|
||||
|
|
|
@ -72,7 +72,8 @@
|
|||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-current-modifier-in-bulk ids))))))))
|
||||
(rx/of (dw/materialize-current-modifier-in-bulk ids)
|
||||
::dw/page-data-update)))))))
|
||||
|
||||
;; --- Controls (Component)
|
||||
|
||||
|
@ -221,21 +222,18 @@
|
|||
[:& controls {:shape shape :zoom zoom :on-click on-click}]))
|
||||
|
||||
(mf/defc selection-handlers
|
||||
[{:keys [wst] :as props}]
|
||||
(let [shapes-map (mf/deref refs/shapes-by-id)
|
||||
shapes (map #(get shapes-map %) (:selected wst))
|
||||
edition (:edition wst)
|
||||
zoom (:zoom wst 1)
|
||||
[{:keys [selected edition zoom] :as props}]
|
||||
(let [data (mf/deref refs/workspace-data)
|
||||
shapes (map #(get-in data [:shapes-by-id %]) selected)
|
||||
num (count shapes)
|
||||
{:keys [id type] :as shape} (first shapes)]
|
||||
|
||||
(cond
|
||||
(zero? num)
|
||||
nil
|
||||
|
||||
(> num 1)
|
||||
[:& multiple-selection-handlers {:shapes shapes
|
||||
:selected (:selected wst)
|
||||
:selected selected
|
||||
:zoom zoom}]
|
||||
|
||||
(and (= type :text)
|
||||
|
|
|
@ -20,27 +20,27 @@
|
|||
|
||||
(mf/defc left-sidebar
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [flags page] :as props}]
|
||||
[{:keys [layout page] :as props}]
|
||||
[:aside.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
(when (contains? layout :sitemap)
|
||||
[:& sitemap-toolbox {:project-id (:project-id page)
|
||||
:current-page-id (:id page)
|
||||
:page page}])
|
||||
(when (contains? flags :document-history)
|
||||
(when (contains? layout :document-history)
|
||||
[:& history-toolbox])
|
||||
(when (contains? flags :layers)
|
||||
(when (contains? layout :layers)
|
||||
[:& layers-toolbox {:page page}])]])
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
(mf/defc right-sidebar
|
||||
[{:keys [flags page] :as props}]
|
||||
[{:keys [layout page] :as props}]
|
||||
[:aside#settings-bar.settings-bar
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :drawtools)
|
||||
[:& draw-toolbox {:flags flags}])
|
||||
(when (contains? flags :element-options)
|
||||
(when (contains? layout :drawtools)
|
||||
[:& draw-toolbox {:layout layout}])
|
||||
(when (contains? layout :element-options)
|
||||
[:& options-toolbox {:page page}])
|
||||
#_(when (contains? flags :icons)
|
||||
#_(when (contains? layout :icons)
|
||||
(icons-toolbox))]])
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
|
||||
(mf/defc layer-item
|
||||
[{:keys [shape selected index] :as props}]
|
||||
;; (prn "layer-item" index (:name shape))
|
||||
(letfn [(toggle-blocking [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [{:keys [id blocked]} shape]
|
||||
|
@ -107,7 +108,7 @@
|
|||
(dw/select-shape id)))))
|
||||
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/persist-page (:page shape))))
|
||||
(st/emit! ::dw/page-data-update))
|
||||
|
||||
(on-hover [item monitor]
|
||||
(st/emit! (dw/change-shape-order {:id (:shape-id item)
|
||||
|
@ -168,10 +169,9 @@
|
|||
(dw/select-shape id)))))
|
||||
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/persist-page (:page canvas))))
|
||||
(st/emit! ::dw/page-data-update))
|
||||
|
||||
(on-hover [item monitor]
|
||||
(prn "canvas-item$hover" (:id canvas))
|
||||
(st/emit! (dw/change-canvas-order {:id (:canvas-id item)
|
||||
:index index})))]
|
||||
(let [selected? (contains? selected (:id canvas))
|
||||
|
@ -236,21 +236,25 @@
|
|||
;; --- Layers Toolbox
|
||||
|
||||
(mf/defc layers-toolbox
|
||||
[{:keys [page selected] :as props}]
|
||||
[{:keys [page] :as props}]
|
||||
(let [on-click #(st/emit! (dw/toggle-flag :layers))
|
||||
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
shapes-by-id (mf/deref shapes-iref)
|
||||
canvas (->> (:canvas page)
|
||||
data (mf/deref refs/workspace-data)
|
||||
|
||||
shapes-by-id (:shapes-by-id data)
|
||||
|
||||
canvas (->> (:canvas data)
|
||||
(map #(get shapes-by-id %))
|
||||
(enumerate))
|
||||
all-shapes (->> (:shapes page)
|
||||
(map #(get shapes-by-id %)))
|
||||
|
||||
shapes (->> all-shapes
|
||||
(filter #(not (:canvas %)))
|
||||
(enumerate))
|
||||
shapes (->> (:shapes data)
|
||||
(map #(get shapes-by-id %)))
|
||||
|
||||
all-shapes (enumerate all-shapes)]
|
||||
all-shapes (enumerate shapes)
|
||||
unc-shapes (->> shapes
|
||||
(filter #(nil? (:canvas %)))
|
||||
(enumerate))]
|
||||
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
@ -261,5 +265,5 @@
|
|||
[:& canvas-list {:canvas canvas
|
||||
:shapes all-shapes
|
||||
:selected selected}]
|
||||
[:& layers-list {:shapes shapes
|
||||
[:& layers-list {:shapes unc-shapes
|
||||
:selected selected}]]]))
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[rumext.alpha :as mf]
|
||||
[rumext.core :as mx]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
|
|
|
@ -15,11 +15,12 @@
|
|||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap-forms :refer [page-form-dialog]]
|
||||
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.data :refer [classnames enumerate]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
@ -37,14 +38,12 @@
|
|||
(dom/stop-propagation event)
|
||||
(modal/show! confirm-dialog {:on-accept delete}))
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/rehash-pages (:project-id page))))
|
||||
(prn "TODO"))
|
||||
(on-hover [item monitor]
|
||||
(st/emit! (udp/move-page {:project-id (:project-id item)
|
||||
:page-id (:page-id item)
|
||||
:index index})))]
|
||||
(st/emit! (dw/change-page-order {:id (:id item)
|
||||
:index index})))]
|
||||
(let [[dprops ref] (use-sortable {:type "page-item"
|
||||
:data {:page-id (:id page)
|
||||
:project-id (:project-id page)
|
||||
:data {:id (:id page)
|
||||
:index index}
|
||||
:on-hover on-hover
|
||||
:on-drop on-drop})]
|
||||
|
@ -64,40 +63,52 @@
|
|||
(when deletable?
|
||||
[:a {:on-click on-delete} i/trash])]]])))
|
||||
|
||||
;; --- Pages List
|
||||
|
||||
(defn- make-pages-iref
|
||||
[{:keys [id pages] :as project}]
|
||||
(-> (l/lens (fn [s] (into [] (map #(get-in s [:pages %])) pages)))
|
||||
(l/derive st/state {:equals? =})))
|
||||
;; --- Page Item Wrapper
|
||||
|
||||
(def ^:private pages-map-iref
|
||||
(-> (l/key :pages)
|
||||
(defn- make-page-ref
|
||||
[page-id]
|
||||
(-> (l/in [:pages page-id])
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc page-item-wrapper
|
||||
[{:keys [page-id index deletable? selected?] :as props}]
|
||||
(let [page-ref (mf/use-memo {:deps #js [page-id]
|
||||
:fn #(make-page-ref page-id)})
|
||||
page (mf/deref page-ref)]
|
||||
[:& page-item {:page page
|
||||
:index index
|
||||
:deletable? deletable?
|
||||
:selected? selected?}]))
|
||||
|
||||
;; --- Pages List
|
||||
|
||||
(mf/defc pages-list
|
||||
[{:keys [project current-page-id] :as props}]
|
||||
(let [pages-map (mf/deref pages-map-iref)
|
||||
pages (->> (vals pages-map)
|
||||
(filter #(= (:project-id %) (:id project))))
|
||||
(let [pages (enumerate (:pages project))
|
||||
deletable? (> (count pages) 1)]
|
||||
[:ul.element-list
|
||||
(for [[index item] (map-indexed vector pages)]
|
||||
[:& page-item {:page item
|
||||
:index index
|
||||
:deletable? deletable?
|
||||
:selected? (= (:id item) current-page-id)
|
||||
:key (:id item)}])]))
|
||||
(for [[index page-id] pages]
|
||||
[:& page-item-wrapper
|
||||
{:page-id page-id
|
||||
:index index
|
||||
:deletable? deletable?
|
||||
:selected? (= page-id current-page-id)
|
||||
:key page-id}])]))
|
||||
|
||||
;; --- Sitemap Toolbox
|
||||
|
||||
(def ^:private workspace-project
|
||||
(letfn [(selector [state]
|
||||
(let [project-id (get-in state [:workspace-page :project-id])]
|
||||
(get-in state [:projects project-id])))]
|
||||
(-> (l/lens selector)
|
||||
(l/derive st/state))))
|
||||
|
||||
(mf/defc sitemap-toolbox
|
||||
[{:keys [project-id current-page-id] :as props}]
|
||||
(let [project-iref (mf/use-memo {:deps #js [project-id]
|
||||
:fn #(-> (l/in [:projects project-id])
|
||||
(l/derive st/state))})
|
||||
project (mf/deref project-iref)
|
||||
create #(modal/show! page-form-dialog {:page {:project project-id}})
|
||||
(let [project (mf/deref workspace-project)
|
||||
create #(modal/show! page-form-dialog {:page {:project-id project-id}})
|
||||
close #(st/emit! (dw/toggle-flag :sitemap))]
|
||||
[:div.sitemap.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.dom :as dom]
|
||||
|
@ -20,22 +21,12 @@
|
|||
[uxbox.util.i18n :refer [tr]]))
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::project ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::name ::us/not-empty-string)
|
||||
(s/def ::width ::us/number-str)
|
||||
(s/def ::height ::us/number-str)
|
||||
|
||||
(s/def ::page-form
|
||||
(s/keys :req-un [::id
|
||||
::project
|
||||
::name
|
||||
::width
|
||||
::height]))
|
||||
|
||||
(def defaults
|
||||
{:name ""
|
||||
:width "1366"
|
||||
:height "768"})
|
||||
(s/keys :req-un [::project-id ::name]
|
||||
:opt-un [::id]))
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
|
@ -43,20 +34,13 @@
|
|||
(modal/hide!)
|
||||
(let [data (:clean-data form)]
|
||||
(if (nil? (:id data))
|
||||
(st/emit! (udp/form->create-page data))
|
||||
(st/emit! (udp/form->update-page data)))))
|
||||
|
||||
(defn- swap-size
|
||||
[event {:keys [data] :as form}]
|
||||
(swap! data assoc
|
||||
:width (:height data)
|
||||
:height (:width data)))
|
||||
(st/emit! (udp/create-page data))
|
||||
(st/emit! (udp/rename-page data)))))
|
||||
|
||||
(defn- initial-data
|
||||
[page]
|
||||
(merge {:name "" :width "1366" :height "768"}
|
||||
(select-keys page [:name :id :project])
|
||||
(select-keys (:metadata page) [:width :height])))
|
||||
(merge {:name ""}
|
||||
(select-keys page [:name :id :project-id])))
|
||||
|
||||
(mf/defc page-form
|
||||
[{:keys [page] :as props}]
|
||||
|
@ -71,32 +55,6 @@
|
|||
:on-change (fm/on-input-change form :name)
|
||||
:value (:name data)
|
||||
:auto-focus true}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span (tr "ds.width")]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder (tr "ds.width")
|
||||
:name "width"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 5000
|
||||
:class (fm/error-class form :width)
|
||||
:on-blur (fm/on-input-blur form :width)
|
||||
:on-change (fm/on-input-change form :width)
|
||||
:value (:width data)}]]
|
||||
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:span (tr "ds.height")]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder (tr "ds.height")
|
||||
:name "height"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 5000
|
||||
:class (fm/error-class form :height)
|
||||
:on-blur (fm/on-input-blur form :height)
|
||||
:on-change (fm/on-input-change form :height)
|
||||
:value (:height data)}]]]
|
||||
[:input.btn-primary
|
||||
{:value (tr "ds.go")
|
||||
:type "submit"
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
[uxbox.main.ui.workspace.streams :as uws]
|
||||
[uxbox.main.ui.workspace.drawarea :refer [start-drawing]]
|
||||
|
||||
[uxbox.main.ui.shapes :as uus]
|
||||
[uxbox.main.ui.shapes :refer [shape-wrapper]]
|
||||
[uxbox.main.ui.workspace.drawarea :refer [draw-area]]
|
||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
|
||||
|
@ -74,17 +74,15 @@
|
|||
|
||||
(def ^:private handle-selrect
|
||||
(letfn [(update-state [state position]
|
||||
(let [id (get-in state [:workspace :current])
|
||||
selrect (get-in state [:workspace id :selrect])]
|
||||
(let [selrect (get-in state [:workspace-local :selrect])]
|
||||
(if selrect
|
||||
(assoc-in state [:workspace id :selrect]
|
||||
(assoc-in state [:workspace-local :selrect]
|
||||
(dw/selection->rect (assoc selrect :stop position)))
|
||||
(assoc-in state [:workspace id :selrect]
|
||||
(assoc-in state [:workspace-local :selrect]
|
||||
(dw/selection->rect {:start position :stop position})))))
|
||||
|
||||
(clear-state [state]
|
||||
(let [id (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace id] dissoc :selrect)))]
|
||||
(update state :workspace-local dissoc :selrect))]
|
||||
(ptk/reify ::handle-selrect
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -129,11 +127,28 @@
|
|||
|
||||
;; --- Viewport
|
||||
|
||||
(mf/defc canvas-and-shapes
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[props]
|
||||
(let [data (mf/deref refs/workspace-data)
|
||||
shapes-by-id (:shapes-by-id data)
|
||||
shapes (map #(get shapes-by-id %) (:shapes data []))
|
||||
canvas (map #(get shapes-by-id %) (:canvas data []))]
|
||||
[:*
|
||||
(for [item canvas]
|
||||
[:& shape-wrapper {:shape item :key (:id item)}])
|
||||
(for [item (reverse shapes)]
|
||||
[:& shape-wrapper {:shape item :key (:id item)}])]))
|
||||
|
||||
(mf/defc viewport
|
||||
[{:keys [page] :as props}]
|
||||
(let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/deref refs/workspace)
|
||||
(let [{:keys [drawing-tool
|
||||
zoom
|
||||
flags
|
||||
edition
|
||||
selected]
|
||||
:as local} (mf/deref refs/workspace-local)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
tooltip (or tooltip (get-shape-tooltip drawing-tool))
|
||||
zoom (or zoom 1)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
|
@ -230,9 +245,6 @@
|
|||
;; (prn "viewport$render")
|
||||
[:*
|
||||
[:& coordinates {:zoom zoom}]
|
||||
#_[:div.tooltip-container
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:tooltip tooltip}])]
|
||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||
:height (* c/viewport-height zoom)
|
||||
:ref viewport-ref
|
||||
|
@ -244,23 +256,23 @@
|
|||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||
(when page
|
||||
[:*
|
||||
[:& uus/canvas-and-shapes {:page page}]
|
||||
|
||||
(when (seq (:selected wst))
|
||||
[:& selection-handlers {:wst wst}])
|
||||
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])])
|
||||
[:*
|
||||
[:& canvas-and-shapes]
|
||||
|
||||
(when (seq selected)
|
||||
[:& selection-handlers {:selected selected
|
||||
:zoom zoom
|
||||
:edition edition}])
|
||||
|
||||
(when-let [drawing-shape (:drawing local)]
|
||||
[:& draw-area {:shape drawing-shape
|
||||
:zoom zoom
|
||||
:modifiers (:modifiers local)}])]
|
||||
|
||||
(if (contains? flags :grid)
|
||||
[:& grid {:page page}])]
|
||||
(when (contains? flags :ruler)
|
||||
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
|
||||
[:& selrect {:data (:selrect wst)}]]])))
|
||||
|
||||
(when (contains? flags :ruler)
|
||||
[:& ruler {:zoom zoom :ruler (:ruler local)}])
|
||||
|
||||
[:& selrect {:data (:selrect local)}]]])))
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
[v]
|
||||
(cond
|
||||
(number? v) v
|
||||
(re-matches number-rx v) (js/parseFloat v)
|
||||
(and (string? v) (re-matches number-rx v)) (js/parseFloat v)
|
||||
:else ::s/invalid))
|
||||
|
||||
(s/def ::number
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(ns uxbox.tests.test-main-data-shapes-impl
|
||||
(:require [cljs.test :as t :include-macros true]
|
||||
#_(:require [cljs.test :as t :include-macros true]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.main.data.shapes :as impl]))
|
||||
|
@ -8,119 +8,119 @@
|
|||
;; Duplicate (one shape)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn constantly-inc
|
||||
[init]
|
||||
(let [v (atom init)]
|
||||
(fn [& args]
|
||||
(let [result @v]
|
||||
(swap! v inc)
|
||||
result))))
|
||||
;; (defn constantly-inc
|
||||
;; [init]
|
||||
;; (let [v (atom init)]
|
||||
;; (fn [& args]
|
||||
;; (let [result @v]
|
||||
;; (swap! v inc)
|
||||
;; result))))
|
||||
|
||||
;; duplicate shape: duplicate simple shape
|
||||
(t/deftest duplicate-shapes-test1
|
||||
(let [initial {:pages {1 {:id 1 :shapes [1]}}
|
||||
:shapes {1 {:id 1 :page 1 :name "a"}}}
|
||||
;; ;; duplicate shape: duplicate simple shape
|
||||
;; (t/deftest duplicate-shapes-test1
|
||||
;; (let [initial {:pages {1 {:id 1 :shapes [1]}}
|
||||
;; :shapes {1 {:id 1 :page 1 :name "a"}}}
|
||||
|
||||
expected (-> initial
|
||||
(assoc-in [:pages 1 :shapes] [2 1])
|
||||
(assoc-in [:shapes 2] {:id 2 :page 1 :name "a-copy-1"}))]
|
||||
;; expected (-> initial
|
||||
;; (assoc-in [:pages 1 :shapes] [2 1])
|
||||
;; (assoc-in [:shapes 2] {:id 2 :page 1 :name "a-copy-1"}))]
|
||||
|
||||
(with-redefs [uxbox.util.uuid/random (constantly 2)]
|
||||
(let [result (impl/duplicate-shapes initial [1])]
|
||||
;; (pprint expected)
|
||||
;; (pprint result)
|
||||
(t/is (= result expected))))))
|
||||
;; (with-redefs [uxbox.util.uuid/random (constantly 2)]
|
||||
;; (let [result (impl/duplicate-shapes initial [1])]
|
||||
;; ;; (pprint expected)
|
||||
;; ;; (pprint result)
|
||||
;; (t/is (= result expected))))))
|
||||
|
||||
;; duplicate shape: duplicate inside group
|
||||
(t/deftest duplicate-shapes-test2
|
||||
(let [initial {:pages {1 {:id 1 :shapes [1]}}
|
||||
:shapes {1 {:id 1 :name "1" :page 1
|
||||
:type :group
|
||||
:items [2 3]}
|
||||
2 {:id 2 :name "2" :page 1 :group 1}
|
||||
3 {:id 3 :name "3" :page 1 :group 1}}}
|
||||
;; ;; duplicate shape: duplicate inside group
|
||||
;; (t/deftest duplicate-shapes-test2
|
||||
;; (let [initial {:pages {1 {:id 1 :shapes [1]}}
|
||||
;; :shapes {1 {:id 1 :name "1" :page 1
|
||||
;; :type :group
|
||||
;; :items [2 3]}
|
||||
;; 2 {:id 2 :name "2" :page 1 :group 1}
|
||||
;; 3 {:id 3 :name "3" :page 1 :group 1}}}
|
||||
|
||||
expected (-> initial
|
||||
(assoc-in [:shapes 1 :items] [5 4 2 3])
|
||||
(assoc-in [:shapes 4] {:id 4 :name "3-copy-1" :page 1 :group 1})
|
||||
(assoc-in [:shapes 5] {:id 5 :name "2-copy-1" :page 1 :group 1}))]
|
||||
(with-redefs [uxbox.util.uuid/random (constantly-inc 4)]
|
||||
(let [result (impl/duplicate-shapes initial [2 3])]
|
||||
;; (pprint expected)
|
||||
;; (pprint result)
|
||||
(t/is (= result expected))))))
|
||||
;; expected (-> initial
|
||||
;; (assoc-in [:shapes 1 :items] [5 4 2 3])
|
||||
;; (assoc-in [:shapes 4] {:id 4 :name "3-copy-1" :page 1 :group 1})
|
||||
;; (assoc-in [:shapes 5] {:id 5 :name "2-copy-1" :page 1 :group 1}))]
|
||||
;; (with-redefs [uxbox.util.uuid/random (constantly-inc 4)]
|
||||
;; (let [result (impl/duplicate-shapes initial [2 3])]
|
||||
;; ;; (pprint expected)
|
||||
;; ;; (pprint result)
|
||||
;; (t/is (= result expected))))))
|
||||
|
||||
;; duplicate shape: duplicate mixed bag
|
||||
(t/deftest duplicate-shapes-test3
|
||||
(let [initial {:pages {1 {:id 1 :shapes [1 4]}}
|
||||
:shapes {1 {:id 1 :name "1" :page 1
|
||||
:type :group
|
||||
:items [2 3]}
|
||||
2 {:id 2 :name "2" :page 1 :group 1}
|
||||
3 {:id 3 :name "3" :page 1 :group 1}
|
||||
4 {:id 4 :name "4" :page 1}}}
|
||||
;; ;; duplicate shape: duplicate mixed bag
|
||||
;; (t/deftest duplicate-shapes-test3
|
||||
;; (let [initial {:pages {1 {:id 1 :shapes [1 4]}}
|
||||
;; :shapes {1 {:id 1 :name "1" :page 1
|
||||
;; :type :group
|
||||
;; :items [2 3]}
|
||||
;; 2 {:id 2 :name "2" :page 1 :group 1}
|
||||
;; 3 {:id 3 :name "3" :page 1 :group 1}
|
||||
;; 4 {:id 4 :name "4" :page 1}}}
|
||||
|
||||
expected (-> initial
|
||||
(assoc-in [:pages 1 :shapes] [6 5 1 4])
|
||||
(assoc-in [:shapes 5] {:id 5 :name "4-copy-1" :page 1})
|
||||
(assoc-in [:shapes 6] {:id 6 :name "3-copy-1" :page 1}))]
|
||||
(with-redefs [uxbox.util.uuid/random (constantly-inc 5)]
|
||||
(let [result (impl/duplicate-shapes initial [3 4])]
|
||||
;; (pprint expected)
|
||||
;; (pprint result)
|
||||
(t/is (= result expected))))))
|
||||
;; expected (-> initial
|
||||
;; (assoc-in [:pages 1 :shapes] [6 5 1 4])
|
||||
;; (assoc-in [:shapes 5] {:id 5 :name "4-copy-1" :page 1})
|
||||
;; (assoc-in [:shapes 6] {:id 6 :name "3-copy-1" :page 1}))]
|
||||
;; (with-redefs [uxbox.util.uuid/random (constantly-inc 5)]
|
||||
;; (let [result (impl/duplicate-shapes initial [3 4])]
|
||||
;; ;; (pprint expected)
|
||||
;; ;; (pprint result)
|
||||
;; (t/is (= result expected))))))
|
||||
|
||||
;; duplicate shape: duplicate one group
|
||||
(t/deftest duplicate-shapes-test4
|
||||
(let [initial {:pages {1 {:id 1 :shapes [1]}}
|
||||
:shapes {1 {:id 1
|
||||
:name "1"
|
||||
:page 1
|
||||
:type :group
|
||||
:items [2]}
|
||||
2 {:id 2
|
||||
:name "2"
|
||||
:page 1
|
||||
:group 1}}}
|
||||
;; ;; duplicate shape: duplicate one group
|
||||
;; (t/deftest duplicate-shapes-test4
|
||||
;; (let [initial {:pages {1 {:id 1 :shapes [1]}}
|
||||
;; :shapes {1 {:id 1
|
||||
;; :name "1"
|
||||
;; :page 1
|
||||
;; :type :group
|
||||
;; :items [2]}
|
||||
;; 2 {:id 2
|
||||
;; :name "2"
|
||||
;; :page 1
|
||||
;; :group 1}}}
|
||||
|
||||
expected (-> initial
|
||||
(assoc-in [:pages 1 :shapes] [3 1])
|
||||
(assoc-in [:shapes 3] {:id 3
|
||||
:name "1-copy-1"
|
||||
:page 1
|
||||
:type :group
|
||||
:items [4]})
|
||||
(assoc-in [:shapes 4] {:id 4
|
||||
:name "2-copy-1"
|
||||
:page 1
|
||||
:group 3}))]
|
||||
(with-redefs [uxbox.util.uuid/random (constantly-inc 3)]
|
||||
(let [result (impl/duplicate-shapes initial [1])]
|
||||
;; (pprint expected)
|
||||
;; (pprint result)
|
||||
(t/is (= result expected))))))
|
||||
;; expected (-> initial
|
||||
;; (assoc-in [:pages 1 :shapes] [3 1])
|
||||
;; (assoc-in [:shapes 3] {:id 3
|
||||
;; :name "1-copy-1"
|
||||
;; :page 1
|
||||
;; :type :group
|
||||
;; :items [4]})
|
||||
;; (assoc-in [:shapes 4] {:id 4
|
||||
;; :name "2-copy-1"
|
||||
;; :page 1
|
||||
;; :group 3}))]
|
||||
;; (with-redefs [uxbox.util.uuid/random (constantly-inc 3)]
|
||||
;; (let [result (impl/duplicate-shapes initial [1])]
|
||||
;; ;; (pprint expected)
|
||||
;; ;; (pprint result)
|
||||
;; (t/is (= result expected))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Delete Shape
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ;; Delete Shape
|
||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; delete shape: delete from page
|
||||
(t/deftest delete-shape-test1
|
||||
(let [initial {:pages {1 {:id 1 :shapes [1 3 4]}}
|
||||
:shapes {1 {:id 1 :page 1
|
||||
:type :group
|
||||
:items [2]}
|
||||
2 {:id 2 :page 1 :group 1}
|
||||
3 {:id 3 :page 1}
|
||||
4 {:id 4 :page 1}}}
|
||||
;; ;; delete shape: delete from page
|
||||
;; (t/deftest delete-shape-test1
|
||||
;; (let [initial {:pages {1 {:id 1 :shapes [1 3 4]}}
|
||||
;; :shapes {1 {:id 1 :page 1
|
||||
;; :type :group
|
||||
;; :items [2]}
|
||||
;; 2 {:id 2 :page 1 :group 1}
|
||||
;; 3 {:id 3 :page 1}
|
||||
;; 4 {:id 4 :page 1}}}
|
||||
|
||||
shape (get-in initial [:shapes 4])
|
||||
expected {:pages {1 {:id 1 :shapes [1 3]}}
|
||||
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
|
||||
2 {:id 2 :page 1 :group 1}
|
||||
3 {:id 3 :page 1}}}
|
||||
;; shape (get-in initial [:shapes 4])
|
||||
;; expected {:pages {1 {:id 1 :shapes [1 3]}}
|
||||
;; :shapes {1 {:id 1 :page 1 :type :group :items [2]}
|
||||
;; 2 {:id 2 :page 1 :group 1}
|
||||
;; 3 {:id 3 :page 1}}}
|
||||
|
||||
result (impl/dissoc-shape initial shape)]
|
||||
;; (pprint expected)
|
||||
;; (pprint result)
|
||||
(t/is (= result expected))))
|
||||
;; result (impl/dissoc-shape initial shape)]
|
||||
;; ;; (pprint expected)
|
||||
;; ;; (pprint result)
|
||||
;; (t/is (= result expected))))
|
||||
|
|
Loading…
Reference in a new issue