0
Fork 0
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:
Andrey Antukh 2019-12-04 20:13:35 +01:00
parent 8c4bdc3f31
commit af62d949d8
33 changed files with 1025 additions and 1124 deletions

View file

@ -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!

View file

@ -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}}

View file

@ -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)))

View file

@ -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

View file

@ -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()

View file

@ -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"}
}

View file

@ -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))))))

View file

@ -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))))))

View file

@ -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)

View file

@ -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)))

View file

@ -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

View file

@ -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)

View file

@ -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]

View file

@ -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."

View file

@ -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]

View file

@ -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]

View file

@ -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}]])

View file

@ -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

View file

@ -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]]

View file

@ -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)

View file

@ -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))]])

View file

@ -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}]]]))

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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

View file

@ -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"

View file

@ -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)}]]])))

View file

@ -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

View file

@ -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))))