diff --git a/frontend/src/uxbox/main/data/history.cljs b/frontend/src/uxbox/main/data/history.cljs index ada0cdde6..866440d7c 100644 --- a/frontend/src/uxbox/main/data/history.cljs +++ b/frontend/src/uxbox/main/data/history.cljs @@ -12,6 +12,8 @@ [uxbox.util.data :refer [replace-by-id index-by]])) +;; TODO: this need refactor (completely broken) + ;; --- Initialize History State (declare fetch-history) @@ -52,7 +54,7 @@ (deftype PinnedPageHistoryFetched [items] ptk/UpdateEvent (update [_ state] - (let [items-map (index-by items :version) + (let [items-map (index-by :version items) items-set (into #{} items)] (update-in state [:workspace :history] (fn [history] @@ -164,7 +166,7 @@ (assoc :history true :data (:data item)))] (-> state - (udp/assoc-page page) + (udp/unpack-page page) (assoc-in [:workspace :history :selected] version))))) (defn select-page-history @@ -203,7 +205,7 @@ (set! noop true) state) (let [packed (get-in state [:packed-pages page-id])] - (-> (udp/assoc-page state packed) + (-> (udp/unpack-page state packed) (assoc-in [:workspace :history :deselecting] true) (assoc-in [:workspace :history :selected] nil)))))) diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index 26e123fc0..d01bac6fd 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -5,18 +5,17 @@ ;; Copyright (c) 2015-2017 Andrey Antukh (ns uxbox.main.data.pages - (:require [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [beicon.core :as rx] - [lentes.core :as l] - [potok.core :as ptk] - [uxbox.main.store :as st] - [uxbox.main.repo :as rp] - [uxbox.main.lenses :as ul] - [uxbox.util.spec :as us] - [uxbox.util.router :as r] - [uxbox.util.timers :as ts] - [uxbox.util.time :as dt])) + (:require + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [lentes.core :as l] + [potok.core :as ptk] + [uxbox.main.repo :as rp] + [uxbox.main.store :as st] + [uxbox.util.spec :as us] + [uxbox.util.timers :as ts] + [uxbox.util.data :refer [index-by-id]])) ;; --- Specs @@ -77,50 +76,43 @@ ;; --- Helpers -;; TODO: make sure remove all :tmp-* related attrs from shape - -(defn pack-page-shapes - "Create a hash-map of shapes indexed by their id that belongs - to the provided page." - [state page] - (let [lookup-shape-xf (map #(get-in state [:shapes %]))] - (reduce (fn reducer [acc {:keys [id type items] :as shape}] - (let [shape (assoc shape :page (:id page))] - (cond - (= type :group) - (reduce reducer - (assoc acc id shape) - (sequence lookup-shape-xf items)) - - (uuid? id) - (assoc acc id shape) - - :else acc))) - {} - (sequence lookup-shape-xf (:shapes page))))) - (defn pack-page "Return a packed version of page object ready for send to remore storage service." [state id] - (let [page (get-in state [:pages id]) - shapes (pack-page-shapes state page)] - (-> page - (assoc-in [:data :shapes] (vec (:shapes page))) - (assoc-in [:data :shapes-map] shapes) - (dissoc :shapes)))) + (letfn [(get-shape [id] + (get-in state [:shapes id])) + (pack-shapes [ids] + (reduce #(assoc %1 %2 (get-shape %2)) {} ids)) + (pack-canvas [ids] + (mapv #(get-in state [:canvas %]) ids))] + (let [page (get-in state [:pages id]) + data {:canvas (pack-canvas (:canvas page)) + :shapes (vec (:shapes page)) + :shapes-map (pack-shapes (:shapes page))}] + (-> page + (assoc :data data) + (dissoc :shapes :canvas))))) -(defn assoc-page +(defn unpack-page "Unpacks packed page object and assocs it to the provided state." [state {:keys [id data] :as page}] (let [shapes (:shapes data) shapes-map (:shapes-map data) + + canvas-data (:canvas data []) + + canvas (mapv :id canvas-data) + canvas-map (index-by-id canvas-data) + page (-> page (dissoc :data) + (assoc :canvas canvas) (assoc :shapes shapes))] (-> state (update :shapes merge shapes-map) + (update :canvas merge canvas-map) (update :pages assoc id page)))) (defn purge-page @@ -160,7 +152,7 @@ (assoc-in $ [:projects id :pages] page-ids) ;; TODO: this is a workaround (assoc-in $ [:projects id :page-id] (first page-ids)) - (reduce assoc-page $ pages) + (reduce unpack-page $ pages) (reduce assoc-packed-page $ pages))))) (defn pages-fetched @@ -194,7 +186,7 @@ ptk/UpdateEvent (update [_ state] (-> state - (assoc-page data) + (unpack-page data) (assoc-packed-page data))) ptk/WatchEvent diff --git a/frontend/src/uxbox/main/data/shapes.cljs b/frontend/src/uxbox/main/data/shapes.cljs index 9d78f2aa9..3c20c9ca5 100644 --- a/frontend/src/uxbox/main/data/shapes.cljs +++ b/frontend/src/uxbox/main/data/shapes.cljs @@ -2,527 +2,537 @@ ;; 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-2017 Andrey Antukh +;; Copyright (c) 2015-2019 Andrey Antukh (ns uxbox.main.data.shapes (:require [cljs.spec.alpha :as s] - [lentes.core :as l] - [beicon.core :as rx] - [potok.core :as ptk] - [uxbox.main.store :as st] - [uxbox.main.constants :as c] - [uxbox.main.refs :as refs] - [uxbox.main.lenses :as ul] [uxbox.main.geom :as geom] - [uxbox.main.workers :as uwrk] - [uxbox.main.data.pages :as udp] - [uxbox.main.data.shapes-impl :as impl] - [uxbox.main.user-events :as uev] - [uxbox.util.data :refer [dissoc-in]] - [uxbox.util.forms :as sc] - [uxbox.util.spec :as us] - [uxbox.util.geom.point :as gpt] [uxbox.util.geom.matrix :as gmt] - [uxbox.util.router :as r] - [uxbox.util.uuid :as uuid])) + [uxbox.util.uuid :as uuid] + [uxbox.util.data :refer [index-of]])) ;; --- Specs +(s/def ::blocked boolean?) +(s/def ::collapsed boolean?) +(s/def ::content string?) (s/def ::fill-color string?) (s/def ::fill-opacity number?) -(s/def ::line-height number?) -(s/def ::letter-spacing number?) -(s/def ::text-align #{"left" "right" "center" "justify"}) (s/def ::font-family string?) +(s/def ::font-size number?) (s/def ::font-style string?) (s/def ::font-weight string?) -(s/def ::font-size number?) -(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed}) -(s/def ::stroke-width number?) -(s/def ::stroke-color string?) -(s/def ::stroke-opacity number?) -(s/def ::rx number?) -(s/def ::ry number?) +(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 ::collapsed boolean?) -(s/def ::hidden boolean?) -(s/def ::blocked boolean?) -(s/def ::locked 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 ::height number?) (s/def ::x1 number?) -(s/def ::y1 number?) (s/def ::x2 number?) +(s/def ::y1 number?) (s/def ::y2 number?) -(s/def ::id uuid?) -(s/def ::page uuid?) -(s/def ::type #{:rect - :group - :path - :circle - :image - :text}) (s/def ::attributes - (s/keys :opt-un [::fill-color + (s/keys :opt-un [::blocked + ::collapsed + ::conent + ::fill-color ::fill-opacity - ::line-height - ::letter-spacing - ::text-align ::font-family + ::font-size ::font-style ::font-weight - ::font-size - ::stroke-style - ::stroke-width + ::hidden + ::letter-spacing + ::line-height + ::locked + ::proportion + ::proportion-lock + ::rx ::ry ::stroke-color ::stroke-opacity - ::rx ::ry + ::stroke-style + ::stroke-width + ::text-align ::x1 ::x2 - ::y1 ::y2 - ::proportion-lock - ::proportion - ::collapsed - ::hidden - ::blocked - ::locked])) + ::y1 ::y2])) + +(s/def ::minimal-shape + (s/keys ::req-un [::id ::page ::type ::name])) (s/def ::shape - (s/merge (s/keys ::req-un [::id ::page ::type]) ::attributes)) + (s/merge ::minimal-shape ::attributes)) (s/def ::rect-like-shape (s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type])) -;; --- Shapes CRUD +;; --- Shape Creation -(deftype AddShape [data] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [shape (geom/setup-proportions data) - page-id (get-in state [:workspace :current])] - (impl/assoc-shape-to-page state shape page-id)))) +(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 add-shape - [data] - {:pre [(us/valid? ::shape data)]} - (AddShape. data)) +(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))))) -;; --- Delete Shape +(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)] + (-> state + (update-in [:pages page :shapes] #(into [] (cons shape-id %))) + (assoc-in [:shapes shape-id] shape)))) -(deftype DeleteShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [shape (get-in state [:shapes id])] - (impl/dissoc-shape state shape)))) +(defn duplicate-shapes' + ([state shapes page] + (duplicate-shapes' state shapes page nil)) + ([state shapes page group] + (letfn [(duplicate-shape [state shape page group] + (if (= (:type shape) :group) + (let [id (uuid/random) + items (:items shape) + name (generate-unique-name state (str (:name shape) "-copy")) + shape (assoc shape + :id id + :page page + :items [] + :name name) + state (if (nil? group) + (-> state + (update-in [:pages page :shapes] + #(into [] (cons id %))) + (assoc-in [:shapes id] shape)) + (-> state + (update-in [:shapes group :items] + #(into [] (cons id %))) + (assoc-in [:shapes id] shape)))] + (->> (map #(get-in state [:shapes %]) items) + (reverse) + (reduce #(duplicate-shape %1 %2 page id) state))) + (let [id (uuid/random) + name (generate-unique-name state (str (:name shape) "-copy")) + shape (-> (dissoc shape :group) + (assoc :id id :page page :name name) + (merge (when group {:group group})))] + (if (nil? group) + (-> state + (update-in [:pages page :shapes] #(into [] (cons id %))) + (assoc-in [:shapes id] shape)) + (-> state + (update-in [:shapes group :items] #(into [] (cons id %))) + (assoc-in [:shapes id] shape))))))] + (reduce #(duplicate-shape %1 %2 page group) state shapes)))) -(defn delete-shape - "Remove the shape using its id." - [id] - {:pre [(uuid? id)]} - (DeleteShape. id)) +(defn duplicate-shapes + ([state shapes] + (duplicate-shapes state shapes nil)) + ([state shapes page] + (letfn [(all-toplevel? [coll] + (every? #(nil? (:group %)) coll)) + (all-same-group? [coll] + (let [group (:group (first coll))] + (every? #(= group (:group %)) coll)))] + (let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))] + (cond + (all-toplevel? shapes) + (let [page (or page (:page (first shapes)))] + (duplicate-shapes' state shapes page)) -;; --- Rename Shape + (all-same-group? shapes) + (let [page (or page (:page (first shapes))) + group (:group (first shapes))] + (duplicate-shapes' state shapes page group)) -(deftype RenameShape [id name] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:shapes id :name] name))) + :else + (let [page (or page (:page (first shapes)))] + (duplicate-shapes' state shapes page))))))) -(defn rename-shape - [id name] - {:pre [(uuid? id) (string? name)]} - (RenameShape. id name)) +;; --- Delete Shapes -;; --- Update Rotation +(defn dissoc-from-index + "A function that dissoc shape from the indexed + data structure of shapes from the state." + [state {:keys [id type] :as shape}] + (if (= :group type) + (let [items (map #(get-in state [:shapes %]) (:items shape))] + (as-> state $ + (update-in $ [:shapes] dissoc id) + (reduce dissoc-from-index $ items))) + (update-in state [:shapes] dissoc id))) -(deftype UpdateShapeRotation [id rotation] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:shapes id :rotation] rotation))) +(defn dissoc-from-page + "Given a shape, try to remove its reference from the + corresponding page." + [state {:keys [id page] :as shape}] + (as-> (get-in state [:pages page :shapes]) $ + (into [] (remove #(= % id) $)) + (assoc-in state [:pages page :shapes] $))) -(defn update-rotation - [id rotation] - {:pre [(uuid? id) - (number? rotation) - (>= rotation 0) - (>= 360 rotation)]} - (UpdateShapeRotation. id rotation)) +(defn dissoc-from-group + "Given a shape, try to remove its reference from the + corresponding group (only if it belongs to one group)." + [state {:keys [id group] :as shape}] + (if-let [group' (get-in state [:shapes group])] + (as-> (:items group') $ + (into [] (remove #(= % id) $)) + (assoc-in state [:shapes group :items] $)) + state)) -;; --- Update Dimensions +(declare dissoc-shape) -(deftype UpdateDimensions [id dimensions] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] geom/resize-dim dimensions))) +(defn clear-empty-groups + "Given the shape, try to clean all empty groups + that this shape belongs to. -(s/def ::update-dimensions-opts - (s/keys :opt-un [::width ::height])) + The main purpose of this function is remove the + all empty parent groups of recently removed + shape." + [state {:keys [group] :as shape}] + (if-let [group' (get-in state [:shapes group])] + (if (empty? (:items group')) + (-> (dissoc-shape state group') + (update-in [:workspace :selected] disj (:id group'))) + state) + state)) -(defn update-dimensions - "A helper event just for update the position - of the shape using the width and height attrs - instread final point of coordinates." - [id opts] - {:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]} - (UpdateDimensions. id opts)) +(defn dissoc-shape + "Given a shape, removes it from the state." + [state shape] + (as-> state $ + (dissoc-from-page $ shape) + (dissoc-from-group $ shape) + (dissoc-from-index $ shape) + (clear-empty-groups $ shape))) -;; --- Update Shape Position +;; --- Shape Movements -(deftype UpdateShapePosition [id point] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] geom/absolute-move point))) +(defn- drop-at-index + [index coll v] + (let [[fst snd] (split-at index coll)] + (into [] (concat fst [v] snd)))) -(defn update-position - "Update the start position coordenate of the shape." - [id point] - {:pre [(uuid? id) (gpt/point? point)]} - (UpdateShapePosition. id point)) +(defn drop-relative + [state loc sid] + {:pre [(not (nil? sid))]} + (let [shape (get-in state [:shapes (first sid)]) + {:keys [page group]} shape + sid (:id shape) -;; --- Update Shape Text + shapes (if group + (get-in state [:shapes group :items]) + (get-in state [:pages page :shapes])) -(deftype UpdateShapeTextContent [id text] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:shapes id :content] text))) + index (case loc + :first 0 + :after (min (- (count shapes) 1) (inc (index-of shapes sid))) + :before (max 0 (- (index-of shapes sid) 1)) + :last (- (count shapes) 1)) -(defn update-text - [id text] - {:pre [(uuid? id) (string? text)]} - (UpdateShapeTextContent. id text)) + state (-> state + (dissoc-from-page shape) + (dissoc-from-group shape)) -;; --- Update Shape Attrs + shapes (if group + (get-in state [:shapes group :items]) + (get-in state [:pages page :shapes])) -(declare UpdateAttrs) -;; TODO: moved -(deftype UpdateAttrs [id attrs] - ptk/WatchEvent - (watch [_ state stream] - (let [{:keys [type] :as shape} (get-in state [:shapes id])] - (if (= type :group) - (rx/from-coll (map #(UpdateAttrs. % attrs) (:items shape))) - (rx/of #(update-in % [:shapes id] merge attrs)))))) + shapes (drop-at-index index shapes sid)] -(defn update-attrs - [id attrs] - {:pre [(uuid? id) (us/valid? ::attributes attrs)]} - (let [atts (us/extract attrs ::attributes)] - (UpdateAttrs. id attrs))) + (if group + (as-> state $ + (assoc-in $ [:shapes group :items] shapes) + (update-in $ [:shapes sid] assoc :group group) + (clear-empty-groups $ shape)) + (as-> state $ + (assoc-in $ [:pages page :shapes] shapes) + (update-in $ [:shapes sid] dissoc :group) + (clear-empty-groups $ shape))))) -;; --- Shape Proportions +(defn drop-aside + [state loc tid sid] + {:pre [(not= tid sid) + (not (nil? tid)) + (not (nil? sid))]} + (let [{:keys [page group]} (get-in state [:shapes tid]) + source (get-in state [:shapes sid]) -(deftype LockShapeProportions [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [[width height] (-> (get-in state [:shapes id]) - (geom/size) - (keep [:width :height])) - proportion (/ width height)] - (update-in state [:shapes id] assoc - :proportion proportion - :proportion-lock true)))) + state (-> state + (dissoc-from-page source) + (dissoc-from-group source)) -(defn lock-proportions - "Mark proportions of the shape locked and save the current - proportion as additional precalculated property." - [id] - {:pre [(uuid? id)]} - (LockShapeProportions. id)) + shapes (if group + (get-in state [:shapes group :items]) + (get-in state [:pages page :shapes])) -(deftype UnlockShapeProportions [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:shapes id :proportion-lock] false))) + index (case loc + :after (inc (index-of shapes tid)) + :before (index-of shapes tid)) -(defn unlock-proportions - [id] - {:pre [(uuid? id)]} - (UnlockShapeProportions. id)) + shapes (drop-at-index index shapes sid)] + (if group + (as-> state $ + (assoc-in $ [:shapes group :items] shapes) + (update-in $ [:shapes sid] assoc :group group) + (clear-empty-groups $ source)) + (as-> state $ + (assoc-in $ [:pages page :shapes] shapes) + (update-in $ [:shapes sid] dissoc :group) + (clear-empty-groups $ source))))) -;; --- Group Collapsing +(def drop-after #(drop-aside %1 :after %2 %3)) +(def drop-before #(drop-aside %1 :before %2 %3)) -(deftype CollapseGroupShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] assoc :collapsed true))) - -(defn collapse-shape - [id] - {:pre [(uuid? id)]} - (CollapseGroupShape. id)) - -(deftype UncollapseGroupShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] assoc :collapsed false))) - -(defn uncollapse-shape - [id] - {:pre [(uuid? id)]} - (UncollapseGroupShape. id)) - -;; --- Shape Visibility - -(deftype HideShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (letfn [(mark-hidden [state id] - (let [shape (get-in state [:shapes id])] - (if (= :group (:type shape)) - (as-> state $ - (assoc-in $ [:shapes id :hidden] true) - (reduce mark-hidden $ (:items shape))) - (assoc-in state [:shapes id :hidden] true))))] - (mark-hidden state id)))) - -(defn hide-shape - [id] - {:pre [(uuid? id)]} - (HideShape. id)) - -(deftype ShowShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (letfn [(mark-visible [state id] - (let [shape (get-in state [:shapes id])] - (if (= :group (:type shape)) - (as-> state $ - (assoc-in $ [:shapes id :hidden] false) - (reduce mark-visible $ (:items shape))) - (assoc-in state [:shapes id :hidden] false))))] - (mark-visible state id)))) - -(defn show-shape - [id] - {:pre [(uuid? id)]} - (ShowShape. id)) - -;; --- Shape Blocking - -(deftype BlockShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (letfn [(mark-blocked [state id] - (let [shape (get-in state [:shapes id])] - (if (= :group (:type shape)) - (as-> state $ - (assoc-in $ [:shapes id :blocked] true) - (reduce mark-blocked $ (:items shape))) - (assoc-in state [:shapes id :blocked] true))))] - (mark-blocked state id)))) - -(defn block-shape - [id] - {:pre [(uuid? id)]} - (BlockShape. id)) - -(deftype UnblockShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (letfn [(mark-unblocked [state id] - (let [shape (get-in state [:shapes id])] - (if (= :group (:type shape)) - (as-> state $ - (assoc-in $ [:shapes id :blocked] false) - (reduce mark-unblocked $ (:items shape))) - (assoc-in state [:shapes id :blocked] false))))] - (mark-unblocked state id)))) - -(defn unblock-shape - [id] - {:pre [(uuid? id)]} - (UnblockShape. id)) - -;; --- Shape Locking - -(deftype LockShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (letfn [(mark-locked [state id] - (let [shape (get-in state [:shapes id])] - (if (= :group (:type shape)) - (as-> state $ - (assoc-in $ [:shapes id :locked] true) - (reduce mark-locked $ (:items shape))) - (assoc-in state [:shapes id :locked] true))))] - (mark-locked state id)))) - -(defn lock-shape - [id] - {:pre [(uuid? id)]} - (LockShape. id)) - -(deftype UnlockShape [id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (letfn [(mark-unlocked [state id] - (let [shape (get-in state [:shapes id])] - (if (= :group (:type shape)) - (as-> state $ - (assoc-in $ [:shapes id :locked] false) - (reduce mark-unlocked $ (:items shape))) - (assoc-in state [:shapes id :locked] false))))] - (mark-unlocked state id)))) - -(defn unlock-shape - [id] - {:pre [(uuid? id)]} - (UnlockShape. id)) - -;; --- Drop Shape - -(deftype DropShape [sid tid loc] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (impl/drop-shape state sid tid loc))) +(defn drop-inside + [state tid sid] + {:pre [(not= tid sid)]} + (let [source (get-in state [:shapes sid]) + state (-> state + (dissoc-from-page source) + (dissoc-from-group source)) + shapes (get-in state [:shapes tid :items])] + (if (seq shapes) + (as-> state $ + (assoc-in $ [:shapes tid :items] (conj shapes sid)) + (update-in $ [:shapes sid] assoc :group tid)) + state))) (defn drop-shape - "Event used in drag and drop for transfer shape - from one position to an other." - [sid tid loc] - {:pre [(uuid? sid) - (uuid? tid) - (keyword? loc)]} - (DropShape. sid tid loc)) + [state sid tid loc] + (if (= tid sid) + state + (case loc + :inside (drop-inside state tid sid) + :before (drop-before state tid sid) + :after (drop-after state tid sid) + (throw (ex-info "Invalid data" {}))))) -;; --- Update Interaction +(defn move-layer + [state shape loc] + (case loc + :up (drop-relative state :before shape) + :down (drop-relative state :after shape) + :top (drop-relative state :first shape) + :bottom (drop-relative state :last shape) + (throw (ex-info "Invalid data" {})))) -(deftype UpdateInteraction [shape interaction] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [id (or (:id interaction) - (uuid/random)) - data (assoc interaction :id id)] - (assoc-in state [:shapes shape :interactions id] data)))) +;; --- Shape Selection -(defn update-interaction - [shape interaction] - (UpdateInteraction. shape interaction)) +(defn- try-match-shape + [xf selrect acc {:keys [type id items] :as shape}] + (cond + (geom/contained-in? shape selrect) + (conj acc id) -;; --- Delete Interaction + (geom/overlaps? shape selrect) + (conj acc id) -(deftype DeleteInteracton [shape id] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes shape :interactions] dissoc id))) + (:locked shape) + acc -(defn delete-interaction - [shape id] - {:pre [(uuid? id) (uuid? shape)]} - (DeleteInteracton. shape id)) + (= type :group) + (reduce (partial try-match-shape xf selrect) + acc (sequence xf items)) -;; --- Path Modifications + :else + acc)) -(deftype UpdatePath [id index delta] - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id :segments index] gpt/add delta))) +(defn match-by-selrect + [state page-id selrect] + (let [xf (comp (map #(get-in state [:shapes %])) + (remove :hidden) + (remove :blocked) + (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 update-path - "Update a concrete point in the path shape." - [id index delta] - {:pre [(uuid? id) (number? index) (gpt/point? delta)]} - (UpdatePath. id index delta)) +(defn group-shapes + [state shapes page] + (letfn [(replace-first-item [pred coll replacement] + (into [] + (concat + (take-while #(not (pred %)) coll) + [replacement] + (drop 1 (drop-while #(not (pred %)) coll))))) -(deftype InitialPathPointAlign [id index] - ptk/WatchEvent - (watch [_ state s] - (let [shape (get-in state [:shapes id]) - point (get-in shape [:segments index])] - (->> (uwrk/align-point point) - (rx/map #(update-path id index %)))))) + (move-shapes-to-new-group [state page shapes new-group] + (reduce (fn [state {:keys [id group] :as shape}] + (-> state + (update-in [:shapes group :items] #(remove (set [id]) %)) + (update-in [:pages page :shapes] #(remove (set [id]) %)) + (clear-empty-groups shape) + (assoc-in [:shapes id :group] new-group) + )) + state + shapes)) -(defn initial-path-point-align - "Event responsible of align a specified point of the - shape by `index` with the grid." - [id index] - {:pre [(uuid? id) - (number? index) - (not (neg? index))]} - (InitialPathPointAlign. id index)) + (update-shapes-on-page [state page shapes group] + (as-> (get-in state [:pages page :shapes]) $ + (replace-first-item (set shapes) $ group) + (remove (set shapes) $) + (into [] $) + (assoc-in state [:pages page :shapes] $))) -;; --- Events (implicit) (for selected) + (update-shapes-on-group [state parent-group shapes group] + (as-> (get-in state [:shapes parent-group :items]) $ + (replace-first-item (set shapes) $ group) + (remove (set shapes) $) + (into [] $) + (assoc-in state [:shapes parent-group :items] $))) -;; NOTE: moved to workspace -(deftype DeselectAll [] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace :selected] #{})) + (update-shapes-on-index [state shapes group] + (reduce (fn [state {:keys [id] :as shape}] + (as-> shape $ + (assoc $ :group group) + (assoc-in state [:shapes id] $))) + state + shapes))] + (let [sid (uuid/random) + shapes' (map #(get-in state [:shapes %]) shapes) + distinct-groups (distinct (map :group shapes')) + parent-group (cond + (not= 1 (count distinct-groups)) :multi + (nil? (first distinct-groups)) :page + :else (first distinct-groups)) + name (generate-unique-name state "Group") + group {:type :group + :name name + :items (into [] shapes) + :id sid + :page page}] + (as-> state $ + (update-shapes-on-index $ shapes' sid) + (cond + (= :multi parent-group) + (-> $ + (move-shapes-to-new-group page shapes' sid) + (update-in [:pages page :shapes] #(into [] (cons sid %)))) + (= :page parent-group) + (update-shapes-on-page $ page shapes sid) + :else + (update-shapes-on-group $ parent-group shapes sid)) + (update $ :shapes assoc sid group) + (cond + (= :multi parent-group) $ + (= :page parent-group) $ + :else (assoc-in $ [:shapes sid :group] parent-group)) + (update $ :workspace assoc :selected #{sid}))))) - ptk/WatchEvent - (watch [_ state stream] - (rx/just ::uev/interrupt))) +(defn degroup-shapes + [state shapes page-id] + (letfn [(get-relocation-position [state {id :id parent-id :group}] + (if (nil? parent-id) + (index-of (get-in state [:pages page-id :shapes]) id) + (index-of (get-in state [:shapes parent-id :items]) id))) -(defn deselect-all - "Clear all possible state of drawing, edition - or any similar action taken by the user." - [] - (DeselectAll.)) + (relocate-shape [state shape-id parent-id position] + (if (nil? parent-id) + (-> state + (update-in [:pages page-id :shapes] #(drop-at-index position % shape-id)) + (update-in [:shapes shape-id] dissoc :group)) + (-> state + (update-in [:shapes parent-id :items] #(drop-at-index position % shape-id)) + (assoc-in [:shapes shape-id :group] parent-id)))) -;; --- Group Selected Shapes + (remove-group [state {id :id parent-id :group}] + (let [xform (remove #{id})] + (as-> state $ + (update $ :shapes dissoc id) + (if (nil? parent-id) + (update-in $ [:pages page-id :shapes] #(into [] xform %)) + (update-in $ [:shapes parent-id :items] #(into [] xform %)))))) -(deftype GroupSelectedShapes [] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace :page]) - selected (get-in state [:workspace :selected])] - (assert (not (empty? selected)) "selected set is empty") - (assert (uuid? id) "selected page is not an uuid") - (impl/group-shapes state selected id)))) + (relocate-group-items [state {id :id parent-id :group items :items :as group}] + (let [position (get-relocation-position state group)] + (as-> state $ + (reduce #(relocate-shape %1 %2 parent-id position) $ (reverse items)) + (remove-group $ group)))) -(defn group-selected - [] - (GroupSelectedShapes.)) + (select-degrouped [state groups] + (let [items (into #{} (mapcat :items groups))] + (assoc-in state [:workspace :selected] items))) -;; --- Ungroup Selected Shapes + (remove-from-parent [state id parent-id] + (assert (not (nil? parent-id)) "parent-id should never be nil here") + (update-in state [:shapes parent-id :items] #(into [] (remove #{id}) %))) -(deftype UngroupSelectedShapes [] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace :page]) - selected (get-in state [:workspace :selected])] - (assert (not (empty? selected)) "selected set is empty") - (assert (uuid? id) "selected page is not an uuid") - (impl/degroup-shapes state selected id)))) + (strip-empty-groups [state parent-id] + (if (nil? parent-id) + state + (let [group (get-in state [:shapes parent-id])] + (if (empty? (:items group)) + (-> state + (remove-group group) + (strip-empty-groups (:group group))) + state)))) -(defn ungroup-selected - [] - (UngroupSelectedShapes.)) + (selective-degroup [state [shape & rest :as shapes]] + (let [group (get-in state [:shapes (:group shape)]) + position (get-relocation-position state group) + parent-id (:group group)] + (as-> state $ + (assoc-in $ [:workspace :selected] (into #{} (map :id shapes))) + (reduce (fn [state {shape-id :id}] + (-> state + (relocate-shape shape-id parent-id position) + (remove-from-parent shape-id (:id group)))) + $ (reverse shapes)) + (strip-empty-groups $ (:id group)))))] -;; --- Duplicate Selected + (let [shapes (into #{} (map #(get-in state [:shapes %])) shapes) + groups (into #{} (filter #(= (:type %) :group)) shapes) + parents (into #{} (map :group) shapes)] + (cond + (and (= (count shapes) (count groups)) + (= 1 (count parents)) + (not (empty? groups))) + (as-> state $ + (reduce relocate-group-items $ groups) + (reduce remove-group $ groups) + (select-degrouped $ groups)) -(deftype DuplicateSelected [] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [selected (get-in state [:workspace :selected])] - (impl/duplicate-shapes state selected)))) - -(defn duplicate-selected - [] - (DuplicateSelected.)) + (and (empty? groups) + (= 1 (count parents)) + (not (nil? (first parents)))) + (selective-degroup state shapes) + :else + (throw (ex-info "invalid condition for degrouping" {})))))) +(defn materialize-xfmt + [state id xfmt] + (let [{:keys [type items] :as shape} (get-in state [:shapes id])] + (if (= type :group) + (reduce #(materialize-xfmt %1 %2 xfmt) state items) + (update-in state [:shapes id] geom/transform xfmt)))) diff --git a/frontend/src/uxbox/main/data/shapes_impl.cljs b/frontend/src/uxbox/main/data/shapes_impl.cljs deleted file mode 100644 index 26f718fef..000000000 --- a/frontend/src/uxbox/main/data/shapes_impl.cljs +++ /dev/null @@ -1,470 +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-2017 Andrey Antukh - -(ns uxbox.main.data.shapes-impl - (:require [lentes.core :as l] - [uxbox.main.geom :as geom] - [uxbox.main.lenses :as ul] - [uxbox.util.geom.matrix :as gmt] - [uxbox.util.uuid :as uuid] - [uxbox.util.data :refer (index-of)])) - -;; --- Shape Creation - -(defn retrieve-used-names - "Returns a set of already used names by shapes - in the current page." - [{:keys [shapes] :as state}] - (let [page (l/focus ul/selected-page state) - xform (comp (map second) - (filter #(= page (:page %))) - (map :name))] - (into #{} xform 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)] - (-> state - (update-in [:pages page :shapes] #(into [] (cons shape-id %))) - (assoc-in [:shapes shape-id] shape)))) - -(defn duplicate-shapes' - ([state shapes page] - (duplicate-shapes' state shapes page nil)) - ([state shapes page group] - (letfn [(duplicate-shape [state shape page group] - (if (= (:type shape) :group) - (let [id (uuid/random) - items (:items shape) - name (generate-unique-name state (str (:name shape) "-copy")) - shape (assoc shape - :id id - :page page - :items [] - :name name) - state (if (nil? group) - (-> state - (update-in [:pages page :shapes] - #(into [] (cons id %))) - (assoc-in [:shapes id] shape)) - (-> state - (update-in [:shapes group :items] - #(into [] (cons id %))) - (assoc-in [:shapes id] shape)))] - (->> (map #(get-in state [:shapes %]) items) - (reverse) - (reduce #(duplicate-shape %1 %2 page id) state))) - (let [id (uuid/random) - name (generate-unique-name state (str (:name shape) "-copy")) - shape (-> (dissoc shape :group) - (assoc :id id :page page :name name) - (merge (when group {:group group})))] - (if (nil? group) - (-> state - (update-in [:pages page :shapes] #(into [] (cons id %))) - (assoc-in [:shapes id] shape)) - (-> state - (update-in [:shapes group :items] #(into [] (cons id %))) - (assoc-in [:shapes id] shape))))))] - (reduce #(duplicate-shape %1 %2 page group) state shapes)))) - -(defn duplicate-shapes - ([state shapes] - (duplicate-shapes state shapes nil)) - ([state shapes page] - (letfn [(all-toplevel? [coll] - (every? #(nil? (:group %)) coll)) - (all-same-group? [coll] - (let [group (:group (first coll))] - (every? #(= group (:group %)) coll)))] - (let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))] - (cond - (all-toplevel? shapes) - (let [page (or page (:page (first shapes)))] - (duplicate-shapes' state shapes page)) - - (all-same-group? shapes) - (let [page (or page (:page (first shapes))) - group (:group (first shapes))] - (duplicate-shapes' state shapes page group)) - - :else - (let [page (or page (:page (first shapes)))] - (duplicate-shapes' state shapes page))))))) - -;; --- Delete Shapes - -(defn dissoc-from-index - "A function that dissoc shape from the indexed - data structure of shapes from the state." - [state {:keys [id type] :as shape}] - (if (= :group type) - (let [items (map #(get-in state [:shapes %]) (:items shape))] - (as-> state $ - (update-in $ [:shapes] dissoc id) - (reduce dissoc-from-index $ items))) - (update-in state [:shapes] dissoc id))) - -(defn dissoc-from-page - "Given a shape, try to remove its reference from the - corresponding page." - [state {:keys [id page] :as shape}] - (as-> (get-in state [:pages page :shapes]) $ - (into [] (remove #(= % id) $)) - (assoc-in state [:pages page :shapes] $))) - -(defn dissoc-from-group - "Given a shape, try to remove its reference from the - corresponding group (only if it belongs to one group)." - [state {:keys [id group] :as shape}] - (if-let [group' (get-in state [:shapes group])] - (as-> (:items group') $ - (into [] (remove #(= % id) $)) - (assoc-in state [:shapes group :items] $)) - state)) - -(declare dissoc-shape) - -(defn clear-empty-groups - "Given the shape, try to clean all empty groups - that this shape belongs to. - - The main purpose of this function is remove the - all empty parent groups of recently removed - shape." - [state {:keys [group] :as shape}] - (if-let [group' (get-in state [:shapes group])] - (if (empty? (:items group')) - (-> (dissoc-shape state group') - (update-in [:workspace :selected] disj (:id group'))) - state) - state)) - -(defn dissoc-shape - "Given a shape, removes it from the state." - [state shape] - (as-> state $ - (dissoc-from-page $ shape) - (dissoc-from-group $ shape) - (dissoc-from-index $ shape) - (clear-empty-groups $ shape))) - -;; --- Shape Movements - -(defn- drop-at-index - [index coll v] - (let [[fst snd] (split-at index coll)] - (into [] (concat fst [v] snd)))) - -(defn drop-relative - [state loc sid] - {:pre [(not (nil? sid))]} - (let [shape (get-in state [:shapes (first sid)]) - {:keys [page group]} shape - sid (:id shape) - - shapes (if group - (get-in state [:shapes group :items]) - (get-in state [:pages page :shapes])) - - index (case loc - :first 0 - :after (min (- (count shapes) 1) (inc (index-of shapes sid))) - :before (max 0 (- (index-of shapes sid) 1)) - :last (- (count shapes) 1)) - - state (-> state - (dissoc-from-page shape) - (dissoc-from-group shape)) - - shapes (if group - (get-in state [:shapes group :items]) - (get-in state [:pages page :shapes])) - - shapes (drop-at-index index shapes sid)] - - (if group - (as-> state $ - (assoc-in $ [:shapes group :items] shapes) - (update-in $ [:shapes sid] assoc :group group) - (clear-empty-groups $ shape)) - (as-> state $ - (assoc-in $ [:pages page :shapes] shapes) - (update-in $ [:shapes sid] dissoc :group) - (clear-empty-groups $ shape))))) - -(defn drop-aside - [state loc tid sid] - {:pre [(not= tid sid) - (not (nil? tid)) - (not (nil? sid))]} - (let [{:keys [page group]} (get-in state [:shapes tid]) - source (get-in state [:shapes sid]) - - state (-> state - (dissoc-from-page source) - (dissoc-from-group source)) - - shapes (if group - (get-in state [:shapes group :items]) - (get-in state [:pages page :shapes])) - - index (case loc - :after (inc (index-of shapes tid)) - :before (index-of shapes tid)) - - shapes (drop-at-index index shapes sid)] - (if group - (as-> state $ - (assoc-in $ [:shapes group :items] shapes) - (update-in $ [:shapes sid] assoc :group group) - (clear-empty-groups $ source)) - (as-> state $ - (assoc-in $ [:pages page :shapes] shapes) - (update-in $ [:shapes sid] dissoc :group) - (clear-empty-groups $ source))))) - -(def drop-after #(drop-aside %1 :after %2 %3)) -(def drop-before #(drop-aside %1 :before %2 %3)) - -(defn drop-inside - [state tid sid] - {:pre [(not= tid sid)]} - (let [source (get-in state [:shapes sid]) - state (-> state - (dissoc-from-page source) - (dissoc-from-group source)) - shapes (get-in state [:shapes tid :items])] - (if (seq shapes) - (as-> state $ - (assoc-in $ [:shapes tid :items] (conj shapes sid)) - (update-in $ [:shapes sid] assoc :group tid)) - state))) - -(defn drop-shape - [state sid tid loc] - (if (= tid sid) - state - (case loc - :inside (drop-inside state tid sid) - :before (drop-before state tid sid) - :after (drop-after state tid sid) - (throw (ex-info "Invalid data" {}))))) - -(defn move-layer - [state shape loc] - (case loc - :up (drop-relative state :before shape) - :down (drop-relative state :after shape) - :top (drop-relative state :first shape) - :bottom (drop-relative state :last shape) - (throw (ex-info "Invalid data" {})))) - -;; --- 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) - - (:locked shape) - acc - - (= type :group) - (reduce (partial try-match-shape xf selrect) - acc (sequence xf items)) - - :else - acc)) - -(defn match-by-selrect - [state page-id selrect] - (let [xf (comp (map #(get-in state [:shapes %])) - (remove :hidden) - (remove :blocked) - (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 group-shapes - [state shapes page] - (letfn [(replace-first-item [pred coll replacement] - (into [] - (concat - (take-while #(not (pred %)) coll) - [replacement] - (drop 1 (drop-while #(not (pred %)) coll))))) - - (move-shapes-to-new-group [state page shapes new-group] - (reduce (fn [state {:keys [id group] :as shape}] - (-> state - (update-in [:shapes group :items] #(remove (set [id]) %)) - (update-in [:pages page :shapes] #(remove (set [id]) %)) - (clear-empty-groups shape) - (assoc-in [:shapes id :group] new-group) - )) - state - shapes)) - - (update-shapes-on-page [state page shapes group] - (as-> (get-in state [:pages page :shapes]) $ - (replace-first-item (set shapes) $ group) - (remove (set shapes) $) - (into [] $) - (assoc-in state [:pages page :shapes] $))) - - (update-shapes-on-group [state parent-group shapes group] - (as-> (get-in state [:shapes parent-group :items]) $ - (replace-first-item (set shapes) $ group) - (remove (set shapes) $) - (into [] $) - (assoc-in state [:shapes parent-group :items] $))) - - (update-shapes-on-index [state shapes group] - (reduce (fn [state {:keys [id] :as shape}] - (as-> shape $ - (assoc $ :group group) - (assoc-in state [:shapes id] $))) - state - shapes))] - (let [sid (uuid/random) - shapes' (map #(get-in state [:shapes %]) shapes) - distinct-groups (distinct (map :group shapes')) - parent-group (cond - (not= 1 (count distinct-groups)) :multi - (nil? (first distinct-groups)) :page - :else (first distinct-groups)) - name (generate-unique-name state "Group") - group {:type :group - :name name - :items (into [] shapes) - :id sid - :page page}] - (as-> state $ - (update-shapes-on-index $ shapes' sid) - (cond - (= :multi parent-group) - (-> $ - (move-shapes-to-new-group page shapes' sid) - (update-in [:pages page :shapes] #(into [] (cons sid %)))) - (= :page parent-group) - (update-shapes-on-page $ page shapes sid) - :else - (update-shapes-on-group $ parent-group shapes sid)) - (update $ :shapes assoc sid group) - (cond - (= :multi parent-group) $ - (= :page parent-group) $ - :else (assoc-in $ [:shapes sid :group] parent-group)) - (update $ :workspace assoc :selected #{sid}))))) - -(defn degroup-shapes - [state shapes page-id] - (letfn [(get-relocation-position [state {id :id parent-id :group}] - (if (nil? parent-id) - (index-of (get-in state [:pages page-id :shapes]) id) - (index-of (get-in state [:shapes parent-id :items]) id))) - - (relocate-shape [state shape-id parent-id position] - (if (nil? parent-id) - (-> state - (update-in [:pages page-id :shapes] #(drop-at-index position % shape-id)) - (update-in [:shapes shape-id] dissoc :group)) - (-> state - (update-in [:shapes parent-id :items] #(drop-at-index position % shape-id)) - (assoc-in [:shapes shape-id :group] parent-id)))) - - (remove-group [state {id :id parent-id :group}] - (let [xform (remove #{id})] - (as-> state $ - (update $ :shapes dissoc id) - (if (nil? parent-id) - (update-in $ [:pages page-id :shapes] #(into [] xform %)) - (update-in $ [:shapes parent-id :items] #(into [] xform %)))))) - - (relocate-group-items [state {id :id parent-id :group items :items :as group}] - (let [position (get-relocation-position state group)] - (as-> state $ - (reduce #(relocate-shape %1 %2 parent-id position) $ (reverse items)) - (remove-group $ group)))) - - (select-degrouped [state groups] - (let [items (into #{} (mapcat :items groups))] - (assoc-in state [:workspace :selected] items))) - - (remove-from-parent [state id parent-id] - (assert (not (nil? parent-id)) "parent-id should never be nil here") - (update-in state [:shapes parent-id :items] #(into [] (remove #{id}) %))) - - (strip-empty-groups [state parent-id] - (if (nil? parent-id) - state - (let [group (get-in state [:shapes parent-id])] - (if (empty? (:items group)) - (-> state - (remove-group group) - (strip-empty-groups (:group group))) - state)))) - - (selective-degroup [state [shape & rest :as shapes]] - (let [group (get-in state [:shapes (:group shape)]) - position (get-relocation-position state group) - parent-id (:group group)] - (as-> state $ - (assoc-in $ [:workspace :selected] (into #{} (map :id shapes))) - (reduce (fn [state {shape-id :id}] - (-> state - (relocate-shape shape-id parent-id position) - (remove-from-parent shape-id (:id group)))) - $ (reverse shapes)) - (strip-empty-groups $ (:id group)))))] - (let [shapes (into #{} (map #(get-in state [:shapes %])) shapes) - groups (into #{} (filter #(= (:type %) :group)) shapes) - parents (into #{} (map :group) shapes)] - (cond - (and (= (count shapes) (count groups)) - (= 1 (count parents)) - (not (empty? groups))) - (as-> state $ - (reduce relocate-group-items $ groups) - (reduce remove-group $ groups) - (select-degrouped $ groups)) - - (and (empty? groups) - (= 1 (count parents)) - (not (nil? (first parents)))) - (selective-degroup state shapes) - - :else - (throw (ex-info "invalid condition for degrouping" {})))))) - -(defn materialize-xfmt - [state id xfmt] - (let [{:keys [type items] :as shape} (get-in state [:shapes id])] - (if (= type :group) - (reduce #(materialize-xfmt %1 %2 xfmt) state items) - (update-in state [:shapes id] geom/transform xfmt)))) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 12904f429..785e3af81 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -7,24 +7,21 @@ (ns uxbox.main.data.workspace (:require [beicon.core :as rx] + ;; [uxbox.main.data.workspace.ruler :as wruler] [cljs.spec.alpha :as s] [potok.core :as ptk] [uxbox.config :as cfg] [uxbox.main.constants :as c] [uxbox.main.data.history :as udh] [uxbox.main.data.icons :as udi] - [uxbox.main.data.lightbox :as udl] [uxbox.main.data.pages :as udp] [uxbox.main.data.projects :as dp] - [uxbox.main.data.shapes :as uds] - [uxbox.main.data.shapes-impl :as simpl] - [uxbox.main.data.workspace.ruler :as wruler] + [uxbox.main.data.shapes :as ds] [uxbox.main.geom :as geom] - [uxbox.main.lenses :as ul] [uxbox.main.refs :as refs] [uxbox.main.store :as st] [uxbox.main.workers :as uwrk] - [uxbox.util.data :refer [dissoc-in index-of]] + [uxbox.util.data :refer [dissoc-in index-of seek]] [uxbox.util.forms :as sc] [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt] @@ -35,8 +32,10 @@ ;; --- Expose inner functions -(def start-ruler wruler/start-ruler) -(def clear-ruler wruler/clear-ruler) +(def start-ruler nil) +(def clear-ruler nil) + +(defn interrupt? [e] (= e :interrupt)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; General workspace events @@ -258,7 +257,7 @@ (->> (:clipboard state) (filter #(= id (:id %))) (first)))] - (simpl/duplicate-shapes state (:items selected) page-id)))) + (ds/duplicate-shapes state (:items selected) page-id)))) (defn paste-from-clipboard "Copy selected shapes to clipboard." @@ -327,18 +326,51 @@ {:pre [(uuid? id)]} (InitializeAlignment. id)) +;; --- Duplicate Selected + +(def duplicate-selected + (reify + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (let [selected (get-in state [:workspace :selected])] + (ds/duplicate-shapes state selected))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Shapes on Workspace events +;; Shapes events ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn add-shape + [data] + {:pre [(us/valid? ::ds/shape data)]} + (reify + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + ;; TODO: revisit the `setup-proportions` seems unnecesary + (let [shape (assoc (geom/setup-proportions data) + :id (uuid/random)) + pid (get-in state [:workspace :current])] + (ds/assoc-shape-to-page state shape pid))))) + +(defn delete-shape + [id] + {:pre [(uuid? id)]} + (reify + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (let [shape (get-in state [:shapes id])] + (ds/dissoc-shape state shape))))) + (defrecord SelectShape [id] ptk/UpdateEvent (update [_ state] - (let [page-id (get-in state [:workspace :current]) - selected (get-in state [:workspace page-id :selected])] + (let [pid (get-in state [:workspace :current]) + selected (get-in state [:workspace pid :selected])] (if (contains? selected id) - (update-in state [:workspace page-id :selected] disj id) - (update-in state [:workspace page-id :selected] conj id)))) + (update-in state [:workspace pid :selected] disj id) + (update-in state [:workspace pid :selected] conj id)))) ptk/WatchEvent (watch [_ state s] @@ -353,9 +385,10 @@ (defrecord DeselectAll [] ptk/UpdateEvent (update [_ state] - (let [page-id (get-in state [:workspace :current])] - (assoc-in state [:workspace page-id :selected] #{}))) - + (let [pid (get-in state [:workspace :current])] + (update-in state [:workspace pid] #(-> % + (assoc :selected #{}) + (dissoc :selected-canvas))))) ptk/WatchEvent (watch [_ state stream] (rx/just :interrupt))) @@ -388,7 +421,7 @@ (update [_ state] (let [pid (get-in state [:workspace :current]) selrect (get-in state [:workspace pid :selrect]) - shapes (simpl/match-by-selrect state pid selrect)] + shapes (ds/match-by-selrect state pid selrect)] (assoc-in state [:workspace pid :selected] shapes))))) ;; --- Update Shape Attrs @@ -400,8 +433,8 @@ (defn update-shape-attrs [id attrs] - {:pre [(uuid? id) (us/valid? ::uds/attributes attrs)]} - (let [atts (us/extract attrs ::uds/attributes)] + {:pre [(uuid? id) (us/valid? ::ds/attributes attrs)]} + (let [atts (us/extract attrs ::ds/attributes)] (UpdateShapeAttrs. id attrs))) ;; --- Update Selected Shapes attrs @@ -416,7 +449,7 @@ (defn update-selected-shapes-attrs [attrs] - {:pre [(us/valid? ::uds/attributes attrs)]} + {:pre [(us/valid? ::ds/attributes attrs)]} (UpdateSelectedShapesAttrs. attrs)) @@ -486,27 +519,49 @@ (update [_ state] (let [id (get-in state [:workspace :current]) selected (get-in state [:workspace id :selected])] - (simpl/move-layer state selected loc)))) + (ds/move-layer state selected loc)))) (defn move-selected-layer [loc] {:pre [(us/valid? ::direction loc)]} (MoveSelectedLayer. loc)) +;; --- Update Shape Position + +(deftype UpdateShapePosition [id point] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (update-in state [:shapes id] geom/absolute-move point))) + +(defn update-position + "Update the start position coordenate of the shape." + [id point] + {:pre [(uuid? id) (gpt/point? point)]} + (UpdateShapePosition. id point)) + ;; --- Delete Selected -(defrecord DeleteSelected [] - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-in state [:workspace :current]) - selected (get-in state [:workspace id :selected])] - (rx/from-coll - (into [(deselect-all)] (map #(uds/delete-shape %) selected)))))) - -(defn delete-selected +(def delete-selected "Deselect all and remove all selected shapes." - [] - (DeleteSelected.)) + (reify + ptk/WatchEvent + (watch [_ state stream] + (let [id (get-in state [:workspace :current]) + selected (get-in state [:workspace id :selected])] + (rx/from-coll + (into [(deselect-all)] (map #(delete-shape %) selected))))))) + +;; --- Rename Shape + +(defn rename-shape + [id name] + {:pre [(uuid? id) (string? name)]} + (reify + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:shapes id :name] name)))) ;; --- Change Shape Order (Ordering) @@ -563,7 +618,7 @@ (let [pid (get-in state [:workspace :current]) displacement (get-in state [:workspace pid :modifiers id :displacement])] (if (gmt/matrix? displacement) - (rx/of #(simpl/materialize-xfmt % id displacement) + (rx/of #(ds/materialize-xfmt % id displacement) #(update-in % [:workspace pid :modifiers id] dissoc :displacement) ::udp/page-update) (rx/empty))))) @@ -595,7 +650,7 @@ (let [pid (get-in state [:workspace :current]) resize (get-in state [:workspace pid :modifiers id :resize])] (if (gmt/matrix? resize) - (rx/of #(simpl/materialize-xfmt % id resize) + (rx/of #(ds/materialize-xfmt % id resize) #(update-in % [:workspace pid :modifiers id] dissoc :resize) ::udp/page-update) (rx/empty))))) @@ -627,19 +682,252 @@ ;; --- Select for Drawing -(defn select-for-drawing - [shape] +(def clear-drawing (reify ptk/UpdateEvent (update [_ state] - (let [pid (get-in state [:workspace :current]) - current (get-in state [:workspace pid :drawing-tool])] - (if (or (nil? shape) - (= shape current)) - (update-in state [:workspace pid] dissoc :drawing :drawing-tool) - (update-in state [:workspace pid] assoc - :drawing shape - :drawing-tool shape)))))) + (let [pid (get-in state [:workspace :current])] + (update-in state [:workspace pid] dissoc :drawing-tool :drawing))))) + +(defn select-for-drawing? + [e] + (= (::type (meta e)) ::select-for-drawing)) + +(defn select-for-drawing + [tool] + (reify + IMeta + (-meta [_] {::type ::select-for-drawing}) + + ptk/UpdateEvent + (update [_ state] + (prn "select-for-drawing" tool) + (let [pid (get-in state [:workspace :current])] + (update-in state [:workspace pid] assoc :drawing-tool tool))))) + +;; --- Shape Proportions + +(deftype LockShapeProportions [id] + ptk/UpdateEvent + (update [_ state] + (let [[width height] (-> (get-in state [:shapes id]) + (geom/size) + (keep [:width :height])) + proportion (/ width height)] + (update-in state [:shapes id] assoc + :proportion proportion + :proportion-lock true)))) + +(defn lock-proportions + "Mark proportions of the shape locked and save the current + proportion as additional precalculated property." + [id] + {:pre [(uuid? id)]} + (LockShapeProportions. id)) + +(deftype UnlockShapeProportions [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:shapes id :proportion-lock] false))) + +(defn unlock-proportions + [id] + {:pre [(uuid? id)]} + (UnlockShapeProportions. id)) + +;; --- Update Dimensions + +(deftype UpdateDimensions [id dimensions] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (update-in state [:shapes id] geom/resize-dim dimensions))) + +(s/def ::update-dimensions-opts + (s/keys :opt-un [::width ::height])) + +(defn update-dimensions + "A helper event just for update the position + of the shape using the width and height attrs + instread final point of coordinates." + [id opts] + {:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]} + (UpdateDimensions. id opts)) + +;; --- Update Interaction + +(deftype UpdateInteraction [shape interaction] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (let [id (or (:id interaction) + (uuid/random)) + data (assoc interaction :id id)] + (assoc-in state [:shapes shape :interactions id] data)))) + +(defn update-interaction + [shape interaction] + (UpdateInteraction. shape interaction)) + +;; --- Delete Interaction + +(deftype DeleteInteracton [shape id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (update-in state [:shapes shape :interactions] dissoc id))) + +(defn delete-interaction + [shape id] + {:pre [(uuid? id) (uuid? shape)]} + (DeleteInteracton. shape id)) + +;; --- Path Modifications + +(deftype UpdatePath [id index delta] + ptk/UpdateEvent + (update [_ state] + (update-in state [:shapes id :segments index] gpt/add delta))) + +(defn update-path + "Update a concrete point in the path shape." + [id index delta] + {:pre [(uuid? id) (number? index) (gpt/point? delta)]} + (UpdatePath. id index delta)) + +;; --- Initial Path Point Alignment + +(deftype InitialPathPointAlign [id index] + ptk/WatchEvent + (watch [_ state s] + (let [shape (get-in state [:shapes id]) + point (get-in shape [:segments index])] + (->> (uwrk/align-point point) + (rx/map #(update-path id index %)))))) + +(defn initial-path-point-align + "Event responsible of align a specified point of the + shape by `index` with the grid." + [id index] + {:pre [(uuid? id) + (number? index) + (not (neg? index))]} + (InitialPathPointAlign. id index)) + +;; --- Shape Visibility + +(deftype HideShape [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (letfn [(mark-hidden [state id] + (let [shape (get-in state [:shapes id])] + (if (= :group (:type shape)) + (as-> state $ + (assoc-in $ [:shapes id :hidden] true) + (reduce mark-hidden $ (:items shape))) + (assoc-in state [:shapes id :hidden] true))))] + (mark-hidden state id)))) + +(defn hide-shape + [id] + {:pre [(uuid? id)]} + (HideShape. id)) + +(deftype ShowShape [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (letfn [(mark-visible [state id] + (let [shape (get-in state [:shapes id])] + (if (= :group (:type shape)) + (as-> state $ + (assoc-in $ [:shapes id :hidden] false) + (reduce mark-visible $ (:items shape))) + (assoc-in state [:shapes id :hidden] false))))] + (mark-visible state id)))) + +(defn show-shape + [id] + {:pre [(uuid? id)]} + (ShowShape. id)) + +;; --- Shape Blocking + +(deftype BlockShape [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (letfn [(mark-blocked [state id] + (let [shape (get-in state [:shapes id])] + (if (= :group (:type shape)) + (as-> state $ + (assoc-in $ [:shapes id :blocked] true) + (reduce mark-blocked $ (:items shape))) + (assoc-in state [:shapes id :blocked] true))))] + (mark-blocked state id)))) + +(defn block-shape + [id] + {:pre [(uuid? id)]} + (BlockShape. id)) + +(deftype UnblockShape [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (letfn [(mark-unblocked [state id] + (let [shape (get-in state [:shapes id])] + (if (= :group (:type shape)) + (as-> state $ + (assoc-in $ [:shapes id :blocked] false) + (reduce mark-unblocked $ (:items shape))) + (assoc-in state [:shapes id :blocked] false))))] + (mark-unblocked state id)))) + +(defn unblock-shape + [id] + {:pre [(uuid? id)]} + (UnblockShape. id)) + +;; --- Shape Locking + +(deftype LockShape [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (letfn [(mark-locked [state id] + (let [shape (get-in state [:shapes id])] + (if (= :group (:type shape)) + (as-> state $ + (assoc-in $ [:shapes id :locked] true) + (reduce mark-locked $ (:items shape))) + (assoc-in state [:shapes id :locked] true))))] + (mark-locked state id)))) + +(defn lock-shape + [id] + {:pre [(uuid? id)]} + (LockShape. id)) + +(deftype UnlockShape [id] + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (letfn [(mark-unlocked [state id] + (let [shape (get-in state [:shapes id])] + (if (= :group (:type shape)) + (as-> state $ + (assoc-in $ [:shapes id :locked] false) + (reduce mark-unlocked $ (:items shape))) + (assoc-in state [:shapes id :locked] false))))] + (mark-unlocked state id)))) + +(defn unlock-shape + [id] + {:pre [(uuid? id)]} + (UnlockShape. id)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Selection Rect IMPL @@ -662,6 +950,42 @@ :y2 end-y :type :rect))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Canvas Interactions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; ;; --- Group Collapsing + +;; (deftype CollapseGroupShape [id] +;; udp/IPageUpdate +;; ptk/UpdateEvent +;; (update [_ state] +;; (update-in state [:shapes id] assoc :collapsed true))) + +;; (defn collapse-shape +;; [id] +;; {:pre [(uuid? id)]} +;; (CollapseGroupShape. id)) + +;; (deftype UncollapseGroupShape [id] +;; udp/IPageUpdate +;; ptk/UpdateEvent +;; (update [_ state] +;; (update-in state [:shapes id] assoc :collapsed false))) + +;; (defn uncollapse-shape +;; [id] +;; {:pre [(uuid? id)]} +;; (UncollapseGroupShape. id)) + +(defn select-canvas + [id] + (reify + ptk/UpdateEvent + (update [_ state] + (let [pid (get-in state [:workspace :current])] + (update-in state [:workspace pid] assoc :selected-canvas id))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Server Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/uxbox/main/data/workspace/ruler.cljs b/frontend/src/uxbox/main/data/workspace/ruler.cljs index 9fb062c86..be34e81fa 100644 --- a/frontend/src/uxbox/main/data/workspace/ruler.cljs +++ b/frontend/src/uxbox/main/data/workspace/ruler.cljs @@ -7,7 +7,7 @@ (ns uxbox.main.data.workspace.ruler "Workspace ruler related events. Mostly or all events are related to UI logic." - (:require [beicon.core :as rx] + #_(:require [beicon.core :as rx] [potok.core :as ptk] [uxbox.main.refs :as refs] [uxbox.main.streams :as streams] @@ -17,79 +17,79 @@ ;; --- Constants -(declare stop-ruler?) -(declare clear-ruler) -(declare update-ruler) +;; (declare stop-ruler?) +;; (declare clear-ruler) +;; (declare update-ruler) -(def ^:private immanted-zones - (let [transform #(vector (- % 7) (+ % 7) %) - right (map transform (range 0 181 15)) - left (map (comp transform -) (range 0 181 15))] - (vec (concat right left)))) +;; (def ^:private immanted-zones +;; (let [transform #(vector (- % 7) (+ % 7) %) +;; right (map transform (range 0 181 15)) +;; left (map (comp transform -) (range 0 181 15))] +;; (vec (concat right left)))) -(defn- align-position - [pos] - (let [angle (gpt/angle pos)] - (reduce (fn [pos [a1 a2 v]] - (if (< a1 angle a2) - (reduced (gpt/update-angle pos v)) - pos)) - pos - immanted-zones))) +;; (defn- align-position +;; [pos] +;; (let [angle (gpt/angle pos)] +;; (reduce (fn [pos [a1 a2 v]] +;; (if (< a1 angle a2) +;; (reduced (gpt/update-angle pos v)) +;; pos)) +;; pos +;; immanted-zones))) -;; --- Start Ruler +;; ;; --- Start Ruler -(deftype StartRuler [] - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current]) - pos (get-in state [:workspace :pointer :viewport])] - (assoc-in state [:workspace pid :ruler] {:start pos :end pos}))) +;; (deftype StartRuler [] +;; ptk/UpdateEvent +;; (update [_ state] +;; (let [pid (get-in state [:workspace :current]) +;; pos (get-in state [:workspace :pointer :viewport])] +;; (assoc-in state [:workspace pid :ruler] {:start pos :end pos}))) - ptk/WatchEvent - (watch [_ state stream] - (let [stoper (->> (rx/filter #(= ::uev/interrupt %) stream) - (rx/take 1))] - (->> streams/mouse-position - (rx/take-until stoper) - (rx/map (juxt :viewport :ctrl)) - (rx/map (fn [[pt ctrl?]] - (update-ruler pt ctrl?))))))) +;; ptk/WatchEvent +;; (watch [_ state stream] +;; (let [stoper (->> (rx/filter #(= ::uev/interrupt %) stream) +;; (rx/take 1))] +;; (->> streams/mouse-position +;; (rx/take-until stoper) +;; (rx/map (juxt :viewport :ctrl)) +;; (rx/map (fn [[pt ctrl?]] +;; (update-ruler pt ctrl?))))))) -(defn start-ruler - [] - (StartRuler.)) +;; (defn start-ruler +;; [] +;; (StartRuler.)) -;; --- Update Ruler +;; ;; --- Update Ruler -(deftype UpdateRuler [point ctrl?] - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current]) - ruler (get-in state [:workspace pid :ruler])] - (if-not ctrl? - (assoc-in state [:workspace pid :ruler :end] point) - (let [start (get-in state [:workspace pid :ruler :start]) - end (-> (gpt/subtract point start) - (align-position) - (gpt/add start))] - (assoc-in state [:workspace pid :ruler :end] end)))))) +;; (deftype UpdateRuler [point ctrl?] +;; ptk/UpdateEvent +;; (update [_ state] +;; (let [pid (get-in state [:workspace :current]) +;; ruler (get-in state [:workspace pid :ruler])] +;; (if-not ctrl? +;; (assoc-in state [:workspace pid :ruler :end] point) +;; (let [start (get-in state [:workspace pid :ruler :start]) +;; end (-> (gpt/subtract point start) +;; (align-position) +;; (gpt/add start))] +;; (assoc-in state [:workspace pid :ruler :end] end)))))) -(defn update-ruler - [point ctrl?] - {:pre [(gpt/point? point) - (boolean? ctrl?)]} - (UpdateRuler. point ctrl?)) +;; (defn update-ruler +;; [point ctrl?] +;; {:pre [(gpt/point? point) +;; (boolean? ctrl?)]} +;; (UpdateRuler. point ctrl?)) -;; --- Clear Ruler +;; ;; --- Clear Ruler -(deftype ClearRuler [] - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current])] - (update-in state [:workspace pid] dissoc :ruler)))) +;; (deftype ClearRuler [] +;; ptk/UpdateEvent +;; (update [_ state] +;; (let [pid (get-in state [:workspace :current])] +;; (update-in state [:workspace pid] dissoc :ruler)))) -(defn clear-ruler - [] - (ClearRuler.)) +;; (defn clear-ruler +;; [] +;; (ClearRuler.)) diff --git a/frontend/src/uxbox/main/data/workspace_drawing.cljs b/frontend/src/uxbox/main/data/workspace_drawing.cljs index 91b1235cb..ad33037f7 100644 --- a/frontend/src/uxbox/main/data/workspace_drawing.cljs +++ b/frontend/src/uxbox/main/data/workspace_drawing.cljs @@ -6,23 +6,7 @@ ;; TODO: DEPRECTATED, maintained just for temporal documentation, delete on near future -(ns uxbox.main.data.workspace-drawing - "Workspace drawing data events and impl." - (:require [beicon.core :as rx] - [potok.core :as ptk] - [lentes.core :as l] - [uxbox.main.store :as st] - [uxbox.main.constants :as c] - [uxbox.main.refs :as refs] - [uxbox.main.streams :as streams] - [uxbox.main.data.shapes :as uds] - [uxbox.main.data.workspace :as udw] - [uxbox.main.geom :as geom] - [uxbox.main.workers :as uwrk] - [uxbox.main.user-events :as uev] - [uxbox.main.lenses :as ul] - [uxbox.util.geom.path :as pth] - [uxbox.util.geom.point :as gpt])) +(ns uxbox.main.data.workspace-drawing) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Events @@ -234,7 +218,7 @@ ;; (rx/filter uev/mouse-up?) ;; (rx/take 1))) ;; start? (volatile! true) -;; mouse (->> streams/viewport-mouse-position +;; mouse (->> streams/mouse-position ;; (rx/take-until stoper) ;; (rx/mapcat conditional-align) ;; (rx/map translate-to-canvas) @@ -325,7 +309,7 @@ ;; (defn- on-init-draw-free-path ;; [shape stoper] ;; (let [stoper (get-path-stoper-stream stoper true) -;; mouse (->> streams/viewport-mouse-position +;; mouse (->> streams/mouse-position ;; (rx/mapcat conditional-align) ;; (rx/map translate-to-canvas)) @@ -341,7 +325,7 @@ ;; [shape stoper] ;; (let [last-point (volatile! @refs/canvas-mouse-position) ;; stoper (get-path-stoper-stream stoper) -;; mouse (->> (rx/sample 10 streams/viewport-mouse-position) +;; mouse (->> (rx/sample 10 streams/mouse-position) ;; (rx/mapcat conditional-align) ;; (rx/map translate-to-canvas)) ;; points (->> (get-path-point-stream) diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index bd5b01efe..c19f03dc1 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -26,6 +26,7 @@ :image (move-rect shape dpoint) :rect (move-rect shape dpoint) :text (move-rect shape dpoint) + :curve (move-path shape dpoint) :path (move-path shape dpoint) :circle (move-circle shape dpoint) :group (move-group shape dpoint))) @@ -125,12 +126,12 @@ "Calculate the size of the shape." [shape] (case (:type shape) - :group (assoc shape :width 100 :height 100) :circle (size-circle shape) :text (size-rect shape) :rect (size-rect shape) :icon (size-rect shape) :image (size-rect shape) + :curve (size-path shape) :path (size-path shape))) (defn- size-path @@ -184,6 +185,7 @@ :icon (setup-proportions-image shape) :image (setup-proportions-image shape) :text shape + :curve (setup-proportions-rect shape) :path (setup-proportions-rect shape))) (defn setup-proportions-image @@ -461,6 +463,7 @@ (case type :circle (circle->rect-shape state shape) :path (path->rect-shape state shape) + :curve (path->rect-shape state shape) shape))) (defn shapes->rect-shape @@ -517,6 +520,7 @@ :text (transform-rect shape xfmt) :image (transform-rect shape xfmt) :path (transform-path shape xfmt) + :curve (transform-path shape xfmt) :circle (transform-circle shape xfmt))) (defn- transform-rect diff --git a/frontend/src/uxbox/main/lenses.cljs b/frontend/src/uxbox/main/lenses.cljs deleted file mode 100644 index 2e2d63f2a..000000000 --- a/frontend/src/uxbox/main/lenses.cljs +++ /dev/null @@ -1,13 +0,0 @@ -(ns uxbox.main.lenses - (:require [lentes.core :as l])) - -;; --- Workspace -;; --- FIXME: remove this ns - -(def workspace (l/key :workspace)) -(def workspace-flags (comp workspace (l/key :flags))) - -(def selected-drawing (comp workspace (l/key :drawing))) -(def selected-shapes (comp workspace (l/key :selected))) -(def selected-page (comp workspace (l/key :page))) -(def selected-project (comp workspace (l/key :project))) diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 58dde791b..eaa147ad2 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -52,6 +52,10 @@ (-> (l/key :selected) (l/derive workspace))) +(def selected-canvas + (-> (l/key :selected-canvas) + (l/derive workspace))) + (def toolboxes (-> (l/key :toolboxes) (l/derive workspace))) @@ -100,28 +104,6 @@ (l/lens alignment-activated?)) (l/derive workspace))) -;; ... - -(def mouse-position - (-> (l/in [:workspace :pointer]) - (l/derive st/state))) - -(def canvas-mouse-position - (-> (l/key :canvas) - (l/derive mouse-position))) - -(def viewport-mouse-position - (-> (l/key :viewport) - (l/derive mouse-position))) - -(def window-mouse-position - (-> (l/key :window) - (l/derive mouse-position))) - -(def workspace-scroll - (-> (l/in [:workspace :scroll]) - (l/derive st/state))) - (def shapes-by-id (-> (l/key :shapes) (l/derive st/state))) diff --git a/frontend/src/uxbox/main/repo/pages.cljs b/frontend/src/uxbox/main/repo/pages.cljs index ddd3b593b..ae6356c1e 100644 --- a/frontend/src/uxbox/main/repo/pages.cljs +++ b/frontend/src/uxbox/main/repo/pages.cljs @@ -2,14 +2,13 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) 2016-2019 Andrey Antukh (ns uxbox.main.repo.pages "A main interface for access to remote resources." - (:require [beicon.core :as rx] - [uxbox.config :refer (url)] - [uxbox.main.repo.impl :refer (request send!)] - [uxbox.util.transit :as t])) + (:require + [uxbox.config :refer [url]] + [uxbox.main.repo.impl :refer [request send!]])) (defmethod request :fetch/pages [type data] diff --git a/frontend/src/uxbox/main/streams.cljs b/frontend/src/uxbox/main/streams.cljs deleted file mode 100644 index c7765f090..000000000 --- a/frontend/src/uxbox/main/streams.cljs +++ /dev/null @@ -1,65 +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) 2017 Andrey Antukh - -(ns uxbox.main.streams - "A collection of derived streams." - (:require [beicon.core :as rx] - [uxbox.main.store :as st] - [uxbox.main.user-events :as uev] - [uxbox.main.refs :as refs] - [uxbox.main.workers :as uwrk] - [uxbox.util.geom.point :as gpt])) - -;; --- Events - -(defn- user-interaction-event? - [event] - (or (uev/keyboard-event? event) - (uev/mouse-event? event))) - -(defonce events - (rx/filter user-interaction-event? st/stream)) - -;; --- Mouse Position Stream - -(defonce mouse-position - (rx/filter uev/pointer-event? st/stream)) - -(defonce canvas-mouse-position - (->> mouse-position - (rx/map :canvas) - (rx/share))) - -(defonce viewport-mouse-position - (->> mouse-position - (rx/map :viewport) - (rx/share))) - -(defonce window-mouse-position - (->> mouse-position - (rx/map :window) - (rx/share))) - -(defonce mouse-position-ctrl - (->> mouse-position - (rx/map :ctrl) - (rx/share))) - -(defn- coords-delta - [[old new]] - (gpt/subtract new old)) - -(defonce mouse-position-deltas - (->> viewport-mouse-position - (rx/sample 10) - (rx/map #(gpt/divide % @refs/selected-zoom)) - (rx/mapcat (fn [point] - (if @refs/selected-alignment - (uwrk/align-point point) - (rx/of point)))) - (rx/buffer 2 1) - (rx/map coords-delta) - (rx/share))) diff --git a/frontend/src/uxbox/main/ui/shapes.cljs b/frontend/src/uxbox/main/ui/shapes.cljs index 8610a6b1f..135bd5ae1 100644 --- a/frontend/src/uxbox/main/ui/shapes.cljs +++ b/frontend/src/uxbox/main/ui/shapes.cljs @@ -20,6 +20,7 @@ [shape] (mf/html (case (:type shape) + :curve [:& path/path-component {:shape shape}] :text [:& text/text-component {:shape shape}] :icon [:& icon/icon-component {:shape shape}] :rect [:& rect/rect-component {:shape shape}] diff --git a/frontend/src/uxbox/main/ui/shapes/common.cljs b/frontend/src/uxbox/main/ui/shapes/common.cljs index 6d972ba25..3c7b447c2 100644 --- a/frontend/src/uxbox/main/ui/shapes/common.cljs +++ b/frontend/src/uxbox/main/ui/shapes/common.cljs @@ -25,7 +25,7 @@ (watch [_ state stream] (let [pid (get-in state [:workspace :current]) wst (get-in state [:workspace pid]) - stoper (->> ws/interaction-events + stoper (->> stream (rx/filter ws/mouse-up?) (rx/take 1)) stream (->> ws/mouse-position-deltas @@ -58,7 +58,8 @@ (and (not selected?) (empty? selected)) (do (dom/stop-propagation event) - (st/emit! (dw/select-shape id) + (st/emit! (dw/deselect-all) + (dw/select-shape id) (start-move-selected))) (and (not selected?) (not (empty? selected))) diff --git a/frontend/src/uxbox/main/ui/shapes/text.cljs b/frontend/src/uxbox/main/ui/shapes/text.cljs index ea093ab70..fdfb72047 100644 --- a/frontend/src/uxbox/main/ui/shapes/text.cljs +++ b/frontend/src/uxbox/main/ui/shapes/text.cljs @@ -126,7 +126,7 @@ style (make-style shape) on-input (fn [ev] (let [content (dom/event->inner-text ev)] - (st/emit! (uds/update-text id content))))] + (st/emit! (udw/update-shape-attrs id {:content content}))))] [:foreignObject {:x x1 :y y1 :width width :height height} [:div {:style (normalize-props style) :ref (::container own) diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index 18a0ee150..296c6d7b7 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -52,7 +52,7 @@ (let [prev-zoom @refs/selected-zoom dom (mf/ref-node canvas) scroll-position (scroll/get-current-position-absolute dom) - mouse-point @uws/viewport-mouse-position] + mouse-point @uws/mouse-position] (dom/prevent-default event) (dom/stop-propagation event) (if (pos? (.-deltaY event)) @@ -62,7 +62,7 @@ (defn- subscribe [canvas page] - (scroll/scroll-to-page-center (mf/ref-node canvas) page) + ;; (scroll/scroll-to-page-center (mf/ref-node canvas) page) (st/emit! (udp/watch-page-changes (:id page)) (udu/watch-page-changes (:id page))) (let [sub (shortcuts/init)] diff --git a/frontend/src/uxbox/main/ui/workspace/canvas.cljs b/frontend/src/uxbox/main/ui/workspace/canvas.cljs index 2ac61597e..21606e406 100644 --- a/frontend/src/uxbox/main/ui/workspace/canvas.cljs +++ b/frontend/src/uxbox/main/ui/workspace/canvas.cljs @@ -8,45 +8,46 @@ (ns uxbox.main.ui.workspace.canvas (:require [rumext.alpha :as mf] + [lentes.core :as l] [uxbox.main.constants :as c] + [uxbox.main.refs :as refs] + [uxbox.main.data.workspace :as dw] [uxbox.main.store :as st] + [uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.shapes :as uus] [uxbox.main.ui.workspace.drawarea :refer [draw-area]] [uxbox.main.ui.workspace.selection :refer [selection-handlers]] + [uxbox.main.ui.workspace.streams :as uws] + [uxbox.util.data :refer [parse-int]] + [uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt])) -;; --- Background +(def selected-canvas + (-> (l/key :selected-canvas) + (l/derive refs/workspace))) -(mf/def background - :mixins [mf/memo] - :render - (fn [own {:keys [background] :as metadata}] - [:rect - {:x 0 :y 0 - :width "100%" - :height "100%" - :fill (or background "#ffffff")}])) - -;; --- Canvas +(defn- make-canvas-iref + [id] + (-> (l/in [:canvas id]) + (l/derive st/state))) (mf/defc canvas - [{:keys [page wst] :as props}] - (let [{:keys [metadata id]} page - zoom (:zoom wst 1) ;; NOTE: maybe forward wst to draw-area - width (:width metadata) - height (:height metadata)] - [:svg.page-canvas {:x c/canvas-start-x - :y c/canvas-start-y - :width width - :height height} - [:& background metadata] - #_[:svg.page-layout - [:g.main - (for [id (reverse (:shapes page))] - [:& uus/shape-component {:id id :key id}]) - (when (seq (:selected wst)) - [:& selection-handlers {:wst wst}]) - (when-let [dshape (:drawing wst)] - [:& draw-area {:shape dshape - :zoom (:zoom wst) - :modifiers (:modifiers wst)}])]]])) + [{:keys [id] :as props}] + (letfn [(on-double-click [event] + (dom/prevent-default event) + (st/emit! (dw/select-canvas id)))] + (let [canvas-iref (mf/use-memo #(make-canvas-iref id) #js [id]) + canvas (mf/deref canvas-iref) + selected (mf/deref selected-canvas) + selected? (= id selected)] + [:rect.page-canvas + {:x (:x canvas) + :class (when selected? "selected") + :y (:y canvas) + :fill (:background canvas "#ffffff") + :width (:width canvas) + :height (:height canvas) + :on-double-click on-double-click}]))) + + + diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index ac854aee4..518de3678 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -21,19 +21,27 @@ [uxbox.main.workers :as uwrk] [uxbox.util.math :as mth] [uxbox.util.dom :as dom] + [uxbox.util.data :refer [seek]] [uxbox.util.geom.path :as path] - [uxbox.util.geom.point :as gpt])) + [uxbox.util.geom.point :as gpt] + [uxbox.util.uuid :as uuid])) + +(defn- rxfinalize + [f ob] + (.pipe ob (.finalize js/rxjs.operators f))) ;; --- Events (declare handle-drawing) (declare handle-drawing-generic) (declare handle-drawing-path) -(declare handle-drawing-free-path) +(declare handle-drawing-curve) (declare handle-finish-drawing) +(declare conditional-align) (defn start-drawing - [object] + [type] + {:pre [(keyword? type)]} (let [id (gensym "drawing")] (reify ptk/UpdateEvent @@ -42,35 +50,65 @@ ptk/WatchEvent (watch [_ state stream] - (let [lock (get-in state [:workspace :drawing-lock])] + (let [pid (get-in state [:workspace :current]) + lock (get-in state [:workspace :drawing-lock])] (if (= lock id) - (rx/merge (->> stream - (rx/filter #(= % handle-finish-drawing)) - (rx/take 1) - (rx/map (fn [_] #(update % :workspace dissoc :drawing-lock)))) - (rx/of (handle-drawing object))) + (rx/merge + (->> (rx/filter #(= % handle-finish-drawing) stream) + (rx/take 1) + (rx/map (fn [_] #(update % :workspace dissoc :drawing-lock)))) + (rx/of (handle-drawing type))) (rx/empty))))))) -(defn- conditional-align [point align?] - (if align? - (uwrk/align-point point) - (rx/of point))) +(def ^:private minimal-shapes + [{:type :rect + :name "Rect" + :stroke-color "#000000"} + {:type :circle + :name "Circle"} + {:type :path + :name "Path" + :stroke-style :solid + :stroke-color "#000000" + :stroke-width 2 + :fill-color "#000000" + :fill-opacity 0 + :segments []} + {:type :curve + :name "Path" + :stroke-style :solid + :stroke-color "#000000" + :stroke-width 2 + :fill-color "#000000" + :fill-opacity 0 + :segments []} + {:type :text + :name "Text" + :content "Type your text here"}]) + +(defn- make-minimal-shape + [type] + (let [tool (seek #(= type (:type %)) minimal-shapes)] + (assert tool "unexpected drawing tool") + (assoc tool :id (uuid/random)))) -;; TODO: maybe this should be a simple function (defn handle-drawing - [shape] + [type] (reify + ptk/UpdateEvent + (update [_ state] + (let [pid (get-in state [:workspace :current]) + shape (make-minimal-shape type)] + (assoc-in state [:workspace pid :drawing] shape))) + ptk/WatchEvent (watch [_ state stream] - (rx/of - (if (= :path (:type shape)) - (if (:free shape) - (handle-drawing-free-path shape) - (handle-drawing-path shape)) - (handle-drawing-generic shape)))))) + (case type + :path (rx/of handle-drawing-path) + :curve (rx/of handle-drawing-curve) + (rx/of handle-drawing-generic))))) -(defn- handle-drawing-generic - [shape] +(def handle-drawing-generic (letfn [(initialize-drawing [state point] (let [pid (get-in state [:workspace :current]) shape (get-in state [:workspace pid :drawing]) @@ -114,11 +152,11 @@ stoper (->> (rx/filter #(or (uws/mouse-up? %) (= % :interrupt)) stream) (rx/take 1)) - mouse (->> uws/viewport-mouse-position + mouse (->> uws/mouse-position (rx/mapcat #(conditional-align % align?)) (rx/with-latest vector uws/mouse-position-ctrl))] (rx/concat - (->> uws/viewport-mouse-position + (->> uws/mouse-position (rx/take 1) (rx/mapcat #(conditional-align % align?)) (rx/map (fn [pt] #(initialize-drawing % pt)))) @@ -127,8 +165,7 @@ (rx/take-until stoper)) (rx/of handle-finish-drawing))))))) -(defn handle-drawing-path - [shape] +(def handle-drawing-path (letfn [(stoper-event? [{:keys [type shift] :as event}] (or (= event :interrupt) (and (uws/mouse-event? event) @@ -166,12 +203,12 @@ flags (get-in state [:workspace pid :flags]) align? (refs/alignment-activated? flags) - last-point (volatile! @uws/viewport-mouse-position) + last-point (volatile! @uws/mouse-position) stoper (->> (rx/filter stoper-event? stream) - (rx/take 1)) + (rx/share)) - mouse (->> (rx/sample 10 uws/viewport-mouse-position) + mouse (->> (rx/sample 10 uws/mouse-position) (rx/mapcat #(conditional-align % align?))) points (->> stream @@ -186,7 +223,6 @@ (rx/with-latest vector counter) (rx/map flatten)) - imm-transform #(vector (- % 7) (+ % 7) %) immanted-zones (vec (concat (map imm-transform (range 0 181 15)) @@ -205,8 +241,8 @@ (->> points (rx/take-until stoper) - (rx/map (fn [pt] - #(insert-point-segment % pt)))) + (rx/map (fn [pt]#(insert-point-segment % pt)))) + (rx/concat (->> stream' (rx/map (fn [[point ctrl? index :as xxx]] @@ -221,8 +257,7 @@ (rx/of remove-dangling-segmnet handle-finish-drawing)))))))) -(defn- handle-drawing-free-path - [shape] +(def handle-drawing-curve (letfn [(stoper-event? [{:keys [type shift] :as event}] (or (= event :interrupt) (and (uws/mouse-event? event) (= type :up)))) @@ -249,7 +284,7 @@ stoper (->> (rx/filter stoper-event? stream) (rx/take 1)) - mouse (->> (rx/sample 10 uws/viewport-mouse-position) + mouse (->> (rx/sample 10 uws/mouse-position) (rx/mapcat #(conditional-align % align?)))] (rx/concat (rx/of initialize-drawing) @@ -275,10 +310,11 @@ #(update-in % [:workspace pid :modifiers] dissoc (:id shape)) ;; Unselect the drawing tool - #(update-in % [:workspace pid] dissoc :drawing :drawing-tool) + ;; TODO; maybe a specific event for clear draw-tool + dw/clear-drawing ;; Add & select the cred shape to the workspace - (ds/add-shape shape) + (dw/add-shape (dissoc shape ::initialized?)) (dw/select-first-shape))) (rx/of #(update-in % [:workspace pid] dissoc :drawing :drawing-tool))))))) @@ -296,8 +332,8 @@ (mf/defc draw-area [{:keys [zoom shape modifiers] :as props}] - (if (= (:type shape) :path) - [:& path-draw-area {:shape shape}] + (case (:type shape) + (:path :curve) [:& path-draw-area {:shape shape}] [:& generic-draw-area {:shape (assoc shape :modifiers modifiers) :zoom zoom}])) @@ -328,7 +364,7 @@ (when-let [{:keys [x y] :as segment} (first (:segments shape))] [:g (shapes/render-shape shape) - (when-not (:free shape) + (when (not= :curve (:type shape)) [:circle.close-bezier {:cx x :cy y @@ -336,3 +372,8 @@ :on-click on-click :on-mouse-enter on-mouse-enter :on-mouse-leave on-mouse-leave}])]))) + +(defn- conditional-align [point align?] + (if align? + (uwrk/align-point point) + (rx/of point))) diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index ea2111c4a..13ed9b9c2 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -11,9 +11,7 @@ [beicon.core :as rx] [lentes.core :as l] [rumext.alpha :as mf] - [uxbox.main.constants :as c] - [uxbox.main.data.shapes :as uds] - [uxbox.main.data.workspace :as udw] + [uxbox.main.data.workspace :as dw] [uxbox.main.geom :as geom] [uxbox.main.refs :as refs] [uxbox.main.store :as st] @@ -42,11 +40,11 @@ (let [result (geom/resize-shape vid shape point lock?) scale (geom/calculate-scale-ratio shape result) mtx (geom/generate-resize-matrix vid shape scale) - xfm (map #(udw/apply-temporal-resize % mtx))] + xfm (map #(dw/apply-temporal-resize % mtx))] (apply st/emit! (sequence xfm ids)))) (on-end [] - (apply st/emit! (map udw/apply-resize ids))) + (apply st/emit! (map dw/apply-resize ids))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Ctrl key and the shapes own proportion @@ -68,10 +66,10 @@ (let [shape (->> (geom/shape->rect-shape shape) (geom/size)) - stoper (->> ws/interaction-events + stoper (->> st/stream (rx/filter ws/mouse-up?) (rx/take 1)) - stream (->> ws/viewport-mouse-position + stream (->> ws/mouse-position (rx/take-until stoper) (rx/map apply-zoom) (rx/mapcat apply-grid-alignment) @@ -160,22 +158,23 @@ (letfn [(on-mouse-down [event index] (dom/stop-propagation event) - (let [stoper (get-edition-stream-stoper ws/interaction-events) + ;; TODO: this need code ux refactor + (let [stoper (get-edition-stream-stoper) stream (rx/take-until stoper ws/mouse-position-deltas)] (when @refs/selected-alignment - (st/emit! (uds/initial-path-point-align (:id shape) index))) + (st/emit! (dw/initial-path-point-align (:id shape) index))) (rx/subscribe stream #(on-handler-move % index)))) - (get-edition-stream-stoper [stream] + (get-edition-stream-stoper [] (let [stoper? #(and (ws/mouse-event? %) (= (:type %) :up))] (rx/merge - (rx/filter stoper? stream) - (->> stream + (rx/filter stoper? st/stream) + (->> st/stream (rx/filter #(= % :interrupt)) (rx/take 1))))) (on-handler-move [delta index] - (st/emit! (uds/update-path (:id shape) index delta)))] + (st/emit! (dw/update-path (:id shape) index delta)))] (let [displacement (:displacement modifiers) segments (cond->> (:segments shape) diff --git a/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs b/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs index 939ec9295..b8712e6df 100644 --- a/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs @@ -12,10 +12,7 @@ [uxbox.main.store :as st] [uxbox.main.data.lightbox :as dl] [uxbox.main.data.workspace :as dw] - [uxbox.main.data.shapes :as uds] - [uxbox.main.data.undo :as udu] - [uxbox.main.data.history :as udh] - [uxbox.main.ui.workspace.sidebar.drawtools :as wsd]) + [uxbox.main.data.undo :as du]) (:import goog.events.EventType goog.events.KeyCodes goog.ui.KeyboardShortcutHandler @@ -27,26 +24,24 @@ (defonce +shortcuts+ {:shift+g #(st/emit! (dw/toggle-flag :grid)) - :ctrl+g #(st/emit! (uds/group-selected)) - :ctrl+shift+g #(st/emit! (uds/ungroup-selected)) :ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap)) :ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools)) :ctrl+shift+i #(st/emit! (dw/toggle-flag :icons)) :ctrl+shift+l #(st/emit! (dw/toggle-flag :layers)) :ctrl+0 #(st/emit! (dw/reset-zoom)) :ctrl+r #(st/emit! (dw/toggle-flag :ruler)) - :ctrl+d #(st/emit! (uds/duplicate-selected)) + :ctrl+d #(st/emit! dw/duplicate-selected) :ctrl+c #(st/emit! (dw/copy-to-clipboard)) :ctrl+v #(st/emit! (dw/paste-from-clipboard)) :ctrl+shift+v #(dl/open! :clipboard) - :ctrl+z #(st/emit! (udu/undo)) - :ctrl+shift+z #(st/emit! (udu/redo)) - :ctrl+y #(st/emit! (udu/redo)) - :ctrl+b #(st/emit! (dw/select-for-drawing wsd/+draw-tool-rect+)) - :ctrl+e #(st/emit! (dw/select-for-drawing wsd/+draw-tool-circle+)) - :ctrl+t #(st/emit! (dw/select-for-drawing wsd/+draw-tool-text+)) + :ctrl+z #(st/emit! (du/undo)) + :ctrl+shift+z #(st/emit! (du/redo)) + :ctrl+y #(st/emit! (du/redo)) + :ctrl+b #(st/emit! (dw/select-for-drawing :rect)) + :ctrl+e #(st/emit! (dw/select-for-drawing :circle)) + :ctrl+t #(st/emit! (dw/select-for-drawing :text)) :esc #(st/emit! (dw/deselect-all)) - :delete #(st/emit! (dw/delete-selected)) + :delete #(st/emit! dw/delete-selected) :ctrl+up #(st/emit! (dw/move-selected-layer :up)) :ctrl+down #(st/emit! (dw/move-selected-layer :down)) :ctrl+shift+up #(st/emit! (dw/move-selected-layer :top)) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs index 6e46c1a5a..684e1be9b 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs @@ -21,17 +21,16 @@ (mf/defc left-sidebar {:wrap [mf/wrap-memo]} [{:keys [flags page] :as props}] - [:aside#settings-bar.settings-bar.settings-bar-left - [:> rdnd/provider {:backend rdnd/html5} - [:div.settings-bar-inside - (when (contains? flags :sitemap) - [:& sitemap-toolbox {:project-id (:project page) - :current-page-id (:id page) - :page page}]) - #_(when (contains? flags :document-history) - (history-toolbox page-id)) - (when (contains? flags :layers) - [:& layers-toolbox {:page page}])]]]) + [:aside.settings-bar.settings-bar-left + [:div.settings-bar-inside + (when (contains? flags :sitemap) + [:& sitemap-toolbox {:project-id (:project page) + :current-page-id (:id page) + :page page}]) + #_(when (contains? flags :document-history) + (history-toolbox page-id)) + (when (contains? flags :layers) + [:& layers-toolbox {:page page}])]]) ;; --- Right Sidebar (Component) @@ -43,6 +42,5 @@ [:& draw-toolbox {:flags flags}]) (when (contains? flags :element-options) [:& options-toolbox {:page page}]) - (when (contains? flags :icons) - #_(icons-toolbox))]]) - + #_(when (contains? flags :icons) + (icons-toolbox))]]) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs index 7d2be8879..191a0d390 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs @@ -17,96 +17,69 @@ ;; --- Constants -(def +draw-tool-rect+ - {:type :rect - :id (uuid/random) - :name "Rect" - :stroke-color "#000000"}) - -(def +draw-tool-circle+ - {:type :circle - :id (uuid/random) - :name "Circle"}) - -(def +draw-tool-path+ - {:type :path - :id (uuid/random) - :name "Path" - :stroke-style :solid - :stroke-color "#000000" - :stroke-width 2 - :fill-color "#000000" - :fill-opacity 0 - ;; :close? true - :points []}) - -(def +draw-tool-curve+ - (assoc +draw-tool-path+ - :id (uuid/random) - :free true)) - -(def +draw-tool-text+ - {:type :text - :id (uuid/random) - :name "Text" - :content "Hello world"}) - (def +draw-tools+ [{:icon i/box :help "ds.help.rect" - :shape +draw-tool-rect+ + :type :rect :priority 1} {:icon i/circle :help "ds.help.circle" - :shape +draw-tool-circle+ + :type :circle :priority 2} {:icon i/text :help "ds.help.text" - :shape +draw-tool-text+ + :type :text :priority 4} {:icon i/curve :help "ds.help.path" - :shape +draw-tool-path+ + :type :path :priority 5} {:icon i/pencil :help "ds.help.curve" - :shape +draw-tool-curve+ - :priority 6}]) + :type :curve + :priority 6} + ;; TODO: we need an icon for canvas creation + {:icon i/box + :help "ds.help.canvas" + :type :canvas + :priority 7}]) ;; --- Draw Toolbox (Component) (mf/defc draw-toolbox {:wrap [mf/wrap-memo]} [{:keys [flags] :as props}] - (let [close #(st/emit! (dw/toggle-flag :drawtools)) - dtool (mf/deref refs/selected-drawing-tool) - tools (->> (into [] +draw-tools+) - (sort-by (comp :priority second))) + (letfn [(close [event] + (st/emit! (dw/deactivate-flag :drawtools))) + (select [event tool] + (st/emit! :interrupt + (dw/deactivate-ruler) + (dw/select-for-drawing tool))) + (toggle-ruler [event] + (st/emit! (dw/select-for-drawing nil) + (dw/deselect-all) + (dw/toggle-ruler)))] - select-drawtool #(st/emit! :interrupt - (dw/deactivate-ruler) - (dw/select-for-drawing %)) - toggle-ruler #(st/emit! (dw/select-for-drawing nil) - (dw/deselect-all) - (dw/toggle-ruler))] + (let [selected (mf/deref refs/selected-drawing-tool) + tools (sort-by (comp :priority second) +draw-tools+)] + [:div.tool-window.drawing-tools + [:div.tool-window-bar + [:div.tool-window-icon i/window] + [:span (tr "ds.draw-tools")] + [:div.tool-window-close {:on-click close} i/close]] + [:div.tool-window-content + (for [item tools] + (let [selected? (= (:type item) selected)] + [:div.tool-btn.tooltip.tooltip-hover + {:alt (tr (:help item)) + :class (when selected? "selected") + :key (:type item) + :on-click #(select % (:type item))} + (:icon item)])) - [:div#form-tools.tool-window.drawing-tools - [:div.tool-window-bar - [:div.tool-window-icon i/window] - [:span (tr "ds.draw-tools")] - [:div.tool-window-close {:on-click close} i/close]] - [:div.tool-window-content - (for [[i props] (map-indexed vector tools)] - (let [selected? (= dtool (:shape props))] - [:div.tool-btn.tooltip.tooltip-hover - {:alt (tr (:help props)) - :class (when selected? "selected") - :key i - :on-click (partial select-drawtool (:shape props))} - (:icon props)])) - [:div.tool-btn.tooltip.tooltip-hover - {:alt (tr "ds.help.ruler") - :on-click toggle-ruler - :class (when (contains? flags :ruler) "selected")} - i/ruler-tool]]])) + #_[:div.tool-btn.tooltip.tooltip-hover + {:alt (tr "ds.help.ruler") + :on-click toggle-ruler + :class (when (contains? flags :ruler) "selected")} + i/ruler-tool]]]))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 56631246e..049d42798 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -11,8 +11,7 @@ [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.main.data.pages :as udp] - [uxbox.main.data.shapes :as uds] - [uxbox.main.data.workspace :as udw] + [uxbox.main.data.workspace :as dw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] [uxbox.main.ui.keyboard :as kbd] @@ -44,9 +43,10 @@ on-blur (fn [event] (let [target (dom/event->target event) parent (.-parentNode target) + parent (.-parentNode parent) name (dom/get-value target)] (set! (.-draggable parent) true) - (st/emit! (uds/rename-shape (:id shape) name)) + (st/emit! (dw/rename-shape (:id shape) name)) (swap! local assoc :edition false))) on-key-down (fn [event] (js/console.log event) @@ -54,7 +54,8 @@ (on-blur event))) on-click (fn [event] (dom/prevent-default event) - (let [parent (.-parentNode (.-target event))] + (let [parent (.-parentNode (.-target event)) + parent (.-parentNode parent)] (set! (.-draggable parent) false)) (swap! local assoc :edition true))] (if (:edition @local) @@ -77,18 +78,18 @@ (let [id (:id shape) blocked? (:blocked shape)] (if blocked? - (st/emit! (uds/unblock-shape id)) - (st/emit! (uds/block-shape id))))) + (st/emit! (dw/unblock-shape id)) + (st/emit! (dw/block-shape id))))) (toggle-visibility [event] (dom/stop-propagation event) (let [id (:id shape) hidden? (:hidden shape)] (if hidden? - (st/emit! (uds/show-shape id)) - (st/emit! (uds/hide-shape id))) + (st/emit! (dw/show-shape id)) + (st/emit! (dw/hide-shape id))) (when (contains? selected id) - (st/emit! (udw/select-shape id))))) + (st/emit! (dw/select-shape id))))) (select-shape [event] (dom/prevent-default event) @@ -99,24 +100,20 @@ nil (.-ctrlKey event) - (st/emit! (udw/select-shape id)) + (st/emit! (dw/select-shape id)) (> (count selected) 1) - (st/emit! (udw/deselect-all) - (udw/select-shape id)) - - (contains? selected id) - (st/emit! (udw/select-shape id)) - + (st/emit! (dw/deselect-all) + (dw/select-shape id)) :else - (st/emit! (udw/deselect-all) - (udw/select-shape id))))) + (st/emit! (dw/deselect-all) + (dw/select-shape id))))) (on-drop [item monitor] (st/emit! (udp/persist-page (:page shape)))) (on-hover [item monitor] - (st/emit! (udw/change-shape-order {:id (:shape-id item) + (st/emit! (dw/change-shape-order {:id (:shape-id item) :index index})))] (let [selected? (contains? selected (:id shape)) [dprops dnd-ref] (use-sortable @@ -132,8 +129,7 @@ :dragging-TODO (:dragging? dprops))} [:div.element-list-body {:class (classnames :selected selected?) :on-click select-shape - :on-double-click #(dom/stop-propagation %) - :draggable true} + :on-double-click #(dom/stop-propagation %)} [:div.element-actions [:div.toggle-element {:class (when-not (:hidden shape) "selected") :on-click toggle-visibility} @@ -144,20 +140,52 @@ [:div.element-icon (element-icon shape)] [:& layer-name {:shape shape}]]]))) + +;; --- Layer Canvas + +;; (mf/defc layer-canvas +;; [{:keys [canvas selected index] :as props}] +;; (letfn [(select-shape [event] +;; (dom/prevent-default event) +;; (st/emit! (dw/select-canvas (:id canvas)))) +;; (let [selected? (contains? selected (:id shape))] +;; [:li {:class (classnames +;; :selected selected?)} +;; [:div.element-list-body {:class (classnames :selected selected?) +;; :on-click select-shape +;; :on-double-click #(dom/stop-propagation %) +;; :draggable true} +;; [:div.element-actions +;; [:div.toggle-element {:class (when-not (:hidden shape) "selected") +;; :on-click toggle-visibility} +;; i/eye] +;; [:div.block-element {:class (when (:blocked shape) "selected") +;; :on-click toggle-blocking} +;; i/lock]] +;; [:div.element-icon (element-icon shape)] +;; [:& layer-name {:shape shape}]]]))) + ;; --- Layers List (def ^:private shapes-iref (-> (l/key :shapes) (l/derive st/state))) +(def ^:private canvas-iref + (-> (l/key :canvas) + (l/derive st/state))) + (mf/defc layers-list [{:keys [shapes selected] :as props}] - (let [shapes-map (mf/deref shapes-iref)] + (let [shapes-map (mf/deref shapes-iref) + canvas-map (mf/deref canvas-iref) + selected-shapes (mf/deref refs/selected-shapes) + selected-canvas (mf/deref refs/selected-canvas)] [:div.tool-window-content [:ul.element-list (for [[index id] (map-indexed vector shapes)] [:& layer-item {:shape (get shapes-map id) - :selected selected + :selected selected-shapes :index index :key id}])]])) @@ -165,7 +193,7 @@ (mf/defc layers-toolbox [{:keys [page selected] :as props}] - (let [on-click #(st/emit! (udw/toggle-flag :layers)) + (let [on-click #(st/emit! (dw/toggle-flag :layers)) selected (mf/deref refs/selected-shapes)] [:div#layers.tool-window [:div.tool-window-bar diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs index 2467cf193..d402fe303 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs @@ -93,14 +93,14 @@ value (parse-int value 0) sid (:id shape) props {attr value}] - (st/emit! (uds/update-dimensions sid props)))) + (st/emit! (udw/update-dimensions sid props)))) (defn- on-rotation-change [event shape] (let [value (dom/event->value event) value (parse-int value 0) sid (:id shape)] - (st/emit! (uds/update-rotation sid value)))) + (st/emit! (udw/update-shape-attrs sid {:rotation value})))) (defn- on-position-change [event shape attr] @@ -108,11 +108,11 @@ value (parse-int value nil) sid (:id shape) point (gpt/point {attr value})] - (st/emit! (uds/update-position sid point)))) + (st/emit! (udw/update-position sid point)))) (defn- on-proportion-lock-change [event shape] (if (:proportion-lock shape) - (st/emit! (uds/unlock-proportions (:id shape))) - (st/emit! (uds/lock-proportions (:id shape))))) + (st/emit! (udw/unlock-proportions (:id shape))) + (st/emit! (udw/lock-proportions (:id shape))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs index 7e7306f72..6b406b804 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs @@ -90,14 +90,13 @@ value (parse-int value 0) sid (:id shape) props {attr value}] - (st/emit! (uds/update-dimensions sid props)))) + (st/emit! (udw/update-dimensions sid props)))) (defn- on-rotation-change [event shape] (let [value (dom/event->value event) - value (parse-int value 0) - sid (:id shape)] - (st/emit! (uds/update-rotation sid value)))) + value (parse-int value 0)] + (st/emit! (udw/update-shape-attrs (:id shape) {:rotation value})))) (defn- on-position-change [event shape attr] @@ -105,11 +104,11 @@ value (parse-int value nil) sid (:id shape) point (gpt/point {attr value})] - (st/emit! (uds/update-position sid point)))) + (st/emit! (udw/update-position sid point)))) (defn- on-proportion-lock-change [event shape] (if (:proportion-lock shape) - (st/emit! (uds/unlock-proportions (:id shape))) - (st/emit! (uds/lock-proportions (:id shape))))) + (st/emit! (udw/unlock-proportions (:id shape))) + (st/emit! (udw/lock-proportions (:id shape))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs index 6116b18d5..963571840 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs @@ -9,8 +9,7 @@ (:require [rumext.alpha :as mf] [uxbox.builtins.icons :as i] - [uxbox.main.data.shapes :as uds] - [uxbox.main.data.workspace :as udw] + [uxbox.main.data.workspace :as dw] [uxbox.main.geom :as geom] [uxbox.main.store :as st] [uxbox.util.data :refer (parse-int parse-float read-string)] @@ -106,30 +105,30 @@ (let [value (dom/event->value event) value (parse-int value 0) props {attr value}] - (st/emit! (uds/update-dimensions (:id shape) props)))) + (st/emit! (dw/update-dimensions (:id shape) props)))) (defn- on-rotation-change [event shape] (let [value (dom/event->value event) value (parse-int value 0)] - (st/emit! (uds/update-rotation (:id shape) value)))) + (st/emit! (dw/update-shape-attrs (:id shape) {:rotation value})))) (defn- on-opacity-change [event shape] (let [value (dom/event->value event) value (parse-float value 1) value (/ value 10000)] - (st/emit! (uds/update-attrs (:id shape) {:opacity value})))) + (st/emit! (dw/update-shape-attrs (:id shape) {:opacity value})))) (defn- on-position-change [event shape attr] (let [value (dom/event->value event) value (parse-int value nil) point (gpt/point {attr value})] - (st/emit! (uds/update-position (:id shape) point)))) + (st/emit! (dw/update-position (:id shape) point)))) (defn- on-proportion-lock-change [event shape] (if (:proportion-lock shape) - (st/emit! (uds/unlock-proportions (:id shape))) - (st/emit! (uds/lock-proportions (:id shape))))) + (st/emit! (dw/unlock-proportions (:id shape))) + (st/emit! (dw/lock-proportions (:id shape))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs index 674256504..3ff8dc675 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs @@ -10,7 +10,7 @@ [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.main.data.lightbox :as udl] - [uxbox.main.data.shapes :as uds] + [uxbox.main.data.workspace :as dw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] [uxbox.main.ui.colorpicker :as cp] @@ -59,7 +59,7 @@ (delete [item] (let [sid (:id shape) id (:id item)] - (st/emit! (uds/delete-interaction sid id)))) + (st/emit! (dw/delete-interaction sid id)))) (on-delete [item event] (dom/prevent-default event) (let [delete (partial delete item)] @@ -455,7 +455,7 @@ (dom/prevent-default event) (let [sid (:id shape) data (deref form)] - (st/emit! (uds/update-interaction sid data)) + (st/emit! (dw/update-interaction sid data)) (reset! form nil))) (on-cancel [event] (dom/prevent-default event) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs index 711e11b8a..f8c47f3c0 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs @@ -87,23 +87,23 @@ [event shape attr] (let [value (-> (dom/event->value event) (parse-int 0))] - (st/emit! (uds/update-dimensions (:id shape) {attr value})))) + (st/emit! (udw/update-dimensions (:id shape) {attr value})))) (defn- on-rotation-change [event shape] - (let [value (-> (dom/event->value event) - (parse-int 0))] - (st/emit! (uds/update-rotation (:id shape) value)))) + (let [value (dom/event->value event) + value (parse-int value 0)] + (st/emit! (udw/update-shape-attrs (:id shape) {:rotation value})))) (defn- on-position-change [event shape attr] (let [value (-> (dom/event->value event) (parse-int nil)) point (gpt/point {attr value})] - (st/emit! (uds/update-position (:id shape) point)))) + (st/emit! (udw/update-position (:id shape) point)))) (defn- on-proportion-lock-change [event shape] (if (:proportion-lock shape) - (st/emit! (uds/unlock-proportions (:id shape))) - (st/emit! (uds/lock-proportions (:id shape))))) + (st/emit! (udw/unlock-proportions (:id shape))) + (st/emit! (udw/lock-proportions (:id shape))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs index dfe0ca283..962b96f56 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs @@ -30,7 +30,7 @@ {:mixins [mx/static]} [menu {:keys [id] :as shape}] (letfn [(update-attrs [attrs] - (st/emit! (uds/update-attrs id attrs))) + (st/emit! (udw/update-shape-attrs id attrs))) (on-font-family-change [event] (let [value (dom/event->value event) attrs {:font-family (read-string value) diff --git a/frontend/src/uxbox/main/ui/workspace/streams.cljs b/frontend/src/uxbox/main/ui/workspace/streams.cljs index 9654c00e3..88bcb2522 100644 --- a/frontend/src/uxbox/main/ui/workspace/streams.cljs +++ b/frontend/src/uxbox/main/ui/workspace/streams.cljs @@ -52,21 +52,7 @@ (and (mouse-event? v) (= :click (:type v)))) -(defrecord PointerEvent [window - viewport - ctrl - shift]) - -(defn pointer-event - [window viewport ctrl shift] - {:pre [(gpt/point? window) - (gpt/point? viewport) - (boolean? ctrl) - (boolean? shift)]} - (PointerEvent. window - viewport - ctrl - shift)) +(defrecord PointerEvent [source pt ctrl shift]) (defn pointer-event? [v] @@ -90,23 +76,14 @@ ;; --- Derived streams -;; TODO: this shoul be DEPRECATED -(defonce interaction-events - (rx/filter interaction-event? st/stream)) - (defonce mouse-position - (rx/filter pointer-event? st/stream)) - -(defonce viewport-mouse-position - (let [sub (rx/behavior-subject nil)] - (-> (rx/map :viewport mouse-position) - (rx/subscribe-with sub)) - sub)) - -(defonce window-mouse-position - (let [sub (rx/behavior-subject nil)] - (-> (rx/map :window mouse-position) - (rx/subscribe-with sub)) + (let [sub (rx/behavior-subject nil) + ob (->> st/stream + (rx/filter pointer-event?) + (rx/filter #(= :viewport (:source %))) + (rx/map :pt) + )] + (rx/subscribe-with ob sub) sub)) (defonce mouse-position-ctrl @@ -116,7 +93,7 @@ sub)) (defonce mouse-position-deltas - (->> viewport-mouse-position + (->> mouse-position (rx/sample 10) (rx/map #(gpt/divide % @refs/selected-zoom)) (rx/mapcat (fn [point] diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 299b5c4ac..b0d623999 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -37,7 +37,7 @@ (mf/defc coordinates [{:keys [zoom] :as props}] - (let [coords (some-> (use-rxsub uws/viewport-mouse-position) + (let [coords (some-> (use-rxsub uws/mouse-position) (gpt/divide zoom) (gpt/round 0))] [:ul.coordinates @@ -60,16 +60,16 @@ :circle "Drag to draw a Circle" nil)) -(mf/defc cursor-tooltip - {:wrap [mf/wrap-memo]} - [{:keys [tooltip]}] - (let [coords (mf/deref refs/window-mouse-position)] - [:span.cursor-tooltip - {:style - {:position "fixed" - :left (str (+ (:x coords) 5) "px") - :top (str (- (:y coords) 25) "px")}} - tooltip])) +;; (mf/defc cursor-tooltip +;; {:wrap [mf/wrap-memo]} +;; [{:keys [tooltip]}] +;; (let [coords (mf/deref refs/window-mouse-position)] +;; [:span.cursor-tooltip +;; {:style +;; {:position "fixed" +;; :left (str (+ (:x coords) 5) "px") +;; :top (str (- (:y coords) 25) "px")}} +;; tooltip])) ;; --- Selection Rect @@ -89,15 +89,13 @@ (reify ptk/WatchEvent (watch [_ state stream] - (let [stoper (->> (rx/merge (rx/filter #(= % :interrupt) stream) - (rx/filter uws/mouse-up? stream)) - (rx/take 1))] + (let [stoper (rx/filter #(or (dw/interrupt? %) (uws/mouse-up? %)) stream)] (rx/concat - (->> uws/viewport-mouse-position + (rx/of (dw/deselect-all)) + (->> uws/mouse-position (rx/map (fn [pos] #(update-state % pos))) (rx/take-until stoper)) - (rx/of (dw/deselect-all) - dw/select-shapes-by-current-selrect + (rx/of dw/select-shapes-by-current-selrect clear-state))))))) (mf/defc selrect @@ -115,33 +113,187 @@ ;; --- Viewport Positioning (def handle-viewport-positioning - (reify - ptk/WatchEvent - (watch [_ state stream] - (let [stoper (->> (rx/filter #(= ::finish-positioning %) stream) - (rx/take 1)) - reference @uws/viewport-mouse-position - dom (dom/get-element "workspace-viewport")] - (->> uws/viewport-mouse-position - (rx/map (fn [point] - (let [{:keys [x y]} (gpt/subtract point reference) - cx (.-scrollLeft dom) - cy (.-scrollTop dom)] - (set! (.-scrollLeft dom) (- cx x)) - (set! (.-scrollTop dom) (- cy y))))) - (rx/take-until stoper) - (rx/ignore)))))) + (letfn [(on-point [dom reference point] + (let [{:keys [x y]} (gpt/subtract point reference) + cx (.-scrollLeft dom) + cy (.-scrollTop dom)] + (set! (.-scrollLeft dom) (- cx x)) + (set! (.-scrollTop dom) (- cy y))))] + (reify + ptk/EffectEvent + (effect [_ state stream] + (let [stoper (rx/filter #(= ::finish-positioning %) stream) + reference @uws/mouse-position + dom (dom/get-element "workspace-viewport")] + (-> (rx/take-until stoper uws/mouse-position) + (rx/subscribe #(on-point dom reference %)))))))) ;; --- Viewport -(mf/def viewport +(mf/defc viewport + [{:keys [page] :as props}] + (let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/deref refs/workspace) + 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) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (uws/mouse-event :down ctrl? shift?))) + (when (not edition) + (if drawing-tool + (st/emit! (start-drawing drawing-tool)) + (st/emit! :interrupt handle-selrect)))) + + (on-context-menu [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (uws/mouse-event :context-menu ctrl? shift?)))) + + (on-mouse-up [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (uws/mouse-event :up ctrl? shift?)))) + + (on-click [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (uws/mouse-event :click ctrl? shift?)))) + + (on-double-click [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (uws/mouse-event :double-click ctrl? shift?)))) + + (translate-point-to-viewport [pt] + (let [viewport (mf/ref-node viewport-ref) + brect (.getBoundingClientRect viewport) + brect (gpt/point (parse-int (.-left brect)) + (parse-int (.-top brect)))] + (gpt/subtract pt brect))) + + (on-key-down [event] + (let [bevent (.getBrowserEvent event) + key (.-keyCode event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:key key + :shift? shift? + :ctrl? ctrl?}] + (when-not (.-repeat bevent) + (st/emit! (uws/keyboard-event :down key ctrl? shift?)) + (when (kbd/space? event) + (st/emit! handle-viewport-positioning) + #_(st/emit! (dw/start-viewport-positioning)))))) + + (on-key-up [event] + (let [key (.-keyCode event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:key key + :shift? shift? + :ctrl? ctrl?}] + (when (kbd/space? event) + (st/emit! ::finish-positioning #_(dw/stop-viewport-positioning))) + (st/emit! (uws/keyboard-event :up key ctrl? shift?)))) + + (on-mouse-move [event] + (let [pt (gpt/point (.-clientX event) + (.-clientY event)) + pt (translate-point-to-viewport pt)] + ;; (prn "viewport:on-mouse-move" pt) + (st/emit! (uws/->PointerEvent :viewport pt (kbd/ctrl? event) (kbd/shift? event))))) + + + ;; ;; ctrl? (kbd/ctrl? event) + ;; ;; shift? (kbd/shift? event) + ;; ;; event {:ctrl ctrl? + ;; ;; :shift shift? + ;; ;; :window-coords wpt + ;; ;; :viewport-coords vpt} + ;; ] + ;; #_(st/emit! (uws/pointer-event wpt vpt ctrl? shift?)))) + + (on-mount [] + (prn "viewport.on-mount" (:id page)) + (let [ + ;; key1 (events/listen js/document EventType.MOUSEMOVE on-mousemove) + key2 (events/listen js/document EventType.KEYDOWN on-key-down) + key3 (events/listen js/document EventType.KEYUP on-key-up)] + (fn [] + ;; (events/unlistenByKey key1) + (events/unlistenByKey key2) + (events/unlistenByKey key3))))] + + (mf/use-effect on-mount) + ;; (prn "viewport.render" (:id page)) + + [:* + [:& 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 + :class (when drawing-tool "drawing") + :on-context-menu on-context-menu + :on-click on-click + :on-double-click on-double-click + :on-mouse-move on-mouse-move + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up} + [:g.zoom {:transform (str "scale(" zoom ", " zoom ")")} + (when page + [:* + (for [id (:canvas page)] + [:& canvas {:key id :id id}]) + + (for [id (reverse (:shapes page))] + [:& uus/shape-component {:id id :key id}]) + + (when (seq (:selected wst)) + [:& selection-handlers {:wst wst}]) + + (when-let [dshape (:drawing wst)] + [:& draw-area {:shape dshape + :zoom (:zoom wst) + :modifiers (:modifiers wst)}])]) + + + + (if (contains? flags :grid) + [:& grid {:page page}])] + (when (contains? flags :ruler) + [:& ruler {:zoom zoom :ruler (:ruler wst)}]) + [:& selrect {:data (:selrect wst)}]]]))) + + +#_(mf/def viewport :init (fn [own props] (assoc own ::viewport (mf/create-ref))) :did-mount (fn [own] - (letfn [(translate-point-to-viewport [pt] + (letfn [ + (translate-point-to-viewport [pt] (let [viewport (mf/ref-node (::viewport own)) brect (.getBoundingClientRect viewport) brect (gpt/point (parse-int (.-left brect)) diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index b1b3367a1..03e6f29a5 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -17,11 +17,11 @@ "Return a indexed map of the collection keyed by the result of executing the getter over each element of the collection." - [coll getter] + [getter coll] (persistent! (reduce #(assoc! %1 (getter %2) %2) (transient {}) coll))) -(def index-by-id #(index-by % :id)) +(def index-by-id #(index-by :id %)) (defn remove-nil-vals "Given a map, return a map removing key-value