diff --git a/backend/src/uxbox/fixtures.clj b/backend/src/uxbox/fixtures.clj index abc0f54a3..7c8b74dfd 100644 --- a/backend/src/uxbox/fixtures.clj +++ b/backend/src/uxbox/fixtures.clj @@ -56,7 +56,6 @@ (let [sql create-project-user-sql project-id (mk-uuid "project" project-index user-index) user-id (mk-uuid "user" (dec user-index))] - (println sql project-id user-id) (db/query-one conn [sql project-id user-id]))) ;; --- Projects creation @@ -114,10 +113,10 @@ (let [canvas {:id (mk-uuid "canvas" 1) :name "Canvas-1" :type :canvas - :x1 200 - :y1 200 - :x2 1224 - :y2 968} + :x 200 + :y 200 + :width 1024 + :height 768} data {:shapes [] :canvas [(:id canvas)] :shapes-by-id {(:id canvas) canvas}} diff --git a/backend/src/uxbox/media_loader.clj b/backend/src/uxbox/media_loader.clj index aea849529..d111ba88c 100644 --- a/backend/src/uxbox/media_loader.clj +++ b/backend/src/uxbox/media_loader.clj @@ -56,7 +56,7 @@ [conn {:keys [name] :as item}] (log/info "Creating or updating icons collection:" name) (let [id (uuid/namespaced +icons-uuid-ns+ name) - sql "insert into icons_collections (id, user_id, name) + sql "insert into icon_collections (id, user_id, name) values ($1, '00000000-0000-0000-0000-000000000000'::uuid, $2) on conflict (id) do update set name = $2 @@ -122,8 +122,8 @@ "Create or replace image collection by its name." [conn {:keys [name] :as item}] (log/info "Creating or updating image collection:" name) - (let [id (uuid/namespaced +icons-uuid-ns+ name) - sql "insert into images_collections (id, user_id, name) + (let [id (uuid/namespaced +images-uuid-ns+ name) + sql "insert into image_collections (id, user_id, name) values ($1, '00000000-0000-0000-0000-000000000000'::uuid, $2) on conflict (id) do update set name = $2 diff --git a/backend/src/uxbox/services/mutations/icons.clj b/backend/src/uxbox/services/mutations/icons.clj index edc1b0330..d9c306526 100644 --- a/backend/src/uxbox/services/mutations/icons.clj +++ b/backend/src/uxbox/services/mutations/icons.clj @@ -45,7 +45,7 @@ (sm/defmutation ::create-icons-collection [{:keys [id user name] :as params}] (let [id (or id (uuid/next)) - sql "insert into icons_collections (id, user_id, name) + sql "insert into icon_collections (id, user_id, name) values ($1, $2, $3) returning *"] (db/query-one db/pool [sql id user name]))) @@ -56,7 +56,7 @@ (sm/defmutation ::update-icons-collection [{:keys [id user name] :as params}] - (let [sql "update icons_collections + (let [sql "update icon_collections set name = $3 where id = $1 and user_id = $2 @@ -97,7 +97,7 @@ (sm/defmutation ::delete-icons-collection [{:keys [user id] :as params}] - (let [sql "update icons_collections + (let [sql "update icon_collections set deleted_at = clock_timestamp() where id = $1 and user_id = $2 diff --git a/backend/src/uxbox/services/mutations/images.clj b/backend/src/uxbox/services/mutations/images.clj index 4b89383dd..b4270e0b4 100644 --- a/backend/src/uxbox/services/mutations/images.clj +++ b/backend/src/uxbox/services/mutations/images.clj @@ -60,7 +60,7 @@ (sm/defmutation ::create-image-collection [{:keys [id user name] :as params}] - (let [sql "insert into images_collections (id, user_id, name) + (let [sql "insert into image_collections (id, user_id, name) values ($1, $2, $3) returning *;"] (db/query-one db/pool [sql (or id (uuid/next)) user name]))) @@ -71,7 +71,7 @@ (sm/defmutation ::update-images-collection [{:keys [id user name] :as params}] - (let [sql "update images_collections + (let [sql "update image_collections set name = $3 where id = $1 and user_id = $2 @@ -85,7 +85,7 @@ (sm/defmutation ::delete-images-collection [{:keys [id user] :as params}] - (let [sql "update images_collections + (let [sql "update image_collections set deleted_at = clock_timestamp() where id = $1 and user_id = $2 @@ -102,13 +102,13 @@ (-> (ds/save storage filename path) (su/handle-on-context)))) -(def ^:private create-image-sql +(su/defstr sql:create-image "insert into images (user_id, name, collection_id, path, width, height, mimetype) values ($1, $2, $3, $4, $5, $6, $7) returning *") (defn- store-image-in-db [conn {:keys [id user name path collection-id height width mimetype]}] - (let [sqlv [create-image-sql user name collection-id + (let [sqlv [sql:create-image user name collection-id path width height mimetype]] (-> (db/query-one conn sqlv) (p/then populate-thumbnail) diff --git a/backend/src/uxbox/services/mutations/project_files.clj b/backend/src/uxbox/services/mutations/project_files.clj index d3e7e294e..f2bfc6046 100644 --- a/backend/src/uxbox/services/mutations/project_files.clj +++ b/backend/src/uxbox/services/mutations/project_files.clj @@ -59,7 +59,7 @@ (ex/raise :type :validation :code :not-authorized)))))) -;; --- Mutation: Create Project +;; --- Mutation: Create Project File (declare create-file) (declare create-page) @@ -72,9 +72,9 @@ [{:keys [user project-id] :as params}] (db/with-atomic [conn db/pool] (proj/check-edition-permissions! conn user project-id) - (p/let [file (create-file conn params)] - (create-page conn (assoc params :file-id (:id file))) - file))) + (p/let [file (create-file conn params) + page (create-page conn (assoc params :file-id (:id file)))] + (assoc file :pages [(:id page)])))) (defn create-file [conn {:keys [id user name project-id] :as params}] @@ -88,7 +88,10 @@ [conn {:keys [user file-id] :as params}] (let [id (uuid/next) name "Page 1" - data (blob/encode {}) + data (blob/encode + {:shapes [] + :canvas [] + :shapes-by-id {}}) sql "insert into project_pages (id, user_id, file_id, name, version, ordering, data) values ($1, $2, $3, $4, 0, 1, $5) returning id"] diff --git a/backend/src/uxbox/services/queries/icons.clj b/backend/src/uxbox/services/queries/icons.clj index 77428755c..eb7faecf1 100644 --- a/backend/src/uxbox/services/queries/icons.clj +++ b/backend/src/uxbox/services/queries/icons.clj @@ -31,7 +31,7 @@ (def ^:private icons-collections-sql "select *, (select count(*) from icons where collection_id = ic.id) as num_icons - from icons_collections as ic + from icon_collections as ic where (ic.user_id = $1 or ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid) and ic.deleted_at is null diff --git a/backend/src/uxbox/services/queries/images.clj b/backend/src/uxbox/services/queries/images.clj index 93a52f0da..45168e8b1 100644 --- a/backend/src/uxbox/services/queries/images.clj +++ b/backend/src/uxbox/services/queries/images.clj @@ -53,7 +53,7 @@ (def ^:private images-collections-sql "select *, (select count(*) from images where collection_id = ic.id) as num_images - from images_collections as ic + from image_collections as ic where (ic.user_id = $1 or ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid) and ic.deleted_at is null @@ -86,23 +86,32 @@ ;; --- Query Images by Collection (id) -(def images-by-collection-sql +(su/defstr sql:images-by-collection "select * from images where (user_id = $1 or user_id = '00000000-0000-0000-0000-000000000000'::uuid) and deleted_at is null - and case when $2::uuid is null then collection_id is null - else collection_id = $2::uuid - end - order by created_at desc;") + order by created_at desc") -(s/def ::images-by-collection-query +(su/defstr sql:images-by-collection1 + "with images as (~{sql:images-by-collection}) + select im.* from images as im + where im.collection_id is null") + +(su/defstr sql:images-by-collection2 + "with images as (~{sql:images-by-collection}) + select im.* from images as im + where im.collection_id = $2") + +(s/def ::images-by-collection (s/keys :req-un [::user] :opt-un [::collection-id])) (sq/defquery ::images-by-collection [{:keys [user collection-id] :as params}] - (let [sqlv [images-by-collection-sql user collection-id]] + (let [sqlv (if (nil? collection-id) + [sql:images-by-collection1 user] + [sql:images-by-collection2 user collection-id])] (-> (db/query db/pool sqlv) (p/then populate-thumbnails) (p/then #(mapv populate-urls %))))) diff --git a/backend/src/vertx/web/interceptors.clj b/backend/src/vertx/web/interceptors.clj index bca322513..eaad85d36 100644 --- a/backend/src/vertx/web/interceptors.clj +++ b/backend/src/vertx/web/interceptors.clj @@ -99,13 +99,13 @@ {:enter (fn [data] (let [context (get-in data [:request ::vw/routing-context]) uploads (reduce (fn [acc ^FileUpload upload] - (assoc acc - (keyword (.name upload)) - {:type :uploaded-file - :mtype (.contentType upload) - :path (.uploadedFileName upload) - :name (.fileName upload) - :size (.size upload)})) + (assoc! acc + (keyword (.name upload)) + {:type :uploaded-file + :mtype (.contentType upload) + :path (.uploadedFileName upload) + :name (.fileName upload) + :size (.size upload)})) (transient {}) (.fileUploads ^RoutingContext context))] (update data :request assoc attr (persistent! uploads))))})) diff --git a/common/uxbox/common/data.cljc b/common/uxbox/common/data.cljc index 1c2ee5c28..9fd9d2b20 100644 --- a/common/uxbox/common/data.cljc +++ b/common/uxbox/common/data.cljc @@ -7,7 +7,13 @@ (ns uxbox.common.data "Data manipulation and query helper functions." (:refer-clojure :exclude [concat]) - (:require [clojure.set :as set])) + (:require [clojure.set :as set] + #?(:cljs [cljs.reader :as r] + :clj [clojure.edn :as r]))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Data Structures Manipulation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn concat [& colls] @@ -18,6 +24,18 @@ (rest colls)) result))) +(defn enumerate + ([items] (enumerate items 0)) + ([items start] + (loop [idx start + items items + res []] + (if (empty? items) + res + (recur (inc idx) + (rest items) + (conj res [idx (first items)])))))) + (defn seek ([pred coll] (seek pred coll nil)) @@ -48,3 +66,75 @@ (recur (first r) (rest r) rs) (recur (first r) (rest r) (conj rs [:mod k vmb])))) rs))))) + +(defn index-by + "Return a indexed map of the collection keyed by the result of + executing the getter over each element of the collection." + [getter coll] + (persistent! + (reduce #(assoc! %1 (getter %2) %2) (transient {}) coll))) + +(defn remove-nil-vals + "Given a map, return a map removing key-value + pairs when value is `nil`." + [data] + (into {} (remove (comp nil? second) data))) + +(defn without-keys + "Return a map without the keys provided + in the `keys` parameter." + [data keys] + (persistent! + (reduce #(dissoc! %1 %2) (transient data) keys))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Data Parsing / Conversion +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- nan? + [v] + (not= v v)) + +(defn- impl-parse-integer + [v] + #?(:cljs (js/parseInt v 10) + :clj (try + (Integer/parseInt v) + (catch Throwable e + nil)))) + +(defn- impl-parse-double + [v] + #?(:cljs (js/parseFloat v) + :clj (try + (Double/parseDouble v) + (catch Throwable e + nil)))) + +(defn parse-integer + ([v] + (parse-integer v nil)) + ([v default] + (let [v (impl-parse-integer v)] + (if (or (nil? v) (nan? v)) + default + v)))) + +(defn parse-double + ([v] + (parse-double v nil)) + ([v default] + (let [v (impl-parse-double v)] + (if (or (nil? v) (nan? v)) + default + v)))) + +(defn read-string + [v] + (r/read-string v)) + +(defn coalesce-str + [val default] + (if (or (nil? val) (nan? val)) + default + (str val))) diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index 437b572d5..16aa9ca7b 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -16,13 +16,12 @@ (s/def ::background string?) (s/def ::background-opacity number?) -;; Page related -(s/def ::file-id uuid?) -(s/def ::user uuid?) -(s/def ::created-at inst?) -(s/def ::modified-at inst?) -(s/def ::version number?) -(s/def ::ordering number?) +(s/def ::metadata + (s/keys :opt-un [::grid-y-axis + ::grid-x-axis + ::grid-color + ::background + ::background-opacity])) ;; Page Data related (s/def ::shape @@ -35,19 +34,9 @@ (s/def ::shapes-by-id (s/map-of uuid? ::shape)) -;; Main - (s/def ::data (s/keys :req-un [::shapes ::canvas ::shapes-by-id])) - -(s/def ::metadata - (s/keys :opt-un [::grid-y-axis - ::grid-x-axis - ::grid-color - ::background - ::background-opacity])) - (s/def ::shape-change (s/tuple #{:add :mod :del} keyword? any?)) diff --git a/frontend/deps.edn b/frontend/deps.edn index 5670a4d05..cedbc2a73 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -3,15 +3,15 @@ org.clojure/clojure {:mvn/version "1.10.1"} com.cognitect/transit-cljs {:mvn/version "0.8.256"} - cljsjs/react {:mvn/version "16.11.0-0"} - cljsjs/react-dom {:mvn/version "16.11.0-0"} - cljsjs/react-dom-server {:mvn/version "16.11.0-0"} + cljsjs/react {:mvn/version "16.12.0-1"} + cljsjs/react-dom {:mvn/version "16.12.0-1"} + cljsjs/react-dom-server {:mvn/version "16.12.0-1"} environ/environ {:mvn/version "1.1.0"} metosin/reitit-core {:mvn/version "0.3.10"} expound/expound {:mvn/version "0.7.2"} - funcool/beicon {:mvn/version "5.1.0"} + funcool/beicon {:mvn/version "6.0.0-SNAPSHOT"} funcool/cuerdas {:mvn/version "2.2.0"} funcool/lentes {:mvn/version "1.3.3"} funcool/potok {:mvn/version "2.8.0-SNAPSHOT"} diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index 71f9305ec..175551b77 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -242,7 +242,7 @@ // IMAGES SECTION &.images-th { - background-color: $primary-ui-bg; + border: 1px dashed $color-gray-light; border-bottom: 2px solid lighten($color-gray-light, 12%); &:hover { diff --git a/frontend/src/uxbox/main/data/colors.cljs b/frontend/src/uxbox/main/data/colors.cljs index 8b2785539..871492be5 100644 --- a/frontend/src/uxbox/main/data/colors.cljs +++ b/frontend/src/uxbox/main/data/colors.cljs @@ -17,21 +17,14 @@ [uxbox.util.time :as dt] [uxbox.util.uuid :as uuid])) +;; TODO: need a good refactor + ;; --- Initialize (declare fetch-collections) (declare persist-collections) (declare collections-fetched?) -(defrecord Initialize [] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:dashboard :colors] {:selected #{}}))) - -(defn initialize - [] - (Initialize.)) - ;; --- Collections Fetched (defrecord CollectionsFetched [data] @@ -56,7 +49,7 @@ (defrecord FetchCollections [] ptk/WatchEvent (watch [_ state s] - (->> (rp/query! :kvstore-entry {:key "color-collections"}) + (->> (rp/query! :user-attr {:key "color-collections"}) (rx/map collections-fetched) (rx/catch (fn [{:keys [type] :as error}] (if (= type :not-found) @@ -99,10 +92,9 @@ version (or (get state ::version) -1) value (->> (get state :colors-collections) (into {} xform)) - data {:id "color-collections" - :version version - :value value}] - (->> (rp/mutation! :upsert-kvstore data) + data {:key "color-collections" + :val value}] + (->> (rp/mutation! :upsert-user-attr data) (rx/map collections-fetched))))) (defn persist-collections diff --git a/frontend/src/uxbox/main/data/icons.cljs b/frontend/src/uxbox/main/data/icons.cljs index 136a5c5e3..202e3a9cb 100644 --- a/frontend/src/uxbox/main/data/icons.cljs +++ b/frontend/src/uxbox/main/data/icons.cljs @@ -6,6 +6,7 @@ (ns uxbox.main.data.icons (:require + [cljs.spec.alpha :as s] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk] @@ -18,30 +19,32 @@ [uxbox.util.router :as r] [uxbox.util.uuid :as uuid])) -;; --- Initialize +(s/def ::id uuid?) +(s/def ::name string?) +(s/def ::created-at inst?) +(s/def ::modified-at inst?) +(s/def ::user-id uuid?) -(def initialize - (ptk/reify ::initialize - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:dashboard :icons] {:selected #{}})))) +;; (s/def ::collection-id (s/nilable ::us/uuid)) -;; --- Select a Collection +;; (s/def ::mimetype string?) +;; (s/def ::thumbnail us/url-str?) +;; (s/def ::width number?) +;; (s/def ::height number?) +;; (s/def ::url us/url-str?) -(defn select-collection - ([type] - (select-collection type nil)) - ([type id] - {:pre [(keyword? type)]} - (ptk/reify ::select-collection - ptk/WatchEvent - (watch [_ state stream] - (rx/of (r/navigate :dashboard/icons {:type type :id id})))))) +(s/def ::collection + (s/keys :req-un [::id + ::name + ::created-at + ::modified-at + ::user-id])) ;; --- Collections Fetched (defn collections-fetched [items] + (s/assert (s/every ::collection) items) (ptk/reify ::collections-fetched cljs.core/IDeref (-deref [_] items) @@ -55,10 +58,6 @@ state items)))) -(defn collections-fetched? - [v] - (= ::collections-fetched (ptk/type v))) - ;; --- Fetch Collections (def fetch-collections @@ -72,15 +71,12 @@ (defn collection-created [item] + (s/assert ::collection item) (ptk/reify ::collection-created ptk/UpdateEvent (update [_ state] (let [{:keys [id] :as item} (assoc item :type :own)] - (update state :icons-collections assoc id item))) - - ptk/WatchEvent - (watch [_ state stream] - (rx/of (select-collection :own (:id item)))))) + (update state :icons-collections assoc id item))))) ;; --- Create Collection @@ -100,7 +96,7 @@ (ptk/reify ::collection-updated ptk/UpdateEvent (update [_ state] - (update-in state [:icons-collections (:id item)] merge item)))) + (update-in state [:icons-collections (:id item)] merge item)))) ;; --- Update Collection @@ -141,7 +137,7 @@ (watch [_ state s] (let [type (get-in state [:dashboard :icons :type])] (->> (rp/mutation! :delete-icons-collection {:id id}) - (rx/map #(select-collection type)))))) + (rx/map #(r/nav :dashboard-icons {:type type})))))) (defn delete-collection [id] @@ -179,44 +175,42 @@ (dom/append-child! gc child)) (recur (dom/get-first-child svg))) (let [width (.. svg -width -baseVal -value) - header (.. svg -height -baseVal -value) + height (.. svg -height -baseVal -value) view-box [(.. svg -viewBox -baseVal -x) (.. svg -viewBox -baseVal -y) (.. svg -viewBox -baseVal -width) (.. svg -viewBox -baseVal -height)] props {:width width :mimetype "image/svg+xml" - :height header + :height height :view-box view-box}] [(dom/get-outer-html g) props]))))) -(defrecord CreateIcons [id files] - ptk/WatchEvent - (watch [_ state s] - (letfn [(parse [file] - (->> (files/read-as-text file) - (rx/map parse-svg))) - (allowed? [file] - (= (.-type file) "image/svg+xml")) - (prepare [[content metadata]] - {:collection-id id - :content content - :id (uuid/random) - ;; TODO Keep the name of the original icon - :name (str "Icon " (gensym "i")) - :metadata metadata})] - (->> (rx/from-coll files) - (rx/filter allowed?) - (rx/flat-map parse) - (rx/map prepare) - (rx/flat-map #(rp/mutation! :create-icon %)) - (rx/map :payload) - (rx/map icon-created))))) (defn create-icons [id files] - {:pre [(or (uuid? id) (nil? id))]} - (CreateIcons. id files)) + (s/assert (s/nilable uuid?) id) + (ptk/reify ::create-icons + ptk/WatchEvent + (watch [_ state s] + (letfn [(parse [file] + (->> (files/read-as-text file) + (rx/map parse-svg))) + (allowed? [file] + (= (.-type file) "image/svg+xml")) + (prepare [[content metadata]] + {:collection-id id + :content content + :id (uuid/random) + ;; TODO Keep the name of the original icon + :name (str "Icon " (gensym "i")) + :metadata metadata})] + (->> (rx/from files) + (rx/filter allowed?) + (rx/flat-map parse) + (rx/map prepare) + (rx/flat-map #(rp/mutation! :create-icon %)) + (rx/map icon-created)))))) ;; --- Icon Persisted @@ -345,14 +339,14 @@ (watch [_ state stream] (let [selected (get-in state [:dashboard :icons :selected])] (rx/merge - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map #(get-in state [:icons %])) (rx/map #(dissoc % :id)) (rx/map #(assoc % :collection-id id)) (rx/flat-map #(rp/mutation :create-icon %)) (rx/map :payload) (rx/map icon-created)) - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map deselect-icon)))))) (defn copy-selected @@ -375,9 +369,9 @@ (watch [_ state stream] (let [selected (get-in state [:dashboard :icons :selected])] (rx/merge - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map persist-icon)) - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map deselect-icon)))))) (defn move-selected @@ -391,7 +385,7 @@ ptk/WatchEvent (watch [_ state stream] (let [selected (get-in state [:dashboard :icons :selected])] - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map delete-icon))))) (defn delete-selected diff --git a/frontend/src/uxbox/main/data/images.cljs b/frontend/src/uxbox/main/data/images.cljs index 39181633b..51802dc1d 100644 --- a/frontend/src/uxbox/main/data/images.cljs +++ b/frontend/src/uxbox/main/data/images.cljs @@ -55,80 +55,50 @@ ::url ::user-id])) -;; --- Initialize - -(defrecord Initialize [] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:dashboard :images] {:selected #{}}))) - -(defn initialize - [] - (Initialize.)) - -;; --- Color Collections Fetched - -(defrecord CollectionsFetched [items] - ptk/UpdateEvent - (update [_ state] - (reduce (fn [state {:keys [id user] :as item}] - (let [type (if (uuid/zero? (:user-id item)) :builtin :own) - item (assoc item :type type)] - (assoc-in state [:images-collections id] item))) - state - items))) +;; --- Collections Fetched (defn collections-fetched [items] - {:pre [(us/valid? (s/every ::collection-entity) items)]} - (CollectionsFetched. items)) + (s/assert (s/every ::collection-entity) items) + (ptk/reify ::collections-fetched + ptk/UpdateEvent + (update [_ state] + (reduce (fn [state {:keys [id user] :as item}] + (let [type (if (uuid/zero? (:user-id item)) :builtin :own) + item (assoc item :type type)] + (assoc-in state [:images-collections id] item))) + state + items)))) ;; --- Fetch Color Collections -(defrecord FetchCollections [] - ptk/WatchEvent - (watch [_ state s] - (->> (rp/query! :images-collections) - (rx/map collections-fetched)))) - -(defn fetch-collections - [] - (FetchCollections.)) +(def fetch-collections + (ptk/reify ::fetch-collections + ptk/WatchEvent + (watch [_ state s] + (->> (rp/query! :images-collections) + (rx/map collections-fetched))))) ;; --- Collection Created -(defrecord CollectionCreated [item] - ptk/UpdateEvent - (update [_ state] - (let [{:keys [id] :as item} (assoc item :type :own)] - (update state :images-collections assoc id item))) - - ptk/WatchEvent - (watch [_ state stream] - (rx/of (rt/nav :dashboard/images nil {:type :own :id (:id item)})))) - (defn collection-created [item] - {:pre [(us/valid? ::collection-entity item)]} - (CollectionCreated. item)) + (s/assert ::collection-entity item) + (ptk/reify ::collection-created + ptk/UpdateEvent + (update [_ state] + (let [{:keys [id] :as item} (assoc item :type :own)] + (update state :images-collections assoc id item))))) ;; --- Create Collection -(defrecord CreateCollection [] - ptk/WatchEvent - (watch [_ state s] - (let [data {:name (tr "ds.default-library-title" (gensym "c"))}] - (->> (rp/mutation! :create-image-collection data) - (rx/map :payload) - (rx/map collection-created))))) - -(defn create-collection - [] - (CreateCollection.)) - -(defn collections-fetched? - [v] - (instance? CollectionsFetched v)) +(def create-collection + (ptk/reify ::create-collection + ptk/WatchEvent + (watch [_ state s] + (let [data {:name (tr "ds.default-library-title" (gensym "c"))}] + (->> (rp/mutation! :create-image-collection data) + (rx/map collection-created)))))) ;; --- Collection Updated @@ -189,61 +159,55 @@ ;; --- Image Created -(defrecord ImageCreated [item] - ptk/UpdateEvent - (update [_ state] - (update state :images assoc (:id item) item))) - (defn image-created [item] - {:pre [(us/valid? ::image-entity item)]} - (ImageCreated. item)) + (s/assert ::image-entity item) + (ptk/reify ::image-created + ptk/UpdateEvent + (update [_ state] + (update state :images assoc (:id item) item)))) ;; --- Create Image (def allowed-file-types #{"image/jpeg" "image/png"}) -(defrecord CreateImages [id files on-uploaded] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:dashboard :images :uploading] true)) - - ptk/WatchEvent - (watch [_ state stream] - (letfn [(image-size [file] - (->> (files/get-image-size file) - (rx/map (partial vector file)))) - (allowed-file? [file] - (contains? allowed-file-types (.-type file))) - (finalize-upload [state] - (assoc-in state [:dashboard :images :uploading] false)) - (prepare [[file [width height]]] - (cond-> {:name (.-name file) - :mimetype (.-type file) - :id (uuid/random) - :file file - :width width - :height height} - id (assoc :collection-id id)))] - (->> (rx/from-coll files) - (rx/filter allowed-file?) - (rx/mapcat image-size) - (rx/map prepare) - (rx/mapcat #(rp/mutation! :create-image %)) - (rx/map :payload) - (rx/reduce conj []) - (rx/do #(st/emit! finalize-upload)) - (rx/do on-uploaded) - (rx/mapcat identity) - (rx/map image-created))))) - (defn create-images - ([id files] - {:pre [(or (uuid? id) (nil? id))]} - (CreateImages. id files (constantly nil))) + ([id files] (create-images id files identity)) ([id files on-uploaded] - {:pre [(or (uuid? id) (nil? id)) (fn? on-uploaded)]} - (CreateImages. id files on-uploaded))) + (s/assert (s/nilable ::us/uuid) id) + (s/assert fn? on-uploaded) + (ptk/reify ::create-images + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:dashboard :images :uploading] true)) + + ptk/WatchEvent + (watch [_ state stream] + (letfn [(image-size [file] + (->> (files/get-image-size file) + (rx/map (partial vector file)))) + (allowed-file? [file] + (contains? allowed-file-types (.-type file))) + (finalize-upload [state] + (assoc-in state [:dashboard :images :uploading] false)) + (prepare [[file [width height]]] + (cond-> {:name (.-name file) + :mimetype (.-type file) + :id (uuid/random) + :file file + :width width + :height height} + id (assoc :collection-id id)))] + (->> (rx/from files) + (rx/filter allowed-file?) + (rx/mapcat image-size) + (rx/map prepare) + (rx/mapcat #(rp/mutation! :create-image %)) + (rx/reduce conj []) + (rx/do #(st/emit! finalize-upload)) + (rx/do on-uploaded) + (rx/mapcat identity) + (rx/map image-created))))))) ;; --- Update Image @@ -259,32 +223,29 @@ ;; --- Images Fetched -(defrecord ImagesFetched [items] - ptk/UpdateEvent - (update [_ state] - (reduce (fn [state {:keys [id] :as image}] - (assoc-in state [:images id] image)) - state - items))) - (defn images-fetched [items] - (ImagesFetched. items)) + (s/assert (s/every ::image-entity) items) + (ptk/reify ::images-fetched + ptk/UpdateEvent + (update [_ state] + (reduce (fn [state {:keys [id] :as image}] + (assoc-in state [:images id] image)) + state + items)))) ;; --- Fetch Images -(defrecord FetchImages [id] - ptk/WatchEvent - (watch [_ state s] - (let [params (cond-> {} id (assoc :collection-id id))] - (->> (rp/query! :images-by-collection params) - (rx/map images-fetched))))) - (defn fetch-images "Fetch a list of images of the selected collection" [id] - {:pre [(or (uuid? id) (nil? id))]} - (FetchImages. id)) + (s/assert (s/nilable ::us/uuid) id) + (ptk/reify ::fetch-images + ptk/WatchEvent + (watch [_ state s] + (let [params (cond-> {} id (assoc :collection-id id))] + (->> (rp/query! :images-by-collection params) + (rx/map images-fetched)))))) ;; --- Fetch Image @@ -392,10 +353,10 @@ (watch [_ state stream] (let [selected (get-in state [:dashboard :images :selected])] (rx/merge - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/flat-map #(rp/mutation! :copy-image {:id % :collection-id id})) (rx/map image-created)) - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map deselect-image)))))) (defn copy-selected @@ -418,9 +379,9 @@ (watch [_ state stream] (let [selected (get-in state [:dashboard :images :selected])] (rx/merge - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map persist-image)) - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map deselect-image)))))) (defn move-selected @@ -434,7 +395,7 @@ ptk/WatchEvent (watch [_ state stream] (let [selected (get-in state [:dashboard :images :selected])] - (->> (rx/from-coll selected) + (->> (rx/from selected) (rx/map delete-image))))) (defn delete-selected diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index 93f27e11e..c6c39bc3b 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -10,8 +10,8 @@ [cljs.spec.alpha :as s] [cuerdas.core :as str] [potok.core :as ptk] + [uxbox.common.pages :as cp] [uxbox.main.repo.core :as rp] - [uxbox.util.data :refer [index-by-id concatv]] [uxbox.util.router :as rt] [uxbox.util.spec :as us] [uxbox.util.time :as dt] @@ -25,12 +25,15 @@ (s/def ::user ::us/uuid) (s/def ::type ::us/keyword) (s/def ::file-id ::us/uuid) +(s/def ::project-id ::us/uuid) (s/def ::created-at ::us/inst) (s/def ::modified-at ::us/inst) (s/def ::version ::us/number) (s/def ::ordering ::us/number) +(s/def ::metadata (s/nilable ::cp/metadata)) +(s/def ::data ::cp/data) -(s/def ::project-entity +(s/def ::project (s/keys ::req-un [::id ::name ::version @@ -38,38 +41,18 @@ ::created-at ::modified-at])) -(s/def ::grid-x-axis ::us/number) -(s/def ::grid-y-axis ::us/number) -(s/def ::grid-color ::us/string) -(s/def ::background ::us/string) -(s/def ::background-opacity ::us/number) - -(s/def ::metadata - (s/keys :opt-un [::grid-y-axis - ::grid-x-axis - ::grid-color - ::background - ::background-opacity])) - -;; TODO: start using uxbox.common.pagedata/data spec ... - -(s/def ::minimal-shape - (s/keys :req-un [::type ::name] - :opt-un [::id])) - -(s/def ::shapes (s/coll-of ::us/uuid :kind vector?)) -(s/def ::canvas (s/coll-of ::us/uuid :kind vector?)) - -(s/def ::shapes-by-id - (s/map-of ::us/uuid ::minimal-shape)) - -(s/def ::data - (s/keys :req-un [::shapes ::canvas ::shapes-by-id])) +(s/def ::file + (s/keys :req-un [::id + ::name + ::created-at + ::modified-at + ::project-id])) (s/def ::page (s/keys :req-un [::id ::name ::file-id + ::version ::created-at ::modified-at ::user-id @@ -77,23 +60,8 @@ ::metadata ::data])) -(s/def ::pages - (s/every ::page :kind vector?)) - - ;; --- Helpers -(defn assoc-project - "A reduce function for assoc the project to the state map." - [state {:keys [id] :as project}] - (s/assert ::project-entity project) - (update-in state [:projects id] merge project)) - -(defn dissoc-project - "A reduce function for dissoc the project from the state map." - [state id] - (update state :projects dissoc id)) - (defn unpack-page [state {:keys [id data metadata] :as page}] (-> state @@ -114,13 +82,10 @@ ;; --- Initialize Dashboard (declare fetch-projects) -(declare projects-fetched?) (declare fetch-files) (declare initialized) -;; TODO: rename to initialize dashboard - (defn initialize [id] (ptk/reify ::initialize @@ -156,21 +121,8 @@ (when order {:order order}) (when filter {:filter filter}))))) -;; --- Projects Fetched - -(defn projects-fetched - [projects] - (s/assert (s/every ::project-entity) projects) - (ptk/reify ::projects-fetched - ptk/UpdateEvent - (update [_ state] - (reduce assoc-project state projects)))) - -(defn projects-fetched? - [v] - (= ::projects-fetched (ptk/type v))) - ;; --- Fetch Projects +(declare projects-fetched) (def fetch-projects (ptk/reify ::fetch-projects @@ -179,6 +131,17 @@ (->> (rp/query :projects) (rx/map projects-fetched))))) +;; --- Projects Fetched + +(defn projects-fetched + [projects] + (s/assert (s/every ::project) projects) + (ptk/reify ::projects-fetched + ptk/UpdateEvent + (update [_ state] + (let [assoc-project #(update-in %1 [:projects (:id %2)] merge %2)] + (reduce assoc-project state projects))))) + ;; --- Fetch Files (declare files-fetched) @@ -205,11 +168,9 @@ ;; --- Files Fetched -(s/def ::files any?) - (defn files-fetched [files] - (s/assert ::files files) + (s/assert (s/every ::file) files) (ptk/reify ::files-fetched cljs.core/IDeref (-deref [_] files) @@ -219,6 +180,34 @@ (let [assoc-file #(assoc-in %1 [:files (:id %2)] %2)] (reduce assoc-file state files))))) +;; --- Create Project + +(declare project-created) + +(def create-project + (ptk/reify ::create-project + ptk/WatchEvent + (watch [this state stream] + (let [name (str "Project Name " (gensym "p"))] + (->> (rp/mutation! :create-project {:name name}) + (rx/map (fn [data] + (projects-fetched [data])))))))) + +;; --- Create File + +(defn create-file + [{:keys [project-id] :as params}] + (ptk/reify ::create-file + ptk/WatchEvent + (watch [this state stream] + (let [name (str "File Name " (gensym "p")) + params {:name name :project-id project-id}] + (->> (rp/mutation! :create-project-file params) + (rx/mapcat + (fn [data] + (rx/of (files-fetched [data]) + #(update-in % [:dashboard-projects :files project-id] conj (:id data)))))))))) + ;; --- Rename Project (defn rename-project @@ -243,7 +232,7 @@ (ptk/reify ::delete-project ptk/UpdateEvent (update [_ state] - (dissoc-project state id)) + (update state :projects dissoc id)) ptk/WatchEvent (watch [_ state s] @@ -265,32 +254,6 @@ (->> (rp/mutation :delete-project-file {:id id}) (rx/ignore))))) -;; --- Create Project - -(declare project-created) - -(s/def ::create-project - (s/keys :req-un [::name])) - -(defn create-project - [{:keys [name] :as params}] - (s/assert ::create-project params) - (ptk/reify ::create-project - ptk/WatchEvent - (watch [this state stream] - (->> (rp/mutation :create-project {:name name}) - (rx/map project-created))))) - -;; --- Project Created - -(defn project-created - [data] - (ptk/reify ::project-created - ptk/UpdateEvent - (update [_ state] - (assoc-project state data)))) - - ;; --- Rename Project (defn rename-file @@ -348,7 +311,7 @@ (defn pages-fetched [pages] - (s/assert ::pages pages) + (s/assert (s/every ::page) pages) (ptk/reify ::pages-fetched IDeref (-deref [_] pages) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index a583e4c5f..79ffa3b77 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -50,7 +50,6 @@ (s/def ::font-size number?) (s/def ::font-style string?) (s/def ::font-weight string?) -(s/def ::height number?) (s/def ::hidden boolean?) (s/def ::id uuid?) (s/def ::letter-spacing number?) @@ -67,12 +66,13 @@ (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 ::type #{:rect :path :circle :image :text :canvas}) +(s/def ::x number?) +(s/def ::y number?) +(s/def ::cx number?) +(s/def ::cy number?) (s/def ::width number?) -(s/def ::x1 number?) -(s/def ::x2 number?) -(s/def ::y1 number?) -(s/def ::y2 number?) +(s/def ::height number?) (s/def ::attributes (s/keys :opt-un [::blocked @@ -91,13 +91,14 @@ ::proportion ::proportion-lock ::rx ::ry + ::cx ::cy + ::x ::y ::stroke-color ::stroke-opacity ::stroke-style ::stroke-width ::text-align - ::x1 ::x2 - ::y1 ::y2])) + ::width ::height])) (s/def ::minimal-shape (s/keys :req-un [::id ::page ::type ::name])) @@ -169,28 +170,6 @@ (ws/-close (get-in state [:ws file-id])) (rx/of ::finalize)))) -;; --- Fetch Workspace Users - -(declare users-fetched) - -(defn fetch-users - [file-id] - (ptk/reify ::fetch-users - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query :project-file-users {:file-id file-id}) - (rx/map users-fetched))))) - -(defn users-fetched - [users] - (ptk/reify ::users-fetched - ptk/UpdateEvent - (update [_ state] - (reduce (fn [state user] - (update-in state [:workspace-users :by-id (:id user)] merge user)) - state - users)))) - ;; --- Handle: Who ;; TODO: assign color @@ -256,66 +235,119 @@ (defn initialize "Initialize the workspace state." - [file-id page-id] + [file-id] (s/assert ::us/uuid file-id) - (s/assert ::us/uuid page-id) (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] (let [local (assoc workspace-default - :file-id file-id - :page-id page-id)] + :file-id file-id) + ;; :page-id page-id) + ;; TODO: this need to be parametrized + uri (str "ws://localhost:6060/sub/" file-id)] (-> state (assoc :workspace-layout default-layout) - (assoc :workspace-local local)))) + (assoc :workspace-local local) + (assoc-in [:ws file-id] (ws/open uri))))) ptk/WatchEvent (watch [_ state stream] - #_(when-not (get-in state [:pages page-id]) - (reset! st/loader true)) + (let [wsession (get-in state [:ws file-id])] + (rx/merge + ;; Stop possible previous watchers and re-fetch the main page + ;; and all project related pages. + (rx/of ::stop-watcher + (dp/fetch-file file-id) + (dp/fetch-pages file-id) + (fetch-users file-id)) - (rx/merge - ;; Stop possible previous watchers and re-fetch the main page - ;; and all project related pages. - (rx/of ::stop-watcher - (dp/fetch-file file-id) - (dp/fetch-pages file-id)) + ;; When main page is fetched, schedule the main initialization. + (->> (rx/zip (rx/filter (ptk/type? ::dp/pages-fetched) stream) + (rx/filter (ptk/type? ::dp/files-fetched) stream)) + (rx/take 1) + (rx/do #(reset! st/loader false)) + (rx/mapcat #(rx/of (initialized file-id) + #_(initialize-alignment page-id)))) - ;; When main page is fetched, schedule the main initialization. - (->> (rx/zip (rx/filter (ptk/type? ::dp/pages-fetched) stream) - (rx/filter (ptk/type? ::dp/files-fetched) stream)) - (rx/take 1) - (rx/do #(reset! st/loader false)) - (rx/mapcat #(rx/of (initialized file-id page-id) - #_(initialize-alignment page-id)))) - - (->> stream - (rx/filter uxbox.main.ui.workspace.streams/pointer-event?) - (rx/sample 150) - (rx/tap (fn [{:keys [pt] :as event}] - (let [msg {:type :pointer-update - :page-id page-id - :x (:x pt) - :y (:y pt)}] - (ws/-send (get-in state [:ws file-id]) (t/encode msg))))) - (rx/ignore) - (rx/take-until (rx/filter #(= ::stop-watcher %) stream))))))) + ;; WebSocket Incoming Messages Handling + (->> (ws/-stream wsession) + (rx/filter #(= :message (:type %))) + (rx/map (comp t/decode :payload)) + (rx/filter #(s/valid? ::message %)) + (rx/map (fn [{:keys [type] :as msg}] + (case type + :who (handle-who msg) + :pointer-update (handle-pointer-update msg) + :page-snapshot (handle-page-snapshot msg) + ::unknown)))) + #_(->> stream + ;; TODO: this need to be rethinked + (rx/filter uxbox.main.ui.workspace.streams/pointer-event?) + (rx/sample 150) + (rx/tap (fn [{:keys [pt] :as event}] + (let [msg {:type :pointer-update + :page-id page-id + :x (:x pt) + :y (:y pt)}] + (ws/-send (get-in state [:ws file-id]) (t/encode msg))))) + (rx/ignore) + (rx/take-until (rx/filter #(= ::finalize %) stream)))))))) (defn- initialized - [file-id page-id] + [file-id] (s/assert ::us/uuid file-id) - (s/assert ::us/uuid page-id) (ptk/reify ::initialized ptk/UpdateEvent (update [_ state] - (let [file (get-in state [:files file-id]) - page (get-in state [:pages page-id]) + (let [file (get-in state [:files file-id])] + (assoc state :workspace-file file))))) + +(defn finalize + [file-id] + (ptk/reify ::finalize + cljs.core/IDeref + (-deref [_] file-id) + + ptk/EffectEvent + (effect [_ state stream] + (ws/-close (get-in state [:ws file-id]))))) + +(defn initialize-page + [page-id] + (ptk/reify ::initialize-page + ptk/UpdateEvent + (update [_ state] + (let [page (get-in state [:pages page-id]) data (get-in state [:pages-data page-id])] (assoc state - :workspace-file file :workspace-data data - :workspace-page page))))) + :workspace-page page))) + + ptk/EffectEvent + (effect [_ state stream]))) + +;; --- Fetch Workspace Users + +(declare users-fetched) + +(defn fetch-users + [file-id] + (ptk/reify ::fetch-users + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :project-file-users {:file-id file-id}) + (rx/map users-fetched))))) + +(defn users-fetched + [users] + (ptk/reify ::users-fetched + ptk/UpdateEvent + (update [_ state] + (reduce (fn [state user] + (update-in state [:workspace-users :by-id (:id user)] merge user)) + state + users)))) ;; --- Toggle layout flag @@ -576,14 +608,22 @@ (declare select-shape) (declare recalculate-shape-canvas-relation) +(def shape-default-attrs + {:stroke-color "#000000" + :stroke-opacity 1 + :fill-color "#000000" + :fill-opacity 1}) + (defn add-shape [data] + (s/assert ::attributes data) (let [id (uuid/random)] (ptk/reify ::add-shape ptk/UpdateEvent (update [_ state] (let [shape (-> (geom/setup-proportions data) (assoc :id id)) + shape (merge shape-default-attrs shape) shape (recalculate-shape-canvas-relation state shape)] (impl-assoc-shape state shape))) @@ -593,6 +633,32 @@ (rx/of (commit-shapes-changes [[:add-shape id shape]]) (select-shape id))))))) +(def canvas-default-attrs + {:stroke-color "#000000" + :stroke-opacity 1 + :fill-color "#ffffff" + :fill-opacity 1}) + +(defn add-canvas + [data] + (s/assert ::attributes data) + (let [id (uuid/random)] + (ptk/reify ::add-canvas + ptk/UpdateEvent + (update [_ state] + (let [shape (-> (geom/setup-proportions data) + (assoc :id id)) + shape (merge canvas-default-attrs shape) + shape (recalculate-shape-canvas-relation state shape)] + (impl-assoc-shape state shape))) + + ptk/WatchEvent + (watch [_ state stream] + (let [shape (get-in state [:workspace-data :shapes-by-id id])] + (rx/of (commit-shapes-changes [[:add-canvas id shape]]) + (select-shape id))))))) + + ;; --- Duplicate Selected (defn impl-duplicate-shape @@ -608,7 +674,7 @@ duplicate (partial impl-duplicate-shape state) shapes (map duplicate selected)] (rx/merge - (rx/from-coll (map (fn [s] #(impl-assoc-shape % s)) shapes)) + (rx/from (map (fn [s] #(impl-assoc-shape % s)) shapes)) (rx/of (commit-shapes-changes (mapv #(vector :add-shape (:id %) %) shapes)))))))) ;; --- Toggle shape's selection status (selected or deselected) @@ -675,41 +741,35 @@ ;; --- Update Shape Attrs -(defn update-shape-attrs - [id attrs] - (s/assert ::us/uuid id) - (let [atts (s/conform ::attributes attrs)] - (ptk/reify ::update-shape-attrs - ptk/UpdateEvent - (update [_ state] - (if (map? attrs) - (update-in state [:workspace-data :shapes-by-id id] merge attrs) - state))))) - (defn update-shape - [id & attrs] - (let [attrs' (->> (apply hash-map attrs) - (s/conform ::attributes))] - (ptk/reify ::update-shape - ptk/UpdateEvent - (update [_ state] - (cond-> state - (not= attrs' ::s/invalid) - (update-in [:workspace-data :shapes-by-id id] merge attrs')))))) + [id attrs] + (s/assert ::attributes attrs) + (ptk/reify ::update-shape + ptk/UpdateEvent + (update [_ state] + (let [shape-old (get-in state [:workspace-data :shapes-by-id id]) + shape-new (merge shape-old attrs) + diff (d/diff-maps shape-old shape-new)] + (-> state + (assoc-in [:workspace-data :shapes-by-id id] shape-new) + (assoc ::tmp-change (into [:mod-shape id] diff))))) + ptk/WatchEvent + (watch [_ state stream] + (let [change (::tmp-change state)] + (prn "update-shape" change) + (rx/of (commit-shapes-changes [change]) + #(dissoc state ::tmp-change)))))) ;; --- Update Selected Shapes attrs -;; TODO: improve performance of this event - -(defn update-selected-shapes-attrs - [attrs] - (s/assert ::attributes attrs) +(defn update-selected-shapes + [& attrs] (ptk/reify ::update-selected-shapes-attrs ptk/WatchEvent (watch [_ state stream] (let [selected (get-in state [:workspace-local :selected])] - (rx/from-coll (map #(update-shape-attrs % attrs) selected)))))) + (rx/from (map #(apply update-shape % attrs) selected)))))) ;; --- Move Selected @@ -763,19 +823,6 @@ (rx/of (apply-temporal-displacement-in-bulk selected displacement)) (rx/of (materialize-temporal-modifier-in-bulk selected))))))) -;; --- Update Shape Position - -(deftype UpdateShapePosition [id point] - 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 (defn impl-dissoc-shape @@ -923,9 +970,10 @@ (defn- recalculate-shape-canvas-relation [state shape] - (let [xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %])) + (let [shape' (geom/shape->rect-shape shape) + xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %])) (map geom/shape->rect-shape) - (filter #(geom/overlaps? % shape)) + (filter #(geom/overlaps? % shape')) (map :id)) id (->> (get-in state [:workspace-data :canvas]) @@ -1035,59 +1083,16 @@ (rx/map (constantly clear-drawing)) (rx/take-until stoper))))))) -;; --- Shape Proportions - -;; (defn toggle-shape-proportion-lock -;; [id] -;; (ptk/reify ::toggle-shape-proportion-lock -;; ptk/UpdateEvent -;; (update [_ state] -;; (let [shape (-> (get-in state [:workspace-data :shapes-by-id id]) -;; (geom/size) - - -;; TODO: revisit - -(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)) - -;; TODO: revisit - -(deftype UnlockShapeProportions [id] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:shapes id :proportion-lock] false))) - -(defn unlock-proportions - [id] - {:pre [(uuid? id)]} - (UnlockShapeProportions. id)) - ;; --- Update Dimensions -;; TODO: revisit - -(s/def ::width (s/and ::us/number ::us/positive)) -(s/def ::height (s/and ::us/number ::us/positive)) +(s/def ::width ::us/number) +(s/def ::height ::us/number) (s/def ::update-dimensions (s/keys :opt-un [::width ::height])) +;; TODO: emit commit-changes + (defn update-dimensions "A helper event just for update the position of the shape using the width and height attrs @@ -1098,35 +1103,31 @@ (ptk/reify ::update-dimensions ptk/UpdateEvent (update [_ state] - (update-in state [:shapes id] geom/resize-dim dimensions)))) + (update-in state [:workspace-data :shapes-by-id id] geom/resize-dim dimensions)))) -;; --- Update Interaction +;; --- Shape Proportions -;; TODO: revisit -(deftype UpdateInteraction [shape interaction] - 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 toggle-shape-proportion-lock + [id] + (ptk/reify ::toggle-shape-proportion-lock + ptk/UpdateEvent + (update [_ state] + (let [shape (get-in state [:workspace-data :shapes-by-id id])] + (if (:proportion-lock shape) + (assoc-in state [:workspace-data :shapes-by-id id :proportion-lock] false) + (->> (geom/assign-proportions (assoc shape :proportion-lock true)) + (assoc-in state [:workspace-data :shapes-by-id id]))))))) -(defn update-interaction - [shape interaction] - (UpdateInteraction. shape interaction)) +;; --- Update Shape Position -;; --- Delete Interaction - -;; TODO: revisit -(deftype DeleteInteracton [shape id] - 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)) +(defn update-position + [id point] + (s/assert ::us/uuid id) + (s/assert gpt/point? point) + (ptk/reify ::update-position + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-data :shapes-by-id id] geom/absolute-move point)))) ;; --- Path Modifications diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index 9195e725c..3f9a5497d 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -15,7 +15,6 @@ (declare move-rect) (declare move-path) (declare move-circle) -(declare move-group) (defn move "Move the shape relativelly to its current @@ -28,18 +27,15 @@ :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))) + :circle (move-circle shape dpoint))) (defn- move-rect "A specialized function for relative movement for rect-like shapes." [shape {dx :x dy :y}] (assoc shape - :x1 (mth/round (+ (:x1 shape) dx)) - :y1 (mth/round (+ (:y1 shape) dy)) - :x2 (mth/round (+ (:x2 shape) dx)) - :y2 (mth/round (+ (:y2 shape) dy)))) + :x (mth/round (+ (:x shape) dx)) + :y (mth/round (+ (:y shape) dy)))) (defn- move-circle "A specialized function for relative movement @@ -49,14 +45,6 @@ :cx (mth/round (+ (:cx shape) dx)) :cy (mth/round (+ (:cy shape) dy)))) -(defn- move-group - "A specialized function for relative movement - for group shapes." - [shape {dx :x dy :y}] - (assoc shape - :dx (mth/round (+ (:dx shape 0) dx)) - :dy (mth/round (+ (:dy shape 0) dy)))) - (defn- move-path "A specialized function for relative movement for path shapes." @@ -71,7 +59,6 @@ (declare absolute-move-rect) (declare absolute-move-circle) -(declare absolute-move-group) (defn absolute-move "Move the shape to the exactly specified position." @@ -80,31 +67,24 @@ :icon (absolute-move-rect shape point) :image (absolute-move-rect shape point) :rect (absolute-move-rect shape point) - :circle (absolute-move-circle shape point) - :group (absolute-move-group shape point))) + :circle (absolute-move-circle shape point))) (defn- absolute-move-rect "A specialized function for absolute moviment for rect-like shapes." [shape {:keys [x y] :as pos}] - (let [dx (if x (- x (:x1 shape)) 0) - dy (if y (- y (:y1 shape)) 0)] + (let [dx (if x (- x (:x shape)) 0) + dy (if y (- y (:y shape)) 0)] (move shape (gpt/point dx dy)))) (defn- absolute-move-circle "A specialized function for absolute moviment for rect-like shapes." [shape {:keys [x y] :as pos}] - (let [dx (if x (- x(:cx shape)) 0) + (let [dx (if x (- x (:cx shape)) 0) dy (if y (- y (:cy shape)) 0)] (move shape (gpt/point dx dy)))) -(defn- absolute-move-group - "A specialized function for absolute moviment - for rect-like shapes." - [shape {:keys [x y] :as pos}] - (throw (ex-info "Not implemented (TODO)" {}))) - ;; --- Rotation ;; TODO: maybe we can consider apply the rotation @@ -159,6 +139,30 @@ (merge shape {:width (* rx 2) :height (* ry 2)})) +;; --- Proportions + +(declare assign-proportions-path) +(declare assign-proportions-circle) +(declare assign-proportions-rect) + +(defn assign-proportions + [{:keys [type] :as shape}] + (case type + :circle (assign-proportions-circle shape) + :path (assign-proportions-path shape) + (assign-proportions-rect shape))) + +(defn- assign-proportions-rect + [{:keys [width height] :as shape}] + (assoc shape :proportion (/ width height))) + +(defn- assign-proportions-circle + [{:as shape}] + (prn "assign-proportions-circle" shape) + (assoc shape :proportion 1)) + +;; TODO: implement the rest of shapes + ;; --- Paths (defn update-path-point @@ -171,20 +175,16 @@ ;; --- Setup Proportions -(declare setup-proportions-rect) +(declare setup-proportions-const) (declare setup-proportions-image) (defn setup-proportions [shape] (case (:type shape) - :canvas (setup-proportions-rect shape) - :rect (setup-proportions-rect shape) - :circle (setup-proportions-rect shape) :icon (setup-proportions-image shape) :image (setup-proportions-image shape) :text shape - :curve (setup-proportions-rect shape) - :path (setup-proportions-rect shape))) + (setup-proportions-const shape))) (defn setup-proportions-image [{:keys [metadata] :as shape}] @@ -193,12 +193,11 @@ :proportion (/ width height) :proportion-lock false))) -(defn setup-proportions-rect +(defn setup-proportions-const [shape] - (let [{:keys [width height]} (size shape)] - (assoc shape - :proportion (/ width height) - :proportion-lock false))) + (assoc shape + :proportion 1 + :proportion-lock false)) ;; --- Resize (Dimentsions) @@ -216,20 +215,19 @@ :circle (resize-dim-circle shape opts))) (defn- resize-dim-rect - [{:keys [proportion proportion-lock x1 y1] :as shape} - {:keys [width height]}] - {:pre [(not (and width height))]} + [{:keys [proportion proportion-lock x y] :as shape} + {:keys [width height] :as dimensions}] (if-not proportion-lock (if width - (assoc shape :x2 (+ x1 width)) - (assoc shape :y2 (+ y1 height))) + (assoc shape :width width) + (assoc shape :height height)) (if width (-> shape - (assoc :x2 (+ x1 width)) - (assoc :y2 (+ y1 (/ width proportion)))) + (assoc :width width) + (assoc :height (/ width proportion))) (-> shape - (assoc :y2 (+ y1 height)) - (assoc :x2 (+ x1 (* height proportion))))))) + (assoc :height height) + (assoc :width (* height proportion)))))) (defn- resize-dim-circle [{:keys [proportion proportion-lock] :as shape} @@ -299,11 +297,11 @@ :bottom-right (-> (gmt/matrix) - (gmt/translate (+ (:x1 shape)) - (+ (:y1 shape))) + (gmt/translate (+ (:x shape)) + (+ (:y shape))) (gmt/scale scalex scaley) - (gmt/translate (- (:x1 shape)) - (- (:y1 shape)))) + (gmt/translate (- (:x shape)) + (- (:y shape)))) :bottom (-> (gmt/matrix) @@ -373,8 +371,8 @@ :height (if lock? (/ width proportion) height))) :bottom-right - (let [width (- x (:x1 shape)) - height (- y (:y1 shape)) + (let [width (- x (:x shape)) + height (- y (:y shape)) proportion (:proportion shape 1)] (assoc shape :width width @@ -421,38 +419,39 @@ (defn- setup-rect "A specialized function for setup rect-like shapes." - [shape {:keys [x1 y1 x2 y2]}] + [shape {:keys [x y width height]}] (assoc shape - :x1 x1 - :y1 y1 - :x2 x2 - :y2 y2)) + :x x + :y y + :width width + :height height)) (defn- setup-circle "A specialized function for setup circle shapes." - [shape {:keys [x1 y1 x2 y2]}] + [shape {:keys [x y width height]}] (assoc shape - :cx x1 - :cy y1 - :rx (mth/abs (- x2 x1)) - :ry (mth/abs (- y2 y1)))) + :cx x + :cy y + :rx (mth/abs width) + :ry (mth/abs height))) (defn- setup-image - [{:keys [metadata] :as shape} {:keys [x1 y1 x2 y2] :as props}] - (let [{:keys [width height]} metadata] - (assoc shape - :x1 x1 - :y1 y1 - :x2 x2 - :y2 y2 - :proportion (/ width height) - :proportion-lock true))) + [{:keys [metadata] :as shape} {:keys [x y width height] :as props}] + (assoc shape + :x x + :y y + :width width + :height height + :proportion (/ (:width metadata) + (:height metadata)) + :proportion-lock true)) ;; --- Coerce to Rect-like shape. (declare circle->rect-shape) (declare path->rect-shape) (declare group->rect-shape) +(declare rect->rect-shape) (defn shape->rect-shape "Coerce shape to rect like shape." @@ -461,7 +460,7 @@ :circle (circle->rect-shape shape) :path (path->rect-shape shape) :curve (path->rect-shape shape) - shape)) + (rect->rect-shape shape))) (defn shapes->rect-shape [shapes] @@ -474,29 +473,41 @@ :y1 miny :x2 maxx :y2 maxy + :x minx + :y miny + :width (- maxx minx) + :height (- maxy miny) :type :rect})) -(defn shapes->rect-shape' - [shapes] - (let [shapes (mapv shape->rect-shape shapes) - total (count shapes)] - (loop [idx (int 0) - minx js/Number.POSITIVE_INFINITY - miny js/Number.POSITIVE_INFINITY - maxx js/Number.NEGATIVE_INFINITY - maxy js/Number.NEGATIVE_INFINITY] - (if (> total idx) - (let [{:keys [x1 y1 x2 y2]} (nth shapes idx)] - (recur (inc idx) - (min minx x1) - (min miny y1) - (max maxx x2) - (max maxy y2))) - {:x1 minx - :y1 miny - :x2 maxx - :y2 maxy - :type :rect})))) +;; (defn shapes->rect-shape' +;; [shapes] +;; (let [shapes (mapv shape->rect-shape shapes) +;; total (count shapes)] +;; (loop [idx (int 0) +;; minx js/Number.POSITIVE_INFINITY +;; miny js/Number.POSITIVE_INFINITY +;; maxx js/Number.NEGATIVE_INFINITY +;; maxy js/Number.NEGATIVE_INFINITY] +;; (if (> total idx) +;; (let [{:keys [x1 y1 x2 y2]} (nth shapes idx)] +;; (recur (inc idx) +;; (min minx x1) +;; (min miny y1) +;; (max maxx x2) +;; (max maxy y2))) +;; {:x1 minx +;; :y1 miny +;; :x2 maxx +;; :y2 maxy +;; :type :rect})))) + +(defn- rect->rect-shape + [{:keys [x y width height] :as shape}] + (assoc shape + :x1 x + :y1 y + :x2 (+ x width) + :y2 (+ y height))) (defn- path->rect-shape [{:keys [segments] :as shape}] @@ -508,7 +519,11 @@ :x1 minx :y1 miny :x2 maxx - :y2 maxy))) + :y2 maxy + :x minx + :y miny + :width (- maxx minx) + :height (- maxy miny)))) (defn- circle->rect-shape [{:keys [cx cy rx ry] :as shape}] @@ -520,7 +535,11 @@ :x1 x1 :y1 y1 :x2 (+ x1 width) - :y2 (+ y1 height)))) + :y2 (+ y1 height) + :x x1 + :y y1 + :width width + :height height))) ;; --- Transform Shape @@ -542,21 +561,23 @@ :circle (transform-circle shape xfmt))) (defn- transform-rect - [{:keys [x1 y1] :as shape} mx] - (let [{:keys [width height]} (size shape) - tl (gpt/transform [x1 y1] mx) - tr (gpt/transform [(+ x1 width) y1] mx) - bl (gpt/transform [x1 (+ y1 height)] mx) - br (gpt/transform [(+ x1 width) (+ y1 height)] mx) + [{:keys [x y width height] :as shape} mx] + (let [tl (gpt/transform [x y] mx) + tr (gpt/transform [(+ x width) y] mx) + bl (gpt/transform [x (+ y height)] mx) + br (gpt/transform [(+ x width) (+ y height)] mx) minx (apply min (map :x [tl tr bl br])) maxx (apply max (map :x [tl tr bl br])) miny (apply min (map :y [tl tr bl br])) maxy (apply max (map :y [tl tr bl br]))] (assoc shape - :x1 minx - :y1 miny - :x2 (+ minx (- maxx minx)) - :y2 (+ miny (- maxy miny))))) + :x minx + :y miny + :width (- maxx minx) + :height (- maxy miny)))) + + ;; :x2 (+ minx (- maxx minx)) + ;; :y2 (+ miny (- maxy miny))))) (defn- transform-circle [{:keys [cx cy rx ry] :as shape} xfmt] @@ -585,8 +606,6 @@ ;; --- Outer Rect -(declare selection-rect-generic) - (defn rotation-matrix "Generate a rotation matrix from shape." [{:keys [x1 y1 rotation] :as shape}] @@ -607,15 +626,12 @@ (defn selection-rect "Return the selection rect for the shape." - ([shape] - (selection-rect @st/state shape)) - ([state shape] - (let [modifier (:modifier-mtx shape)] - (-> (shape->rect-shape shape) - (assoc :type :rect :id (:id shape)) - (transform (or modifier (gmt/matrix))) - (rotate-shape) - (size))))) + [shape] + (let [modifier (:modifier-mtx shape)] + (-> (shape->rect-shape shape) + (assoc :type :rect :id (:id shape)) + (transform (or modifier (gmt/matrix))) + #_(rotate-shape)))) ;; --- Helpers @@ -623,8 +639,8 @@ "Check if a shape is contained in the provided selection rect." [shape selrect] - (let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} selrect - {rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} shape] + (let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (shape->rect-shape selrect) + {rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (shape->rect-shape shape)] (and (neg? (- sy1 ry1)) (neg? (- sx1 rx1)) (pos? (- sy2 ry2)) @@ -633,8 +649,8 @@ (defn overlaps? "Check if a shape overlaps with provided selection rect." [shape selrect] - (let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} selrect - {rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} shape] + (let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (shape->rect-shape selrect) + {rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (shape->rect-shape shape)] (and (< rx1 sx2) (> rx2 sx1) (< ry1 sy2) diff --git a/frontend/src/uxbox/main/locales/en.cljs b/frontend/src/uxbox/main/locales/en.cljs index f1b6194e0..76cf13802 100644 --- a/frontend/src/uxbox/main/locales/en.cljs +++ b/frontend/src/uxbox/main/locales/en.cljs @@ -80,33 +80,8 @@ "ds.project.placeholder" "New project name" "ds.project.new" "New project" - "ds.radius" "Radius" - "ds.size" "Size" - "ds.width" "Width" - "ds.height" "Height" - "ds.style" "Style" - "ds.none" "None" - "ds.solid" "Solid" - "ds.dotted" "Dotted" - "ds.dashed" "Dashed" - "ds.mixed" "Mixed" - "ds.position" "Position" - "ds.rotation" "Rotation" - "ds.opacity" "Opacity" - "ds.color" "Color" - "ds.background-color" "Background color" - "ds.font-family" "Font family" - "ds.size-weight" "Size and Weight" - "ds.font-size" "Font Size" - "ds.line-height-letter-spacing" "Line height and Letter spacing" - "ds.line-height" "Line height" - "ds.letter-spacing" "Letter spacing" - "ds.text-align" "Text align" - "ds.name" "Name" - "ds.go" "Go go go!" - - "ds.accept" "Accept" - "ds.cancel" "Cancel" + "common.accept" "Accept" + "common.cancel" "Cancel" "ds.settings.icons" "Icons" "ds.settings.element-options" "Element options" @@ -122,43 +97,69 @@ "ds.history.versions" "History" "ds.history.pinned" "Pinned" - "ds.help.rect" "Box (Ctrl + B)" - "ds.help.circle" "Circle (Ctrl + E)" - "ds.help.line" "Line (Ctrl + L)" - "ds.help.text" "Text" - "ds.help.path" "Path" - "ds.help.curve" "Curve" - "ds.help.ruler" "Ruler" - "ds.help.canvas" "Canvas" + "workspace.header.rect" "Box (Ctrl + B)" + "workspace.header.circle" "Circle (Ctrl + E)" + "workspace.header.line" "Line (Ctrl + L)" + "workspace.header.text" "Text" + "workspace.header.path" "Path" + "workspace.header.curve" "Curve" + "workspace.header.ruler" "Ruler" + "workspace.header.canvas" "Canvas" "ds.user.profile" "Profile" "ds.user.password" "Password" "ds.user.notifications" "Notifications" "ds.user.exit" "Exit" - "header.sitemap" "Sitemap (Ctrl + Shift + M)" - "header.draw-tools" "Draw tools (Ctrl + Shift + S)" - "header.color-palette" "Color Palette (---)" - "header.icons" "Icons (Ctrl + Shift + I)" - "header.layers" "Layers (Ctrl + Shift + L)" - "header.element-options" "Element options (Ctrl + Shift + O)" - "header.document-history" "History (Ctrl + Shift + H)" - "header.undo" "Undo (Ctrl + Z)" - "header.redo" "Redo (Ctrl + Shift + Z)" - "header.download" "Download (Ctrl + E)" - "header.image" "Image (Ctrl + I)" - "header.rules" "Rules" - "header.grid" "Grid (Ctrl + G)" - "header.grid-snap" "Snap to grid" - "header.align" "Align (Ctrl + A)" - "header.view-mode" "View mode (Ctrl + P)" + "workspace.header.sitemap" "Sitemap (Ctrl + Shift + M)" + "workspace.header.draw-tools" "Draw tools (Ctrl + Shift + S)" + "workspace.header.color-palette" "Color Palette (---)" + "workspace.header.icons" "Icons (Ctrl + Shift + I)" + "workspace.header.layers" "Layers (Ctrl + Shift + L)" + "workspace.header.element-options" "Element options (Ctrl + Shift + O)" + "workspace.header.document-history" "History (Ctrl + Shift + H)" + "workspace.header.undo" "Undo (Ctrl + Z)" + "workspace.header.redo" "Redo (Ctrl + Shift + Z)" + "workspace.header.download" "Download (Ctrl + E)" + "workspace.header.image" "Image (Ctrl + I)" + "workspace.header.rules" "Rules" + "workspace.header.grid" "Grid (Ctrl + G)" + "workspace.header.grid-snap" "Snap to grid" + "workspace.header.align" "Align (Ctrl + A)" + "workspace.header.view-mode" "View mode (Ctrl + P)" + + "workspace.options.radius" "Radius" + "workspace.options.size" "Size" + "workspace.options.width" "Width" + "workspace.options.height" "Height" + "workspace.options.stroke.style" "Style" + "workspace.options.stroke.none" "None" + "workspace.options.stroke.solid" "Solid" + "workspace.options.stroke.dotted" "Dotted" + "workspace.options.stroke.dashed" "Dashed" + "workspace.options.stroke.mixed" "Mixed" + "workspace.options.position" "Position" + "workspace.options.rotation" "Rotation" + "workspace.options.opacity" "Opacity" + "workspace.options.color" "Color" + "workspace.options.background-color" "Background color" + "workspace.options.font-family" "Font family" + "workspace.options.size-weight" "Size and Weight" + "workspace.options.font-size" "Font Size" + "workspace.options.line-height-letter-spacing" "Line height and Letter spacing" + "workspace.options.line-height" "Line height" + "workspace.options.letter-spacing" "Letter spacing" + "workspace.options.text-align" "Text align" + "workspace.options.name" "Name" + "workspace.options.go" "Go go go!" + "workspace.options.measures" "Size, position & rotation" + "workspace.options.rotation-radius" "Rotation & Radius" - "element.measures" "Size, position & rotation" "element.fill" "Fill" - "element.stroke" "Stroke" + "workspace.options.stroke" "Stroke" "element.text" "Text" "element.interactions" "Interactions" - "element.page-measures" "Page settings" + "workspace.options.page-measures" "Page settings" "element.page-grid-options" "Grid settings" "image.new" "New image" diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs index 07e77dabf..2016ece95 100644 --- a/frontend/src/uxbox/main/ui.cljs +++ b/frontend/src/uxbox/main/ui.cljs @@ -82,9 +82,6 @@ ;; Something else :else (do - (js/console.error "Unhandled Error:" - "\n - message:" (ex-message error) - "\n - data:" (pr-str (ex-data error))) (js/console.error error) (ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic"))))))) diff --git a/frontend/src/uxbox/main/ui/dashboard.cljs b/frontend/src/uxbox/main/ui/dashboard.cljs index 5d0524bed..18403ede4 100644 --- a/frontend/src/uxbox/main/ui/dashboard.cljs +++ b/frontend/src/uxbox/main/ui/dashboard.cljs @@ -17,28 +17,28 @@ id (when (uuid-str? id) (uuid id))] [:main.dashboard-main [:& messages-widget] - [:& header {:section :dashboard/projects}] + [:& header {:section :dashboard-projects}] [:& projects/projects-page {:id id}]])) (mf/defc dashboard-assets [{:keys [route] :as props}] - (let [section (:name route) + (let [section (get-in route [:data :name]) {:keys [id type]} (get-in route [:params :query]) id (cond ;; (str/digits? id) (parse-int id) (uuid-str? id) (uuid id) (str/empty-or-nil? id) nil :else id) - type (when (str/alpha? type) (keyword type))] + type (if (str/alpha? type) (keyword type) :own)] [:main.dashboard-main [:& messages-widget] [:& header {:section section}] (case section - :dashboard/icons + :dashboard-icons [:& icons/icons-page {:type type :id id}] - :dashboard/images + :dashboard-images [:& images/images-page {:type type :id id}] - :dashboard/colors + :dashboard-colors [:& colors/colors-page {:type type :id id}])])) diff --git a/frontend/src/uxbox/main/ui/dashboard/colors.cljs b/frontend/src/uxbox/main/ui/dashboard/colors.cljs index 52c9ea673..6059542d9 100644 --- a/frontend/src/uxbox/main/ui/dashboard/colors.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/colors.cljs @@ -61,7 +61,7 @@ (delete [] (st/emit! (dc/delete-collection (:id coll)) - (rt/nav :dashboard/colors nil {:type (:type coll)}))) + (rt/nav :dashboard-colors nil {:type (:type coll)}))) (on-delete [] (modal/show! confirm-dialog {:on-accept delete}))] @@ -79,7 +79,7 @@ editable? (= type :own)] (letfn [(on-click [event] (let [type (or type :own)] - (st/emit! (rt/nav :dashboard/colors nil {:type type :id id})))) + (st/emit! (rt/nav :dashboard-colors nil {:type type :id id})))) (on-input-change [event] (let [value (dom/get-target event) value (dom/get-value value)] @@ -107,14 +107,14 @@ :on-key-down on-input-keyup}] [:span.close {:on-click on-cancel} i/close]] [:span.element-title name]) - [:span.element-subtitle + #_[:span.element-subtitle (tr "ds.num-elements" (t/c colors))]]))) (mf/defc nav [{:keys [id type colls selected-coll] :as props}] (let [own? (= type :own) builtin? (= type :builtin) - select-tab #(st/emit! (rt/nav :dashboard/colors nil {:type %}))] + select-tab #(st/emit! (rt/nav :dashboard-colors nil {:type %}))] [:div.library-bar [:div.library-bar-inside [:ul.library-tabs @@ -259,7 +259,8 @@ [{:keys [id type coll] :as props}] (let [selected (mf/deref selected-colors-iref)] [:section.dashboard-grid.library - [:& grid-header {:coll coll}] + (when coll + [:& grid-header {:coll coll}]) [:& grid {:coll coll :id id :type type :selected selected}] (when (seq selected) [:& grid-options {:id id :type type @@ -282,14 +283,12 @@ (first colls)) id (:id selected-coll)] - (mf/use-effect #(st/emit! (dc/initialize)) #js [id type]) (mf/use-effect #(st/emit! (dc/fetch-collections))) [:section.dashboard-content [:& nav {:type type :id id - :colls colls - :selected-coll selected-coll}] + :colls colls}] [:& content {:type type :id id :coll selected-coll}]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/header.cljs b/frontend/src/uxbox/main/ui/dashboard/header.cljs index c32d885db..80c797031 100644 --- a/frontend/src/uxbox/main/ui/dashboard/header.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/header.cljs @@ -25,26 +25,26 @@ (mf/defc header [{:keys [section] :as props}] - (let [projects? (= section :dashboard/projects) - icons? (= section :dashboard/icons) - images? (= section :dashboard/images) - colors? (= section :dashboard/colors)] + (let [projects? (= section :dashboard-projects) + icons? (= section :dashboard-icons) + images? (= section :dashboard-images) + colors? (= section :dashboard-colors)] [:header#main-bar.main-bar [:div.main-logo - [:& header-link {:section :dashboard/projects + [:& header-link {:section :dashboard-projects :content i/logo}]] [:ul.main-nav [:li {:class (when projects? "current")} - [:& header-link {:section :dashboard/projects + [:& header-link {:section :dashboard-projects :content (tr "ds.projects")}]] [:li {:class (when icons? "current")} - [:& header-link {:section :dashboard/icons + [:& header-link {:section :dashboard-icons :content (tr "ds.icons")}]] [:li {:class (when images? "current")} - [:& header-link {:section :dashboard/images + [:& header-link {:section :dashboard-images :content (tr "ds.images")}]] [:li {:class (when colors? "current")} - [:& header-link {:section :dashboard/colors + [:& header-link {:section :dashboard-colors :content (tr "ds.colors")}]]] [:& user]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/icons.cljs b/frontend/src/uxbox/main/ui/dashboard/icons.cljs index c0a9091c4..1eb72fc35 100644 --- a/frontend/src/uxbox/main/ui/dashboard/icons.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/icons.cljs @@ -79,29 +79,14 @@ ;; --- Nav -(defn- make-num-icons-iref - [id] - (letfn [(selector [icons] - (->> (vals icons) - (filter #(= id (:collection-id %))) - (count)))] - (-> (comp (l/key :icons) - (l/lens selector)) - (l/derive st/state)))) - (mf/defc nav-item [{:keys [coll selected?] :as props}] (let [local (mf/use-state {}) - {:keys [id type name num-icons]} coll - ;; TODO: recalculate the num-icons on crud operations for - ;; avod doing this on UI. - ;; num-icons-iref (mf/use-memo {:deps #js [id] - ;; :fn #(make-num-icons-iref (:id coll))}) - ;; num-icons (mf/deref num-icons-iref) + {:keys [id type name]} coll editable? (= type :own)] (letfn [(on-click [event] (let [type (or type :own)] - (st/emit! (rt/nav :dashboard/icons {} {:type type :id id})))) + (st/emit! (rt/nav :dashboard-icons {} {:type type :id id})))) (on-input-change [event] (-> (dom/get-target event) (dom/get-value) @@ -127,15 +112,14 @@ :on-change on-input-change :on-key-down on-input-keyup}] [:span.close {:on-click on-cancel} i/close]] - [:span.element-title (if id name "Storage")]) - [:span.element-subtitle (tr "ds.num-elements" (t/c num-icons))]]))) + [:span.element-title (if id name "Storage")])]))) (mf/defc nav [{:keys [id type colls selected-coll] :as props}] (let [own? (= type :own) builtin? (= type :builtin) - select-tab #(st/emit! (rt/nav :dashboard/icons nil {:type %}))] + select-tab #(st/emit! (rt/nav :dashboard-icons nil {:type %}))] [:div.library-bar [:div.library-bar-inside [:ul.library-tabs @@ -350,54 +334,12 @@ :selected (contains? (:selected opts) (:id icon)) :edition? (= (:edition opts) (:id icon))}])]]])) -;; --- Menu - -(mf/defc menu - [{:keys [opts coll] :as props}] - (let [ordering (:order opts :name) - filtering (:filter opts "") - icount (count (:icons coll))] - (letfn [(on-term-change [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (di/update-opts :filter term)))) - (on-ordering-change [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (di/update-opts :order value)))) - (on-clear [event] - (st/emit! (di/update-opts :filter "")))] - [:section.dashboard-bar.library-gap - [:div.dashboard-info - - ;; Counter - [:span.dashboard-icons (tr "ds.num-icons" (t/c icount))] - - ;; Sorting - [:div - [:span (tr "ds.ordering")] - [:select.input-select {:on-change on-ordering-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - [:option {:key key :value (pr-str key)} (tr value)])]] - - ;; Search - [:form.dashboard-search - [:input.input-text {:key :icons-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value filtering}] - [:div.clear-search {:on-click on-clear} i/close]]]]))) - ;; --- Content (mf/defc content [{:keys [id type coll] :as props}] (let [opts (mf/deref opts-iref)] [:* - [:& menu {:opts opts :coll coll}] [:section.dashboard-grid.library (when coll [:& grid-header {:coll coll}]) @@ -425,15 +367,13 @@ :else (first colls)) id (:id selected-coll)] (mf/use-effect #(st/emit! di/fetch-collections)) - (mf/use-effect {:fn #(st/emit! di/initialize - (di/fetch-icons id)) - :deps #js [id type]}) + (mf/use-effect {:fn #(st/emit! (di/fetch-icons id)) + :deps #js [(str id)]}) [:section.dashboard-content [:& nav {:type type :id id - :colls colls - :selected-coll selected-coll}] + :colls colls}] [:& content {:type type :id id :coll selected-coll}]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/images.cljs b/frontend/src/uxbox/main/ui/dashboard/images.cljs index f1028ae63..424b509d4 100644 --- a/frontend/src/uxbox/main/ui/dashboard/images.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/images.cljs @@ -26,30 +26,6 @@ [uxbox.util.router :as rt] [uxbox.util.time :as dt])) -;; --- Helpers & Constants - -(def +ordering-options+ - {:name "ds.ordering.by-name" - :created "ds.ordering.by-creation-date"}) - -(defn- sort-images-by - [ordering images] - (case ordering - :name (sort-by :name images) - :created (reverse (sort-by :created-at images)) - images)) - -(defn- contains-term? - [phrase term] - (let [term (name term)] - (str/includes? (str/lower phrase) (str/trim (str/lower term))))) - -(defn- filter-images-by - [term images] - (if (str/blank? term) - images - (filter #(contains-term? (:name %) term) images))) - ;; --- Refs (def collections-iref @@ -80,61 +56,41 @@ ;; --- Nav -(defn- make-num-images-iref - [id] - (letfn [(selector [images] - (->> (vals images) - (filter #(= id (:collection-id %))) - (count)))] - (-> (comp (l/key :images) - (l/lens selector)) - (l/derive st/state)))) - (mf/defc nav-item [{:keys [coll selected?] :as props}] (let [local (mf/use-state {}) {:keys [id type name num-images]} coll - ;; TODO: recalculate the num-images on crud operations for - ;; avod doing this on UI. - num-images-iref (mf/use-memo #(make-num-images-iref (:id coll)) #js [id]) - num-images (mf/deref num-images-iref) - editable? (= type :own)] - (letfn [(on-click [event] - (let [type (or type :own)] - (st/emit! (rt/nav :dashboard/images {} {:type type :id id})))) - (on-input-change [event] - (-> (dom/get-target event) - (dom/get-value) - (swap! local assoc :name))) - (on-cancel [event] - (swap! local dissoc :name :edit)) - (on-double-click [event] - (when editable? - (swap! local assoc :edit true))) - (on-input-keyup [event] - (when (kbd/enter? event) - (let [value (-> (dom/get-target event) (dom/get-value))] - (st/emit! (di/rename-collection id (str/trim (:name @local)))) - (swap! local assoc :edit false))))] - [:li {:on-click on-click - :on-double-click on-double-click - :class-name (when selected? "current")} - (if (:edit @local) - [:div - [:input.element-title {:value (if (:name @local) - (:name @local) - (if id name "Storage")) - :on-change on-input-change - :on-key-down on-input-keyup}] - [:span.close {:on-click on-cancel} i/close]] - [:span.element-title (if id name "Storage")]) - [:span.element-subtitle (tr "ds.num-elements" (t/c num-images))]]))) + editable? (= type :own) + on-click + (fn [event] + (let [type (or type :own)] + (st/emit! (rt/nav :dashboard-images {} {:type type :id id})))) + + on-cancel-edition #(swap! local dissoc :edit) + on-double-click #(when editable? (swap! local assoc :edit true)) + on-input-keyup + (fn [event] + (when (kbd/enter? event) + (let [value (-> (dom/get-target event) + (dom/get-value) + (str/trim))] + (st/emit! (di/rename-collection id value)) + (swap! local assoc :edit false))))] + [:li {:on-click on-click + :on-double-click on-double-click + :class-name (when selected? "current")} + (if (:edit @local) + [:div + [:input.element-title {:default-value name + :on-key-down on-input-keyup}] + [:span.close {:on-click on-cancel-edition} i/close]] + [:span.element-title (if id name "Storage")])])) (mf/defc nav - [{:keys [id type colls selected-coll] :as props}] + [{:keys [id type colls] :as props}] (let [own? (= type :own) builtin? (= type :builtin) - select-tab #(st/emit! (rt/nav :dashboard/images nil {:type %}))] + select-tab #(st/emit! (rt/nav :dashboard-images nil {:type %}))] [:div.library-bar [:div.library-bar-inside [:ul.library-tabs @@ -148,7 +104,7 @@ [:ul.library-elements (when own? [:li - [:a.btn-primary {:on-click #(st/emit! (di/create-collection))} + [:a.btn-primary {:on-click #(st/emit! di/create-collection)} (tr "ds.images-collection.new")]]) (when own? [:& nav-item {:selected? (nil? id)}]) @@ -168,7 +124,7 @@ colls (->> (vals colls) (filter #(= :own (:type %))) (remove #(= selected (:id %))) - (sort-by :name colls)) + #_(sort-by :name colls)) on-select (fn [event id] (dom/prevent-default event) (dom/stop-propagation event) @@ -306,20 +262,21 @@ ;; --- Grid (defn- make-images-iref - [id] - (-> (comp (l/key :images) - (l/lens (fn [images] - (->> (vals images) - (filter #(= id (:collection-id %))))))) - (l/derive st/state))) + [id type] + (letfn [(selector-fn [state] + (let [images (vals (:images state))] + (filterv #(= id (:collection-id %)) images)))] + (-> (l/lens selector-fn) + (l/derive st/state)))) (mf/defc grid [{:keys [id type coll opts] :as props}] (let [editable? (or (= type :own) (nil? id)) - images-iref (mf/use-memo #(make-images-iref id) #js [id]) + images-iref (mf/use-memo + {:fn #(make-images-iref id type) + :deps (mf/deps id type)}) images (->> (mf/deref images-iref) - (filter-images-by (:filter opts "")) - (sort-images-by (:order opts :name)))] + (sort-by :created-at))] [:div.dashboard-grid-content [:div.dashboard-grid-row (when editable? @@ -332,53 +289,53 @@ ;; --- Menu -(mf/defc menu - [{:keys [opts coll] :as props}] - (let [ordering (:order opts :name) - filtering (:filter opts "") - icount (count (:images coll))] - (letfn [(on-term-change [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (di/update-opts :filter term)))) - (on-ordering-change [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (di/update-opts :order value)))) - (on-clear [event] - (st/emit! (di/update-opts :filter "")))] - [:section.dashboard-bar.library-gap - [:div.dashboard-info +;; (mf/defc menu +;; [{:keys [opts coll] :as props}] +;; (let [ordering (:order opts :name) +;; filtering (:filter opts "") +;; icount (count (:images coll))] +;; (letfn [(on-term-change [event] +;; (let [term (-> (dom/get-target event) +;; (dom/get-value))] +;; (st/emit! (di/update-opts :filter term)))) +;; (on-ordering-change [event] +;; (let [value (dom/event->value event) +;; value (read-string value)] +;; (st/emit! (di/update-opts :order value)))) +;; (on-clear [event] +;; (st/emit! (di/update-opts :filter "")))] +;; [:section.dashboard-bar.library-gap +;; [:div.dashboard-info - ;; Counter - [:span.dashboard-images (tr "ds.num-images" (t/c icount))] +;; ;; Counter +;; [:span.dashboard-images (tr "ds.num-images" (t/c icount))] - ;; Sorting - [:div - [:span (tr "ds.ordering")] - [:select.input-select {:on-change on-ordering-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - [:option {:key key :value (pr-str key)} (tr value)])]] +;; ;; Sorting +;; [:div +;; [:span (tr "ds.ordering")] +;; [:select.input-select {:on-change on-ordering-change +;; :value (pr-str ordering)} +;; (for [[key value] (seq +ordering-options+)] +;; [:option {:key key :value (pr-str key)} (tr value)])]] - ;; Search - [:form.dashboard-search - [:input.input-text {:key :images-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value filtering}] - [:div.clear-search {:on-click on-clear} i/close]]]]))) +;; ;; Search +;; [:form.dashboard-search +;; [:input.input-text {:key :images-search-box +;; :type "text" +;; :on-change on-term-change +;; :auto-focus true +;; :placeholder (tr "ds.search.placeholder") +;; :value filtering}] +;; [:div.clear-search {:on-click on-clear} i/close]]]]))) (mf/defc content [{:keys [id type coll] :as props}] (let [opts (mf/deref opts-iref)] [:* - [:& menu {:opts opts :coll coll}] [:section.dashboard-grid.library (when coll [:& grid-header {:coll coll}]) + [:& grid {:id id :type type :coll coll @@ -390,28 +347,26 @@ (mf/defc images-page [{:keys [id type] :as props}] - (let [type (or type :own) - colls (mf/deref collections-iref) + (let [colls (mf/deref collections-iref) colls (cond->> (vals colls) (= type :own) (filter #(= :own (:type %))) (= type :builtin) (filter #(= :builtin (:type %))) true (sort-by :created-at)) - selected-coll (cond - (and (= type :own) (nil? id)) nil - (uuid? id) (seek #(= id (:id %)) colls) - :else (first colls)) - id (:id selected-coll)] - (mf/use-effect #(st/emit! (di/fetch-collections))) - (mf/use-effect {:fn #(st/emit! (di/initialize) - (di/fetch-images id)) - :deps #js [id type]}) + coll (cond + (and (= type :own) (nil? id)) nil + (uuid? id) (seek #(= id (:id %)) colls) + :else (first colls)) + id (:id coll)] + + (mf/use-effect #(st/emit! di/fetch-collections)) + (mf/use-effect {:fn #(st/emit! (di/fetch-images (:id coll))) + :deps #js [(str (:id coll))]}) [:section.dashboard-content [:& nav {:type type :id id - :colls colls - :selected-coll selected-coll}] + :colls colls}] [:& content {:type type :id id - :coll selected-coll}]])) + :coll coll}]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index 11d957f06..e63cc3fcc 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -64,60 +64,6 @@ files (filter #(contains-term? (:name %) term) files))) -;; --- Menu (Filter & Sort) - -(mf/defc menu - [{:keys [id opts files] :as props}] - (let [ordering (:order opts :modified) - filtering (:filter opts "") - - on-term-change - (fn [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (udp/update-opts :filter term)))) - - on-order-change - (fn [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (udp/update-opts :order value)))) - - on-clear - (fn [event] - (st/emit! (udp/update-opts :filter "")))] - - [:section.dashboard-bar.library-gap - [:div.dashboard-info - - ;; Counter - [:span.dashboard-images (tr "ds.num-files" (t/c (count files)))] - - [:div - ;; Sorting - ;; TODO: convert to separate component? - (when id - [:* - [:span (tr "ds.ordering")] - [:select.input-select {:on-change on-order-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - (let [key (pr-str key)] - [:option {:key key :value key} (tr value)]))]])] - - ;; Search - ;; TODO: convert to separate component? - ; [:form.dashboard-search - ; [:input.input-text - ; {:key :images-search-box - ; :type "text" - ; :on-change on-term-change - ; :auto-focus true - ; :placeholder (tr "ds.search.placeholder") - ; :value (or filtering "")}] - ; [:div.clear-search {:on-click on-clear} i/close]] - ]])) - ;; --- Grid Item Thumbnail (mf/defc grid-item-thumbnail @@ -164,9 +110,9 @@ ;; [:div.project-th-icon.pages ;; i/page ;; #_[:span (:total-pages project)]] - #_[:div.project-th-icon.comments - i/chat - [:span "0"]] + ;; [:div.project-th-icon.comments + ;; i/chat + ;; [:span "0"]] [:div.project-th-icon.edit {:on-click on-edit} i/pencil] @@ -177,7 +123,7 @@ ;; --- Grid (mf/defc grid - [{:keys [opts files] :as props}] + [{:keys [id opts files] :as props}] (let [order (:order opts :modified) filter (:filter opts "") files (->> files @@ -185,15 +131,13 @@ (sort-by order)) on-click #(do (dom/prevent-default %) - #_(modal/show! create-project-dialog {}) - #_(udl/open! :create-project)) - ] + (st/emit! (udp/create-file {:project-id id})))] [:section.dashboard-grid - ;; [:h2 (tr "ds.projects.file-name")] [:div.dashboard-grid-content [:div.dashboard-grid-row - [:div.grid-item.add-project #_{:on-click on-click} - [:span (tr "ds.new-file")]] + (when id + [:div.grid-item.add-project {:on-click on-click} + [:span (tr "ds.new-file")]]) (for [item files] [:& grid-item {:file item :key (:id item)}])]]])) @@ -245,10 +189,6 @@ :placeholder (tr "ds.search.placeholder")}] [:div.clear-search i/close]] [:ul.library-elements - ; [:li - ; [:a.btn-primary #_{:on-click #(st/emit! di/create-collection)} - ; "new project +"]] - [:li.recent-projects {:on-click #(st/emit! (udp/go-to-project nil)) :class-name (when (nil? id) "current")} [:span.element-title "Recent"]] @@ -280,10 +220,8 @@ [{:keys [id] :as props}] (let [opts (mf/deref opts-iref) files (mf/deref files-ref)] - [:* - #_[:& menu {:id id :opts opts :files files}] - [:section.dashboard-grid.library - [:& grid {:id id :opts opts :files files}]]])) + [:section.dashboard-grid.library + [:& grid {:id id :opts opts :files files}]])) ;; --- Projects Page diff --git a/frontend/src/uxbox/main/ui/shapes/attrs.cljs b/frontend/src/uxbox/main/ui/shapes/attrs.cljs index 6ad2d7303..4c3f56baf 100644 --- a/frontend/src/uxbox/main/ui/shapes/attrs.cljs +++ b/frontend/src/uxbox/main/ui/shapes/attrs.cljs @@ -4,7 +4,43 @@ ;; ;; Copyright (c) 2016-2017 Andrey Antukh -(ns uxbox.main.ui.shapes.attrs) +(ns uxbox.main.ui.shapes.attrs + (:require [cuerdas.core :as str])) + + +;; (defn camel-case +;; "Returns camel case version of the key, e.g. :http-equiv becomes :httpEquiv." +;; [k] +;; (if (or (keyword? k) +;; (string? k) +;; (symbol? k)) +;; (let [[first-word & words] (str/split (name k) #"-")] +;; (if (or (empty? words) +;; (= "aria" first-word) +;; (= "data" first-word)) +;; k +;; (-> (map str/capital words) +;; (conj first-word) +;; str/join +;; keyword))) +;; k)) + +(defn- process-key + [k] + (if (keyword? k) + (cond + (keyword-identical? k :stroke-color) :stroke + (keyword-identical? k :fill-color) :fill + (str/includes? (name k) "-") (str/camel k) + :else k))) + +(defn- process-attrs + [m] + (persistent! + (reduce-kv (fn [m k v] + (assoc! m (process-key k) v)) + (transient {}) + m))) (def shape-style-attrs #{:fill-color @@ -17,12 +53,6 @@ :rx :ry}) -(def shape-default-attrs - {:stroke-color "#000000" - :stroke-opacity 1 - :fill-color "#000000" - :fill-opacity 1}) - (defn- stroke-type->dasharray [style] (case style @@ -30,24 +60,23 @@ :dotted "5,5" :dashed "10,10")) -(defn- rename-attr - [[key value :as pair]] - (case key - :stroke-color [:stroke value] - :fill-color [:fill value] - pair)) +;; (defn- rename-attr +;; [[key value :as pair]] +;; (case key +;; :stroke-color [:stroke value] +;; :fill-color [:fill value] +;; pair)) -(defn- rename-attrs - [attrs] - (into {} (map rename-attr) attrs)) +;; (defn- rename-attrs +;; [attrs] +;; (into {} (map rename-attr) attrs)) (defn- transform-stroke-attrs [{:keys [stroke-style] :or {stroke-style :none} :as attrs}] (case stroke-style :none (dissoc attrs :stroke-style :stroke-width :stroke-opacity :stroke-color) - :solid (-> (merge shape-default-attrs attrs) - (dissoc :stroke-style)) - (-> (merge shape-default-attrs attrs) + :solid (dissoc attrs :stroke-style) + (-> attrs (assoc :stroke-dasharray (stroke-type->dasharray stroke-style)) (dissoc :stroke-style)))) @@ -56,4 +85,4 @@ [shape] (-> (select-keys shape shape-style-attrs) (transform-stroke-attrs) - (rename-attrs))) + (process-attrs))) diff --git a/frontend/src/uxbox/main/ui/shapes/image.cljs b/frontend/src/uxbox/main/ui/shapes/image.cljs index 04136cd9a..bde963f92 100644 --- a/frontend/src/uxbox/main/ui/shapes/image.cljs +++ b/frontend/src/uxbox/main/ui/shapes/image.cljs @@ -47,18 +47,19 @@ (mf/defc image-shape [{:keys [shape image] :as props}] - (let [{:keys [id x1 y1 width height modifier-mtx]} (geom/size shape) + (let [{:keys [id x y width height modifier-mtx]} shape moving? (boolean modifier-mtx) transform (when (gmt/matrix? modifier-mtx) (str modifier-mtx)) - props {:x x1 :y y1 - :id (str "shape-" id) - :preserve-aspect-ratio "none" - :class (classnames :move-cursor moving?) - :xlink-href (:url image) - :transform transform - :width width - :height height} - attrs (merge props (attrs/extract-style-attrs shape))] - [:> :image (normalize-props attrs)])) + props (-> (attrs/extract-style-attrs shape) + (assoc :x x + :y y + :id (str "shape-" id) + :preserveAspectRatio "none" + :class (classnames :move-cursor moving?) + :xlinkHref (:url image) + :transform transform + :width width + :height height))] + [:& "image" props])) diff --git a/frontend/src/uxbox/main/ui/shapes/rect.cljs b/frontend/src/uxbox/main/ui/shapes/rect.cljs index f1f38928c..1b2184072 100644 --- a/frontend/src/uxbox/main/ui/shapes/rect.cljs +++ b/frontend/src/uxbox/main/ui/shapes/rect.cljs @@ -7,6 +7,7 @@ (ns uxbox.main.ui.shapes.rect (:require [rumext.alpha :as mf] + [cuerdas.core :as str] [uxbox.main.geom :as geom] [uxbox.main.refs :as refs] [uxbox.main.ui.shapes.attrs :as attrs] @@ -46,18 +47,19 @@ (gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx) :else shape) - {:keys [x1 y1 width height] :as shape} (geom/size shape) + {:keys [x y width height]} shape transform (when (pos? rotation) (str (rotate (gmt/matrix) shape))) moving? (boolean modifier-mtx) - props {:x x1 :y y1 - :id (str "shape-" id) - :className (classnames :move-cursor moving?) - :width width - :height height - :transform transform} - attrs (merge (attrs/extract-style-attrs shape) props)] - [:& "rect" attrs])) + props (-> (attrs/extract-style-attrs shape) + (assoc :x x + :y y + :id (str "shape-" id) + :className (classnames :move-cursor moving?) + :width width + :height height + :transform transform))] + [:& "rect" props])) diff --git a/frontend/src/uxbox/main/ui/shapes/text.cljs b/frontend/src/uxbox/main/ui/shapes/text.cljs index 834563b60..2ce42d6dd 100644 --- a/frontend/src/uxbox/main/ui/shapes/text.cljs +++ b/frontend/src/uxbox/main/ui/shapes/text.cljs @@ -122,7 +122,7 @@ style (make-style shape) on-input (fn [ev] (let [content (dom/event->inner-text ev)] - (st/emit! (udw/update-shape-attrs id {:content 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 fd0a82477..e60a9f317 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -60,7 +60,7 @@ (scroll/scroll-to-point dom mouse-point scroll-position)))) (mf/defc workspace-content - [{:keys [layout page file] :as params}] + [{:keys [layout page file flags] :as params}] (let [canvas (mf/use-ref nil) left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history]))) @@ -93,28 +93,13 @@ (when right-sidebar? [:& right-sidebar {:page page :layout layout}])])) -(mf/defc workspace - [{:keys [file-id page-id] :as props}] - +(mf/defc workspace-page + [{:keys [file-id page-id layout file flags] :as props}] (mf/use-effect - {:deps #js [(str file-id)] - :fn (fn [] - (st/emit! (dw/initialize-ws file-id)) - #(st/emit! (dw/finalize-ws file-id)))}) - - (mf/use-effect - {:deps #js [(str file-id) - (str page-id)] - :fn (fn [] - (let [sub (shortcuts/init)] - (st/emit! (dw/initialize file-id page-id)) - #(rx/cancel! sub)))}) - - (let [layout (mf/deref refs/workspace-layout) - file (mf/deref refs/workspace-file) - page (mf/deref refs/workspace-page) - flags (mf/deref refs/selected-flags)] + {:deps (mf/deps file-id page-id) + :fn #(st/emit! (dw/initialize-page page-id))}) + (let [page (mf/deref refs/workspace-page)] [:> rdnd/provider {:backend rdnd/html5} [:& messages-widget] [:& header {:page page :layout layout :flags flags}] @@ -124,5 +109,34 @@ (when (and layout page) [:& workspace-content {:layout layout + :flags flags :file file :page page}])])) + + + +(mf/defc workspace + [{:keys [file-id page-id] :as props}] + (mf/use-effect + {:deps (mf/deps file-id) + :fn (fn [] + (st/emit! (dw/initialize file-id)) + #(st/emit! (dw/finalize file-id)))}) + + (mf/use-effect + {:deps (mf/deps file-id page-id) + :fn (fn [] + (let [sub (shortcuts/init)] + #(rx/cancel! sub)))}) + + (let [layout (mf/deref refs/workspace-layout) + file (mf/deref refs/workspace-file) + flags (mf/deref refs/selected-flags)] + + ;; TODO: maybe loading state? + (when file + [:& workspace-page {:layout layout + :file file + :flags flags + :page-id page-id + :file-id file-id}]))) diff --git a/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs b/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs index 15dcb1d04..83e52b472 100644 --- a/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs @@ -30,10 +30,9 @@ (mf/defc palette-item [{:keys [color] :as props}] (letfn [(select-color [event] - (let [attrs (if (kbd/shift? event) - {:stroke-color color} - {:fill-color color})] - (st/emit! (udw/update-selected-shapes-attrs attrs))))] + (if (kbd/shift? event) + (st/emit! (udw/update-selected-shapes :stroke-color color)) + (st/emit! (udw/update-selected-shapes :fill-color color))))] (let [rgb-vec (hex->rgb color) rgb-color (apply str "" (interpose ", " rgb-vec))] [:div.color-cell {:key (str color) diff --git a/frontend/src/uxbox/main/ui/workspace/download.cljs b/frontend/src/uxbox/main/ui/workspace/download.cljs index 25bdaa623..7d524c6fc 100644 --- a/frontend/src/uxbox/main/ui/workspace/download.cljs +++ b/frontend/src/uxbox/main/ui/workspace/download.cljs @@ -75,7 +75,7 @@ (let [event (js/MouseEvent. "click") link (.createElement js/document "a") now (dt/now) - stream (->> (rx/from-coll (generate-files pages)) + stream (->> (rx/from (generate-files pages)) (rx/reduce conj []) (rx/mapcat zip/build) (rx/map blob/create-uri) diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index 28f8b5243..c9828b665 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -51,8 +51,7 @@ :fill-opacity 0 :segments []} {:type :canvas - :name "Canvas" - :stroke-color "#000000"} + :name "Canvas"} {:type :curve :name "Path" :stroke-style :solid @@ -109,18 +108,17 @@ (def handle-drawing-generic (letfn [(initialize-drawing [state point] (let [shape (get-in state [:workspace-local :drawing]) - shape (geom/setup shape {:x1 (:x point) - :y1 (:y point) - :x2 (+ (:x point) 2) - :y2 (+ (:y point) 2)})] + shape (geom/setup shape {:x (:x point) + :y (:y point) + :width 2 + :height 2})] (assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true)))) (resize-shape [shape point lock?] - (let [shape (-> (geom/shape->rect-shape shape) - (geom/size)) - result (geom/resize-shape :bottom-right shape point lock?) - scale (geom/calculate-scale-ratio shape result) - mtx (geom/generate-resize-matrix :bottom-right shape scale)] + (let [shape' (geom/shape->rect-shape shape) + result (geom/resize-shape :bottom-right shape' point lock?) + scale (geom/calculate-scale-ratio shape' result) + mtx (geom/generate-resize-matrix :bottom-right shape' scale)] (assoc shape :modifier-mtx mtx))) (update-drawing [state point lock?] @@ -150,13 +148,13 @@ (def handle-drawing-path (letfn [(stoper-event? [{:keys [type shift] :as event}] - (or (= event :interrupt) - (and (uws/mouse-event? event) - (or (and (= type :double-click) shift) - (= type :context-menu))) - (and (uws/keyboard-event? event) - (= type :down) - (= 13 (:key event))))) + (or (= event ::end-path-drawing) + (and (uws/mouse-event? event) + (or (and (= type :double-click) shift) + (= type :context-menu))) + (and (uws/keyboard-event? event) + (= type :down) + (= 13 (:key event))))) (initialize-drawing [state point] (-> state @@ -238,8 +236,7 @@ (def handle-drawing-curve (letfn [(stoper-event? [{:keys [type shift] :as event}] - (or (= event :interrupt) - (and (uws/mouse-event? event) (= type :up)))) + (uws/mouse-event? event) (= type :up)) (initialize-drawing [state] (assoc-in state [:workspace-local :drawing ::initialized?] true)) @@ -282,7 +279,9 @@ shape (dissoc shape ::initialized? :modifier-mtx)] ;; Add & select the created shape to the workspace (rx/of dw/deselect-all - (dw/add-shape shape))))))))) + (if (= :canvas (:type shape)) + (dw/add-canvas shape) + (dw/add-shape shape)))))))))) (def close-drawing-path (ptk/reify ::close-drawing-path @@ -322,7 +321,8 @@ (dom/stop-propagation event) (st/emit! (dw/set-tooltip nil) close-drawing-path - :interrupt)) + ::end-path-drawing)) + (on-mouse-enter [event] (st/emit! (dw/set-tooltip "Click to close the path"))) (on-mouse-leave [event] diff --git a/frontend/src/uxbox/main/ui/workspace/header.cljs b/frontend/src/uxbox/main/ui/workspace/header.cljs index 98aadeb6f..579a7e997 100644 --- a/frontend/src/uxbox/main/ui/workspace/header.cljs +++ b/frontend/src/uxbox/main/ui/workspace/header.cljs @@ -7,22 +7,19 @@ (ns uxbox.main.ui.workspace.header (:require - [rumext.alpha :as mf] [lentes.core :as l] + [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.config :as cfg] [uxbox.main.data.history :as udh] [uxbox.main.data.undo :as udu] [uxbox.main.data.workspace :as dw] - [uxbox.main.ui.workspace.images :refer [import-image-modal]] - [uxbox.main.ui.modal :as modal] [uxbox.main.refs :as refs] [uxbox.main.store :as st] + [uxbox.main.ui.modal :as modal] [uxbox.main.ui.users :refer [user]] - [uxbox.main.ui.workspace.clipboard] - [uxbox.util.data :refer [index-of]] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.geom.point :as gpt] + [uxbox.main.ui.workspace.images :refer [import-image-modal]] + [uxbox.util.i18n :refer [tr]] [uxbox.util.math :as mth] [uxbox.util.router :as rt])) @@ -66,6 +63,7 @@ (mf/defc header [{:keys [page layout flags] :as props}] (let [toggle #(st/emit! (dw/toggle-flag %)) + toggle-layout #(st/emit! (dw/toggle-layout-flag %)) on-undo #(st/emit! (udu/undo)) on-redo #(st/emit! (udu/redo)) on-image #(modal/show! import-image-modal {}) @@ -91,100 +89,100 @@ [:div.workspace-options [:ul.options-btn [:li.tooltip.tooltip-bottom - {:alt (tr "ds.help.canvas") + {:alt (tr "workspace.header.canvas") :class (when (= selected-drawtool :canvas) "selected") :on-click (partial select-drawtool :canvas)} i/artboard] [:li.tooltip.tooltip-bottom - {:alt (tr "ds.help.rect") + {:alt (tr "workspace.header.rect") :class (when (= selected-drawtool :rect) "selected") :on-click (partial select-drawtool :rect)} i/box] [:li.tooltip.tooltip-bottom - {:alt (tr "ds.help.circle") + {:alt (tr "workspace.header.circle") :class (when (= selected-drawtool :circle) "selected") :on-click (partial select-drawtool :circle)} i/circle] [:li.tooltip.tooltip-bottom - {:alt (tr "ds.help.text") + {:alt (tr "workspace.header.text") :class (when (= selected-drawtool :text) "selected") :on-click (partial select-drawtool :text)} i/text] [:li.tooltip.tooltip-bottom - {:alt (tr "ds.help.path") + {:alt (tr "workspace.header.path") :class (when (= selected-drawtool :path) "selected") :on-click (partial select-drawtool :path)} i/curve] [:li.tooltip.tooltip-bottom - {:alt (tr "ds.help.curve") + {:alt (tr "workspace.header.curve") :class (when (= selected-drawtool :curve) "selected") :on-click (partial select-drawtool :curve)} i/pencil] [:li.tooltip.tooltip-bottom - {:alt (tr "header.color-palette") + {:alt (tr "workspace.header.color-palette") :class (when (contains? layout :colorpalette) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :colorpalette))} i/palette] [:li.tooltip.tooltip-bottom - {:alt (tr "header.icons") + {:alt (tr "workspace.header.icons") :class (when (contains? layout :icons) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :icons))} i/icon-set] - ; [:li.tooltip.tooltip-bottom - ; {:alt (tr "header.layers") - ; :class (when (contains? layout :layers) "selected") - ; :on-click #(st/emit! (dw/toggle-layout-flag :layers))} - ; i/layers] - ; [:li.tooltip.tooltip-bottom - ; {:alt (tr "header.element-options") - ; :class (when (contains? layout :element-options) "selected") - ; :on-click #(st/emit! (dw/toggle-layout-flag :element-options))} - ; i/options] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.layers") + ;; :class (when (contains? layout :layers) "selected") + ;; :on-click #(st/emit! (dw/toggle-layout-flag :layers))} + ;; i/layers] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.element-options") + ;; :class (when (contains? layout :element-options) "selected") + ;; :on-click #(st/emit! (dw/toggle-layout-flag :element-options))} + ;; i/options] [:li.tooltip.tooltip-bottom - {:alt (tr "header.document-history") + {:alt (tr "workspace.header.document-history") :class (when (contains? layout :document-history) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :document-history))} i/undo-history] - ; [:li.tooltip.tooltip-bottom - ; {:alt (tr "header.undo") - ; :on-click on-undo} - ; i/undo] - ; [:li.tooltip.tooltip-bottom - ; {:alt (tr "header.redo") - ; :on-click on-redo} - ; i/redo] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.undo") + ;; :on-click on-undo} + ;; i/undo] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.redo") + ;; :on-click on-redo} + ;; i/redo] [:li.tooltip.tooltip-bottom - {:alt (tr "header.download") + {:alt (tr "workspace.header.download") ;; :on-click on-download } i/download] [:li.tooltip.tooltip-bottom - {:alt (tr "header.image") + {:alt (tr "workspace.header.image") :on-click on-image} i/image] [:li.tooltip.tooltip-bottom - {:alt (tr "header.rules") - :class (when (contains? flags :rules) "selected") - :on-click (partial toggle :rules)} + {:alt (tr "workspace.header.rules") + :class (when (contains? layout :rules) "selected") + :on-click (partial toggle-layout :rules)} i/ruler] [:li.tooltip.tooltip-bottom - {:alt (tr "header.grid") + {:alt (tr "workspace.header.grid") :class (when (contains? flags :grid) "selected") :on-click (partial toggle :grid)} i/grid] [:li.tooltip.tooltip-bottom - {:alt (tr "header.grid-snap") + {:alt (tr "workspace.header.grid-snap") :class (when (contains? flags :grid-snap) "selected") :on-click (partial toggle :grid-snap)} i/grid-snap]]] - ;; [:li.tooltip.tooltip-bottom - ;; {:alt (tr "header.align")} - ;; i/alignment]] - ;;[:& user] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.align")} + ;; i/alignment]] + ;; [:& user] [:div.secondary-options [:& zoom-widget] [:a.tooltip.tooltip-bottom.view-mode - {:alt (tr "header.view-mode") + {:alt (tr "workspace.header.view-mode") ;; :on-click #(st/emit! (dw/->OpenView (:id page))) } i/play]] diff --git a/frontend/src/uxbox/main/ui/workspace/images.cljs b/frontend/src/uxbox/main/ui/workspace/images.cljs index c080f2ecb..0091eaf0c 100644 --- a/frontend/src/uxbox/main/ui/workspace/images.cljs +++ b/frontend/src/uxbox/main/ui/workspace/images.cljs @@ -129,13 +129,9 @@ (read-string) (swap! local assoc :id))] - (mf/use-effect - {:fn #(do (st/emit! (udi/fetch-collections)) - (st/emit! (udi/fetch-images nil)))}) - - (mf/use-effect - {:deps #js [type id] - :fn #(st/emit! (udi/fetch-images id))}) + (mf/use-effect #(st/emit! udi/fetch-collections)) + (mf/use-effect {:deps #js [(str id)] + :fn #(st/emit! (udi/fetch-images id))}) [:div.lightbox-body.big-lightbox [:h3 (tr "image.import-library")] diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index 05706d214..fc8ac7186 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -99,10 +99,10 @@ (mf/defc controls [{:keys [shape zoom on-click] :as props}] - (let [{:keys [x1 y1 width height]} shape + (let [{:keys [x y width height]} shape radius (if (> (max width height) handler-size-threshold) 6.0 4.0)] [:g.controls - [:rect.main {:x x1 :y y1 + [:rect.main {:x x :y y :width width :height height :stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom)) @@ -111,42 +111,42 @@ [:& control-item {:class "top" :on-click #(on-click :top %) :r (/ radius zoom) - :cx (+ x1 (/ width 2)) - :cy (- y1 2)}] + :cx (+ x (/ width 2)) + :cy (- y 2)}] [:& control-item {:on-click #(on-click :right %) :r (/ radius zoom) - :cy (+ y1 (/ height 2)) - :cx (+ x1 width 1) + :cy (+ y (/ height 2)) + :cx (+ x width 1) :class "right"}] [:& control-item {:on-click #(on-click :bottom %) :r (/ radius zoom) - :cx (+ x1 (/ width 2)) - :cy (+ y1 height 2) + :cx (+ x (/ width 2)) + :cy (+ y height 2) :class "bottom"}] [:& control-item {:on-click #(on-click :left %) :r (/ radius zoom) - :cy (+ y1 (/ height 2)) - :cx (- x1 3) + :cy (+ y (/ height 2)) + :cx (- x 3) :class "left"}] [:& control-item {:on-click #(on-click :top-left %) :r (/ radius zoom) - :cx x1 - :cy y1 + :cx x + :cy y :class "top-left"}] [:& control-item {:on-click #(on-click :top-right %) :r (/ radius zoom) - :cx (+ x1 width) - :cy y1 + :cx (+ x width) + :cy y :class "top-right"}] [:& control-item {:on-click #(on-click :bottom-left %) :r (/ radius zoom) - :cx x1 - :cy (+ y1 height) + :cx x + :cy (+ y height) :class "bottom-left"}] [:& control-item {:on-click #(on-click :bottom-right %) :r (/ radius zoom) - :cx (+ x1 width) - :cy (+ y1 height) + :cx (+ x width) + :cy (+ y height) :class "bottom-right"}]])) ;; --- Selection Handlers (Component) @@ -203,9 +203,9 @@ (mf/defc text-edition-selection-handlers [{:keys [shape zoom] :as props}] - (let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)] + (let [{:keys [x y width height] :as shape} (geom/selection-rect shape)] [:g.controls - [:rect.main {:x x1 :y y1 + [:rect.main {:x x :y y :width width :height height ;; :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom)) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 5c7e76f5b..28c92b326 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -205,7 +205,7 @@ :class (when-not collapsed? "inverse")} i/arrow-slide]] [:ul - (for [[index shape] shapes] + (for [[index shape] (reverse shapes)] [:& layer-item {:shape shape :selected selected :index index diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs index 9ff0b56cf..8d0cd7dc6 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs @@ -9,28 +9,15 @@ (:require [lentes.core :as l] [rumext.alpha :as mf] - [rumext.core :as mx] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw] - [uxbox.main.geom :as geom] [uxbox.main.store :as st] [uxbox.main.refs :as refs] - [uxbox.main.ui.shapes.attrs :refer [shape-default-attrs]] - [uxbox.main.ui.workspace.sidebar.options.rect :as rect] [uxbox.main.ui.workspace.sidebar.options.circle :as circle] + [uxbox.main.ui.workspace.sidebar.options.path :as path] + [uxbox.main.ui.workspace.sidebar.options.image :as image] [uxbox.main.ui.workspace.sidebar.options.page :as page] - - ;; [uxbox.main.ui.workspace.sidebar.options.circle-measures :as options-circlem] - ;; [uxbox.main.ui.workspace.sidebar.options.fill :as options-fill] - ;; [uxbox.main.ui.workspace.sidebar.options.icon-measures :as options-iconm] - ;; [uxbox.main.ui.workspace.sidebar.options.image-measures :as options-imagem] - ;; [uxbox.main.ui.workspace.sidebar.options.interactions :as options-interactions] - ;; [uxbox.main.ui.workspace.sidebar.options.rect-measures :as options-rectm] - ;; [uxbox.main.ui.workspace.sidebar.options.stroke :as options-stroke] - ;; [uxbox.main.ui.workspace.sidebar.options.text :as options-text] - [uxbox.util.data :as data] - [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]])) ;; --- Constants @@ -44,43 +31,6 @@ ;; :image [::image-measures] ;; ::page [::page-measures ::page-grid-options]}) -;; (def ^:private +menus+ -;; [{:name "element.measures" -;; :id ::icon-measures -;; :icon i/infocard -;; :comp options-iconm/icon-measures-menu} -;; {:name "element.measures" -;; :id ::image-measures -;; :icon i/infocard -;; :comp options-imagem/image-measures-menu} -;; {:name "element.measures" -;; :id ::rect-measures -;; :icon i/infocard -;; :comp options-rectm/rect-measures-menu} -;; {:name "element.measures" -;; :id ::circle-measures -;; :icon i/infocard -;; :comp options-circlem/circle-measures-menu} -;; {:name "element.fill" -;; :id ::fill -;; :icon i/fill -;; :comp options-fill/fill-menu} -;; {:name "element.stroke" -;; :id ::stroke -;; :icon i/stroke -;; :comp options-stroke/stroke-menu} -;; {:name "element.text" -;; :id ::text -;; :icon i/text -;; :comp options-text/text-menu} -;; {:name "element.interactions" -;; :id ::interactions -;; :icon i/action -;; :comp options-interactions/interactions-menu}]) - -;; (def ^:private +menus-by-id+ -;; (data/index-by-id +menus+)) - ;; --- Options (mf/defc shape-options @@ -93,6 +43,9 @@ (case (:type shape) :rect [:& rect/options {:shape shape}] :circle [:& circle/options {:shape shape}] + :path [:& path/options {:shape shape}] + :curve [:& path/options {:shape shape}] + :image [:& image/options {:shape shape}] nil)])) (mf/defc options-toolbox @@ -102,9 +55,9 @@ selected (mf/deref refs/selected-shapes)] [:div.elementa-options.tool-window ;; [:div.tool-window-bar - ;; [:div.tool-window-icon i/options] - ;; [:span (tr "ds.settings.element-options")] - ;; [:div.tool-window-close {:on-click close} i/close]] + ;; [:div.tool-window-icon i/options] + ;; [:span (tr "ds.settings.element-options")] + ;; [:div.tool-window-close {:on-click close} i/close]] [:div.tool-window-content [:div.element-options (if (= (count selected) 1) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs index 488175526..782eef4f2 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs @@ -8,126 +8,125 @@ (ns uxbox.main.ui.workspace.sidebar.options.circle (:require [rumext.alpha :as mf] + [uxbox.common.data :as d] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw] [uxbox.main.geom :as geom] [uxbox.main.store :as st] [uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]] [uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]] - [uxbox.util.data :refer (parse-int parse-float read-string)] [uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.math :refer (precision-or-0)])) + [uxbox.util.i18n :refer [tr]] + [uxbox.util.math :as math :refer (precision-or-0)])) -(mf/defc size-options +(mf/defc measures-menu [{:keys [shape] :as props}] - [:* - [:span (tr "ds.size")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text {:placeholder (tr "ds.width") - :type "number" - :min "0" - ;; :on-change #(on-size-change % shape :rx) - :value (precision-or-0 (:rx shape 0) 2)}]] - [:div.lock-size {:class (when (:proportion-lock shape) "selected") - ;; :on-click #(on-proportion-lock-change % shape) - } - (if (:proportion-lock shape) i/lock i/unlock)] + (let [on-size-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-dimensions (:id shape) {attr value})))) - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.height") - :type "number" - :min "0" - ;; :on-change #(on-size-change % shape :ry) - :value (precision-or-0 (:ry shape 0) 2)}]]]]) + on-proportion-lock-change + (fn [event] + (st/emit! (udw/toggle-shape-proportion-lock (:id shape)))) -(mf/defc position-options - [{:keys [shape] :as props}] - [:* - [:span (tr "ds.position")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "cx" - :type "number" - ;; :on-change #(on-position-change % shape :x) - :value (precision-or-0 (:cx shape 0) 2)}]] - [:div.input-element.pixels - [:input.input-text - {:placeholder "cy" - :type "number" - ;; :on-change #(on-position-change % shape :y) - :value (precision-or-0 (:cy shape 0) 2)}]]]]) + on-size-rx-change #(on-size-change % :rx) + on-size-ry-change #(on-size-change % :ry) -(mf/defc rotation-options - [{:keys [shape] :as props}] - [:* - [:span (tr "ds.rotation")] - [:div.row-flex - [:input.slidebar - {:type "range" - :min 0 - :max 360 - ;; :on-change #(on-rotation-change % shape) - :value (:rotation shape 0)}]] + on-position-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer)) + point (gpt/point {attr value})] + (st/emit! (udw/update-position (:id shape) point)))) - [:div.row-flex - [:div.input-element.degrees - [:input.input-text - {:placeholder "" - :type "number" - :min 0 - :max 360 - ;; :on-change #(on-rotation-change % shape) - :value (precision-or-0 (:rotation shape 0) 2)}]] - [:input.input-text - {:style {:visibility "hidden"}}]]]) + on-pos-cx-change #(on-position-change % :x) + on-pos-cy-change #(on-position-change % :y) -(mf/defc measures-options - [{:keys [shape] :as props}] - [:div.element-set - [:div.element-set-title (tr "element.measures")] - [:div.element-set-content - [:& size-options {:shape shape}] - [:& position-options {:shape shape}] - [:& rotation-options {:shape shape}]]]) + on-rotation-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-shape (:id shape) {:rotation value})))) -;; (defn- on-size-change -;; [event shape attr] -;; (let [value (dom/event->value event) -;; value (parse-int value 0) -;; sid (:id shape) -;; props {attr value}] -;; (st/emit! (udw/update-dimensions sid props)))) + on-radius-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 0))] + (st/emit! (udw/update-shape (:id shape) {:rx value :ry value}))))] -;; (defn- on-rotation-change -;; [event shape] -;; (let [value (dom/event->value event) -;; value (parse-int value 0) -;; sid (:id shape)] -;; (st/emit! (udw/update-shape-attrs sid {:rotation value})))) + [:div.element-set + [:div.element-set-title (tr "workspace.options.measures")] + [:div.element-set-content -;; (defn- on-position-change -;; [event shape attr] -;; (let [value (dom/event->value event) -;; value (parse-int value nil) -;; sid (:id shape) -;; point (gpt/point {attr value})] -;; (st/emit! (udw/update-position sid point)))) + ;; SIZE + [:span (tr "workspace.options.size")] + [:div.row-flex + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :on-change on-size-rx-change + :value (-> (:rx shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + [:div.lock-size {:class (when (:proportion-lock shape) "selected") + :on-click on-proportion-lock-change} + (if (:proportion-lock shape) + i/lock + i/unlock)] -;; (defn- on-proportion-lock-change -;; [event shape] -;; (if (:proportion-lock shape) -;; (st/emit! (udw/unlock-proportions (:id shape))) -;; (st/emit! (udw/lock-proportions (:id shape))))) + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :on-change on-size-ry-change + :value (-> (:ry shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + ;; POSITION + [:span (tr "workspace.options.position")] + [:div.row-flex + [:div.input-element.pixels + [:input.input-text {:type "number" + :on-change on-pos-cx-change + :value (-> (:cx shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + [:div.input-element.pixels + [:input.input-text {:type "number" + :on-change on-pos-cy-change + :value (-> (:cy shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + ;; ROTATION & RADIUS + [:span (tr "workspace.options.rotation-radius")] + [:div.row-flex + [:div.input-element.degrees + [:input.input-text {:placeholder "" + :type "number" + :min 0 + :max 360 + :on-change on-rotation-change + :value (-> (:rotation shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + + [:div.input-element.pixels + [:input.input-text {:type "number" + :on-change on-radius-change + :value (-> (:rx shape) + (math/precision 2) + (d/coalesce-str "0"))}]]]]])) (mf/defc options [{:keys [shape] :as props}] [:div - [:& measures-options {:shape shape}] + [:& measures-menu {:shape shape}] [:& fill-menu {:shape shape}] [:& stroke-menu {:shape shape}]]) 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 deleted file mode 100644 index cc95ccf8c..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs +++ /dev/null @@ -1,117 +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-2016 Juan de la Cruz -;; Copyright (c) 2015-2019 Andrey Antukh - -(ns uxbox.main.ui.workspace.sidebar.options.circle-measures - (:require - [rumext.alpha :as mf] - [uxbox.builtins.icons :as i] - [uxbox.main.data.workspace :as udw] - [uxbox.main.geom :as geom] - [uxbox.main.store :as st] - [uxbox.util.data :refer (parse-int parse-float read-string)] - [uxbox.util.dom :as dom] - [uxbox.util.geom.point :as gpt] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.math :refer (precision-or-0)])) - -(declare on-size-change) -(declare on-rotation-change) -(declare on-position-change) -(declare on-proportion-lock-change) - -(mf/defc circle-measures-menu - [{:keys [menu shape] :as props}] - [:div.element-set {:key (str (:id menu))} - [:div.element-set-title (:name menu)] - [:div.element-set-content - ;; SLIDEBAR FOR ROTATION AND OPACITY - [:span (tr "ds.size")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.width") - :type "number" - :min "0" - :value (precision-or-0 (:rx shape 0) 2) - :on-change #(on-size-change % shape :rx)}]] - [:div.lock-size {:class (when (:proportion-lock shape) "selected") - :on-click #(on-proportion-lock-change % shape)} - (if (:proportion-lock shape) i/lock i/unlock)] - - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.height") - :type "number" - :min "0" - :value (precision-or-0 (:ry shape 0) 2) - :on-change #(on-size-change % shape :ry)}]]] - - [:span (tr "ds.position")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "cx" - :type "number" - :value (precision-or-0 (:cx shape 0) 2) - :on-change #(on-position-change % shape :x)}]] - [:div.input-element.pixels - [:input.input-text - {:placeholder "cy" - :type "number" - :value (precision-or-0 (:cy shape 0) 2) - :on-change #(on-position-change % shape :y)}]]] - - [:span (tr "ds.rotation")] - [:div.row-flex - [:input.slidebar - {:type "range" - :min 0 - :max 360 - :value (:rotation shape 0) - :on-change #(on-rotation-change % shape)}]] - - [:div.row-flex - [:div.input-element.degrees - [:input.input-text - {:placeholder "" - :type "number" - :min 0 - :max 360 - :value (precision-or-0 (:rotation shape 0) 2) - :on-change #(on-rotation-change % shape)}]] - [:input.input-text - {:style {:visibility "hidden"}}]]]]) - -(defn- on-size-change - [event shape attr] - (let [value (dom/event->value event) - value (parse-int value 0) - sid (:id shape) - props {attr value}] - (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! (udw/update-shape-attrs sid {:rotation value})))) - -(defn- on-position-change - [event shape attr] - (let [value (dom/event->value event) - value (parse-int value nil) - sid (:id shape) - point (gpt/point {attr value})] - (st/emit! (udw/update-position sid point)))) - -(defn- on-proportion-lock-change - [event shape] - (if (:proportion-lock 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/fill.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs index f9a34f270..c6de18aac 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs @@ -9,31 +9,33 @@ (:require [rumext.alpha :as mf] [uxbox.builtins.icons :as i] + [uxbox.common.data :as d] [uxbox.main.data.workspace :as udw] [uxbox.main.store :as st] [uxbox.main.ui.modal :as modal] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]] - [uxbox.util.data :refer [parse-float]] [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]])) (mf/defc fill-menu [{:keys [shape] :as props}] - (letfn [(change-attrs [attrs] - (st/emit! (udw/update-shape-attrs (:id shape) attrs))) + (letfn [(update-shape! [attr value] + (st/emit! (udw/update-shape (:id shape) {attr value}))) (on-color-change [event] - (let [value (dom/event->value event)] - (change-attrs {:fill-color value}))) + (let [value (-> (dom/get-target event) + (dom/get-value))] + (update-shape! :fill-color value))) (on-opacity-change [event] - (let [value (dom/event->value event) - value (parse-float value 1) - value (/ value 10000)] - (change-attrs {:fill-opacity value}))) + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 1) + (/ 10000))] + (update-shape! :fill-opacity value))) (show-color-picker [event] (let [x (.-clientX event) y (.-clientY event) props {:x x :y y - :on-change #(change-attrs {:fill-color %}) + :on-change #(update-shape! :fill-color %) :default "#ffffff" :value (:fill-color shape) :transparent? true}] @@ -42,10 +44,10 @@ [:div.element-set-title (tr "element.fill")] [:div.element-set-content - [:span (tr "ds.color")] + [:span (tr "workspace.options.color")] [:div.row-flex.color-data [:span.color-th - {:style {:background-color (:fill-color shape)} + {:style {:background-color (:fill-color shape "#000000")} :on-click show-color-picker}] [:div.color-info [:input @@ -53,7 +55,7 @@ :value (:fill-color shape "")}]]] ;; SLIDEBAR FOR ROTATION AND OPACITY - [:span (tr "ds.opacity")] + [:span (tr "workspace.options.opacity")] [:div.row-flex [:input.slidebar {:type "range" 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 eb17169b1..881a63af0 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 @@ -95,7 +95,7 @@ [event shape] (let [value (dom/event->value event) value (parse-int value 0)] - (st/emit! (udw/update-shape-attrs (:id shape) {:rotation value})))) + #_(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value})))) (defn- on-position-change [event shape attr] @@ -107,7 +107,7 @@ (defn- on-proportion-lock-change [event shape] - (if (:proportion-lock shape) + #_(if (:proportion-lock 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 deleted file mode 100644 index 4f75f4bc6..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs +++ /dev/null @@ -1,134 +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 -;; Copyright (c) 2015-2017 Juan de la Cruz - -(ns uxbox.main.ui.workspace.sidebar.options.image-measures - (:require - [rumext.alpha :as mf] - [uxbox.builtins.icons :as i] - [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)] - [uxbox.util.dom :as dom] - [uxbox.util.geom.point :as gpt] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.math :refer (precision-or-0)])) - -(declare on-size-change) -(declare on-rotation-change) -(declare on-opacity-change) -(declare on-position-change) -(declare on-proportion-lock-change) - -(mf/defc image-measures-menu - [{:keys [menu shape] :as props}] - (let [size (geom/size shape)] - [:div.element-set - [:div.element-set-title (:name menu)] - [:div.element-set-content - ;; SLIDEBAR FOR ROTATION AND OPACITY - [:span (tr "ds.size")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.width") - :type "number" - :min "0" - :value (precision-or-0 (:width size) 2) - :on-change #(on-size-change % shape :width)}]] - [:div.lock-size - {:class (when (:proportion-lock shape) "selected") - :on-click #(on-proportion-lock-change % shape)} - (if (:proportion-lock shape) i/lock i/unlock)] - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.height") - :type "number" - :min "0" - :value (precision-or-0 (:height size) 2) - :on-change #(on-size-change % shape :height)}]]] - - [:span (tr "ds.position")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "X" - :type "number" - :value (precision-or-0 (:x1 shape 0) 2) - :on-change #(on-position-change % shape :x)}]] - [:div.input-element.pixels - [:input.input-text - {:placeholder "Y" - :type "number" - :value (precision-or-0 (:y1 shape 0) 2) - :on-change #(on-position-change % shape :y)}]]] - - ;; [:span (tr "ds.rotation")] - ;; [:div.row-flex - ;; [:input.slidebar - ;; {:type "range" - ;; :min 0 - ;; :max 360 - ;; :value (:rotation shape 0) - ;; :on-change on-rotation-change}]] - - ;; [:div.row-flex - ;; [:div.input-element.degrees - ;; [:input.input-text - ;; {:placeholder "" - ;; :type "number" - ;; :min 0 - ;; :max 360 - ;; :value (precision-or-0 (:rotation shape 0) 2) - ;; :on-change on-rotation-change - ;; }]] - ;; [:input.input-text - ;; {:style {:visibility "hidden"}}]] - - - [:span (tr "ds.opacity")] - [:div.row-flex - [:input.slidebar - {:type "range" - :min "0" - :max "10000" - :value (* 10000 (:opacity shape 1)) - :step "1" - :on-change #(on-opacity-change % shape)}]]]])) - -(defn- on-size-change - [event shape attr] - (let [value (dom/event->value event) - value (parse-int value 0) - props {attr value}] - (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! (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! (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! (dw/update-position (:id shape) point)))) - -(defn- on-proportion-lock-change - [event shape] - (if (:proportion-lock 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 3ff8dc675..8df360ec0 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs @@ -22,452 +22,452 @@ ;; --- Helpers -(defn- on-change - ([form attr event] - (dom/prevent-default event) - (let [value (dom/event->value event) - value (read-string value)] - (swap! form assoc attr value))) - ([form attr keep event] - (let [data (select-keys @form keep)] - (reset! form data) - (on-change form attr event)))) +;; (defn- on-change +;; ([form attr event] +;; (dom/prevent-default event) +;; (let [value (dom/event->value event) +;; value (read-string value)] +;; (swap! form assoc attr value))) +;; ([form attr keep event] +;; (let [data (select-keys @form keep)] +;; (reset! form data) +;; (on-change form attr event)))) -;; --- Interactions List +;; ;; --- Interactions List -(defn- translate-trigger-name - [trigger] - (case trigger - :click "Click" - :doubleclick "Double Click" - :rightclick "Right Click" - :hover "Hover" - :mousein "Mouse In" - :mouseout "Mouse Out" - ;; :swiperight "Swipe Right" - ;; :swipeleft "Swipe Left" - ;; :swipedown "Swipe Down" - ;; :touchandhold "Touch and Hold" - ;; :holdrelease "Hold release" - (pr-str trigger))) +;; (defn- translate-trigger-name +;; [trigger] +;; (case trigger +;; :click "Click" +;; :doubleclick "Double Click" +;; :rightclick "Right Click" +;; :hover "Hover" +;; :mousein "Mouse In" +;; :mouseout "Mouse Out" +;; ;; :swiperight "Swipe Right" +;; ;; :swipeleft "Swipe Left" +;; ;; :swipedown "Swipe Down" +;; ;; :touchandhold "Touch and Hold" +;; ;; :holdrelease "Hold release" +;; (pr-str trigger))) -(mf/defc interactions-list - [{:keys [shape form] :as props}] - (letfn [(on-edit [item event] - (dom/prevent-default event) - (reset! form item)) - (delete [item] - (let [sid (:id shape) - id (:id item)] - (st/emit! (dw/delete-interaction sid id)))) - (on-delete [item event] - (dom/prevent-default event) - (let [delete (partial delete item)] - (udl/open! :confirm {:on-accept delete})))] - [:ul.element-list - (for [item (vals (:interactions shape)) - :let [key (pr-str (:id item))]] - [:li {:key key} - [:div.list-icon i/action] - [:span (translate-trigger-name (:trigger item))] - [:div.list-actions - [:a {:on-click (partial on-edit item)} i/pencil] - [:a {:on-click (partial on-delete item)} i/trash]]])])) +;; (mf/defc interactions-list +;; [{:keys [shape form] :as props}] +;; (letfn [(on-edit [item event] +;; (dom/prevent-default event) +;; (reset! form item)) +;; (delete [item] +;; (let [sid (:id shape) +;; id (:id item)] +;; (st/emit! (dw/delete-interaction sid id)))) +;; (on-delete [item event] +;; (dom/prevent-default event) +;; (let [delete (partial delete item)] +;; (udl/open! :confirm {:on-accept delete})))] +;; [:ul.element-list +;; (for [item (vals (:interactions shape)) +;; :let [key (pr-str (:id item))]] +;; [:li {:key key} +;; [:div.list-icon i/action] +;; [:span (translate-trigger-name (:trigger item))] +;; [:div.list-actions +;; [:a {:on-click (partial on-edit item)} i/pencil] +;; [:a {:on-click (partial on-delete item)} i/trash]]])])) -;; --- Trigger Input +;; ;; --- Trigger Input -(mf/defc trigger-input - [{:keys [form] :as props}] - ;; (mf/use-effect - ;; {:init #(when-not (:trigger @form) (swap! form assoc :trigger :click)) - ;; :deps true}) - [:div - [:span "Trigger"] - [:div.row-flex - [:select.input-select {:placeholder "Choose a trigger" - :on-change (partial on-change form :trigger) - :value (pr-str (:trigger @form))} - [:option {:value ":click"} "Click"] - [:option {:value ":doubleclick"} "Double-click"] - [:option {:value ":rightclick"} "Right-click"] - [:option {:value ":hover"} "Hover"] - [:option {:value ":mousein"} "Mouse in"] - [:option {:value ":mouseout"} "Mouse out"] - #_[:option {:value ":swiperight"} "Swipe right"] - #_[:option {:value ":swipeleft"} "Swipe left"] - #_[:option {:value ":swipedown"} "Swipe dpwn"] - #_[:option {:value ":touchandhold"} "Touch and hold"] - #_[:option {:value ":holdrelease"} "Hold release"] - #_[:option {:value ":keypress"} "Key press"] - #_[:option {:value ":pageisloaded"} "Page is loaded"] - #_[:option {:value ":windowscroll"} "Window is scrolled to"]]]]) +;; (mf/defc trigger-input +;; [{:keys [form] :as props}] +;; ;; (mf/use-effect +;; ;; {:init #(when-not (:trigger @form) (swap! form assoc :trigger :click)) +;; ;; :deps true}) +;; [:div +;; [:span "Trigger"] +;; [:div.row-flex +;; [:select.input-select {:placeholder "Choose a trigger" +;; :on-change (partial on-change form :trigger) +;; :value (pr-str (:trigger @form))} +;; [:option {:value ":click"} "Click"] +;; [:option {:value ":doubleclick"} "Double-click"] +;; [:option {:value ":rightclick"} "Right-click"] +;; [:option {:value ":hover"} "Hover"] +;; [:option {:value ":mousein"} "Mouse in"] +;; [:option {:value ":mouseout"} "Mouse out"] +;; #_[:option {:value ":swiperight"} "Swipe right"] +;; #_[:option {:value ":swipeleft"} "Swipe left"] +;; #_[:option {:value ":swipedown"} "Swipe dpwn"] +;; #_[:option {:value ":touchandhold"} "Touch and hold"] +;; #_[:option {:value ":holdrelease"} "Hold release"] +;; #_[:option {:value ":keypress"} "Key press"] +;; #_[:option {:value ":pageisloaded"} "Page is loaded"] +;; #_[:option {:value ":windowscroll"} "Window is scrolled to"]]]]) -;; --- URL Input +;; ;; --- URL Input -(mf/defc url-input - [form] - [:div - [:span "Url"] - [:div.row-flex - [:input.input-text - {:placeholder "http://" - :on-change (partial on-change form :url) - :value (:url @form "") - :type "url"}]]]) - -;; --- Elements Input - -(defn- collect-shapes - [state page] - (let [shapes-by-id (:shapes state) - shapes (get-in state [:pages page :shapes])] - (letfn [(resolve-shape [acc id] - (let [shape (get shapes-by-id id)] - (if (= (:type shape) :group) - (reduce resolve-shape (conj acc shape) (:items shape)) - (conj acc shape))))] - (reduce resolve-shape [] shapes)))) - -(mf/defc elements-input - [{:keys [page-id form] :as props}] - (let [shapes (collect-shapes @st/state page-id)] - [:div - [:span "Element"] - [:div.row-flex - [:select.input-select - {:placeholder "Choose an element" - :on-change (partial on-change form :element) - :value (pr-str (:element @form))} - [:option {:value "nil"} "---"] - (for [shape shapes - :let [key (pr-str (:id shape))]] - [:option {:key key :value key} (:name shape)])]]])) - -;; --- Page Input - -(mf/defc pages-input - [form-ref path] - ;; FIXME: react on ref - #_(let [pages (mx/react refs/selected-project-pages)] - (when (and (not (:page @form-ref)) - (pos? (count pages))) - (swap! form-ref assoc :page (:id (first pages)))) - [:div - [:span "Page"] - [:div.row-flex - [:select.input-select {:placeholder "Choose a page" - :on-change (partial on-change form-ref :page) - :value (pr-str (:page @form-ref))} - (for [page pages - :let [key (pr-str (:id page))]] - [:option {:key key :value key} (:name page)])]]])) - -;; --- Animation - -(mf/defc animation-input - [{:keys [form] :as props}] - (when-not (:action @form) - (swap! form assoc :animation :none)) - [:div - [:span "Animation"] - [:div.row-flex - [:select.input-select - {:placeholder "Animation" - :on-change (partial on-change form :animation) - :value (pr-str (:animation @form))} - [:option {:value ":none"} "None"] - [:option {:value ":fade"} "Fade"] - [:option {:value ":slide"} "Slide"]]]]) - -;; --- MoveTo Input - -(mf/defc moveto-input - [{:keys [form] :as props}] - (when-not (:moveto-x @form) - (swap! form assoc :moveto-x 0)) - (when-not (:moveto-y @form) - (swap! form assoc :moveto-y 0)) - [:div - [:span "Move to position"] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "X" - :on-change (partial on-change form :moveto-x) - :type "number" - :value (:moveto-x @form "")}]] - [:div.input-element.pixels - [:input.input-text - {:placeholder "Y" - :on-change (partial on-change form :moveto-y) - :type "number" - :value (:moveto-y @form "")}]]]]) - -;; --- MoveBy Input - -(mf/defc moveby-input - [{:keys [form] :as props}] - (when-not (:moveby-x @form) - (swap! form assoc :moveby-x 0)) - (when-not (:moveby-y @form) - (swap! form assoc :moveby-y 0)) - [:div - [:span "Move to position"] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "X" - :on-change (partial on-change form :moveby-x) - :type "number" - :value (:moveby-x @form "")}]] - [:div.input-element.pixels - [:input.input-text - {:placeholder "Y" - :on-change (partial on-change form :moveby-y) - :type "number" - :value (:moveby-y @form "")}]]]]) - -;; --- Opacity Input - -(mf/defc opacity-input - [{:keys [form] :as props}] - (when-not (:opacity @form) - (swap! form assoc :opacity 100)) - [:div - [:span "Opacity"] - [:div.row-flex - [:div.input-element.percentail - [:input.input-text - {:placeholder "%" - :on-change (partial on-change form :opacity) - :min "0" - :max "100" - :type "number" - :value (:opacity @form "")}]]]]) - -;; --- Rotate Input - -;; (mx/defc rotate-input +;; (mf/defc url-input ;; [form] ;; [:div -;; [:span "Rotate (dg)"] +;; [:span "Url"] ;; [:div.row-flex -;; [:div.input-element.degrees +;; [:input.input-text +;; {:placeholder "http://" +;; :on-change (partial on-change form :url) +;; :value (:url @form "") +;; :type "url"}]]]) + +;; ;; --- Elements Input + +;; (defn- collect-shapes +;; [state page] +;; (let [shapes-by-id (:shapes state) +;; shapes (get-in state [:pages page :shapes])] +;; (letfn [(resolve-shape [acc id] +;; (let [shape (get shapes-by-id id)] +;; (if (= (:type shape) :group) +;; (reduce resolve-shape (conj acc shape) (:items shape)) +;; (conj acc shape))))] +;; (reduce resolve-shape [] shapes)))) + +;; (mf/defc elements-input +;; [{:keys [page-id form] :as props}] +;; (let [shapes (collect-shapes @st/state page-id)] +;; [:div +;; [:span "Element"] +;; [:div.row-flex +;; [:select.input-select +;; {:placeholder "Choose an element" +;; :on-change (partial on-change form :element) +;; :value (pr-str (:element @form))} +;; [:option {:value "nil"} "---"] +;; (for [shape shapes +;; :let [key (pr-str (:id shape))]] +;; [:option {:key key :value key} (:name shape)])]]])) + +;; ;; --- Page Input + +;; (mf/defc pages-input +;; [form-ref path] +;; ;; FIXME: react on ref +;; #_(let [pages (mx/react refs/selected-project-pages)] +;; (when (and (not (:page @form-ref)) +;; (pos? (count pages))) +;; (swap! form-ref assoc :page (:id (first pages)))) +;; [:div +;; [:span "Page"] +;; [:div.row-flex +;; [:select.input-select {:placeholder "Choose a page" +;; :on-change (partial on-change form-ref :page) +;; :value (pr-str (:page @form-ref))} +;; (for [page pages +;; :let [key (pr-str (:id page))]] +;; [:option {:key key :value key} (:name page)])]]])) + +;; ;; --- Animation + +;; (mf/defc animation-input +;; [{:keys [form] :as props}] +;; (when-not (:action @form) +;; (swap! form assoc :animation :none)) +;; [:div +;; [:span "Animation"] +;; [:div.row-flex +;; [:select.input-select +;; {:placeholder "Animation" +;; :on-change (partial on-change form :animation) +;; :value (pr-str (:animation @form))} +;; [:option {:value ":none"} "None"] +;; [:option {:value ":fade"} "Fade"] +;; [:option {:value ":slide"} "Slide"]]]]) + +;; ;; --- MoveTo Input + +;; (mf/defc moveto-input +;; [{:keys [form] :as props}] +;; (when-not (:moveto-x @form) +;; (swap! form assoc :moveto-x 0)) +;; (when-not (:moveto-y @form) +;; (swap! form assoc :moveto-y 0)) +;; [:div +;; [:span "Move to position"] +;; [:div.row-flex +;; [:div.input-element.pixels ;; [:input.input-text -;; {:placeholder "dg" -;; :on-change (partial on-change form :rotation) +;; {:placeholder "X" +;; :on-change (partial on-change form :moveto-x) ;; :type "number" -;; :value (:rotation @form "")}]]]]) +;; :value (:moveto-x @form "")}]] +;; [:div.input-element.pixels +;; [:input.input-text +;; {:placeholder "Y" +;; :on-change (partial on-change form :moveto-y) +;; :type "number" +;; :value (:moveto-y @form "")}]]]]) -;; --- Resize Input +;; ;; --- MoveBy Input -(mf/defc resize-input - [{:keys [form] :as props}] - [:div - [:span "Resize"] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "Width" - :on-change (partial on-change form :resize-width) - :type "number" - :value (:resize-width @form "")}]] - [:div.input-element.pixels - [:input.input-text - {:placeholder "Height" - :on-change (partial on-change form :resize-height) - :type "number" - :value (:resize-height @form "")}]]]]) +;; (mf/defc moveby-input +;; [{:keys [form] :as props}] +;; (when-not (:moveby-x @form) +;; (swap! form assoc :moveby-x 0)) +;; (when-not (:moveby-y @form) +;; (swap! form assoc :moveby-y 0)) +;; [:div +;; [:span "Move to position"] +;; [:div.row-flex +;; [:div.input-element.pixels +;; [:input.input-text +;; {:placeholder "X" +;; :on-change (partial on-change form :moveby-x) +;; :type "number" +;; :value (:moveby-x @form "")}]] +;; [:div.input-element.pixels +;; [:input.input-text +;; {:placeholder "Y" +;; :on-change (partial on-change form :moveby-y) +;; :type "number" +;; :value (:moveby-y @form "")}]]]]) -;; --- Color Input +;; ;; --- Opacity Input -(mf/defc colorpicker - [{:keys [x y on-change value]}] - (let [left (- x 260) - top (- y 50)] - [:div.colorpicker-tooltip - {:style {:left (str left "px") - :top (str top "px")}} +;; (mf/defc opacity-input +;; [{:keys [form] :as props}] +;; (when-not (:opacity @form) +;; (swap! form assoc :opacity 100)) +;; [:div +;; [:span "Opacity"] +;; [:div.row-flex +;; [:div.input-element.percentail +;; [:input.input-text +;; {:placeholder "%" +;; :on-change (partial on-change form :opacity) +;; :min "0" +;; :max "100" +;; :type "number" +;; :value (:opacity @form "")}]]]]) - (cp/colorpicker - :theme :small - :value value - :on-change on-change)])) +;; ;; --- Rotate Input -(defmethod lbx/render-lightbox :interactions/colorpicker - [params] - (colorpicker params)) +;; ;; (mx/defc rotate-input +;; ;; [form] +;; ;; [:div +;; ;; [:span "Rotate (dg)"] +;; ;; [:div.row-flex +;; ;; [:div.input-element.degrees +;; ;; [:input.input-text +;; ;; {:placeholder "dg" +;; ;; :on-change (partial on-change form :rotation) +;; ;; :type "number" +;; ;; :value (:rotation @form "")}]]]]) -(mf/defc color-input - [{:keys [form] :as props}] - (when-not (:fill-color @form) - (swap! form assoc :fill-color "#000000")) - (when-not (:stroke-color @form) - (swap! form assoc :stroke-color "#000000")) - (letfn [(on-change [attr color] - (swap! form assoc attr color)) - (on-change-fill-color [event] - (let [value (dom/event->value event)] - (when (color? value) - (on-change :fill-color value)))) - (on-change-stroke-color [event] - (let [value (dom/event->value event)] - (when (color? value) - (on-change :stroke-color value)))) - (show-picker [attr event] - (let [x (.-clientX event) - y (.-clientY event) - opts {:x x :y y - :on-change (partial on-change attr) - :value (get @form attr) - :transparent? true}] - (udl/open! :interactions/colorpicker opts)))] - (let [stroke-color (:stroke-color @form) - fill-color (:fill-color @form)] - [:div - [:div.row-flex - [:div.column-half - [:span "Fill"] - [:div.color-data - [:span.color-th - {:style {:background-color fill-color} - :on-click (partial show-picker :fill-color)}] - [:div.color-info - [:input - {:on-change on-change-fill-color - :value fill-color}]]]] - [:div.column-half - [:span "Stroke"] - [:div.color-data - [:span.color-th - {:style {:background-color stroke-color} - :on-click (partial show-picker :stroke-color)}] - [:div.color-info - [:input - {:on-change on-change-stroke-color - :value stroke-color}]]]]]]))) +;; ;; --- Resize Input -;; --- Easing Input +;; (mf/defc resize-input +;; [{:keys [form] :as props}] +;; [:div +;; [:span "Resize"] +;; [:div.row-flex +;; [:div.input-element.pixels +;; [:input.input-text +;; {:placeholder "Width" +;; :on-change (partial on-change form :resize-width) +;; :type "number" +;; :value (:resize-width @form "")}]] +;; [:div.input-element.pixels +;; [:input.input-text +;; {:placeholder "Height" +;; :on-change (partial on-change form :resize-height) +;; :type "number" +;; :value (:resize-height @form "")}]]]]) -(mf/defc easing-input - [{:keys [form] :as props}] - (when-not (:easing @form) - (swap! form assoc :easing :linear)) - [:div - [:span "Easing"] - [:div.row-flex - [:select.input-select - {:placeholder "Easing" - :on-change (partial on-change form :easing) - :value (pr-str (:easing @form))} - [:option {:value ":linear"} "Linear"] - [:option {:value ":easein"} "Ease in"] - [:option {:value ":easeout"} "Ease out"] - [:option {:value ":easeinout"} "Ease in out"]]]]) +;; ;; --- Color Input -;; --- Duration Input +;; (mf/defc colorpicker +;; [{:keys [x y on-change value]}] +;; (let [left (- x 260) +;; top (- y 50)] +;; [:div.colorpicker-tooltip +;; {:style {:left (str left "px") +;; :top (str top "px")}} -(mf/defc duration-input - [{:keys [form] :as props}] - (when-not (:duration @form) - (swap! form assoc :duration 300)) - (when-not (:delay @form) - (swap! form assoc :delay 0)) - [:div - [:span "Duration | Delay"] - [:div.row-flex - [:div.input-element.miliseconds - [:input.input-text - {:placeholder "Duration" - :type "number" - :on-change (partial on-change form :duration) - :value (pr-str (:duration @form))}]] - [:div.input-element.miliseconds - [:input.input-text {:placeholder "Delay" - :type "number" - :on-change (partial on-change form :delay) - :value (pr-str (:delay @form))}]]]]) +;; (cp/colorpicker +;; :theme :small +;; :value value +;; :on-change on-change)])) -;; --- Action Input +;; (defmethod lbx/render-lightbox :interactions/colorpicker +;; [params] +;; (colorpicker params)) -(mf/defc action-input - [{:keys [shape form] :as props}] - ;; (when-not (:action @form) - ;; (swap! form assoc :action :show)) - (let [form-data (deref form) - simple? #{:gotourl :gotopage} - elements? (complement simple?) - animation? #{:show :hide :toggle} - only-easing? (complement animation?)] - [:div - [:span "Action"] - [:div.row-flex - [:select.input-select - {:placeholder "Choose an action" - :on-change (partial on-change form :action [:trigger]) - :value (pr-str (:action form-data))} - [:option {:value ":show"} "Show"] - [:option {:value ":hide"} "Hide"] - [:option {:value ":toggle"} "Toggle"] - ;; [:option {:value ":moveto"} "Move to"] - [:option {:value ":moveby"} "Move by"] - [:option {:value ":opacity"} "Opacity"] - [:option {:value ":size"} "Size"] - [:option {:value ":color"} "Color"] - ;; [:option {:value ":rotate"} "Rotate"] - [:option {:value ":gotopage"} "Go to page"] - [:option {:value ":gotourl"} "Go to URL"] - #_[:option {:value ":goback"} "Go back"] - [:option {:value ":scrolltoelement"} "Scroll to element"]]] +;; (mf/defc color-input +;; [{:keys [form] :as props}] +;; (when-not (:fill-color @form) +;; (swap! form assoc :fill-color "#000000")) +;; (when-not (:stroke-color @form) +;; (swap! form assoc :stroke-color "#000000")) +;; (letfn [(on-change [attr color] +;; (swap! form assoc attr color)) +;; (on-change-fill-color [event] +;; (let [value (dom/event->value event)] +;; (when (color? value) +;; (on-change :fill-color value)))) +;; (on-change-stroke-color [event] +;; (let [value (dom/event->value event)] +;; (when (color? value) +;; (on-change :stroke-color value)))) +;; (show-picker [attr event] +;; (let [x (.-clientX event) +;; y (.-clientY event) +;; opts {:x x :y y +;; :on-change (partial on-change attr) +;; :value (get @form attr) +;; :transparent? true}] +;; (udl/open! :interactions/colorpicker opts)))] +;; (let [stroke-color (:stroke-color @form) +;; fill-color (:fill-color @form)] +;; [:div +;; [:div.row-flex +;; [:div.column-half +;; [:span "Fill"] +;; [:div.color-data +;; [:span.color-th +;; {:style {:background-color fill-color} +;; :on-click (partial show-picker :fill-color)}] +;; [:div.color-info +;; [:input +;; {:on-change on-change-fill-color +;; :value fill-color}]]]] +;; [:div.column-half +;; [:span "Stroke"] +;; [:div.color-data +;; [:span.color-th +;; {:style {:background-color stroke-color} +;; :on-click (partial show-picker :stroke-color)}] +;; [:div.color-info +;; [:input +;; {:on-change on-change-stroke-color +;; :value stroke-color}]]]]]]))) - (case (:action form-data) - :gotourl [:& url-input {:form form}] - ;; :gotopage (pages-input form) - :color [:& color-input {:form form}] - ;; :rotate (rotate-input form) - :size [:& resize-input {:form form}] - :moveto [:& moveto-input {:form form}] - :moveby [:& moveby-input {:form form}] - :opacity [:& opacity-input {:form form}] - nil) +;; ;; --- Easing Input - (when (elements? (:action form-data)) - [:& elements-input {:page-id (:page shape) - :form form}]) +;; (mf/defc easing-input +;; [{:keys [form] :as props}] +;; (when-not (:easing @form) +;; (swap! form assoc :easing :linear)) +;; [:div +;; [:span "Easing"] +;; [:div.row-flex +;; [:select.input-select +;; {:placeholder "Easing" +;; :on-change (partial on-change form :easing) +;; :value (pr-str (:easing @form))} +;; [:option {:value ":linear"} "Linear"] +;; [:option {:value ":easein"} "Ease in"] +;; [:option {:value ":easeout"} "Ease out"] +;; [:option {:value ":easeinout"} "Ease in out"]]]]) - (when (and (animation? (:action form-data)) - (:element form-data)) - [:& animation-input {:form form}]) +;; ;; --- Duration Input - (when (or (not= (:animation form-data :none) :none) - (and (only-easing? (:action form-data)) - (:element form-data))) - [:* - [:& easing-input {:form form}] - [:& duration-input {:form form}]])])) +;; (mf/defc duration-input +;; [{:keys [form] :as props}] +;; (when-not (:duration @form) +;; (swap! form assoc :duration 300)) +;; (when-not (:delay @form) +;; (swap! form assoc :delay 0)) +;; [:div +;; [:span "Duration | Delay"] +;; [:div.row-flex +;; [:div.input-element.miliseconds +;; [:input.input-text +;; {:placeholder "Duration" +;; :type "number" +;; :on-change (partial on-change form :duration) +;; :value (pr-str (:duration @form))}]] +;; [:div.input-element.miliseconds +;; [:input.input-text {:placeholder "Delay" +;; :type "number" +;; :on-change (partial on-change form :delay) +;; :value (pr-str (:delay @form))}]]]]) + +;; ;; --- Action Input + +;; (mf/defc action-input +;; [{:keys [shape form] :as props}] +;; ;; (when-not (:action @form) +;; ;; (swap! form assoc :action :show)) +;; (let [form-data (deref form) +;; simple? #{:gotourl :gotopage} +;; elements? (complement simple?) +;; animation? #{:show :hide :toggle} +;; only-easing? (complement animation?)] +;; [:div +;; [:span "Action"] +;; [:div.row-flex +;; [:select.input-select +;; {:placeholder "Choose an action" +;; :on-change (partial on-change form :action [:trigger]) +;; :value (pr-str (:action form-data))} +;; [:option {:value ":show"} "Show"] +;; [:option {:value ":hide"} "Hide"] +;; [:option {:value ":toggle"} "Toggle"] +;; ;; [:option {:value ":moveto"} "Move to"] +;; [:option {:value ":moveby"} "Move by"] +;; [:option {:value ":opacity"} "Opacity"] +;; [:option {:value ":size"} "Size"] +;; [:option {:value ":color"} "Color"] +;; ;; [:option {:value ":rotate"} "Rotate"] +;; [:option {:value ":gotopage"} "Go to page"] +;; [:option {:value ":gotourl"} "Go to URL"] +;; #_[:option {:value ":goback"} "Go back"] +;; [:option {:value ":scrolltoelement"} "Scroll to element"]]] + +;; (case (:action form-data) +;; :gotourl [:& url-input {:form form}] +;; ;; :gotopage (pages-input form) +;; :color [:& color-input {:form form}] +;; ;; :rotate (rotate-input form) +;; :size [:& resize-input {:form form}] +;; :moveto [:& moveto-input {:form form}] +;; :moveby [:& moveby-input {:form form}] +;; :opacity [:& opacity-input {:form form}] +;; nil) + +;; (when (elements? (:action form-data)) +;; [:& elements-input {:page-id (:page shape) +;; :form form}]) + +;; (when (and (animation? (:action form-data)) +;; (:element form-data)) +;; [:& animation-input {:form form}]) + +;; (when (or (not= (:animation form-data :none) :none) +;; (and (only-easing? (:action form-data)) +;; (:element form-data))) +;; [:* +;; [:& easing-input {:form form}] +;; [:& duration-input {:form form}]])])) -;; --- Form +;; ;; --- Form -(mf/defc interactions-form - [{:keys [shape form] :as props}] - (letfn [(on-submit [event] - (dom/prevent-default event) - (let [sid (:id shape) - data (deref form)] - (st/emit! (dw/update-interaction sid data)) - (reset! form nil))) - (on-cancel [event] - (dom/prevent-default event) - (reset! form nil))] - [:form {:on-submit on-submit} - [:& trigger-input {:form form}] - [:& action-input {:shape shape :form form}] - [:div.row-flex - [:input.btn-primary.btn-small.save-btn - {:value "Save" :type "submit"}] - [:a.cancel-btn {:on-click on-cancel} - "Cancel"]]])) +;; (mf/defc interactions-form +;; [{:keys [shape form] :as props}] +;; (letfn [(on-submit [event] +;; (dom/prevent-default event) +;; (let [sid (:id shape) +;; data (deref form)] +;; (st/emit! (dw/update-interaction sid data)) +;; (reset! form nil))) +;; (on-cancel [event] +;; (dom/prevent-default event) +;; (reset! form nil))] +;; [:form {:on-submit on-submit} +;; [:& trigger-input {:form form}] +;; [:& action-input {:shape shape :form form}] +;; [:div.row-flex +;; [:input.btn-primary.btn-small.save-btn +;; {:value "Save" :type "submit"}] +;; [:a.cancel-btn {:on-click on-cancel} +;; "Cancel"]]])) ;; --- Interactions Menu @@ -477,7 +477,7 @@ (mf/defc interactions-menu [{:keys [menu shape] :as props}] - (let [form (mf/use-state nil) + #_(let [form (mf/use-state nil) interactions (:interactions shape)] [:div.element-set {:key (str (:id menu))} [:div.element-set-title (:name menu)] diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs index 2fec27af8..9f5b6bab1 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs @@ -46,9 +46,9 @@ (modal/show! colorpicker-modal props)))] [:div.element-set - [:div.element-set-title (tr "element.page-measures")] + [:div.element-set-title (tr "workspace.options.page-measures")] [:div.element-set-content - [:span (tr "ds.background-color")] + [:span (tr "workspace.options.background-color")] [:div.row-flex.color-data [:span.color-th {:style {:background-color (:background metadata "#ffffff")} @@ -92,7 +92,7 @@ [:div.element-set [:div.element-set-title (tr "element.page-grid-options")] [:div.element-set-content - [:span (tr "ds.size")] + [:span (tr "workspace.options.size")] [:div.row-flex [:div.input-element.pixels [:input.input-text @@ -106,7 +106,7 @@ :value (:grid-y-axis metadata) :on-change on-y-change :placeholder "y"}]]] - [:span (tr "ds.color")] + [:span (tr "workspace.options.color")] [:div.row-flex.color-data [:span.color-th {:style {:background-color (:grid-color metadata)} diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs index faeed1748..8b3c3e2b2 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs @@ -8,111 +8,130 @@ (ns uxbox.main.ui.workspace.sidebar.options.rect (:require [rumext.alpha :as mf] + [uxbox.common.data :as d] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw] [uxbox.main.geom :as geom] [uxbox.main.store :as st] - [uxbox.util.data :refer [parse-int parse-float read-string]] [uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]] [uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]] [uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt] [uxbox.util.i18n :refer [tr]] - [uxbox.util.math :refer [precision-or-0]])) + [uxbox.util.math :as math])) -(declare on-size-change) -(declare on-rotation-change) -(declare on-position-change) -(declare on-proportion-lock-change) +(mf/defc measures-menu + [{:keys [shape] :as props}] + (let [on-size-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-dimensions (:id shape) {attr value})))) + + on-proportion-lock-change + (fn [event] + (st/emit! (udw/toggle-shape-proportion-lock (:id shape)))) + + on-position-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer)) + point (gpt/point {attr value})] + (st/emit! (udw/update-position (:id shape) point)))) + + on-rotation-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-shape (:id shape) {:rotation value})))) + + on-radius-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 0))] + (st/emit! (udw/update-shape (:id shape) {:rx value :ry value})))) + + on-width-change #(on-size-change % :width) + on-height-change #(on-size-change % :height) + on-pos-x-change #(on-position-change % :x) + on-pos-y-change #(on-position-change % :y)] -(mf/defc measures - [{:keys [menu shape] :as props}] - (let [size (geom/size shape)] [:div.element-set - [:div.element-set-title (tr "element.measures")] + [:div.element-set-title (tr "workspace.options.measures")] [:div.element-set-content - [:span (tr "ds.size")] + [:span (tr "workspace.options.size")] ;; WIDTH & HEIGHT [:div.row-flex [:div.input-element.pixels - [:input.input-text {:placeholder (tr "ds.width") - :type "number" + [:input.input-text {:type "number" :min "0" - :value (precision-or-0 (:width size) 2)}]] + :on-change on-width-change + :value (-> (:width shape) + (math/precision 2) + (d/coalesce-str "0"))}]] [:div.lock-size {:class (when (:proportion-lock shape) "selected") - :on-click #(on-proportion-lock-change % shape)} - (if (:proportion-lock shape) i/lock i/unlock)] + :on-click on-proportion-lock-change} + (if (:proportion-lock shape) + i/lock + i/unlock)] [:div.input-element.pixels - [:input.input-text {:placeholder (tr "ds.height") - :type "number" + [:input.input-text {:type "number" :min "0" - :value (precision-or-0 (:height size) 2)}]]] + :on-change on-height-change + :value (-> (:height shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] ;; POSITION - [:span (tr "ds.position")] + [:span (tr "workspace.options.position")] [:div.row-flex [:div.input-element.pixels [:input.input-text {:placeholder "x" :type "number" - :value (precision-or-0 (:x1 shape 0) 2)}]] + :on-change on-pos-x-change + :value (-> (:x shape) + (math/precision 2) + (d/coalesce-str "0"))}]] [:div.input-element.pixels [:input.input-text {:placeholder "y" :type "number" - :value (precision-or-0 (:y1 shape 0) 2)}]]] - - ;; ROTATION - [:span (tr "ds.rotation")] - [:div.row-flex - [:input.slidebar {:type "range" - :min 0 - :max 360 - ;; :on-change #(on-rotation-change % shape) - :value (:rotation shape 0)}]] + :on-change on-pos-y-change + :value (-> (:y shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + [:span (tr "workspace.options.rotation-radius")] [:div.row-flex [:div.input-element.degrees [:input.input-text {:placeholder "" :type "number" :min 0 :max 360 - :on-change #(on-rotation-change % shape) - :value (precision-or-0 (:rotation shape "0") 2)}]] - [:input.input-text {:style {:visibility "hidden"}}]]]])) + :on-change on-rotation-change + :value (-> (:rotation shape 0) + (math/precision 2) + (d/coalesce-str "0"))}]] -;; (defn- on-size-change -;; [event shape attr] -;; (let [value (-> (dom/event->value event) -;; (parse-int 0))] -;; (st/emit! (udw/update-dimensions (:id shape) {attr value})))) + [:div.input-element.pixels + [:input.input-text + {:placeholder "rx" + :type "number" + :on-change on-radius-change + :value (-> (:rx shape) + (math/precision 2) + (d/coalesce-str "0"))}]]]]])) -;; (defn- on-rotation-change -;; [event shape] -;; (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! (udw/update-position (:id shape) point)))) - -;; (defn- on-proportion-lock-change -;; [event shape] -;; (if (:proportion-lock shape) -;; (st/emit! (udw/unlock-proportions (:id shape))) -;; (st/emit! (udw/lock-proportions (:id shape))))) - - - ;; :rect [::rect-measures ::fill ::stroke] (mf/defc options [{:keys [shape] :as props}] [:div - [:& measures {:shape shape}] + [:& measures-menu {:shape shape}] [:& fill-menu {:shape shape}] [:& stroke-menu {:shape shape}]]) 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 deleted file mode 100644 index d4867c085..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs +++ /dev/null @@ -1,108 +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 -;; Copyright (c) 2015-2017 Juan de la Cruz - -(ns uxbox.main.ui.workspace.sidebar.options.rect-measures - (:require - [rumext.alpha :as mf] - [uxbox.builtins.icons :as i] - [uxbox.main.data.workspace :as udw] - [uxbox.main.geom :as geom] - [uxbox.main.store :as st] - [uxbox.util.data :refer [parse-int parse-float read-string]] - [uxbox.util.dom :as dom] - [uxbox.util.geom.point :as gpt] - [uxbox.util.i18n :refer [tr]] - [uxbox.util.math :refer [precision-or-0]])) - -(declare on-size-change) -(declare on-rotation-change) -(declare on-position-change) -(declare on-proportion-lock-change) - -(mf/defc rect-measures-menu - [{:keys [menu shape] :as props}] - (let [size (geom/size shape)] - [:div.element-set - [:div.element-set-title (:name menu)] - [:div.element-set-content - ;; SLIDEBAR FOR ROTATION AND OPACITY - [:span (tr "ds.size")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text {:placeholder (tr "ds.width") - :type "number" - :min "0" - :value (precision-or-0 (:width size) 2) - :on-change #(on-size-change % shape :width)}]] - - [:div.lock-size {:class (when (:proportion-lock shape) "selected") - :on-click #(on-proportion-lock-change % shape)} - (if (:proportion-lock shape) i/lock i/unlock)] - - [:div.input-element.pixels - [:input.input-text {:placeholder (tr "ds.height") - :type "number" - :min "0" - :value (precision-or-0 (:height size) 2) - :on-change #(on-size-change % shape :height)}]]] - - [:span (tr "ds.position")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text {:placeholder "x" - :type "number" - :value (precision-or-0 (:x1 shape 0) 2) - :on-change #(on-position-change % shape :x)}]] - [:div.input-element.pixels - [:input.input-text {:placeholder "y" - :type "number" - :value (precision-or-0 (:y1 shape 0) 2) - :on-change #(on-position-change % shape :y)}]]] - - [:span (tr "ds.rotation")] - [:div.row-flex - [:input.slidebar {:type "range" - :min 0 - :max 360 - :value (:rotation shape 0) - :on-change #(on-rotation-change % shape)}]] - - [:div.row-flex - [:div.input-element.degrees - [:input.input-text {:placeholder "" - :type "number" - :min 0 - :max 360 - :value (precision-or-0 (:rotation shape "0") 2) - :on-change #(on-rotation-change % shape)}]] - [:input.input-text {:style {:visibility "hidden"}}]]]])) - - -(defn- on-size-change - [event shape attr] - (let [value (-> (dom/event->value event) - (parse-int 0))] - (st/emit! (udw/update-dimensions (:id shape) {attr value})))) - -(defn- on-rotation-change - [event shape] - (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! (udw/update-position (:id shape) point)))) - -(defn- on-proportion-lock-change - [event shape] - (if (:proportion-lock 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/stroke.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs index 878faac00..b64c8c821 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs @@ -8,6 +8,7 @@ (ns uxbox.main.ui.workspace.sidebar.options.stroke (:require [rumext.alpha :as mf] + [uxbox.common.data :as d] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw] [uxbox.main.store :as st] @@ -16,126 +17,82 @@ [uxbox.util.data :refer [parse-int parse-float read-string]] [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]] - [uxbox.util.math :refer [precision-or-0]])) - -(declare on-width-change) -(declare on-opacity-change) -(declare on-stroke-style-change) -(declare on-stroke-color-change) -(declare on-border-change) -(declare show-color-picker) + [uxbox.util.math :as math])) (mf/defc stroke-menu [{:keys [shape] :as props}] - (let [local (mf/use-state {}) - on-border-lock #(swap! local update :border-lock not) - on-stroke-style-change #(on-stroke-style-change % shape) - on-width-change #(on-width-change % shape) - on-stroke-color-change #(on-stroke-color-change % shape) - on-border-change-rx #(on-border-change % shape local :rx) - on-border-change-ry #(on-border-change % shape local :ry) - on-opacity-change #(on-opacity-change % shape) - show-color-picker #(show-color-picker % shape)] + (let [on-stroke-style-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/read-string))] + (st/emit! (udw/update-shape (:id shape) {:stroke-style value})))) + + on-stroke-width-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 1))] + (st/emit! (udw/update-shape (:id shape) {:stroke-width value})))) + + on-stroke-opacity-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 1) + (/ 10000))] + (st/emit! (udw/update-shape (:id shape) {:stroke-opacity value})))) + + show-color-picker + (fn [event] + (let [x (.-clientX event) + y (.-clientY event) + props {:x x :y y + :default "#ffffff" + :value (:stroke-color shape) + :on-change #(st/emit! (udw/update-shape (:id shape) {:stroke-color %})) + :transparent? true}] + (modal/show! colorpicker-modal props)))] + [:div.element-set - [:div.element-set-title (tr "element.stroke")] + [:div.element-set-title (tr "workspace.options.stroke")] [:div.element-set-content - [:span (tr "ds.style")] + + ;; Stroke Style & Width + [:span (tr "workspace.options.stroke.style")] [:div.row-flex - [:select#style.input-select {:placeholder (tr "ds.style") - :value (pr-str (:stroke-style shape)) + [:select#style.input-select {:value (pr-str (:stroke-style shape)) :on-change on-stroke-style-change} - [:option {:value ":none"} (tr "ds.none")] - [:option {:value ":solid"} (tr "ds.solid")] - [:option {:value ":dotted"} (tr "ds.dotted")] - [:option {:value ":dashed"} (tr "ds.dashed")] - [:option {:value ":mixed"} (tr "ds.mixed")]] - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.width") - :type "number" - :min "0" - :value (precision-or-0 (:stroke-width shape 1) 2) - :on-change on-width-change}]]] + [:option {:value ":none"} (tr "workspace.options.stroke.none")] + [:option {:value ":solid"} (tr "workspace.options.stroke.solid")] + [:option {:value ":dotted"} (tr "workspace.options.stroke.dotted")] + [:option {:value ":dashed"} (tr "workspace.options.stroke.dashed")] + [:option {:value ":mixed"} (tr "workspace.options.stroke.mixed")]] - [:span (tr "ds.color")] + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :value (-> (:stroke-width shape) + (math/precision 2) + (d/coalesce-str "1")) + :on-change on-stroke-width-change}]]] + + ;; Stroke Color + [:span (tr "workspace.options.color")] [:div.row-flex.color-data - [:span.color-th - {:style {:background-color (:stroke-color shape)} - :on-click show-color-picker}] + [:span.color-th {:style {:background-color (:stroke-color shape)} + :on-click show-color-picker}] [:div.color-info - [:input - {:on-change on-stroke-color-change - :value (:stroke-color shape "")}]]] + [:input {:read-only true + :default-value (:stroke-color shape "")}]]] - [:span (tr "ds.radius")] + [:span (tr "workspace.options.opacity")] [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "rx" - :type "number" - :value (precision-or-0 (:rx shape 0) 2) - :on-change on-border-change-rx}]] - [:div.lock-size - {:class (when (:border-lock @local) "selected") - :on-click on-border-lock} - i/lock] - [:div.input-element.pixels - [:input.input-text - {:placeholder "ry" - :type "number" - :value (precision-or-0 (:ry shape 0) 2) - :on-change on-border-change-ry}]]] - - [:span (tr "ds.opacity")] - [:div.row-flex - [:input.slidebar - {:type "range" - :min "0" - :max "10000" - :value (* 10000 (:stroke-opacity shape 1)) - :step "1" - :on-change on-opacity-change}]]]])) - -(defn- on-width-change - [event shape] - (let [value (-> (dom/event->value event) - (parse-float 1))] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-width value})))) - -(defn- on-opacity-change - [event shape] - (let [value (-> (dom/event->value event) - (parse-float 1) - (/ 10000))] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-opacity value})))) - -(defn- on-stroke-style-change - [event shape] - (let [value (-> (dom/event->value event) - (read-string))] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-style value})))) - -(defn- on-stroke-color-change - [event shape] - (let [value (dom/event->value event)] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color value})))) - -(defn- on-border-change - [event shape local attr] - (let [value (-> (dom/event->value event) - (parse-int nil)) - id (:id shape)] - (if (:border-lock @local) - (st/emit! (udw/update-shape-attrs id {:rx value :ry value})) - (st/emit! (udw/update-shape-attrs id {attr value}))))) - -(defn- show-color-picker - [event shape] - (let [x (.-clientX event) - y (.-clientY event) - props {:x x :y y - :default "#ffffff" - :value (:stroke-color shape) - :on-change #(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color %})) - :transparent? true}] - (modal/show! colorpicker-modal props))) + [:input.slidebar {:type "range" + :min "0" + :max "10000" + :value (-> (:stroke-opacity shape 1) + (* 10000) + (d/coalesce-str "1")) + :step "1" + :on-change on-stroke-opacity-change}]]]])) 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 b236c4fe4..9e424149f 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs @@ -29,7 +29,7 @@ {:mixins [mx/static]} [menu {:keys [id] :as shape}] (letfn [(update-attrs [attrs] - (st/emit! (udw/update-shape-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/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 9810cc812..fc8176bc7 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -82,11 +82,11 @@ end-x (max (:x start) (:x stop)) end-y (max (:y start) (:y stop))] (assoc data - :x1 start-x - :y1 start-y - :x2 end-x - :y2 end-y - :type :rect))) + :type :rect + :x start-x + :y start-y + :width (- end-x start-x) + :height (- end-y start-y)))) (def ^:private handle-selrect (letfn [(update-state [state position] @@ -115,13 +115,11 @@ {:wrap [mf/wrap-memo]} [{:keys [data] :as props}] (when data - (let [{:keys [x1 y1 width height]} (geom/size data)] - [:rect.selection-rect - {:x x1 - :y y1 - :width width - :height height}]))) - + [:rect.selection-rect + {:x (:x data) + :y (:y data) + :width (:width data) + :height (:height data)}])) ;; --- Viewport Positioning @@ -155,7 +153,7 @@ [:* (for [item canvas] [:& shape-wrapper {:shape item :key (:id item)}]) - (for [item (reverse shapes)] + (for [item shapes] [:& shape-wrapper {:shape item :key (:id item)}])])) (mf/defc viewport diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index f26dcf61e..778fb4ea2 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -201,6 +201,14 @@ (str/camel (name key)))))) +;; (defn coalesce +;; [^number v ^number n] +;; (if (.-toFixed v) +;; (js/parseFloat (.toFixed v n)) +;; 0)) + + + ;; (defmacro mirror-map [& fields] ;; (let [keys# (map #(keyword (name %)) fields) ;; vals# fields] diff --git a/frontend/src/uxbox/util/math.cljs b/frontend/src/uxbox/util/math.cljs index ae5a83b0d..0810d4683 100644 --- a/frontend/src/uxbox/util/math.cljs +++ b/frontend/src/uxbox/util/math.cljs @@ -81,7 +81,8 @@ (defn precision [^number v ^number n] - (js/parseFloat (.toFixed v n))) + (when (and (number? v) (number? n)) + (js/parseFloat (.toFixed v n)))) (defn precision-or-0 [^number v ^number n] diff --git a/frontend/src/uxbox/util/router.cljs b/frontend/src/uxbox/util/router.cljs index 19b523179..299214559 100644 --- a/frontend/src/uxbox/util/router.cljs +++ b/frontend/src/uxbox/util/router.cljs @@ -5,12 +5,15 @@ ;; Copyright (c) 2015-2019 Andrey Antukh (ns uxbox.util.router - (:require [reitit.core :as r] - [cuerdas.core :as str] - [potok.core :as ptk] - [uxbox.util.html.history :as html-history]) - (:import goog.Uri - goog.Uri.QueryData)) + (:require + [reitit.core :as r] + [cuerdas.core :as str] + [potok.core :as ptk] + [uxbox.common.data :as d] + [uxbox.util.html.history :as html-history]) + (:import + goog.Uri + goog.Uri.QueryData)) (defonce +router+ nil) @@ -42,7 +45,9 @@ (if (empty? qparams) (r/match->path match) (let [uri (.parse goog.Uri (r/match->path match)) - qdt (.createFromMap QueryData (clj->js qparams))] + qdt (.createFromMap QueryData (-> qparams + (d/remove-nil-vals) + (clj->js)))] (.setQueryData uri qdt) (.toString uri)))))) diff --git a/frontend/src/uxbox/util/timers.cljs b/frontend/src/uxbox/util/timers.cljs index 9e60b1d1e..fb91729fa 100644 --- a/frontend/src/uxbox/util/timers.cljs +++ b/frontend/src/uxbox/util/timers.cljs @@ -12,20 +12,20 @@ (schedule 0 func)) ([ms func] (let [sem (js/setTimeout #(func) ms)] - (reify rx/ICancellable - (-cancel [_] + (reify rx/IDisposable + (-dispose [_] (js/clearTimeout sem)))))) (defn interval [ms func] (let [sem (js/setInterval #(func) ms)] - (reify rx/ICancellable - (-cancel [_] + (reify rx/IDisposable + (-dispose [_] (js/clearInterval sem))))) (defn schedule-on-idle [func] (let [sem (js/requestIdleCallback #(func))] - (reify rx/ICancellable - (-cancel [_] + (reify rx/IDisposable + (-dispose [_] (js/cancelIdleCallback sem))))) diff --git a/frontend/src/uxbox/util/zip.cljs b/frontend/src/uxbox/util/zip.cljs index 3373553c7..169845e8c 100644 --- a/frontend/src/uxbox/util/zip.cljs +++ b/frontend/src/uxbox/util/zip.cljs @@ -16,4 +16,4 @@ (let [zobj (js/JSZip.)] (run! (partial attach-file zobj) files) (->> (.generateAsync zobj #js {:type "blob"}) - (rx/from-promise))))) + (rx/from)))))