♻️ mainly workspace refactor

This commit is contained in:
Andrey Antukh 2019-08-02 20:18:05 +02:00
parent 4e382d456f
commit 212ae89c50
85 changed files with 18494 additions and 6609 deletions

View file

@ -1,11 +1,7 @@
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"} {:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
org.clojure/clojure {:mvn/version "1.10.1"} org.clojure/clojure {:mvn/version "1.10.1"}
funcool/promesa {:mvn/version "2.0.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.256"} com.cognitect/transit-cljs {:mvn/version "0.8.256"}
funcool/rumext {:git/url "https://github.com/funcool/rumext.git"
:sha "ed9bf4c9c19110c6494a0571083a62b3b08eb17b"}
cljsjs/react-dom-server {:mvn/version "16.8.6-0"} cljsjs/react-dom-server {:mvn/version "16.8.6-0"}
environ/environ {:mvn/version "1.1.0"} environ/environ {:mvn/version "1.1.0"}
@ -13,8 +9,10 @@
funcool/beicon {:mvn/version "5.0.0"} funcool/beicon {:mvn/version "5.0.0"}
funcool/cuerdas {:mvn/version "2.2.0"} funcool/cuerdas {:mvn/version "2.2.0"}
funcool/lentes {:mvn/version "1.2.0"} funcool/lentes {:mvn/version "1.3.0-SNAPSHOT"}
funcool/potok {:mvn/version "2.3.0"} funcool/potok {:mvn/version "2.3.0"}
funcool/promesa {:mvn/version "2.0.1"}
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
} }
:paths ["src" "vendor" "resources"] :paths ["src" "vendor" "resources"]
:aliases :aliases

View file

@ -11,9 +11,9 @@
"dev": true "dev": true
}, },
"ajv": { "ajv": {
"version": "6.10.0", "version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true, "dev": true,
"requires": { "requires": {
"fast-deep-equal": "^2.0.1", "fast-deep-equal": "^2.0.1",
@ -313,18 +313,18 @@
"dev": true "dev": true
}, },
"autoprefixer": { "autoprefixer": {
"version": "9.6.0", "version": "9.6.1",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz",
"integrity": "sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==", "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==",
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "^4.6.1", "browserslist": "^4.6.3",
"caniuse-lite": "^1.0.30000971", "caniuse-lite": "^1.0.30000980",
"chalk": "^2.4.2", "chalk": "^2.4.2",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"num2fraction": "^1.2.2", "num2fraction": "^1.2.2",
"postcss": "^7.0.16", "postcss": "^7.0.17",
"postcss-value-parser": "^3.3.1" "postcss-value-parser": "^4.0.0"
} }
}, },
"aws-sign2": { "aws-sign2": {
@ -481,14 +481,14 @@
} }
}, },
"browserslist": { "browserslist": {
"version": "4.6.3", "version": "4.6.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.3.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz",
"integrity": "sha512-CNBqTCq22RKM8wKJNowcqihHJ4SkI8CGeK7KOR9tPboXUuS5Zk5lQgzzTbs4oxD8x+6HUshZUa2OyNI9lR93bQ==", "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-lite": "^1.0.30000975", "caniuse-lite": "^1.0.30000984",
"electron-to-chromium": "^1.3.164", "electron-to-chromium": "^1.3.191",
"node-releases": "^1.1.23" "node-releases": "^1.1.25"
} }
}, },
"buffer-equal": { "buffer-equal": {
@ -550,9 +550,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30000979", "version": "1.0.30000988",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000979.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000988.tgz",
"integrity": "sha512-gcu45yfq3B7Y+WB05fOMfr0EiSlq+1u+m6rPHyJli/Wy3PVQNGaU7VA4bZE5qw+AU2UVOBR/N5g1bzADUqdvFw==", "integrity": "sha512-lPj3T8poYrRc/bniW5SQPND3GRtSrQdUM/R4mCYTbZxyi3jQiggLvZH4+BYUuX0t4TXjU+vMM7KFDQg+rSzZUQ==",
"dev": true "dev": true
}, },
"caseless": { "caseless": {
@ -973,9 +973,9 @@
} }
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.3.183", "version": "1.3.207",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.183.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.207.tgz",
"integrity": "sha512-WbKCYs7yAFOfpuoa2pK5kbOngriUtlPC+8mcQW5L/686wv04w7hYXfw5ScDrsl9kixFw1SPsALEob5V/gtlDxw==", "integrity": "sha512-RIgAnfqbjZNECBLjslfy4cIYvcPl3GAXmnENrcoo0TZ8fGkyEEAealAbO7MoevW4xYUPe+e68cWAj6eP0DmMHw==",
"dev": true "dev": true
}, },
"end-of-stream": { "end-of-stream": {
@ -2198,9 +2198,9 @@
} }
}, },
"gulp-match": { "gulp-match": {
"version": "1.0.3", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.0.3.tgz", "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz",
"integrity": "sha1-kcfA1/Kb7NZgbVfYCn+Hdqh6uo4=", "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"minimatch": "^3.0.3" "minimatch": "^3.0.3"
@ -2874,9 +2874,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.11", "version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true "dev": true
}, },
"lodash.clonedeep": { "lodash.clonedeep": {
@ -3164,9 +3164,9 @@
} }
}, },
"node-releases": { "node-releases": {
"version": "1.1.24", "version": "1.1.26",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.24.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.26.tgz",
"integrity": "sha512-wym2jptfuKowMmkZsfCSTsn8qAVo8zm+UiQA6l5dNqUcpfChZSnS/vbbpOeXczf+VdPhutxh+99lWHhdd6xKzg==", "integrity": "sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"semver": "^5.3.0" "semver": "^5.3.0"
@ -3612,9 +3612,9 @@
} }
}, },
"postcss-value-parser": { "postcss-value-parser": {
"version": "3.3.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==",
"dev": true "dev": true
}, },
"pretty-hrtime": { "pretty-hrtime": {
@ -4140,9 +4140,9 @@
} }
}, },
"source-map-support": { "source-map-support": {
"version": "0.5.12", "version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
"integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
"requires": { "requires": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"source-map": "^0.6.0" "source-map": "^0.6.0"
@ -4187,9 +4187,9 @@
} }
}, },
"spdx-license-ids": { "spdx-license-ids": {
"version": "3.0.4", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
"integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true "dev": true
}, },
"split-string": { "split-string": {
@ -4353,9 +4353,9 @@
} }
}, },
"ternary-stream": { "ternary-stream": {
"version": "2.0.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.0.1.tgz", "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz",
"integrity": "sha1-Bk5Im0tb9gumpre8fy9cJ07Pgmk=", "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==",
"dev": true, "dev": true,
"requires": { "requires": {
"duplexify": "^3.5.0", "duplexify": "^3.5.0",
@ -4789,9 +4789,9 @@
"dev": true "dev": true
}, },
"xtend": { "xtend": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true "dev": true
}, },
"y18n": { "y18n": {

View file

@ -11,6 +11,7 @@
<section id="app" tabindex="1"></section> <section id="app" tabindex="1"></section>
<section id="lightbox"></section> <section id="lightbox"></section>
<section id="loader"></section> <section id="loader"></section>
<section id="modal"></section>
<script src="{{& jsfile}}"></script> <script src="{{& jsfile}}"></script>
<script>uxbox.main.init()</script> <script>uxbox.main.init()</script>
</body> </body>

View file

@ -97,7 +97,6 @@
display: flex; display: flex;
transition: all .6s ease; transition: all .6s ease;
width: 100%; width: 100%;
overflow-x: scroll;
scroll-behavior: smooth; scroll-behavior: smooth;
} }

View file

@ -174,7 +174,6 @@
} }
.colorpicker-tooltip { .colorpicker-tooltip {
background: $primary-ui-bg;
border-radius: $br-small; border-radius: $br-small;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -14,6 +14,7 @@
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui :as ui] [uxbox.main.ui :as ui]
[uxbox.main.ui.lightbox :refer [lightbox]] [uxbox.main.ui.lightbox :refer [lightbox]]
[uxbox.main.ui.modal :refer [modal]]
[uxbox.main.ui.loader :refer [loader]] [uxbox.main.ui.loader :refer [loader]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.html.history :as html-history] [uxbox.util.html.history :as html-history]
@ -70,6 +71,7 @@
(mf/mount (ui/app) (dom/get-element "app")) (mf/mount (ui/app) (dom/get-element "app"))
(mf/mount (lightbox) (dom/get-element "lightbox")) (mf/mount (lightbox) (dom/get-element "lightbox"))
(mf/mount (mf/element modal) (dom/get-element "modal"))
(mf/mount (loader) (dom/get-element "loader")) (mf/mount (loader) (dom/get-element "loader"))
(on-navigate router cpath))) (on-navigate router cpath)))

View file

@ -2,7 +2,7 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.colors (ns uxbox.main.data.colors
(:require (:require
@ -23,25 +23,14 @@
(declare persist-collections) (declare persist-collections)
(declare collections-fetched?) (declare collections-fetched?)
(defrecord Initialize [type id] (defrecord Initialize []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [type (or type :own) (assoc-in state [:dashboard :colors] {:selected #{}})))
data {:type type
:id id
:selected #{}}]
(-> state
(assoc-in [:dashboard :colors] data)
(assoc-in [:dashboard :section] :dashboard/colors))))
ptk/WatchEvent
(watch [_ state s]
(rx/of (fetch-collections))))
(defn initialize (defn initialize
[type id] []
(prn "colors$initialize" type id) (Initialize.))
(Initialize. type id))
;; --- Collections Fetched ;; --- Collections Fetched
@ -142,9 +131,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(let [type (get-in state [:dashboard :colors :type])] (rx/of (persist-collections))))
(rx/of (persist-collections)
(rt/nav :dashboard/colors nil {:type type})))))
(defn delete-collection (defn delete-collection
[id] [id]
@ -152,20 +139,19 @@
;; --- Replace Color ;; --- Replace Color
(defrecord ReplaceColor [id from to] (defrecord AddColor [coll-id color]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [replacer #(-> (disj % from) (conj to))] (update-in state [:colors-collections coll-id :colors] set/union #{color}))
(update-in state [:colors-collections id :colors] (fnil replacer #{}))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(rx/of (persist-collections)))) (rx/of (persist-collections))))
(defn replace-color (defn add-color
"Add or replace color in a collection." "Add or replace color in a collection."
[{:keys [id from to] :as params}] [coll-id color]
(ReplaceColor. id from to)) (AddColor. coll-id color))
;; --- Remove Color ;; --- Remove Color
@ -247,15 +233,17 @@
(or (uuid? to) (nil? to))]} (or (uuid? to) (nil? to))]}
(MoveSelected. from to)) (MoveSelected. from to))
;; --- Delete Selected Colors ;; --- Delete Colors
(defrecord DeleteColors [coll-id colors]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :colors :selected] #{}))
(defrecord DeleteSelectedColors []
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [{:keys [id selected]} (get-in state [:dashboard :colors])] (rx/of (remove-colors coll-id colors))))
(rx/of (remove-colors id selected)
#(assoc-in % [:dashboard :colors :selected] #{})))))
(defn delete-selected-colors (defn delete-colors
[] [coll-id colors]
(DeleteSelectedColors.)) (DeleteColors. coll-id colors))

View file

@ -22,8 +22,8 @@
(s/def ::grid-x-axis number?) (s/def ::grid-x-axis number?)
(s/def ::grid-y-axis number?) (s/def ::grid-y-axis number?)
(s/def ::grid-color us/color?) (s/def ::grid-color string?)
(s/def ::background us/color?) (s/def ::background string?)
(s/def ::background-opacity number?) (s/def ::background-opacity number?)
(s/def ::grid-alignment boolean?) (s/def ::grid-alignment boolean?)
(s/def ::width number?) (s/def ::width number?)

View file

@ -5,7 +5,7 @@
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.shapes (ns uxbox.main.data.shapes
(:require [cljs.spec.alpha :as s :include-macros true] (:require [cljs.spec.alpha :as s]
[lentes.core :as l] [lentes.core :as l]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
@ -55,6 +55,14 @@
(s/def ::y1 number?) (s/def ::y1 number?)
(s/def ::x2 number?) (s/def ::x2 number?)
(s/def ::y2 number?) (s/def ::y2 number?)
(s/def ::id uuid?)
(s/def ::page uuid?)
(s/def ::type #{:rect
:group
:path
:circle
:image
:text})
(s/def ::attributes (s/def ::attributes
(s/keys :opt-un [::fill-color (s/keys :opt-un [::fill-color
@ -80,24 +88,12 @@
::blocked ::blocked
::locked])) ::locked]))
(s/def ::id uuid?)
(s/def ::page uuid?)
(s/def ::type #{:rect
:group
:path
:circle
:image
:text})
(s/def ::shape (s/def ::shape
(s/merge (s/keys ::req-un [::id ::page ::type]) ::attributes)) (s/merge (s/keys ::req-un [::id ::page ::type]) ::attributes))
(s/def ::rect-like-shape (s/def ::rect-like-shape
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type])) (s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))
(s/def ::direction #{:up :down :right :left})
(s/def ::speed #{:std :fast})
;; --- Shapes CRUD ;; --- Shapes CRUD
(deftype AddShape [data] (deftype AddShape [data]
@ -105,8 +101,8 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [shape (geom/setup-proportions data) (let [shape (geom/setup-proportions data)
page (get-in state [:workspace :page])] page-id (get-in state [:workspace :current])]
(impl/assoc-shape-to-page state shape page)))) (impl/assoc-shape-to-page state shape page-id))))
(defn add-shape (defn add-shape
[data] [data]
@ -141,31 +137,6 @@
{:pre [(uuid? id) (string? name)]} {:pre [(uuid? id) (string? name)]}
(RenameShape. id name)) (RenameShape. id name))
;; --- Shape Transformations
(def ^:private canvas-coords
(gpt/point c/canvas-start-x
c/canvas-start-y))
(declare apply-temporal-displacement)
(deftype InitialShapeAlign [id]
ptk/WatchEvent
(watch [_ state s]
(let [{:keys [x1 y1] :as shape} (->> (get-in state [:shapes id])
(geom/shape->rect-shape state))
point1 (gpt/point x1 y1)
point2 (gpt/add point1 canvas-coords)]
(->> (uwrk/align-point point2)
(rx/map #(gpt/subtract % canvas-coords))
(rx/map (fn [{:keys [x y] :as pt}]
(apply-temporal-displacement id (gpt/subtract pt point1))))))))
(defn initial-shape-align
[id]
{:pre [(uuid? id)]}
(InitialShapeAlign. id))
;; --- Update Rotation ;; --- Update Rotation
(deftype UpdateShapeRotation [id rotation] (deftype UpdateShapeRotation [id rotation]
@ -201,69 +172,6 @@
{:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]} {:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]}
(UpdateDimensions. id opts)) (UpdateDimensions. id opts))
;; --- Apply Temporal Displacement
(deftype ApplyTemporalDisplacement [id delta]
ptk/UpdateEvent
(update [_ state]
(let [prev (get-in state [:workspace :modifiers id :displacement] (gmt/matrix))
curr (gmt/translate prev delta)]
(assoc-in state [:workspace :modifiers id :displacement] curr))))
(defn apply-temporal-displacement
[id pt]
{:pre [(uuid? id) (gpt/point? pt)]}
(ApplyTemporalDisplacement. id pt))
;; --- Apply Displacement
(deftype ApplyDisplacement [id]
udp/IPageUpdate
ptk/WatchEvent
(watch [_ state stream]
(let [displacement (get-in state [:workspace :modifiers id :displacement])]
(if (gmt/matrix? displacement)
(rx/of #(impl/materialize-xfmt % id displacement)
#(update-in % [:workspace :modifiers id] dissoc :displacement)
::udp/page-update)
(rx/empty)))))
(defn apply-displacement
[id]
{:pre [(uuid? id)]}
(ApplyDisplacement. id))
;; --- Apply Temporal Resize Matrix
(deftype ApplyTemporalResize [id xfmt]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace :modifiers id :resize] xfmt)))
(defn apply-temporal-resize
"Attach temporal resize transformation to the shape."
[id xfmt]
{:pre [(gmt/matrix? xfmt) (uuid? id)]}
(ApplyTemporalResize. id xfmt))
;; --- Apply Resize Matrix
(deftype ApplyResize [id]
ptk/WatchEvent
(watch [_ state stream]
(let [resize (get-in state [:workspace :modifiers id :resize])]
(if (gmt/matrix? resize)
(rx/of #(impl/materialize-xfmt % id resize)
#(update-in % [:workspace :modifiers id] dissoc :resize)
::udp/page-update)
(rx/empty)))))
(defn apply-resize
"Apply definitivelly the resize matrix transformation to the shape."
[id]
{:pre [(uuid? id)]}
(ApplyResize. id))
;; --- Update Shape Position ;; --- Update Shape Position
(deftype UpdateShapePosition [id point] (deftype UpdateShapePosition [id point]
@ -294,7 +202,7 @@
;; --- Update Shape Attrs ;; --- Update Shape Attrs
(declare UpdateAttrs) (declare UpdateAttrs)
;; TODO: moved
(deftype UpdateAttrs [id attrs] (deftype UpdateAttrs [id attrs]
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -496,52 +404,6 @@
(keyword? loc)]} (keyword? loc)]}
(DropShape. sid tid loc)) (DropShape. sid tid loc))
;; --- Select First Shape
(deftype SelectFirstShape []
ptk/UpdateEvent
(update [_ state]
(let [page (get-in state [:workspace :page])
id (first (get-in state [:pages page :shapes]))]
(assoc-in state [:workspace :selected] #{id}))))
(defn select-first-shape
"Mark a shape selected for drawing in the canvas."
[]
(SelectFirstShape.))
;; --- Mark Shape Selected
(deftype SelectShape [id]
ptk/UpdateEvent
(update [_ state]
(let [selected (get-in state [:workspace :selected])
state (if (contains? selected id)
(update-in state [:workspace :selected] disj id)
(update-in state [:workspace :selected] conj id))]
(update-in state [:workspace :flags] conj :element-options))))
(defn select-shape
"Mark a shape selected for drawing in the canvas."
[id]
{:pre [(uuid? id)]}
(SelectShape. id))
;; --- Select Shapes (By selrect)
(deftype SelectShapesBySelrect [selrect]
ptk/UpdateEvent
(update [_ state]
(let [page (get-in state [:workspace :page])
shapes (impl/match-by-selrect state page selrect)]
(assoc-in state [:workspace :selected] shapes))))
(defn select-shapes-by-selrect
"Select shapes that matches the select rect."
[selrect]
{:pre [(us/valid? ::rect-like-shape selrect)]}
(SelectShapesBySelrect. selrect))
;; --- Update Interaction ;; --- Update Interaction
(deftype UpdateInteraction [shape interaction] (deftype UpdateInteraction [shape interaction]
@ -583,6 +445,10 @@
{:pre [(uuid? id) (number? index) (gpt/point? delta)]} {:pre [(uuid? id) (number? index) (gpt/point? delta)]}
(UpdatePath. id index delta)) (UpdatePath. id index delta))
(def ^:private canvas-coords
(gpt/point c/canvas-start-x
c/canvas-start-y))
(deftype InitialPathPointAlign [id index] (deftype InitialPathPointAlign [id index]
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
@ -624,6 +490,7 @@
;; --- Events (implicit) (for selected) ;; --- Events (implicit) (for selected)
;; NOTE: moved to workspace
(deftype DeselectAll [] (deftype DeselectAll []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -684,91 +551,4 @@
[] []
(DuplicateSelected.)) (DuplicateSelected.))
;; --- Delete Selected
(deftype DeleteSelected []
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:workspace :selected])]
(rx/from-coll
(into [(deselect-all)] (map #(delete-shape %) selected))))))
(defn delete-selected
"Deselect all and remove all selected shapes."
[]
(DeleteSelected.))
(deftype UpdateSelectedShapesAttrs [attrs]
ptk/WatchEvent
(watch [_ state stream]
(let [xf (map #(update-attrs % attrs))]
(rx/from-coll (sequence xf (get-in state [:workspace :selected]))))))
(defn update-selected-shapes-attrs
[attrs]
{:pre [(us/valid? ::attributes attrs)]}
(UpdateSelectedShapesAttrs. attrs))
;; --- Move Selected Layer
(deftype MoveSelectedLayer [loc]
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(let [selected (get-in state [:workspace :selected])]
(impl/move-layer state selected loc))))
(defn move-selected-layer
[loc]
{:pre [(us/valid? ::direction loc)]}
(MoveSelectedLayer. loc))
;; --- Move Selected
(defn- get-displacement
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
[direction speed distance]
(case direction
:up (gpt/point 0 (- (get-in distance [speed :y])))
:down (gpt/point 0 (get-in distance [speed :y]))
:left (gpt/point (- (get-in distance [speed :x])) 0)
:right (gpt/point (get-in distance [speed :x]) 0)))
(defn- get-displacement-distance
"Retrieve displacement distances thresholds for
defined displacement speeds."
[metadata align?]
(let [gx (:grid-x-axis metadata)
gy (:grid-y-axis metadata)]
{:std (gpt/point (if align? gx 1)
(if align? gy 1))
:fast (gpt/point (if align? (* 3 gx) 10)
(if align? (* 3 gy) 10))}))
;; --- Move Selected
;; Event used for apply displacement transformation
;; to the selected shapes throught the keyboard shortcuts.
(deftype MoveSelected [direction speed]
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [page selected]} (:workspace state)
align? (refs/alignment-activated? state)
metadata (merge c/page-metadata (get-in state [:pages page :metadata]))
distance (get-displacement-distance metadata align?)
displacement (get-displacement direction speed distance)]
(rx/concat
(when align?
(rx/concat
(rx/from-coll (map initial-shape-align selected))
(rx/from-coll (map apply-displacement selected))))
(rx/from-coll (map #(apply-temporal-displacement % displacement) selected))
(rx/from-coll (map apply-displacement selected))))))
(defn move-selected
[direction speed]
{:pre [(us/valid? ::direction direction)
(us/valid? ::speed speed)]}
(MoveSelected. direction speed))

View file

@ -301,13 +301,13 @@
acc)) acc))
(defn match-by-selrect (defn match-by-selrect
[state page selrect] [state page-id selrect]
(let [xf (comp (map #(get-in state [:shapes %])) (let [xf (comp (map #(get-in state [:shapes %]))
(remove :hidden) (remove :hidden)
(remove :blocked) (remove :blocked)
(map geom/selection-rect)) (map geom/selection-rect))
match (partial try-match-shape xf selrect) match (partial try-match-shape xf selrect)
shapes (get-in state [:pages page :shapes])] shapes (get-in state [:pages page-id :shapes])]
(reduce match #{} (sequence xf shapes)))) (reduce match #{} (sequence xf shapes))))
(defn group-shapes (defn group-shapes

View file

@ -7,42 +7,50 @@
(ns uxbox.main.data.workspace (ns uxbox.main.data.workspace
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.main.store :as st]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.lenses :as ul]
[uxbox.main.workers :as uwrk]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.shapes :as uds]
[uxbox.main.data.icons :as udi]
[uxbox.main.data.shapes-impl :as shimpl]
[uxbox.main.data.lightbox :as udl]
[uxbox.main.data.history :as udh] [uxbox.main.data.history :as udh]
[uxbox.main.data.workspace.scroll :as wscroll] [uxbox.main.data.icons :as udi]
[uxbox.main.data.workspace.drawing :as wdrawing] [uxbox.main.data.lightbox :as udl]
[uxbox.main.data.workspace.selrect :as wselrect] [uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.shapes :as uds]
[uxbox.main.data.shapes-impl :as simpl]
;; [uxbox.main.data.workspace.drawing :as wdrawing]
[uxbox.main.data.workspace.ruler :as wruler] [uxbox.main.data.workspace.ruler :as wruler]
[uxbox.util.uuid :as uuid] [uxbox.main.data.workspace.scroll :as wscroll]
[uxbox.util.spec :as us] [uxbox.main.lenses :as ul]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.streams :as streams]
[uxbox.main.user-events :as uev]
[uxbox.main.workers :as uwrk]
[uxbox.util.data :refer [index-of]]
[uxbox.util.forms :as sc] [uxbox.util.forms :as sc]
[uxbox.main.geom :as geom]
[uxbox.util.geom.point :as gpt] [uxbox.util.geom.point :as gpt]
[uxbox.util.time :as dt] [uxbox.util.geom.matrix :as gmt]
[uxbox.util.math :as mth] [uxbox.util.math :as mth]
[uxbox.util.data :refer [index-of]])) [uxbox.util.spec :as us]
[uxbox.util.time :as dt]
[uxbox.util.uuid :as uuid]))
;; --- Expose inner functions ;; --- Expose inner functions
(def start-viewport-positioning wscroll/start-viewport-positioning) (def start-viewport-positioning wscroll/start-viewport-positioning)
(def stop-viewport-positioning wscroll/stop-viewport-positioning) (def stop-viewport-positioning wscroll/stop-viewport-positioning)
(def start-drawing wdrawing/start-drawing) ;; (def start-drawing wdrawing/start-drawing)
(def close-drawing-path wdrawing/close-drawing-path) ;; (def close-drawing-path wdrawing/close-drawing-path)
(def select-for-drawing wdrawing/select-for-drawing) ;; (def select-for-drawing wdrawing/select-for-drawing)
(def start-selrect wselrect/start-selrect)
(def start-ruler wruler/start-ruler) (def start-ruler wruler/start-ruler)
(def clear-ruler wruler/clear-ruler) (def clear-ruler wruler/clear-ruler)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General workspace events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Initialize Workspace ;; --- Initialize Workspace
(declare initialize-alignment) (declare initialize-alignment)
@ -50,24 +58,18 @@
(defrecord Initialize [project-id page-id] (defrecord Initialize [project-id page-id]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [default-flags #{:sitemap :drawtools :layers :element-options :rules}] (let [default-flags #{:sitemap :drawtools :layers :element-options :rules}
(if (:workspace state) initial-workspace {:project-id project-id
(update state :workspace merge :page-id page-id
{:project project-id :zoom 1
:page page-id :flags default-flags
:selected #{} :selected #{}
:drawing nil :drawing nil
:drawing-tool nil :drawing-tool nil
:tooltip nil}) :tooltip nil}]
(assoc state :workspace (-> state
{:project project-id (update-in [:workspace page-id] #(if (nil? %) initial-workspace %))
:zoom 1 (assoc-in [:workspace :current] page-id))))
:page page-id
:flags default-flags
:selected #{}
:drawing nil
:drawing-tool nil
:tooltip nil}))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -104,7 +106,8 @@
(defrecord SetTooltip [text] (defrecord SetTooltip [text]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace :tooltip] text))) (let [page-id (get-in state [:workspace :current])]
(assoc-in state [:workspace page-id :tooltip] text))))
(defn set-tooltip (defn set-tooltip
[text] [text]
@ -112,30 +115,33 @@
;; --- Workspace Flags ;; --- Workspace Flags
(deftype ActivateFlag [flag] (defrecord ActivateFlag [flag]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:workspace :flags] conj flag))) (let [page-id (get-in state [:workspace :current])]
(update-in state [:workspace page-id :flags] conj flag))))
(defn activate-flag (defn activate-flag
[flag] [flag]
{:pre [(keyword? flag)]} {:pre [(keyword? flag)]}
(ActivateFlag. flag)) (ActivateFlag. flag))
(deftype DeactivateFlag [flag] (defrecord DeactivateFlag [flag]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:workspace :flags] disj flag))) (let [page-id (get-in state [:workspace :current])]
(update-in state [:workspace page-id :flags] disj flag))))
(defn deactivate-flag (defn deactivate-flag
[flag] [flag]
{:pre [(keyword? flag)]} {:pre [(keyword? flag)]}
(DeactivateFlag. flag)) (DeactivateFlag. flag))
(deftype ToggleFlag [flag] (defrecord ToggleFlag [flag]
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [flags (get-in state [:workspace :flags])] (let [page-id (get-in state [:workspace :current])
flags (get-in state [:workspace page-id :flags])]
(if (contains? flags flag) (if (contains? flags flag)
(rx/of (deactivate-flag flag)) (rx/of (deactivate-flag flag))
(rx/of (activate-flag flag)))))) (rx/of (activate-flag flag))))))
@ -146,7 +152,7 @@
;; --- Workspace Ruler ;; --- Workspace Ruler
(deftype ActivateRuler [] (defrecord ActivateRuler []
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(rx/of (set-tooltip "Drag to use the ruler") (rx/of (set-tooltip "Drag to use the ruler")
@ -156,7 +162,7 @@
[] []
(ActivateRuler.)) (ActivateRuler.))
(deftype DeactivateRuler [] (defrecord DeactivateRuler []
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(rx/of (set-tooltip nil) (rx/of (set-tooltip nil)
@ -166,10 +172,11 @@
[] []
(DeactivateRuler.)) (DeactivateRuler.))
(deftype ToggleRuler [] (defrecord ToggleRuler []
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [flags (get-in state [:workspace :flags])] (let [page-id (get-in state [:workspace :current])
flags (get-in state [:workspace page-id :flags])]
(if (contains? flags :ruler) (if (contains? flags :ruler)
(rx/of (deactivate-ruler)) (rx/of (deactivate-ruler))
(rx/of (activate-ruler)))))) (rx/of (activate-ruler))))))
@ -180,10 +187,11 @@
;; --- Icons Toolbox ;; --- Icons Toolbox
(deftype SelectIconsToolboxCollection [id] (defrecord SelectIconsToolboxCollection [id]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace :icons-toolbox] id)) (let [page-id (get-in state [:workspace :current])]
(assoc-in state [:workspace page-id :icons-toolbox] id)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -194,11 +202,7 @@
{:pre [(or (nil? id) (uuid? id))]} {:pre [(or (nil? id) (uuid? id))]}
(SelectIconsToolboxCollection. id)) (SelectIconsToolboxCollection. id))
(deftype InitializeIconsToolbox [] (defrecord InitializeIconsToolbox []
ptk/UpdateEvent
(update [_ state]
state)
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(letfn [(get-first-with-icons [colls] (letfn [(get-first-with-icons [colls]
@ -215,7 +219,8 @@
;; Only perform the autoselection if it is not ;; Only perform the autoselection if it is not
;; previously already selected by the user. ;; previously already selected by the user.
(when-not (contains? (:workspace state) :icons-toolbox) ;; TODO
#_(when-not (contains? (:workspace state) :icons-toolbox)
(->> stream (->> stream
(rx/filter udi/collections-fetched?) (rx/filter udi/collections-fetched?)
(rx/take 1) (rx/take 1)
@ -230,7 +235,8 @@
(defrecord CopyToClipboard [] (defrecord CopyToClipboard []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [selected (get-in state [:workspace :selected]) (let [page-id (get-in state [:workspace :current])
selected (get-in state [:workspace page-id :selected])
item {:id (uuid/random) item {:id (uuid/random)
:created-at (dt/now) :created-at (dt/now)
:items selected} :items selected}
@ -251,13 +257,13 @@
udp/IPageUpdate udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [page (get-in state [:workspace :page]) (let [page-id (get-in state [:workspace :current])
selected (if (nil? id) selected (if (nil? id)
(first (:clipboard state)) (first (:clipboard state))
(->> (:clipboard state) (->> (:clipboard state)
(filter #(= id (:id %))) (filter #(= id (:id %)))
(first)))] (first)))]
(shimpl/duplicate-shapes state (:items selected) page)))) (simpl/duplicate-shapes state (:items selected) page-id))))
(defn paste-from-clipboard (defn paste-from-clipboard
"Copy selected shapes to clipboard." "Copy selected shapes to clipboard."
@ -266,34 +272,37 @@
;; --- Zoom Management ;; --- Zoom Management
(deftype IncreaseZoom [] (defrecord IncreaseZoom []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [increase #(nth c/zoom-levels (let [increase #(nth c/zoom-levels
(+ (index-of c/zoom-levels %) 1) (+ (index-of c/zoom-levels %) 1)
(last c/zoom-levels))] (last c/zoom-levels))
(update-in state [:workspace :zoom] (fnil increase 1))))) page-id (get-in state [:workspace :current])]
(update-in state [:workspace page-id :zoom] (fnil increase 1)))))
(defn increase-zoom (defn increase-zoom
[] []
(IncreaseZoom.)) (IncreaseZoom.))
(deftype DecreaseZoom [] (defrecord DecreaseZoom []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [decrease #(nth c/zoom-levels (let [decrease #(nth c/zoom-levels
(- (index-of c/zoom-levels %) 1) (- (index-of c/zoom-levels %) 1)
(first c/zoom-levels))] (first c/zoom-levels))
(update-in state [:workspace :zoom] (fnil decrease 1))))) page-id (get-in state [:workspace :current])]
(update-in state [:workspace page-id :zoom] (fnil decrease 1)))))
(defn decrease-zoom (defn decrease-zoom
[] []
(DecreaseZoom.)) (DecreaseZoom.))
(deftype ResetZoom [] (defrecord ResetZoom []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace :zoom] 1))) (let [page-id (get-in state [:workspace :current])]
(assoc-in state [:workspace page-id :zoom] 1))))
(defn reset-zoom (defn reset-zoom
[] []
@ -323,6 +332,434 @@
{:pre [(uuid? id)]} {:pre [(uuid? id)]}
(InitializeAlignment. id)) (InitializeAlignment. id))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shapes on Workspace events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defrecord SelectShape [id]
ptk/UpdateEvent
(update [_ state]
(let [page-id (get-in state [:workspace :current])
selected (get-in state [:workspace page-id :selected])]
(if (contains? selected id)
(update-in state [:workspace page-id :selected] disj id)
(update-in state [:workspace page-id :selected] conj id))))
ptk/WatchEvent
(watch [_ state s]
(rx/of (activate-flag :element-options))))
(defn select-shape
"Mark a shape selected for drawing in the canvas."
[id]
{:pre [(uuid? id)]}
(SelectShape. id))
(defrecord DeselectAll []
ptk/UpdateEvent
(update [_ state]
(let [page-id (get-in state [:workspace :current])]
(assoc-in state [:workspace page-id :selected] #{})))
ptk/WatchEvent
(watch [_ state stream]
(rx/just ::uev/interrupt)))
(defn deselect-all
"Clear all possible state of drawing, edition
or any similar action taken by the user."
[]
(DeselectAll.))
;; --- Select First Shape
(deftype SelectFirstShape []
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace :current])
sid (first (get-in state [:pages pid :shapes]))]
(assoc-in state [:workspace pid :selected] #{sid}))))
(defn select-first-shape
"Mark a shape selected for drawing in the canvas."
[]
(SelectFirstShape.))
;; --- Select Shapes (By selrect)
(defrecord SelectShapesBySelrect [selrect]
ptk/UpdateEvent
(update [_ state]
(let [page-id (get-in state [:workspace :current])
shapes (simpl/match-by-selrect state page-id selrect)]
(assoc-in state [:workspace page-id :selected] shapes))))
(defn select-shapes-by-selrect
"Select shapes that matches the select rect."
[selrect]
{:pre [(us/valid? ::uds/rect-like-shape selrect)]}
(SelectShapesBySelrect. selrect))
;; --- Update Shape Attrs
(deftype UpdateShapeAttrs [id attrs]
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] merge attrs)))
(defn update-shape-attrs
[id attrs]
{:pre [(uuid? id) (us/valid? ::uds/attributes attrs)]}
(let [atts (us/extract attrs ::uds/attributes)]
(UpdateShapeAttrs. id attrs)))
;; --- Update Selected Shapes attrs
(deftype UpdateSelectedShapesAttrs [attrs]
ptk/WatchEvent
(watch [_ state stream]
(let [pid (get-in state [:workspace :current])
selected (get-in state [:workspace pid :selected])]
(rx/from-coll (map #(update-shape-attrs % attrs) selected)))))
(defn update-selected-shapes-attrs
[attrs]
{:pre [(us/valid? ::uds/attributes attrs)]}
(UpdateSelectedShapesAttrs. attrs))
;; --- Move Selected
;; Event used for apply displacement transformation
;; to the selected shapes throught the keyboard shortcuts.
(defn- get-displacement
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
[direction speed distance]
(case direction
:up (gpt/point 0 (- (get-in distance [speed :y])))
:down (gpt/point 0 (get-in distance [speed :y]))
:left (gpt/point (- (get-in distance [speed :x])) 0)
:right (gpt/point (get-in distance [speed :x]) 0)))
(defn- get-displacement-distance
"Retrieve displacement distances thresholds for
defined displacement speeds."
[metadata align?]
(let [gx (:grid-x-axis metadata)
gy (:grid-y-axis metadata)]
{:std (gpt/point (if align? gx 1)
(if align? gy 1))
:fast (gpt/point (if align? (* 3 gx) 10)
(if align? (* 3 gy) 10))}))
(declare apply-temporal-displacement)
(declare initial-shape-align)
(declare apply-displacement)
(defrecord MoveSelected [direction speed]
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace :current])
workspace (get-in state [:workspace page-id])
selected (:selected workspace)
flags (:flags workspace)
align? (refs/alignment-activated? flags)
metadata (merge c/page-metadata (get-in state [:pages page-id :metadata]))
distance (get-displacement-distance metadata align?)
displacement (get-displacement direction speed distance)]
(rx/concat
(when align?
(rx/concat
(rx/from-coll (map initial-shape-align selected))
(rx/from-coll (map apply-displacement selected))))
(rx/from-coll (map #(apply-temporal-displacement % displacement) selected))
(rx/from-coll (map apply-displacement selected))))))
(s/def ::direction #{:up :down :right :left})
(s/def ::speed #{:std :fast})
(defn move-selected
[direction speed]
{:pre [(us/valid? ::direction direction)
(us/valid? ::speed speed)]}
(MoveSelected. direction speed))
;; --- Move Selected Layer
(defrecord MoveSelectedLayer [loc]
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace :current])
selected (get-in state [:workspace id :selected])]
(simpl/move-layer state selected loc))))
(defn move-selected-layer
[loc]
{:pre [(us/valid? ::direction loc)]}
(MoveSelectedLayer. loc))
;; --- Delete Selected
(defrecord DeleteSelected []
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-in state [:workspace :current])
selected (get-in state [:workspace id :selected])]
(rx/from-coll
(into [(deselect-all)] (map #(uds/delete-shape %) selected))))))
(defn delete-selected
"Deselect all and remove all selected shapes."
[]
(DeleteSelected.))
;; --- Shape Transformations
(def ^:private canvas-coords
(gpt/point c/canvas-start-x
c/canvas-start-y))
(defrecord InitialShapeAlign [id]
ptk/WatchEvent
(watch [_ state s]
(let [{:keys [x1 y1] :as shape} (->> (get-in state [:shapes id])
(geom/shape->rect-shape state))
point1 (gpt/point x1 y1)
point2 (gpt/add point1 canvas-coords)]
(->> (uwrk/align-point point2)
(rx/map #(gpt/subtract % canvas-coords))
(rx/map (fn [{:keys [x y] :as pt}]
(apply-temporal-displacement id (gpt/subtract pt point1))))))))
(defn initial-shape-align
[id]
{:pre [(uuid? id)]}
(InitialShapeAlign. id))
;; --- Apply Temporal Displacement
(defrecord ApplyTemporalDisplacement [id delta]
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace :current])
prev (get-in state [:workspace pid :modifiers id :displacement] (gmt/matrix))
curr (gmt/translate prev delta)]
(assoc-in state [:workspace pid :modifiers id :displacement] curr))))
(defn apply-temporal-displacement
[id pt]
{:pre [(uuid? id) (gpt/point? pt)]}
(ApplyTemporalDisplacement. id pt))
;; --- Apply Displacement
(defrecord ApplyDisplacement [id]
udp/IPageUpdate
ptk/WatchEvent
(watch [_ state stream]
(let [pid (get-in state [:workspace :current])
displacement (get-in state [:workspace pid :modifiers id :displacement])]
(if (gmt/matrix? displacement)
(rx/of #(simpl/materialize-xfmt % id displacement)
#(update-in % [:workspace pid :modifiers id] dissoc :displacement)
::udp/page-update)
(rx/empty)))))
(defn apply-displacement
[id]
{:pre [(uuid? id)]}
(ApplyDisplacement. id))
;; --- Apply Temporal Resize Matrix
(deftype ApplyTemporalResize [id xfmt]
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace :current])]
(assoc-in state [:workspace pid :modifiers id :resize] xfmt))))
(defn apply-temporal-resize
"Attach temporal resize transformation to the shape."
[id xfmt]
{:pre [(gmt/matrix? xfmt) (uuid? id)]}
(ApplyTemporalResize. id xfmt))
;; --- Apply Resize Matrix
(deftype ApplyResize [id]
ptk/WatchEvent
(watch [_ state stream]
(let [pid (get-in state [:workspace :current])
resize (get-in state [:workspace pid :modifiers id :resize])]
(if (gmt/matrix? resize)
(rx/of #(simpl/materialize-xfmt % id resize)
#(update-in % [:workspace pid :modifiers id] dissoc :resize)
::udp/page-update)
(rx/empty)))))
(defn apply-resize
"Apply definitivelly the resize matrix transformation to the shape."
[id]
{:pre [(uuid? id)]}
(ApplyResize. id))
;; --- Shape Movement (by mouse)
(defrecord StartMove [id]
ptk/WatchEvent
(watch [_ state stream]
(let [pid (get-in state [:workspace :current])
wst (get-in state [:workspace pid])
stoper (->> streams/events
(rx/filter uev/mouse-up?)
(rx/take 1))
stream (->> streams/mouse-position-deltas
(rx/take-until stoper))]
(rx/concat
(when (refs/alignment-activated? (:flags wst))
(rx/of (initial-shape-align id)))
(rx/map #(apply-temporal-displacement id %) stream)
(rx/of (apply-displacement id))))))
(defn start-move
[id]
{:pre [(uuid? id)]}
(StartMove. id))
(defrecord StartMoveSelected []
ptk/WatchEvent
(watch [_ state stream]
(let [pid (get-in state [:workspace :current])
selected (get-in state [:workspace pid :selected])]
(rx/from-coll (map start-move selected)))))
(defn start-move-selected
[]
(StartMoveSelected.))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Selection Rect Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare stop-selrect)
(declare update-selrect)
(declare get-selection-stoper)
(declare selection->rect)
(declare translate-to-canvas)
;; --- Start Selrect
(defrecord StartSelrect []
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace :current])
position (get-in state [:workspace :pointer :viewport])
selection {::start position ::stop position}]
(assoc-in state [:workspace id :selrect] (selection->rect selection))))
ptk/WatchEvent
(watch [_ state stream]
(let [stoper (get-selection-stoper stream)]
;; NOTE: the `viewport-mouse-position` can be derived from `stream`
;; but it used from `streams/` ns just for convenience
(rx/concat
(->> streams/viewport-mouse-position
(rx/take-until stoper)
(rx/map update-selrect))
(rx/just (stop-selrect))))))
(defn start-selrect
[]
(StartSelrect.))
;; --- Update Selrect
(defrecord UpdateSelrect [position]
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace :current])]
(-> state
(assoc-in [:workspace id :selrect ::stop] position)
(update-in [:workspace id :selrect] selection->rect)))))
(defn update-selrect
[position]
{:pre [(gpt/point? position)]}
(UpdateSelrect. position))
;; --- Clear Selrect
(defrecord ClearSelrect []
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace :current])]
(update-in state [:workspace id] dissoc :selrect))))
(defn clear-selrect
[]
(ClearSelrect.))
;; --- Stop Selrect
(defrecord StopSelrect []
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-in state [:workspace :current])
zoom (get-in state [:workspace id :zoom])
rect (-> (get-in state [:workspace id :selrect])
(translate-to-canvas zoom))]
(rx/of
(clear-selrect)
(deselect-all)
(select-shapes-by-selrect rect)))))
(defn stop-selrect
[]
(StopSelrect.))
;; --- Impl
(defn- selection->rect
[data]
(let [start (::start data)
stop (::stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
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)))
(defn- get-selection-stoper
[stream]
(->> (rx/merge (rx/filter #(= % ::uev/interrupt) stream)
(rx/filter uev/mouse-up? stream))
(rx/take 1)))
(defn- translate-to-canvas
"Translate the given rect to the canvas coordinates system."
[rect zoom]
(let [startx (* c/canvas-start-x zoom)
starty (* c/canvas-start-y zoom)]
(assoc rect
:x1 (/ (- (:x1 rect) startx) zoom)
:y1 (/ (- (:y1 rect) starty) zoom)
:x2 (/ (- (:x2 rect) startx) zoom)
:y2 (/ (- (:y2 rect) starty) zoom))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Server Interactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Update Metadata ;; --- Update Metadata
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event. ;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.

View file

@ -12,7 +12,6 @@
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.streams :as streams] [uxbox.main.streams :as streams]
[uxbox.main.user-events :as uev] [uxbox.main.user-events :as uev]
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt])) [uxbox.util.geom.point :as gpt]))
@ -43,8 +42,9 @@
(deftype StartRuler [] (deftype StartRuler []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [pos (get-in state [:workspace :pointer :viewport])] (let [pid (get-in state [:workspace :current])
(assoc-in state [:workspace :ruler] [pos pos]))) pos (get-in state [:workspace :pointer :viewport])]
(assoc-in state [:workspace pid :ruler] {:start pos :end pos})))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -65,13 +65,15 @@
(deftype UpdateRuler [point ctrl?] (deftype UpdateRuler [point ctrl?]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [[start end] (get-in state [:workspace :ruler])] (let [pid (get-in state [:workspace :current])
ruler (get-in state [:workspace pid :ruler])]
(if-not ctrl? (if-not ctrl?
(assoc-in state [:workspace :ruler] [start point]) (assoc-in state [:workspace pid :ruler :end] point)
(let [end (-> (gpt/subtract point start) (let [start (get-in state [:workspace pid :ruler :start])
end (-> (gpt/subtract point start)
(align-position) (align-position)
(gpt/add start))] (gpt/add start))]
(assoc-in state [:workspace :ruler] [start end])))))) (assoc-in state [:workspace pid :ruler :end] end))))))
(defn update-ruler (defn update-ruler
[point ctrl?] [point ctrl?]
@ -84,7 +86,8 @@
(deftype ClearRuler [] (deftype ClearRuler []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update state :workspace dissoc :ruler))) (let [pid (get-in state [:workspace :current])]
(update-in state [:workspace pid] dissoc :ruler))))
(defn clear-ruler (defn clear-ruler
[] []

View file

@ -11,7 +11,6 @@
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.streams :as streams] [uxbox.main.streams :as streams]
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt])) [uxbox.util.geom.point :as gpt]))

View file

@ -1,131 +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 <niwi@niwi.nz>
(ns uxbox.main.data.workspace.selrect
"Workspace selection rect."
(:require [beicon.core :as rx]
[potok.core :as ptk]
[uxbox.main.store :as st]
[uxbox.main.constants :as c]
[uxbox.main.refs :as refs]
[uxbox.main.streams :as streams]
[uxbox.main.data.shapes :as uds]
[uxbox.main.user-events :as uev]
[uxbox.util.geom.point :as gpt]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare stop-selrect)
(declare update-selrect)
(declare get-selection-stoper)
(declare selection->rect)
(declare translate-to-canvas)
;; --- Start Selrect
(deftype StartSelrect []
ptk/UpdateEvent
(update [_ state]
(let [position @refs/viewport-mouse-position
selection {::start position
::stop position}]
(assoc-in state [:workspace :selrect] (selection->rect selection))))
ptk/WatchEvent
(watch [_ state stream]
(let [stoper (get-selection-stoper stream)]
;; NOTE: the `viewport-mouse-position` can be derived from `stream`
;; but it used from `streams/` ns just for convenience
(rx/concat
(->> streams/viewport-mouse-position
(rx/take-until stoper)
(rx/map update-selrect))
(rx/just (stop-selrect))))))
(defn start-selrect
[]
(StartSelrect.))
;; --- Update Selrect
(deftype UpdateSelrect [position]
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace :selrect ::stop] position)
(update-in [:workspace :selrect] selection->rect))))
(defn update-selrect
[position]
{:pre [(gpt/point? position)]}
(UpdateSelrect. position))
;; --- Clear Selrect
(deftype ClearSelrect []
ptk/UpdateEvent
(update [_ state]
(update state :workspace dissoc :selrect)))
(defn clear-selrect
[]
(ClearSelrect.))
;; --- Stop Selrect
(deftype StopSelrect []
ptk/WatchEvent
(watch [_ state stream]
(let [rect (-> (get-in state [:workspace :selrect])
(translate-to-canvas))]
(rx/of
(clear-selrect)
(uds/deselect-all)
(uds/select-shapes-by-selrect rect)))))
(defn stop-selrect
[]
(StopSelrect.))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Selection Rect Implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- selection->rect
[data]
(let [start (::start data)
stop (::stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
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)))
(defn- get-selection-stoper
[stream]
(->> (rx/merge (rx/filter #(= % ::uev/interrupt) stream)
(rx/filter uev/mouse-up? stream))
(rx/take 1)))
(defn- translate-to-canvas
"Translate the given rect to the canvas coordinates system."
[rect]
(let [zoom @refs/selected-zoom
startx (* c/canvas-start-x zoom)
starty (* c/canvas-start-y zoom)]
(assoc rect
:x1 (/ (- (:x1 rect) startx) zoom)
:y1 (/ (- (:y1 rect) starty) zoom)
:x2 (/ (- (:x2 rect) startx) zoom)
:y2 (/ (- (:y2 rect) starty) zoom))))

View file

@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.workspace.drawing (ns uxbox.main.data.workspace-drawing
"Workspace drawing data events and impl." "Workspace drawing data events and impl."
(:require [beicon.core :as rx] (:require [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
@ -14,6 +14,7 @@
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.streams :as streams] [uxbox.main.streams :as streams]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.shapes :as uds]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom] [uxbox.main.geom :as geom]
[uxbox.main.workers :as uwrk] [uxbox.main.workers :as uwrk]
[uxbox.main.user-events :as uev] [uxbox.main.user-events :as uev]
@ -30,13 +31,14 @@
(deftype SelectForDrawing [shape] (deftype SelectForDrawing [shape]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [current (l/focus ul/selected-drawing state)] (let [pid (get-in state [:workspace :current])
current (l/focus ul/selected-drawing state)]
(if (or (nil? shape) (if (or (nil? shape)
(= shape current)) (= shape current))
(update state :workspace dissoc :drawing :drawing-tool) (update-in state [:workspace pid] dissoc :drawing :drawing-tool)
(update state :workspace assoc (update-in state [:workspace pid] assoc
:drawing shape :drawing shape
:drawing-tool shape))))) :drawing-tool shape)))))
(defn select-for-drawing (defn select-for-drawing
[shape] [shape]
@ -48,7 +50,8 @@
(deftype ClearDrawingState [] (deftype ClearDrawingState []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update state :workspace dissoc :drawing-tool :drawing))) (let [pid (get-in state [:workspace :current])]
(update-in state [:workspace pid] dissoc :drawing-tool :drawing))))
(defn clear-drawing-state (defn clear-drawing-state
[] []
@ -89,12 +92,13 @@
(deftype InitializeDrawing [point] (deftype InitializeDrawing [point]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [shape (get-in state [:workspace :drawing]) (let [pid (get-in state [:workspace :current])
shape (get-in state [:workspace pid :drawing])
shape (geom/setup shape {:x1 (:x point) shape (geom/setup shape {:x1 (:x point)
:y1 (:y point) :y1 (:y point)
:x2 (+ (:x point) 2) :x2 (+ (:x point) 2)
:y2 (+ (:y point) 2)})] :y2 (+ (:y point) 2)})]
(assoc-in state [:workspace :drawing] shape)))) (assoc-in state [:workspace pid :drawing] shape))))
(defn initialize-drawing (defn initialize-drawing
[point] [point]
@ -106,13 +110,14 @@
(deftype UpdateDrawing [position lock?] (deftype UpdateDrawing [position lock?]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [{:keys [id] :as shape} (-> (get-in state [:workspace :drawing]) (let [pid (get-in state [:workspace :current])
{:keys [id] :as shape} (-> (get-in state [:workspace pid :drawing])
(geom/shape->rect-shape) (geom/shape->rect-shape)
(geom/size)) (geom/size))
result (geom/resize-shape :bottom-right shape position lock?) result (geom/resize-shape :bottom-right shape position lock?)
scale (geom/calculate-scale-ratio shape result) scale (geom/calculate-scale-ratio shape result)
resize-mtx (geom/generate-resize-matrix :bottom-right shape scale)] resize-mtx (geom/generate-resize-matrix :bottom-right shape scale)]
(assoc-in state [:workspace :modifiers id] {:resize resize-mtx})))) (assoc-in state [:workspace pid :modifiers id] {:resize resize-mtx}))))
(defn update-drawing (defn update-drawing
[position lock?] [position lock?]
@ -124,15 +129,17 @@
(deftype FinishDrawing [] (deftype FinishDrawing []
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [{:keys [id] :as shape} (get-in state [:workspace :drawing]) (let [pid (get-in state [:workspace :current])
resize-mtx (get-in state [:workspace :modifiers id :resize]) {:keys [id] :as shape} (get-in state [:workspace pid :drawing])
resize-mtx (get-in state [:workspace pid :modifiers id :resize])
shape (cond-> shape shape (cond-> shape
resize-mtx (geom/transform resize-mtx))] resize-mtx (geom/transform resize-mtx))]
(prn "finish-drawing" shape)
(if-not shape (if-not shape
(rx/empty) (rx/empty)
(rx/of (clear-drawing-state) (rx/of (clear-drawing-state)
(uds/add-shape shape) (uds/add-shape shape)
(uds/select-first-shape) (udw/select-first-shape)
::uev/interrupt))))) ::uev/interrupt)))))
(defn finish-drawing (defn finish-drawing
@ -144,7 +151,8 @@
(deftype FinishPathDrawing [] (deftype FinishPathDrawing []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:workspace :drawing :segments] #(vec (butlast %))))) (let [pid (get-in state [:workspace :current])]
(update-in state [:workspace pid :drawing :segments] #(vec (butlast %))))))
(defn finish-path-drawing (defn finish-path-drawing
[] []
@ -155,7 +163,8 @@
(deftype InsertDrawingPathPoint [point] (deftype InsertDrawingPathPoint [point]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:workspace :drawing :segments] (fnil conj []) point))) (let [pid (get-in state [:workspace :current])]
(update-in state [:workspace pid :drawing :segments] (fnil conj []) point))))
(defn insert-drawing-path-point (defn insert-drawing-path-point
[point] [point]
@ -167,10 +176,11 @@
(deftype UpdateDrawingPathPoint [index point] (deftype UpdateDrawingPathPoint [index point]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [segments (count (get-in state [:workspace :drawing :segments])) (let [pid (get-in state [:workspace :current])
segments (count (get-in state [:workspace pid :drawing :segments]))
exists? (< -1 index segments)] exists? (< -1 index segments)]
(cond-> state (cond-> state
exists? (assoc-in [:workspace :drawing :segments index] point))))) exists? (assoc-in [:workspace pid :drawing :segments index] point)))))
(defn update-drawing-path-point (defn update-drawing-path-point
[index point] [index point]
@ -182,7 +192,8 @@
(deftype CloseDrawingPath [] (deftype CloseDrawingPath []
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace :drawing :close?] true)) (let [pid (get-in state [:workspace :current])]
(assoc-in state [:workspace pid :drawing :close?] true)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -197,7 +208,8 @@
(deftype SimplifyDrawingPath [tolerance] (deftype SimplifyDrawingPath [tolerance]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:workspace :drawing :segments] pth/simplify tolerance))) (let [pid (get-in state [:workspace :current])]
(update-in state [:workspace pid :drawing :segments] pth/simplify tolerance))))
(defn simplify-drawing-path (defn simplify-drawing-path
[tolerance] [tolerance]
@ -282,7 +294,7 @@
:y2 (+ y (/ 200 proportion))} :y2 (+ y (/ 200 proportion))}
shape (geom/setup shape props)] shape (geom/setup shape props)]
(st/emit! (uds/add-shape shape) (st/emit! (uds/add-shape shape)
(uds/select-first-shape) (udw/select-first-shape)
(select-for-drawing nil) (select-for-drawing nil)
::uev/interrupt))) ::uev/interrupt)))
@ -298,7 +310,7 @@
:y2 (+ y height)} :y2 (+ y height)}
shape (geom/setup shape props)] shape (geom/setup shape props)]
(st/emit! (uds/add-shape shape) (st/emit! (uds/add-shape shape)
(uds/select-first-shape) (udw/select-first-shape)
(select-for-drawing nil) (select-for-drawing nil)
::uev/interrupt))) ::uev/interrupt)))
@ -401,5 +413,3 @@
(rx/subscribe points on-point) (rx/subscribe points on-point)
(rx/subscribe stream on-draw nil on-finish)))) (rx/subscribe stream on-draw nil on-finish))))

View file

@ -71,7 +71,6 @@
"ds.your-libraries-title" "YOUR LIBRARIES" "ds.your-libraries-title" "YOUR LIBRARIES"
"ds.default-library-title" "Unnamed Collection (%s)" "ds.default-library-title" "Unnamed Collection (%s)"
"ds.recent-colors" "Recent colors"
"ds.element-options" "Element options" "ds.element-options" "Element options"
"ds.draw-tools" "Draw tools" "ds.draw-tools" "Draw tools"
"ds.sitemap" "Sitemap" "ds.sitemap" "Sitemap"

View file

@ -71,7 +71,6 @@
"ds.your-libraries-title" "VOS LIBRAIRIES" "ds.your-libraries-title" "VOS LIBRAIRIES"
"ds.default-library-title" "Collection sans nom (%s)" "ds.default-library-title" "Collection sans nom (%s)"
"ds.recent-colors" "Couleurs récentes"
"ds.element-options" "Options d'élément" "ds.element-options" "Options d'élément"
"ds.draw-tools" "Outils de dessin" "ds.draw-tools" "Outils de dessin"
"ds.sitemap" "Plan du site" "ds.sitemap" "Plan du site"

View file

@ -2,7 +2,7 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2017-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.refs (ns uxbox.main.refs
"A collection of derived refs." "A collection of derived refs."
@ -11,6 +11,8 @@
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.store :as st])) [uxbox.main.store :as st]))
;; TODO: move inside workspaces because this is workspace only refs
;; --- Helpers ;; --- Helpers
(defn resolve-project (defn resolve-project
@ -32,35 +34,19 @@
(filter #(= project (:project %))) (filter #(= project (:project %)))
(sort-by get-order)))) (sort-by get-order))))
(def workspace (def ^:deprecated selected-page
(-> (l/key :workspace)
(l/derive st/state)))
(def selected-project
"Ref to the current selected project."
(-> (l/lens resolve-project)
(l/derive st/state)))
(def selected-project-id
"Ref to the current selected project id."
(-> (l/key :project)
(l/derive selected-project)))
(def selected-project-pages
(-> (l/lens resolve-project-pages)
(l/derive st/state)))
;; DEPRECATED
(def selected-page
"Ref to the current selected page." "Ref to the current selected page."
(-> (l/lens resolve-page) (-> (l/lens resolve-page)
(l/derive st/state))) (l/derive st/state)))
;; DEPRECATED ;; --- NOT DEPRECATED
(def selected-page-id
"Ref to the current selected page id." (def workspace
(-> (l/key :id) (letfn [(selector [state]
(l/derive selected-page))) (let [id (get-in state [:workspace :current])]
(get-in state [:workspace id])))]
(-> (l/lens selector)
(l/derive st/state))))
(def selected-shapes (def selected-shapes
(-> (l/key :selected) (-> (l/key :selected)
@ -74,10 +60,6 @@
(-> (l/key :flags) (-> (l/key :flags)
(l/derive workspace))) (l/derive workspace)))
(def shapes-by-id
(-> (l/key :shapes)
(l/derive st/state)))
(def selected-zoom (def selected-zoom
(-> (l/key :zoom) (-> (l/key :zoom)
(l/derive workspace))) (l/derive workspace)))
@ -109,31 +91,40 @@
(l/derive workspace))) (l/derive workspace)))
(defn alignment-activated? (defn alignment-activated?
[state] [flags]
(let [{:keys [flags]} (:workspace state)] (and (contains? flags :grid-indexed)
(and (contains? flags :grid-indexed) (contains? flags :grid-snap)))
(contains? flags :grid-snap))))
(def selected-alignment (def selected-alignment
(-> (l/lens alignment-activated?) (-> (comp (l/key :flags)
(l/lens alignment-activated?))
(l/derive workspace)))
;; ...
(def mouse-position
(-> (l/in [:workspace :pointer])
(l/derive st/state))) (l/derive st/state)))
(def canvas-mouse-position (def canvas-mouse-position
(-> (l/in [:pointer :canvas]) (-> (l/key :canvas)
(l/derive workspace))) (l/derive mouse-position)))
(def viewport-mouse-position (def viewport-mouse-position
(-> (l/in [:pointer :viewport]) (-> (l/key :viewport)
(l/derive workspace))) (l/derive mouse-position)))
(def window-mouse-position (def window-mouse-position
(-> (l/in [:pointer :window]) (-> (l/key :window)
(l/derive workspace))) (l/derive mouse-position)))
(def workspace-scroll (def workspace-scroll
(-> (l/key :scroll) (-> (l/in [:workspace :scroll])
(l/derive workspace))) (l/derive st/state)))
(def shapes-by-id
(-> (l/key :shapes)
(l/derive st/state)))

View file

@ -13,8 +13,6 @@
[uxbox.main.workers :as uwrk] [uxbox.main.workers :as uwrk]
[uxbox.util.geom.point :as gpt])) [uxbox.util.geom.point :as gpt]))
(def page-id-ref-s (rx/from-atom refs/selected-page-id))
;; --- Events ;; --- Events
(defn- user-interaction-event? (defn- user-interaction-event?

View file

@ -19,11 +19,9 @@
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.auth :as auth] [uxbox.main.ui.auth :as auth]
[uxbox.main.ui.dashboard :as dashboard] [uxbox.main.ui.dashboard :as dashboard]
[uxbox.main.ui.lightbox :refer [lightbox]]
[uxbox.main.ui.loader :refer [loader]]
[uxbox.main.ui.settings :as settings] [uxbox.main.ui.settings :as settings]
[uxbox.main.ui.shapes] [uxbox.main.ui.shapes]
[uxbox.main.ui.workspace :refer [workspace]] [uxbox.main.ui.workspace :refer [workspace-page]]
[uxbox.util.data :refer [parse-int uuid-str?]] [uxbox.util.data :refer [parse-int uuid-str?]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.html.history :as html-history] [uxbox.util.html.history :as html-history]
@ -106,18 +104,20 @@
(:settings/profile (:settings/profile
:settings/password :settings/password
:settings/notifications) :settings/notifications)
(mf/element settings/settings {:route route}) (mf/element settings/settings #js {:route route})
(:dashboard/projects (:dashboard/projects
:dashboard/icons :dashboard/icons
:dashboard/images :dashboard/images
:dashboard/colors) :dashboard/colors)
(mf/element dashboard/dashboard {:route route}) (mf/element dashboard/dashboard #js {:route route})
:workspace/page :workspace/page
(let [project (uuid (get-in route [:params :path :project])) (let [project-id (uuid (get-in route [:params :path :project]))
page (uuid (get-in route [:params :path :page]))] page-id (uuid (get-in route [:params :path :page]))]
[:& workspace {:project project :page page :key page}]) [:& workspace-page {:project-id project-id
:page-id page-id
:key page-id}])
nil nil
)))) ))))

View file

@ -57,7 +57,7 @@
" the projects will be periodicaly wiped."]]) " the projects will be periodicaly wiped."]])
(mf/defc login-form (mf/defc login-form
{:wrap [mf/reactive*]} {:wrap [mf/wrap-reactive]}
[] []
(let [data (mf/react form-data) (let [data (mf/react form-data)
valid? (fm/valid? ::login-form data)] valid? (fm/valid? ::login-form data)]

View file

@ -2,202 +2,19 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.colorpicker (ns uxbox.main.ui.colorpicker
(:require [lentes.core :as l] (:require
[goog.events :as events] [goog.object :as gobj]
[uxbox.util.forms :as sc] [rumext.alpha :as mf]
[rumext.core :as mx :include-macros true] [vendor.react-color]))
[uxbox.util.math :as mth]
[uxbox.util.data :as data]
[uxbox.util.dom :as dom]
[uxbox.util.color :as color])
(:import goog.events.EventType))
;; --- Picker Box (mf/defc colorpicker
[{:keys [on-change value colors] :as props}]
(let [on-change-complete #(on-change (gobj/get % "hex"))]
[:> js/SketchPicker {:color value
:disableAlpha true
:presetColors colors
:onChangeComplete on-change-complete}]))
(mx/defc picker-box
[]
[:svg {:width "100%" :height "100%" :version "1.1"}
[:defs
[:linearGradient {:id "gradient-black"
:x1 "0%" :y1 "100%"
:x2 "0%" :y2 "0%"}
[:stop {:offset "0%" :stopColor "#000000" :stopOpacity "1"}]
[:stop {:offset "100%" :stopColor "#CC9A81" :stopOpacity "0"}]]
[:linearGradient {:id "gradient-white"
:x1 "0%" :y1 "100%"
:x2 "100%" :y2 "100%"}
[:stop {:offset "0%" :stopColor "#FFFFFF" :stopOpacity "1"}]
[:stop {:offset "100%" :stopColor "#CC9A81" :stopOpacity "0"}]]]
[:rect {:x "0" :y "0" :width "100%" :height "100%"
:fill "url(#gradient-white)"}]
[:rect {:x "0" :y "0" :width "100%" :height "100%"
:fill "url(#gradient-black)"}]])
;; --- Slider Box
(mx/defc slider-box
[]
[:svg {:width "100%" :height "100%" :version "1.1"}
[:defs
[:linearGradient {:id "gradient-hsv"
:x1 "0%" :y1 "100%"
:x2 "0%" :y2 "0%"}
[:stop {:offset "0%" :stopColor "#FF0000" :stopOpacity "1"}]
[:stop {:offset "13%" :stopColor "#FF00FF" :stopOpacity "1"}]
[:stop {:offset "25%" :stopColor "#8000FF" :stopOpacity "1"}]
[:stop {:offset "38%" :stopColor "#0040FF" :stopOpacity "1"}]
[:stop {:offset "50%" :stopColor "#00FFFF" :stopOpacity "1"}]
[:stop {:offset "63%" :stopColor "#00FF40" :stopOpacity "1"}]
[:stop {:offset "75%" :stopColor "#0BED00" :stopOpacity "1"}]
[:stop {:offset "88%" :stopColor "#FFFF00" :stopOpacity "1"}]
[:stop {:offset "100%" :stopColor "#FF0000" :stopOpacity "1"}]]]
[:rect {:x 0 :y 0 :width "100%" :height "100%"
:fill "url(#gradient-hsv)"}]])
(def default-dimensions
{:pi-height 5
:pi-width 5
:si-height 10
:p-height 200
:p-width 200
:s-height 200})
(def small-dimensions
{:pi-height 5
:pi-width 5
:si-height 10
:p-height 170
:p-width 170
:s-height 170})
;; --- Color Picker
(defn- on-picker-click
[local dimensions on-change color event]
(let [event (.-nativeEvent event)
my (.-offsetY event)
height (:p-height dimensions)
width (:p-width dimensions)
mx (.-offsetX event)
my (.-offsetY event)
[h] color
s (/ mx width)
v (/ (- height my) height)
hex (color/hsv->hex [h s (* v 255)])]
(swap! local assoc :color [h s (* v 255)])
(on-change hex)))
(defn- on-slide-click
[local dimensions on-change color event]
(let [event (.-nativeEvent event)
my (.-offsetY event)
h (* (/ my (:s-height dimensions)) 360)
hsv [(+ h 15) (second color) (nth color 2)]
hex (color/hsv->hex hsv)]
(swap! local assoc :color hsv)
(on-change hex)))
(mx/defcs colorpicker
{:mixins [mx/static (mx/local)]}
[{:keys [::mx/local] :as own} & {:keys [value on-change theme]
:or {value "#d4edfb" theme :default}}]
(let [value-rgb (color/hex->rgb value)
classes (case theme
:default "theme-default"
:small "theme-small")
dimensions (case theme
:default default-dimensions
:small small-dimensions
default-dimensions)
[h s v :as color] (or (:color @local)
(color/hex->hsv value))
bg (color/hsv->hex [h 1 255])
pit (- (* s (:p-width dimensions))
(/ (:pi-height dimensions) 2))
pil (- (- (:p-height dimensions) (* (/ v 255) (:p-height dimensions)))
(/ (:pi-width dimensions) 2))
sit (- (/ (* (- h 15) (:s-height dimensions)) 360)
(/ (:si-height dimensions) 2))]
(letfn [(on-mouse-down [event]
(swap! local assoc :mousedown true))
(on-mouse-up [event]
(swap! local assoc :mousedown false))
(on-mouse-move-slide [event]
(when (:mousedown @local)
(on-slide-click local dimensions on-change color event)))
(on-mouse-move-picker [event]
(when (:mousedown @local)
(on-picker-click local dimensions on-change color event)))
(on-hex-changed [event]
(let [value (-> (dom/get-target event)
(dom/get-value))]
(when (color/hex? value)
(on-change value))))
(on-rgb-change [rgb id event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(data/parse-int 0))
rgb (assoc rgb id value)
hex (color/rgb->hex rgb)]
(when (color/hex? hex)
(on-change hex))))]
[:div.color-picker {:class classes}
[:div.picker-area
#_[:div.tester {:style {:width "100px" :height "100px"
:border "1px solid black"
:position "fixed" :top "50px" :left "50px"
:backgroundColor (color/hsv->hex color)}}]
[:div.picker-wrapper
[:div.picker
{:ref "picker"
:on-click (partial on-picker-click local dimensions on-change color)
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up
:on-mouse-move on-mouse-move-picker
:style {:backgroundColor bg}}
(picker-box)]
(when-not (:mousedown @local)
[:div.picker-indicator
{:ref "picker-indicator"
:style {:top (str pil "px")
:left (str pit "px")
:pointerEvents "none"}}])]
[:div.slide-wrapper
[:div.slide
{:ref "slide"
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up
:on-mouse-move on-mouse-move-slide
:on-click (partial on-slide-click local dimensions on-change color)}
(slider-box)]
[:div.slide-indicator
{:ref "slide-indicator"
:style {:top (str sit "px")
:pointerEvents "none"}}]]]
[:div.inputs-area
[:input.input-text
{:placeholder "#"
:type "text"
:value value
:on-change on-hex-changed}]
[:div.row-flex
[:input.input-text
{:placeholder "R"
:on-change (partial on-rgb-change value-rgb 0)
:value (nth value-rgb 0)
:type "number"}]
[:input.input-text
{:placeholder "G"
:on-change (partial on-rgb-change value-rgb 1)
:value (nth value-rgb 1)
:type "number"}]
[:input.input-text
{:placeholder "B"
:on-change (partial on-rgb-change value-rgb 2)
:value (nth value-rgb 2)
:type "number"}]]]])))

View file

@ -6,22 +6,22 @@
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.confirm (ns uxbox.main.ui.confirm
(:require [uxbox.main.data.lightbox :as udl] (:require
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[rumext.core :as mx :include-macros true] [rumext.alpha :as mf]
[uxbox.util.i18n :refer (tr)] [uxbox.main.ui.modal :as modal]
[uxbox.util.dom :as dom] [uxbox.util.i18n :refer (tr)]
[uxbox.main.ui.lightbox :as lbx])) [uxbox.util.dom :as dom]))
(mx/defc confirm-dialog (mf/defc confirm-dialog
[{:keys [on-accept on-cancel hint] :as ctx}] [{:keys [on-accept on-cancel hint] :as ctx}]
(letfn [(accept [event] (letfn [(accept [event]
(dom/prevent-default event) (dom/prevent-default event)
(udl/close!) (modal/hide!)
(on-accept (dissoc ctx :on-accept :on-cancel))) (on-accept (dissoc ctx :on-accept :on-cancel)))
(cancel [event] (cancel [event]
(dom/prevent-default event) (dom/prevent-default event)
(udl/close!) (modal/hide!)
(when on-cancel (when on-cancel
(on-cancel (dissoc ctx :on-accept :on-cancel))))] (on-cancel (dissoc ctx :on-accept :on-cancel))))]
[:div.lightbox-body.confirm-dialog [:div.lightbox-body.confirm-dialog
@ -38,9 +38,7 @@
:value (tr "ds.confirm-cancel") :value (tr "ds.confirm-cancel")
:on-click cancel}]] :on-click cancel}]]
[:a.close {:href "#" [:a.close {:href "#"
:on-click #(do (dom/prevent-default %) :on-click #(do
(udl/close!))} i/close]])) (dom/prevent-default %)
(modal/hide!))}
(defmethod lbx/render-lightbox :confirm i/close]]))
[context]
(confirm-dialog context))

View file

@ -19,19 +19,23 @@
(uuid-str? id) (uuid id) (uuid-str? id) (uuid id)
:else nil) :else nil)
type (when (str/alpha? type) (keyword type))] type (when (str/alpha? type) (keyword type))]
{:section (:name data) [(:name data) type id]))
:type type
:id id}))
(mf/defc dashboard (mf/defc dashboard
{:wrap [mf/memo*]}
[{:keys [route] :as props}] [{:keys [route] :as props}]
(let [{:keys [section] :as props} (parse-route route)] (let [[section type id] (parse-route route)]
[:main.dashboard-main [:main.dashboard-main
(messages-widget) (messages-widget)
[:& header props] [:& header {:section section}]
(case section (case section
:dashboard/icons (mf/element icons/icons-page props) :dashboard/icons
:dashboard/images (mf/element images/images-page props) [:& icons/icons-page {:type type :id id}]
:dashboard/projects (mf/element projects/projects-page props)
:dashboard/colors (mf/element colors/colors-page props))])) :dashboard/images
[:& images/images-page {:type type :id id}]
:dashboard/projects
[:& projects/projects-page]
:dashboard/colors
[:& colors/colors-page {:type type :id id}])]))

View file

@ -2,215 +2,208 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.dashboard.colors (ns uxbox.main.ui.dashboard.colors
(:require (:require
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[rumext.core :as mx]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.colors :as dc] [uxbox.main.data.colors :as dc]
[uxbox.main.data.dashboard :as dd]
[uxbox.main.data.lightbox :as udl]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.colorpicker :refer [colorpicker]] [uxbox.main.ui.colorpicker :refer [colorpicker]]
[uxbox.main.ui.dashboard.header :refer [header]] [uxbox.main.ui.confirm :refer [confirm-dialog]]
[uxbox.main.ui.keyboard :as k] [uxbox.main.ui.keyboard :as k]
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.modal :as modal]
[uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.util.color :refer [hex->rgb]] [uxbox.util.color :refer [hex->rgb]]
[uxbox.util.data :refer [seek]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :as t :refer [tr]] [uxbox.util.i18n :as t :refer [tr]]
[uxbox.util.lens :as ul]
[uxbox.util.router :as rt])) [uxbox.util.router :as rt]))
;; --- Refs ;; --- Refs
(def collections-ref (def collections-iref
(-> (l/key :colors-collections) (-> (l/key :colors-collections)
(l/derive st/state))) (l/derive st/state)))
(def opts-ref (def selected-colors-iref
(-> (l/in [:dashboard :colors]) (-> (l/in [:dashboard :colors :selected])
(l/derive st/state))) (l/derive st/state)))
;; --- Colors Modal (Component)
(mf/defc color-modal
[{:keys [on-submit value] :as props}]
(let [local (mf/use-ref value)]
[:div.lightbox-body
[:h3 (tr "ds.color-lightbox.title")]
[:form
[:div.row-flex.center
[:& colorpicker {:value (or @local "#00ccff")
:on-change #(reset! local %)}]]
[:input#project-btn.btn-primary
{:value (tr "ds.color-lightbox.add")
:on-click #(on-submit @local)
:type "button"}]]]))
;; --- Page Title ;; --- Page Title
(mf/def page-title (mf/defc page-title
:mixins [(mf/local) mf/memo mf/reactive] [{:keys [coll] :as props}]
(let [edit? (mf/use-state false)
input (mf/use-ref* nil)
own? (= :own (:type coll))]
(letfn [(save []
(let [dom (mf/ref-node input)
name (dom/get-inner-text dom)
id (:id coll)]
(st/emit! (dc/rename-collection id (str/trim name)))
(reset! edit? false)))
(cancel []
(reset! edit? false))
(edit []
(reset! edit? true))
(on-input-keydown [e]
(cond
(k/esc? e) (cancel)
(k/enter? e)
(do
(dom/prevent-default e)
(dom/stop-propagation e)
(save))))
(delete []
(st/emit! (dc/delete-collection (:id coll))
(rt/nav :dashboard/colors nil {:type (:type coll)})))
:render (on-delete []
(fn [{:keys [::mf/local] :as own} (modal/show! confirm-dialog {:on-accept delete}))]
{:keys [id] :as coll}] [:div.dashboard-title
(let [own? (= :own (:type coll)) [:h2
edit? (:edit @local)] (if @edit?
(letfn [(save [] [:div.dashboard-title-field
(let [dom (mx/ref-node own "input") [:span.edit {:content-editable true
name (dom/get-inner-text dom)] :ref input
(st/emit! (dc/rename-collection id (str/trim name))) :on-key-down on-input-keydown
(swap! local assoc :edit false))) :dangerouslySetInnerHTML {"__html" (:name coll)}}]
(cancel [] [:span.close {:on-click cancel} i/close]]
(swap! local assoc :edit false)) (if own?
(edit [] [:span.dashboard-title-field {:on-double-click edit}
(swap! local assoc :edit true)) (:name coll)]
(on-input-keydown [e] [:span.dashboard-title-field
(cond (:name coll)]))]
(k/esc? e) (cancel) (when (and own? coll)
(k/enter? e) [:div.edition
(do (if @edit?
(dom/prevent-default e) [:span {:on-click save} i/save]
(dom/stop-propagation e) [:span {:on-click edit} i/pencil])
(save)))) [:span {:on-click on-delete} i/trash]])])))
(delete []
(st/emit! (dc/delete-collection id)))
(on-delete []
(udl/open! :confirm {:on-accept delete}))]
[:div.dashboard-title
[:h2
(if edit?
[:div.dashboard-title-field
[:span.edit {:content-editable true
:ref "input"
:on-key-down on-input-keydown}
(:name coll)]
[:span.close {:on-click cancel} i/close]]
(if own?
[:span.dashboard-title-field {:on-double-click edit}
(:name coll)]
[:span.dashboard-title-field
(:name coll)]))]
(when (and own? coll)
[:div.edition
(if edit?
[:span {:on-click save} i/save]
[:span {:on-click edit} i/pencil])
[:span {:on-click on-delete} i/trash]])]))))
;; --- Nav ;; --- Nav
(mf/def nav-item (mf/defc nav-item
:mixins [(mf/local) mf/memo] [{:keys [coll selected?] :as props}]
(let [local (mf/use-state {})
:render {:keys [id type name]} coll
(fn [{:keys [::mf/local] :as own} colors (count (:colors coll))
{:keys [id type name ::selected?] :as coll}] editable? (= type :own)]
(let [colors (count (:colors coll)) (letfn [(on-click [event]
editable? (= type :own)] (let [type (or type :own)]
(letfn [(on-click [event] (st/emit! (rt/nav :dashboard/colors nil {:type type :id id}))))
(let [type (or type :own)] (on-input-change [event]
(st/emit! (rt/navigate :dashboard/colors nil {:type type :id id}))))
(on-input-change [event]
(let [value (dom/get-target event) (let [value (dom/get-target event)
value (dom/get-value value)] value (dom/get-value value)]
(swap! local assoc :name value))) (swap! local assoc :name value)))
(on-cancel [event] (on-cancel [event]
(swap! local dissoc :name) (swap! local dissoc :name)
(swap! local dissoc :edit)) (swap! local dissoc :edit))
(on-double-click [event] (on-double-click [event]
(when editable? (when editable?
(swap! local assoc :edit true))) (swap! local assoc :edit true)))
(on-input-keyup [event] (on-input-keyup [event]
(when (k/enter? event) (when (k/enter? event)
(let [value (dom/get-target event) (let [value (dom/get-target event)
value (dom/get-value value)] value (dom/get-value value)]
(st/emit! (dc/rename-collection id (str/trim (:name @local)))) (st/emit! (dc/rename-collection id (str/trim (:name @local))))
(swap! local assoc :edit false))))] (swap! local assoc :edit false))))]
[:li {:on-click on-click [:li {:on-click on-click
:on-double-click on-double-click :on-double-click on-double-click
:class-name (when selected? "current")} :class-name (when selected? "current")}
(if (:edit @local) (if (:edit @local)
[:div [:div
[:input.element-title [:input.element-title
{:value (if (:name @local) (:name @local) name) {:value (if (:name @local) (:name @local) name)
:on-change on-input-change :on-change on-input-change
:on-key-down on-input-keyup}] :on-key-down on-input-keyup}]
[:span.close {:on-click on-cancel} i/close]] [:span.close {:on-click on-cancel} i/close]]
[:span.element-title name]) [:span.element-title name])
[:span.element-subtitle [:span.element-subtitle
(tr "ds.num-elements" (t/c colors))]])))) (tr "ds.num-elements" (t/c colors))]])))
(mf/def nav (mf/defc nav
:mixins [mf/memo mf/reactive] [{:keys [id type colls selected-coll] :as props}]
(let [own? (= type :own)
:render builtin? (= type :builtin)
(fn [own {:keys [id type] :as props}] select-tab #(st/emit! (rt/nav :dashboard/colors nil {:type %}))]
(let [own? (= type :own) [:div.library-bar
builtin? (= type :builtin) [:div.library-bar-inside
colls (mf/react collections-ref) [:ul.library-tabs
select-tab (fn [type] [:li {:class-name (when own? "current")
(if-let [coll (->> (vals colls) :on-click (partial select-tab :own)}
(filter #(= type (:type %))) (tr "ds.your-colors-title")]
(sort-by :created-at) [:li {:class-name (when builtin? "current")
(first))] :on-click (partial select-tab :builtin)}
(st/emit! (rt/nav :dashboard/colors nil {:type type :id (:id coll)})) (tr "ds.store-colors-title")]]
(st/emit! (rt/nav :dashboard/colors nil {:type type}))))] [:ul.library-elements
(when own?
[:div.library-bar [:li
[:div.library-bar-inside [:a.btn-primary {:on-click #(st/emit! (dc/create-collection))}
[:ul.library-tabs (tr "ds.colors-collection.new")]])
[:li {:class-name (when own? "current") (for [item colls]
:on-click (partial select-tab :own)} (let [selected? (= (:id item) (:id selected-coll))]
(tr "ds.your-colors-title")] [:& nav-item {:coll item :selected? selected? :key (:id item)}]))]]]))
[:li {:class-name (when builtin? "current")
:on-click (partial select-tab :builtin)}
(tr "ds.store-colors-title")]]
[:ul.library-elements
(when own?
[:li
[:a.btn-primary {:on-click #(st/emit! (dc/create-collection))}
(tr "ds.colors-collection.new")]])
(for [coll (cond->> (vals colls)
own? (filter #(= :own (:type %)))
builtin? (filter #(= :builtin (:type %)))
true (sort-by :created-at))]
(let [selected? (= (:id coll) id)]
(nav-item (assoc coll ::selected? selected?))))]]])))
;; --- Grid ;; --- Grid
(mx/defc grid-form (mf/defc grid-form
[coll-id] [{:keys [id] :as props}]
[:div.grid-item.small-item.add-project (letfn [(on-submit [val]
{:on-click #(udl/open! :color-form {:coll coll-id})} (st/emit! (dc/add-color id val))
[:span (tr "ds.color-new")]]) (modal/hide!))
(on-click [event]
(modal/show! color-modal {:on-submit on-submit}))]
[:div.grid-item.small-item.add-project {:on-click on-click}
[:span (tr "ds.color-new")]]))
(mf/def grid-options-tooltip (mf/defc grid-options-tooltip
:mixins [mf/reactive mf/memo] [{:keys [selected on-select title] :as props}]
{:pre [(uuid? selected)
(fn? on-select)
(string? title)]}
(let [colls (mf/deref collections-iref)
colls (->> (vals colls)
(filter #(= :own (:type %)))
(remove #(= selected (:id %)))
(sort-by :name colls))
on-select (fn [event id]
(dom/prevent-default event)
(dom/stop-propagation event)
(on-select id))]
[:ul.move-list
[:li.title title]
(for [{:keys [id name] :as coll} colls]
[:li {:key (str id)}
[:a {:on-click #(on-select % id)} name]])]))
:render (mf/defc grid-options
(fn [own {:keys [selected on-select title]}] [{:keys [id type coll selected] :as props}]
{:pre [(uuid? selected) (let [local (mf/use-state {})]
(fn? on-select)
(string? title)]}
(let [colls (mf/react collections-ref)
colls (->> (vals colls)
(filter #(= :own (:type %)))
(remove #(= selected (:id %)))
(sort-by :name colls))
on-select (fn [event id]
(dom/prevent-default event)
(dom/stop-propagation event)
(on-select id))]
[:ul.move-list
[:li.title title]
(for [{:keys [id name] :as coll} colls]
[:li {:key (str id)}
[:a {:on-click #(on-select % id)} name]])])))
(mf/def grid-options
:mixins [mf/memo (mf/local)]
:render
(fn [{:keys [::mf/local] :as own}
{:keys [type id] :as coll}]
(letfn [(delete [event] (letfn [(delete [event]
(st/emit! (dc/delete-selected-colors))) (st/emit! (dc/delete-colors id selected)))
(on-delete [event] (on-delete [event]
(udl/open! :confirm {:on-accept delete})) (modal/show! confirm-dialog {:on-accept delete}))
(on-toggle-copy [event] (on-toggle-copy [event]
(swap! local update :show-copy-tooltip not) (swap! local update :show-copy-tooltip not)
(swap! local assoc :show-move-tooltip false)) (swap! local assoc :show-move-tooltip false))
@ -237,17 +230,17 @@
{:alt (tr "ds.multiselect-bar.copy") {:alt (tr "ds.multiselect-bar.copy")
:on-click on-toggle-copy} :on-click on-toggle-copy}
(when (:show-copy-tooltip @local) (when (:show-copy-tooltip @local)
(grid-options-tooltip {:selected id [:& grid-options-tooltip {:selected id
:title (tr "ds.multiselect-bar.copy-to-library") :title (tr "ds.multiselect-bar.copy-to-library")
:on-select on-copy})) :on-select on-copy}])
i/copy] i/copy]
[:span.move-item.tooltip.tooltip-top [:span.move-item.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.move") {:alt (tr "ds.multiselect-bar.move")
:on-click on-toggle-move} :on-click on-toggle-move}
(when (:show-move-tooltip @local) (when (:show-move-tooltip @local)
(grid-options-tooltip {:selected id [:& grid-options-tooltip {:selected id
:title (tr "ds.multiselect-bar.move-to-library") :title (tr "ds.multiselect-bar.move-to-library")
:on-select on-move})) :on-select on-move}])
i/move] i/move]
[:span.delete.tooltip.tooltip-top [:span.delete.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.delete") {:alt (tr "ds.multiselect-bar.delete")
@ -260,114 +253,76 @@
{:alt (tr "ds.multiselect-bar.copy") {:alt (tr "ds.multiselect-bar.copy")
:on-click on-toggle-copy} :on-click on-toggle-copy}
(when (:show-copy-tooltip @local) (when (:show-copy-tooltip @local)
(grid-options-tooltip {:selected id [:& grid-options-tooltip {:selected id
:title (tr "ds.multiselect-bar.copy-to-library") :title (tr "ds.multiselect-bar.copy-to-library")
:on-select on-copy})) :on-select on-copy}])
i/organize]])]))) i/organize]])])))
(mf/def grid-item (mf/defc grid-item
:key-fn :color [{:keys [color selected?] :as props}]
:mixins [mf/memo] (letfn [(toggle-selection [event]
(st/emit! (dc/toggle-color-selection color)))]
[:div.grid-item.small-item.project-th {:on-click toggle-selection}
[:span.color-swatch {:style {:background-color color}}]
[:div.input-checkbox.check-primary
[:input {:type "checkbox"
:id color
:on-change toggle-selection
:checked selected?}]
[:label {:for color}]]
[:span.color-data color]
[:span.color-data (apply str "RGB " (interpose ", " (hex->rgb color)))]]))
:render (mf/defc grid
(fn [own {:keys [color selected?] :as props}] [{:keys [id type coll selected] :as props}]
(letfn [(toggle-selection [event] (let [{:keys [colors]} coll
(st/emit! (dc/toggle-color-selection color)))] editable? (= :own type)
[:div.grid-item.small-item.project-th {:on-click toggle-selection} colors (->> (remove nil? colors)
[:span.color-swatch {:style {:background-color color}}] (sort-by identity))]
[:div.input-checkbox.check-primary [:div.dashboard-grid-content
[:input {:type "checkbox" [:div.dashboard-grid-row
:id color (when (and editable? id)
:on-change toggle-selection [:& grid-form {:id id}])
:checked selected?}] (for [color colors]
[:label {:for color}]] (let [selected? (contains? selected color)]
[:span.color-data color] [:& grid-item {:color color :selected? selected? :key color}]))]]))
[:span.color-data (apply str "RGB " (interpose ", " (hex->rgb color)))]])))
(mf/def grid (mf/defc content
:mixins [mf/memo] [{:keys [id type coll] :as props}]
(let [selected (mf/deref selected-colors-iref)]
:render [:section.dashboard-grid.library
(fn [own {:keys [selected ::coll] :as props}] [:& page-title {:coll coll}]
(let [{:keys [id type colors]} coll [:& grid {:coll coll :id id :type type :selected selected}]
editable? (or (= :own type) (nil? id)) (when (seq selected)
colors (->> (remove nil? colors) [:& grid-options {:id id :type type
(sort-by identity))] :selected selected
[:div.dashboard-grid-content :coll coll}])]))
[:div.dashboard-grid-row
(when editable? (grid-form props))
(for [color colors]
(let [selected? (contains? selected color)]
(grid-item {:color color :selected? selected?})))]])))
(mf/def content
:mixins [mf/reactive mf/memo]
:init
(fn [own {:keys [id] :as props}]
(assoc own ::coll-ref (-> (l/in [:colors-collections id])
(l/derive st/state))))
:render
(fn [own props]
(let [opts (mf/react opts-ref)
coll (mf/react (::coll-ref own))
props (merge opts props)]
[:section.dashboard-grid.library
(page-title coll)
(grid (assoc props ::coll coll))
(when (seq (:selected opts))
(grid-options props))])))
;; --- Colors Page ;; --- Colors Page
(mf/def colors-page (mf/defc colors-page
:key-fn identity [{:keys [id type] :as props}]
:mixins #{mf/memo mf/reactive} (let [type (or type :own)
:init colls (mf/deref collections-iref)
(fn [own props] colls (cond->> (vals colls)
(let [{:keys [type id]} (::mf/props own)] (= type :own) (filter #(= :own (:type %)))
(st/emit! (dc/initialize type id)) (= type :builtin) (filter #(= :builtin (:type %)))
own)) true (sort-by :created-at))
selected-coll (if id
(seek #(= id (:id %)) colls)
(first colls))
id (:id selected-coll)]
:render (mf/use-effect {:init #(st/emit! (dc/initialize)) :deps #js [id type]})
(fn [own {:keys [type] :as props}] (mf/use-effect {:init #(st/emit! (dc/fetch-collections))})
(let [type (or type :own)
props (assoc props :type type)]
[:section.dashboard-content
(nav props)
(content props)])))
;; --- Colors Lightbox (Component) [:section.dashboard-content
[:& nav {:type type
:id id
:colls colls
:selected-coll selected-coll}]
[:& content {:type type
:id id
:coll selected-coll}]]))
(mx/defcs color-lightbox
{:mixins [(mx/local {}) mx/static]}
[{:keys [::mx/local]} {:keys [coll color] :as params}]
(letfn [(on-submit [event]
(let [params {:id coll
:from color
:to (:hex @local)}]
(st/emit! (dc/replace-color params))
(udl/close!)))
(on-change [event]
(let [value (str/trim (dom/event->value event))]
(swap! local assoc :hex value)))
(on-close [event]
(udl/close!))]
[:div.lightbox-body
[:h3 (tr "ds.color-lightbox.title")]
[:form
[:div.row-flex.center
(colorpicker
:value (or (:hex @local) color "#00ccff")
:on-change #(swap! local assoc :hex %))]
[:input#project-btn.btn-primary {:value (tr "ds.color-lightbox.add")
:on-click on-submit
:type "button"}]]
[:a.close {:on-click on-close} i/close]]))
(defmethod lbx/render-lightbox :color-form
[params]
(color-lightbox params))

View file

@ -73,9 +73,9 @@
(l/lens #(-> % vals count))) (l/lens #(-> % vals count)))
(l/derive st/state)))) (l/derive st/state))))
:render :render
(fn [own props] (fn [own {:keys [opts] :as props}]
(let [ordering (:order props :created) (let [ordering (:order opts :created)
filtering (:filter props "") filtering (:filter opts "")
num-projects (mf/react (::num-projects own))] num-projects (mf/react (::num-projects own))]
(letfn [(on-term-change [event] (letfn [(on-term-change [event]
(let [term (-> (dom/get-target event) (let [term (-> (dom/get-target event)
@ -119,7 +119,7 @@
[{:keys [project] :as props}] [{:keys [project] :as props}]
(let [url (mf/use-state nil)] (let [url (mf/use-state nil)]
(mf/use-effect (mf/use-effect
{:watch (:page-id project) {:deps #js [(:page-id project)]
:init (fn [] :init (fn []
(when-let [page-id (:page-id project)] (when-let [page-id (:page-id project)]
(let [svg (exports/render-page page-id) (let [svg (exports/render-page page-id)
@ -141,6 +141,7 @@
;; --- Grid Item ;; --- Grid Item
(mf/defc grid-item (mf/defc grid-item
{:wrap [mf/wrap-memo]}
[{:keys [project] :as props}] [{:keys [project] :as props}]
(let [local (mf/use-state {}) (let [local (mf/use-state {})
on-navigate #(st/emit! (udp/go-to (:id project))) on-navigate #(st/emit! (udp/go-to (:id project)))
@ -188,9 +189,11 @@
;; --- Grid ;; --- Grid
(mf/defc grid (mf/defc grid
{:wrap [mf/reactive*]} [{:keys [opts] :as props}]
[{:keys [order filter] :or {order :created filter ""} :as props}] (let [order (:order opts :created)
(let [projects (->> (vals (mf/deref projects-ref)) filter (:filter opts "")
projects (mf/deref projects-ref)
projects (->> (vals projects)
(filter-projects-by filter) (filter-projects-by filter)
(sort-projects-by order)) (sort-projects-by order))
on-click #(do on-click #(do
@ -208,13 +211,10 @@
;; --- Projects Page ;; --- Projects Page
(mf/defc projects-page (mf/defc projects-page
{:wrap [mf/reactive*]} [_]
[props] (mf/use-effect
(let [opts (mf/deref opts-ref) {:init #(st/emit! (udp/initialize))})
props (merge opts props)] (let [opts (mf/deref opts-ref)]
(mf/use-effect
{:init #(st/emit! (udp/initialize))})
[:section.dashboard-content [:section.dashboard-content
[:& menu props] [:& menu {:opts opts}]
[:& grid props]])) [:& grid {:opts opts}]]))

View file

@ -1,4 +1,5 @@
(ns uxbox.main.ui.lightbox (ns uxbox.main.ui.lightbox
"DEPRECATED: should be replaced by uxbox.main.ui.modal"
(:require (:require
[goog.events :as events] [goog.events :as events]
[lentes.core :as l] [lentes.core :as l]

View file

@ -0,0 +1,56 @@
(ns uxbox.main.ui.modal
(:require
[goog.events :as events]
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as k]
[uxbox.util.data :refer [classnames]]
[uxbox.util.dom :as dom])
(:import goog.events.EventType))
(defonce state (atom nil))
(defn show!
[component props]
(reset! state {:component component :props props}))
(defn hide!
[]
(reset! state nil))
(defn- on-esc-clicked
[event]
(when (k/esc? event)
(reset! state nil)
(dom/stop-propagation event)))
(defn- on-parent-clicked
[event parent-ref]
(let [parent (mf/ref-node parent-ref)
current (dom/get-target event)]
(when (dom/equals? parent current)
(reset! state nil)
#_(st/emit! (udl/hide-lightbox)))))
(mf/defc modal-wrapper
[{:keys [component props]}]
(mf/use-effect
{:init #(events/listen js/document EventType.KEYDOWN on-esc-clicked)
:end #(events/unlistenByKey %)})
(let [classes (classnames :transparent (:transparent? props))
parent-ref (mf/use-ref* nil)]
[:div.lightbox {:class classes
:ref parent-ref
:on-click #(on-parent-clicked % parent-ref)}
(mf/element component props)]))
(mf/defc modal
[_]
(when-let [{:keys [component props]} (mf/deref state)]
[:& modal-wrapper {:component component
:props props
:key (random-uuid)}]))

View file

@ -18,7 +18,7 @@
[uxbox.main.ui.settings.profile :as profile])) [uxbox.main.ui.settings.profile :as profile]))
(mf/defc settings (mf/defc settings
{:wrap [mf/memo*]} {:wrap [mf/wrap-memo]}
[{:keys [route] :as props}] [{:keys [route] :as props}]
(let [section (get-in route [:data :name])] (let [section (get-in route [:data :name])]
[:main.dashboard-main [:main.dashboard-main

View file

@ -113,7 +113,7 @@
;; --- Profile Photo Form ;; --- Profile Photo Form
(mf/defc profile-photo-form (mf/defc profile-photo-form
{:wrap [mf/reactive*]} {:wrap [mf/wrap-reactive]}
[] []
(letfn [(on-change [event] (letfn [(on-change [event]
(let [target (dom/get-target event) (let [target (dom/get-target event)

View file

@ -5,7 +5,55 @@
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.shapes (ns uxbox.main.ui.shapes
(:require [uxbox.main.ui.shapes.group :as group])) (:require
[lentes.core :as l]
[rumext.alpha :as mf]
[rumext.core :as mx]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.common :as common]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.shapes.image :as image]
[uxbox.main.ui.shapes.path :as path]
[uxbox.main.ui.shapes.rect :as rect]
[uxbox.main.ui.shapes.text :as text]
[uxbox.util.data :refer [classnames]]
[uxbox.util.geom.matrix :as gmt]))
;; (def render-component group/render-component)
;; (def shape group/component-container)
(defn render-shape
[shape]
(case (:type shape)
;; :group (group-component shape)
:text (text/text-component shape)
:icon (icon/icon-component shape)
:rect (rect/rect-component shape)
:path (path/path-component shape)
:image (image/image-component shape)
:circle (circle/circle-component shape)))
(mf/def shape-container
:mixins [mf/reactive mf/memo]
:init
(fn [own {:keys [id] :as props}]
(assoc own ::shape-ref (-> (l/in [:shapes id])
(l/derive st/state))))
:render
(fn [own {:keys [id] :as props}]
(when-let [shape (mf/react (::shape-ref own))]
(when-not (:hidden shape)
(render-shape shape)))))
;; NOTE: temporal workaround
(mx/defc shape
[id]
(mf/element shape-container {:id id}))
(def render-component group/render-component)
(def shape group/component-container)

View file

@ -2,43 +2,15 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.shapes.common (ns uxbox.main.ui.shapes.common
(:require [lentes.core :as l] (:require
[beicon.core :as rx] [uxbox.main.data.workspace :as udw]
[potok.core :as ptk] [uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.refs :as refs] [uxbox.main.ui.keyboard :as kbd]
[uxbox.main.streams :as streams] [uxbox.util.dom :as dom]))
[uxbox.main.geom :as geom]
[uxbox.main.user-events :as uev]
[uxbox.main.data.shapes :as uds]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.util.dom :as dom]))
;; --- Movement
;; TODO: implement in the same way as drawing (move under uxbox.main.data.workspace.)
(defn start-move
[]
(letfn [(on-move [shape delta]
(st/emit! (uds/apply-temporal-displacement shape delta)))
(on-stop [shape]
(st/emit! (uds/apply-displacement shape)))
(on-start [shape]
(let [stoper (->> streams/events
(rx/filter uev/mouse-up?)
(rx/take 1))
stream (->> streams/mouse-position-deltas
(rx/take-until stoper))
on-move (partial on-move shape)
on-stop (partial on-stop shape)]
(when @refs/selected-alignment
(st/emit! (uds/initial-shape-align shape)))
(rx/subscribe stream on-move nil on-stop)))]
(run! on-start @refs/selected-shapes)))
;; --- Events ;; --- Events
@ -48,27 +20,24 @@
drawing? @refs/selected-drawing-tool] drawing? @refs/selected-drawing-tool]
(when-not (:blocked shape) (when-not (:blocked shape)
(cond (cond
(or drawing? drawing?
(and group (:locked (geom/resolve-parent shape))))
nil nil
(and (not selected?) (empty? selected)) (and (not selected?) (empty? selected))
(do (do
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (uds/select-shape id)) (st/emit! (udw/select-shape id)
(start-move)) (udw/start-move-selected)))
(and (not selected?) (not (empty? selected))) (and (not selected?) (not (empty? selected)))
(do (do
(dom/stop-propagation event) (dom/stop-propagation event)
(if (kbd/shift? event) (if (kbd/shift? event)
(st/emit! (uds/select-shape id)) (st/emit! (udw/select-shape id))
(do (st/emit! (udw/deselect-all)
(st/emit! (uds/deselect-all) (udw/select-shape id)
(uds/select-shape id)) (udw/start-move-selected))))
(start-move))))
:else :else
(do (do
(dom/stop-propagation event) (dom/stop-propagation event)
(start-move)))))) (st/emit! (udw/start-move-selected)))))))

View file

@ -2,24 +2,25 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.shapes.group (ns uxbox.main.ui.shapes.group
(:require [lentes.core :as l] (:require
[uxbox.main.store :as st] [lentes.core :as l]
[uxbox.main.geom :as geom] [rumext.core :as mx]
[uxbox.main.refs :as refs] [uxbox.main.geom :as geom]
[uxbox.main.ui.shapes.common :as common] [uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs] [uxbox.main.store :as st]
[uxbox.main.ui.shapes.icon :as icon] [uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.rect :as rect] [uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.circle :as circle] [uxbox.main.ui.shapes.common :as common]
[uxbox.main.ui.shapes.text :as text] [uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.shapes.path :as path] [uxbox.main.ui.shapes.image :as image]
[uxbox.main.ui.shapes.image :as image] [uxbox.main.ui.shapes.path :as path]
[uxbox.util.data :refer [classnames]] [uxbox.main.ui.shapes.rect :as rect]
[uxbox.util.geom.matrix :as gmt] [uxbox.main.ui.shapes.text :as text]
[rumext.core :as mx :include-macros true])) [uxbox.util.data :refer [classnames]]
[uxbox.util.geom.matrix :as gmt]))
;; --- Helpers ;; --- Helpers

View file

@ -1,290 +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 <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.shapes.selection
"Multiple selection handlers component."
(:require [lentes.core :as l]
[beicon.core :as rx]
[potok.core :as ptk]
[uxbox.main.store :as st]
[uxbox.main.constants :as c]
[uxbox.main.refs :as refs]
[uxbox.main.streams :as streams]
[uxbox.main.workers :as uwrk]
[uxbox.main.user-events :as uev]
[uxbox.main.data.shapes :as uds]
[uxbox.main.ui.shapes.common :as scommon]
[uxbox.main.geom :as geom]
[rumext.core :as mx]
[rumext.alpha :as mf]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.dom :as dom]))
;; --- Refs & Constants
(def ^:private +circle-props+
{:r 6
:style {:fillOpacity "1"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"}
:fill "#31e6e0"
:stroke "#28c4d4"})
(defn- focus-selected-shapes
[state]
(let [selected (get-in state [:workspace :selected])]
(mapv #(get-in state [:shapes %]) selected)))
(def ^:private selected-shapes-ref
"A customized version of `refs/selected-shapes` that
additionally resolves the shapes to the real object
instead of just return a set of ids."
(-> (l/lens focus-selected-shapes)
(l/derive st/state)))
(def ^:private selected-modifers-ref
"A customized version of `refs/selected-modifiers`
that instead of focus to one concrete id, it focuses
on the whole map."
(-> (l/key :modifiers)
(l/derive refs/workspace)))
;; --- Resize Implementation
(defn- start-resize
[vid ids shape]
(letfn [(on-resize [shape [point lock?]]
(let [result (geom/resize-shape vid shape point lock?)
scale (geom/calculate-scale-ratio shape result)
mtx (geom/generate-resize-matrix vid shape scale)
xfm (map #(uds/apply-temporal-resize % mtx))]
(apply st/emit! (sequence xfm ids))))
(on-end []
(apply st/emit! (map uds/apply-resize ids)))
;; Unifies the instantaneous proportion lock modifier
;; activated by Ctrl key and the shapes own proportion
;; lock flag that can be activated on element options.
(normalize-proportion-lock [[point ctrl?]]
(let [proportion-lock? (:proportion-lock shape)]
[point (or proportion-lock? ctrl?)]))
;; Applies alginment to point if it is currently
;; activated on the current workspace
(apply-grid-alignment [point]
(if @refs/selected-alignment
(uwrk/align-point point)
(rx/of point)))
;; Apply the current zoom factor to the point.
(apply-zoom [point]
(gpt/divide point @refs/selected-zoom))]
(let [shape (->> (geom/shape->rect-shape shape)
(geom/size))
stoper (->> streams/events
(rx/filter uev/mouse-up?)
(rx/take 1))
stream (->> streams/canvas-mouse-position
(rx/take-until stoper)
(rx/map apply-zoom)
(rx/mapcat apply-grid-alignment)
(rx/with-latest vector streams/mouse-position-ctrl)
(rx/map normalize-proportion-lock))]
(rx/subscribe stream (partial on-resize shape) nil on-end))))
;; --- Controls (Component)
(def ^:private handler-size-threshold
"The size in pixels that shape width or height
should reach in order to increase the handler
control pointer radius from 4 to 6."
60)
(mx/defc control-item
[{:keys [class on-mouse-down r cy cx]}]
[:circle {:class-name class
:on-mouse-down on-mouse-down
:r r
:style {:fillOpacity "1"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"}
:fill "#31e6e0"
:stroke "#28c4d4"
:cx cx
:cy cy}])
(mx/defc controls
[{:keys [x1 y1 width height] :as shape} zoom on-mouse-down]
(let [radius (if (> (max width height) handler-size-threshold) 6.0 4.0)]
[:g.controls
[:rect.main {:x x1 :y y1
:width width
:height height
:stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom))
:style {:stroke "#333" :fill "transparent"
:stroke-opacity "1"}}]
(control-item {:class "top"
:on-mouse-down #(on-mouse-down :top %)
:r (/ radius zoom)
:cx (+ x1 (/ width 2))
:cy (- y1 2)})
(control-item {:on-mouse-down #(on-mouse-down :right %)
:r (/ radius zoom)
:cy (+ y1 (/ height 2))
:cx (+ x1 width 1)
:class "right"})
(control-item {:on-mouse-down #(on-mouse-down :bottom %)
:r (/ radius zoom)
:cx (+ x1 (/ width 2))
:cy (+ y1 height 2)
:class "bottom"})
(control-item {:on-mouse-down #(on-mouse-down :left %)
:r (/ radius zoom)
:cy (+ y1 (/ height 2))
:cx (- x1 3)
:class "left"})
(control-item {:on-mouse-down #(on-mouse-down :top-left %)
:r (/ radius zoom)
:cx x1
:cy y1
:class "top-left"})
(control-item {:on-mouse-down #(on-mouse-down :top-right %)
:r (/ radius zoom)
:cx (+ x1 width)
:cy y1
:class "top-right"})
(control-item {:on-mouse-down #(on-mouse-down :bottom-left %)
:r (/ radius zoom)
:cx x1
:cy (+ y1 height)
:class "bottom-left"})
(control-item {:on-mouse-down #(on-mouse-down :bottom-right %)
:r (/ radius zoom)
:cx (+ x1 width)
:cy (+ y1 height)
:class "bottom-right"})]))
;; --- Selection Handlers (Component)
(defn get-path-edition-stoper
[stream]
(letfn [(stoper-event? [{:keys [type shift] :as event}]
(and (uev/mouse-event? event) (= type :up)))]
(rx/merge
(rx/filter stoper-event? stream)
(->> stream
(rx/filter #(= % ::uev/interrupt))
(rx/take 1)))))
(defn start-path-edition
[shape-id index]
(letfn [(on-move [delta]
(st/emit! (uds/update-path shape-id index delta)))]
(let [stoper (get-path-edition-stoper streams/events)
stream (rx/take-until stoper streams/mouse-position-deltas)]
(when @refs/selected-alignment
(st/emit! (uds/initial-path-point-align shape-id index)))
(rx/subscribe stream on-move))))
(mx/defc path-edition-selection-handlers
[{:keys [id segments modifiers] :as shape} zoom]
(letfn [(on-mouse-down [index event]
(dom/stop-propagation event)
(start-path-edition id index))]
(let [{:keys [displacement]} modifiers
segments (if displacement
(map #(gpt/transform % displacement) segments)
segments)]
[:g.controls
(for [[index {:keys [x y]}] (map-indexed vector segments)]
[:circle {:cx x :cy y
:r (/ 6.0 zoom)
:key index
:on-mouse-down (partial on-mouse-down index)
:fill "#31e6e0"
:stroke "#28c4d4"
:style {:cursor "pointer"}}])])))
(mx/defc multiple-selection-handlers
{:mixins [mx/static]}
[[shape & rest :as shapes] modifiers zoom]
(let [selection (->> shapes
(map #(assoc % :modifiers (get modifiers (:id %))))
(map #(geom/selection-rect %))
(geom/shapes->rect-shape)
(geom/selection-rect))
shape (geom/shapes->rect-shape shapes)
on-click #(do (dom/stop-propagation %2)
(start-resize %1 (map :id shapes) shape))]
(controls selection zoom on-click)))
(mx/defc single-selection-handlers
[{:keys [id] :as shape} zoom]
(let [on-click #(do (dom/stop-propagation %2)
(start-resize %1 #{id} shape))
shape (geom/selection-rect shape)]
(controls shape zoom on-click)))
(mx/defc text-edition-selection-handlers
{:mixins [mx/static]}
[{:keys [id] :as shape} zoom]
(let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)]
[:g.controls
[:rect.main {:x x1 :y y1
:width width
:height height
;; :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom))
:style {:stroke "#333"
:stroke-width "0.5"
:stroke-opacity "0.5"
:fill "transparent"}}]]))
(mf/def selection-handlers
:mixins [mf/reactive mf/memo]
:render
(fn [own props]
(let [shapes (mf/react selected-shapes-ref)
modifiers (mf/react selected-modifers-ref)
;; Edition is a workspace global flag
;; because only one shape can be on
;; the edition mode.
edition? (mf/react refs/selected-edition)
zoom (mf/react refs/selected-zoom)
num (count shapes)
{:keys [id type] :as shape} (first shapes)]
(cond
(zero? num)
nil
(> num 1)
(multiple-selection-handlers shapes modifiers zoom)
(and (= type :text) edition?)
(-> (assoc shape :modifiers (get modifiers id))
(text-edition-selection-handlers zoom))
(= type :path)
(if (= edition? (:id shape))
(-> (assoc shape :modifiers (get modifiers id))
(path-edition-selection-handlers zoom))
(-> (assoc shape :modifiers (get modifiers id))
(single-selection-handlers zoom)))
:else
(-> (assoc shape :modifiers (get modifiers id))
(single-selection-handlers zoom))))))

View file

@ -49,7 +49,7 @@
(l/derive st/state))) (l/derive st/state)))
(mf/defc user (mf/defc user
{:wrap [mf/reactive*]} {:wrap [mf/wrap-reactive]}
[_] [_]
(let [open (mf/use-state false) (let [open (mf/use-state false)
profile (mf/react profile-ref) profile (mf/react profile-ref)

View file

@ -22,14 +22,14 @@
[uxbox.main.ui.confirm] [uxbox.main.ui.confirm]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.workspace.canvas :refer [viewport]] [uxbox.main.ui.workspace.viewport :refer [viewport]]
[uxbox.main.ui.workspace.colorpalette :refer [colorpalette]] [uxbox.main.ui.workspace.colorpalette :refer [colorpalette]]
[uxbox.main.ui.workspace.download] [uxbox.main.ui.workspace.download]
[uxbox.main.ui.workspace.header :refer [header]] [uxbox.main.ui.workspace.header :refer [header]]
[uxbox.main.ui.workspace.images] [uxbox.main.ui.workspace.images]
[uxbox.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]] [uxbox.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
[uxbox.main.ui.workspace.scroll :as scroll] [uxbox.main.ui.workspace.scroll :as scroll]
[uxbox.main.ui.workspace.shortcuts :refer [shortcuts-mixin]] [uxbox.main.ui.workspace.shortcuts :as shortcuts]
[uxbox.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]] [uxbox.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
[uxbox.main.ui.workspace.sidebar.history :refer [history-dialog]] [uxbox.main.ui.workspace.sidebar.history :refer [history-dialog]]
[uxbox.main.user-events :as uev] [uxbox.main.user-events :as uev]
@ -47,10 +47,10 @@
(st/emit! (uev/scroll-event (gpt/point left top))))) (st/emit! (uev/scroll-event (gpt/point left top)))))
(defn- on-wheel (defn- on-wheel
[own event] [event canvas]
(when (kbd/ctrl? event) (when (kbd/ctrl? event)
(let [prev-zoom @refs/selected-zoom (let [prev-zoom @refs/selected-zoom
dom (mf/ref-node (::canvas own)) dom (mf/ref-node canvas)
scroll-position (scroll/get-current-position-absolute dom) scroll-position (scroll/get-current-position-absolute dom)
mouse-point @refs/viewport-mouse-position] mouse-point @refs/viewport-mouse-position]
(dom/prevent-default event) (dom/prevent-default event)
@ -60,80 +60,90 @@
(st/emit! (dw/increase-zoom))) (st/emit! (dw/increase-zoom)))
(scroll/scroll-to-point dom mouse-point scroll-position)))) (scroll/scroll-to-point dom mouse-point scroll-position))))
(mf/def workspace (defn- subscibe
:mixins #{mf/reactive [canvas page]
shortcuts-mixin} (let [canvas-dom (mf/ref-node canvas)]
;; TODO: scroll stuff need to be refactored
(scroll/scroll-to-page-center canvas-dom page)
(st/emit! (udp/watch-page-changes (:id page))
(udu/watch-page-changes (:id page)))
(shortcuts/init)))
(defn- unsubscribe
[shortcuts-subscription]
(st/emit! ::udp/stop-page-watcher)
(rx/cancel! shortcuts-subscription))
(mf/defc workspace
[{:keys [page wst] :as props}]
(let [flags (:flags wst)
canvas (mf/use-ref* nil)
left-sidebar? (not (empty? (keep flags [:layers :sitemap
:document-history])))
right-sidebar? (not (empty? (keep flags [:icons :drawtools
:element-options])))
classes (classnames
:no-tool-bar-right (not right-sidebar?)
:no-tool-bar-left (not left-sidebar?)
:scrolling (:viewport-positionig workspace))]
(mf/use-effect {:deps (:id page)
:init #(subscibe canvas page)
:end unsubscribe})
[:*
(messages-widget)
[:& header {:page page
:flags flags
:key (:id page)}]
(when (:colorpalette flags)
[:& colorpalette])
[:main.main-content
[:section.workspace-content
{:class classes
:on-scroll on-scroll
:on-wheel #(on-wheel % canvas)}
(history-dialog)
;; Rules
(when (contains? flags :rules)
[:& horizontal-rule {:zoom (:zoom wst)}])
(when (contains? flags :rules)
[:& vertical-rule {:zoom (:zoom wst)}])
;; Canvas
[:section.workspace-canvas {:id "workspace-canvas"
:ref canvas}
[:& viewport {:page page
:wst wst
:key (:id page)}]]]
;; Aside
(when left-sidebar?
[:& left-sidebar {:wst wst :page page}])
(when right-sidebar?
[:& right-sidebar {:wst wst :page page}])]]))
;; TODO: consider using `derive-state` instead of `key` for
;; performance reasons
(mf/def workspace-page
:mixins [mf/reactive]
:init :init
(fn [own {:keys [project page] :as props}] (fn [own {:keys [project-id page-id] :as props}]
(st/emit! (dw/initialize project page)) (st/emit! (dw/initialize project-id page-id))
(assoc own (assoc own
::canvas (mf/create-ref) ::page-ref (-> (l/in [:pages page-id])
::page-ref (-> (l/in [:pages page]) (l/derive st/state))
(l/derive st/state)))) ::workspace-ref (-> (l/in [:workspace page-id])
(l/derive st/state))))
:did-mount
(fn [own]
(let [{:keys [project page]} (::mf/props own)
dom (mf/ref-node (::canvas own))
scroll-to-page-center #(scroll/scroll-to-page-center dom @refs/selected-page)
sub (rx/subscribe streams/page-id-ref-s scroll-to-page-center)]
(scroll-to-page-center)
(st/emit! (udp/watch-page-changes page)
(udu/watch-page-changes page))
(assoc own ::sub sub)))
:will-unmount
(fn [own]
(st/emit! ::udp/stop-page-watcher)
(rx/cancel! (::sub own))
(dissoc own ::sub))
:render :render
(fn [own props] (fn [own props]
(let [flags (mf/deref refs/flags) (let [wst (mf/react (::workspace-ref own))
page (mf/deref (::page-ref own)) page (mf/react (::page-ref own))]
;; project-id (get props :project) (when page
;; page-id (get props :page) [:& workspace {:page page :wst wst}]))))
left-sidebar? (not (empty? (keep flags [:layers :sitemap
:document-history])))
right-sidebar? (not (empty? (keep flags [:icons :drawtools
:element-options])))
classes (classnames
:no-tool-bar-right (not right-sidebar?)
:no-tool-bar-left (not left-sidebar?)
:scrolling (:viewport-positionig workspace))]
[:*
(messages-widget)
[:& header {:page page
:flags flags
:key (:id page)}]
(colorpalette)
[:main.main-content
[:section.workspace-content
{:class classes
:on-scroll on-scroll
:on-wheel (partial on-wheel own)}
(history-dialog)
;; Rules
(when (contains? flags :rules)
[:& horizontal-rule])
(when (contains? flags :rules)
[:& vertical-rule])
;; Canvas
[:section.workspace-canvas {:id "workspace-canvas"
:ref (::canvas own)}
[:& viewport {:page page
:flags flags
:key (:id page)}]]]
;; Aside
(when left-sidebar?
[:& left-sidebar {:flags flags :page-id (:id page)}])
(when right-sidebar?
[:& right-sidebar {:flags flags :page-id (:id page)}])]])))

View file

@ -7,30 +7,12 @@
(ns uxbox.main.ui.workspace.canvas (ns uxbox.main.ui.workspace.canvas
(:require (:require
[beicon.core :as rx]
[goog.events :as events]
[lentes.core :as l]
[potok.core :as ptk]
[rumext.core :as mx]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.shapes :as uds]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.streams :as streams]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.shapes :as uus] [uxbox.main.ui.shapes :as uus]
[uxbox.main.ui.shapes.selection :refer [selection-handlers]]
[uxbox.main.ui.workspace.drawarea :refer [draw-area]] [uxbox.main.ui.workspace.drawarea :refer [draw-area]]
[uxbox.main.ui.workspace.grid :refer [grid]] [uxbox.main.ui.workspace.selection :refer [selection-handlers]]
[uxbox.main.ui.workspace.ruler :refer [ruler]]
[uxbox.main.ui.workspace.scroll :as scroll]
[uxbox.main.user-events :as uev]
[uxbox.util.data :refer [parse-int]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]) [uxbox.util.geom.point :as gpt])
(:import goog.events.EventType)) (:import goog.events.EventType))
@ -46,235 +28,26 @@
:height "100%" :height "100%"
:fill (or background "#ffffff")}])) :fill (or background "#ffffff")}]))
;; --- Coordinates Widget
(mf/def coordinates
:mixins [mf/reactive mf/memo]
:render
(fn [own props]
(let [zoom (mf/react refs/selected-zoom)
coords (some-> (mf/react refs/canvas-mouse-position)
(gpt/divide zoom)
(gpt/round 0))]
[:ul.coordinates {}
[:span {:alt "x"}
(str "X: " (:x coords "-"))]
[:span {:alt "y"}
(str "Y: " (:y coords "-"))]])))
;; --- Selection Rect
(def selrect-ref
(-> (l/key :selrect)
(l/derive refs/workspace)))
(mf/def selrect
:mixins [mf/memo mf/reactive]
:render
(fn [own props]
(when-let [rect (mf/react selrect-ref)]
(let [{:keys [x1 y1 width height]} (geom/size rect)]
[:rect.selection-rect
{:x x1
:y y1
:width width
:height height}]))))
;; --- Cursor tooltip
(defn- get-shape-tooltip
"Return the shape tooltip text"
[shape]
(case (:type shape)
:icon "Click to place the Icon"
:image "Click to place the Image"
:rect "Drag to draw a Box"
:text "Drag to draw a Text Box"
:path "Click to draw a Path"
:circle "Drag to draw a Circle"
nil))
(mf/def cursor-tooltip
:mixins [mf/reactive mf/memo]
:render
(fn [own tooltip]
(let [coords (mf/react refs/window-mouse-position)]
[:span.cursor-tooltip
{:style
{:position "fixed"
:left (str (+ (:x coords) 5) "px")
:top (str (- (:y coords) 25) "px")}}
tooltip])))
;; --- Canvas ;; --- Canvas
(mf/def canvas (mf/defc canvas
:mixins [mf/memo mf/reactive] [{:keys [page wst] :as props}]
:render (let [{:keys [metadata id]} page
(fn [own {:keys [page zoom] :as props}] zoom (:zoom wst 1) ;; NOTE: maybe forward wst to draw-area
(let [{:keys [metadata id]} page width (:width metadata)
width (:width metadata) height (:height metadata)]
height (:height metadata)] [:svg.page-canvas {:x c/canvas-start-x
[:svg.page-canvas {:x c/canvas-start-x :y c/canvas-start-y
:y c/canvas-start-y :width width
:ref (str "canvas" id) :height height}
:width width [:& background metadata]
:height height} [:svg.page-layout
(background metadata) [:g.main
[:svg.page-layout (for [item (reverse (:shapes page))]
[:g.main (-> (uus/shape item)
(for [item (reverse (:shapes page))] (mf/with-key (str item))))
(-> (uus/shape item) [:& selection-handlers {:wst wst}]
(mf/with-key (str item)))) (when-let [dshape (:drawing wst)]
(selection-handlers) [:& draw-area {:shape dshape
(draw-area zoom)]]]))) :zoom (:zoom wst)
:modifiers (:modifiers wst)}])]]]))
;; --- Viewport
(mf/def viewport
:mixins [mf/reactive]
:init
(fn [own props]
(assoc own ::viewport (mf/create-ref)))
:did-mount
(fn [own]
(letfn [(translate-point-to-viewport [pt]
(let [viewport (mf/ref-node (::viewport own))
brect (.getBoundingClientRect viewport)
brect (gpt/point (parse-int (.-left brect))
(parse-int (.-top brect)))]
(gpt/subtract pt brect)))
(translate-point-to-canvas [pt]
(let [viewport (mf/ref-node (::viewport own))]
(when-let [canvas (dom/get-element-by-class "page-canvas" viewport)]
(let [brect (.getBoundingClientRect canvas)
bbox (.getBBox canvas)
brect (gpt/point (parse-int (.-left brect))
(parse-int (.-top brect)))
bbox (gpt/point (.-x bbox) (.-y bbox))]
(-> (gpt/add pt bbox)
(gpt/subtract brect))))))
(on-key-down [event]
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/keyboard-event :down key ctrl? shift?))
(when (kbd/space? event)
(st/emit! (udw/start-viewport-positioning)))))
(on-key-up [event]
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(when (kbd/space? event)
(st/emit! (udw/stop-viewport-positioning)))
(st/emit! (uev/keyboard-event :up key ctrl? shift?))))
(on-mousemove [event]
(let [wpt (gpt/point (.-clientX event)
(.-clientY event))
vpt (translate-point-to-viewport wpt)
cpt (translate-point-to-canvas wpt)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
event {:ctrl ctrl?
:shift shift?
:window-coords wpt
:viewport-coords vpt
:canvas-coords cpt}]
(st/emit! (uev/pointer-event wpt vpt cpt ctrl? shift?))))]
(let [key1 (events/listen js/document EventType.MOUSEMOVE on-mousemove)
key2 (events/listen js/document EventType.KEYDOWN on-key-down)
key3 (events/listen js/document EventType.KEYUP on-key-up)]
(assoc own
::key1 key1
::key2 key2
::key3 key3))))
:will-unmount
(fn [own]
(events/unlistenByKey (::key1 own))
(events/unlistenByKey (::key2 own))
(events/unlistenByKey (::key3 own))
(dissoc own ::key1 ::key2 ::key3))
;; TODO: use an ad-hoc ref for required keys from workspace state
:render
(fn [own {:keys [page flags] :as props}]
(let [drawing (mf/react refs/selected-drawing-tool)
tooltip (or (mf/react refs/selected-tooltip)
(get-shape-tooltip drawing))
zoom (or (mf/react refs/selected-zoom) 1)]
(letfn [(on-mouse-down [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :down ctrl? shift?)))
(if drawing
(st/emit! (udw/start-drawing drawing))
(st/emit! ::uev/interrupt (udw/start-selrect))))
(on-context-menu [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :context-menu ctrl? shift?))))
(on-mouse-up [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :up ctrl? shift?))))
(on-click [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :click ctrl? shift?))))
(on-double-click [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :double-click ctrl? shift?))))]
[:*
(coordinates)
[:div.tooltip-container
(when tooltip
(cursor-tooltip tooltip))]
[:svg.viewport {:width (* c/viewport-width zoom)
:height (* c/viewport-height zoom)
:ref (::viewport own)
:class (when drawing "drawing")
:on-context-menu on-context-menu
:on-click on-click
:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
(when page
(canvas {:page page :zoom zoom}))
(if (contains? flags :grid)
(grid))]
(when (contains? flags :ruler)
(ruler zoom))
(selrect)]]))))

View file

@ -6,121 +6,115 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.colorpalette (ns uxbox.main.ui.workspace.colorpalette
(:require [beicon.core :as rx] (:require
[lentes.core :as l] [beicon.core :as rx]
[potok.core :as ptk] [lentes.core :as l]
[uxbox.main.store :as st] [rumext.alpha :as mf]
[uxbox.main.refs :as refs] [uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as dw] [uxbox.main.data.colors :as udc]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.colors :as dc] [uxbox.main.store :as st]
[uxbox.main.ui.dashboard.colors :refer (collections-ref)] [uxbox.main.ui.keyboard :as kbd]
[uxbox.builtins.icons :as i] [uxbox.util.color :refer [hex->rgb]]
[uxbox.main.ui.keyboard :as kbd] [uxbox.util.data :refer [read-string seek]]
[uxbox.util.lens :as ul] [uxbox.util.dom :as dom]))
[uxbox.util.data :refer (read-string)]
[uxbox.util.color :refer (hex->rgb)]
[uxbox.util.dom :as dom]
[rumext.core :as mx :include-macros true]))
(defn- get-selected-collection ;; --- Refs
[local collections]
(if-let [selected (:selected @local)]
(first (filter #(= selected (:id %)) collections))
(first (filter #(and (:id %) (> (count (:colors %)) 0)) collections))))
(mx/defc palette-item (def collections-iref
{:mixins [mx/static]} (-> (l/key :colors-collections)
[color] (l/derive st/state)))
;; --- Components
(mf/defc palette-item
[{:keys [color] :as props}]
(letfn [(select-color [event] (letfn [(select-color [event]
(let [attrs (if (kbd/shift? event) (let [attrs (if (kbd/shift? event)
{:stroke-color color} {:stroke-color color}
{:fill-color color})] {:fill-color color})]
(st/emit! (uds/update-selected-shapes-attrs attrs))))] (st/emit! (udw/update-selected-shapes-attrs attrs))))]
(let [rgb-vec (hex->rgb color) (let [rgb-vec (hex->rgb color)
rgb-color (apply str "" (interpose ", " rgb-vec))] rgb-color (apply str "" (interpose ", " rgb-vec))]
[:div.color-cell {:key (str color) [:div.color-cell {:key (str color)
:on-click select-color} :on-click select-color}
[:span.color {:style {:background color}}] [:span.color {:style {:background color}}]
[:span.color-text {} color] [:span.color-text color]
[:span.color-text {} rgb-color]]))) [:span.color-text rgb-color]])))
(defn- palette-after-render
[{:keys [::mx/local] :as own}]
(let [dom (mx/ref-node own "container")
width (.-clientWidth dom)]
(when (not= (:width @local) width)
(swap! local assoc :width :width width))
own))
(defn- document-width (defn- document-width
[] []
(.. js/document -documentElement -clientWidth)) (.. js/document -documentElement -clientWidth))
(mx/defcs palette (mf/defc palette
{:mixins [mx/static mx/reactive (mx/local)] [{:keys [colls] :as props}]
:after-render palette-after-render} (let [local (mf/use-state {})
[{:keys [::mx/local] :as own}] colls (->> colls
(let [collections (->> (mx/react collections-ref) (filter :id)
(vals) (sort-by :name))
(filter :id) coll (or (:selected @local)
(sort-by :name)) (first colls))
{:keys [colors] :as selected-coll} (get-selected-collection local collections)
width (:width @local (* (document-width) 0.84)) width (:width @local (* (document-width) 0.84))
offset (:offset @local 0) offset (:offset @local 0)
visible (/ width 86) visible (/ width 86)
invisible (- (count colors) visible)] invisible (- (count (:colors coll)) visible)
(letfn [(select-collection [event] close #(st/emit! (udw/toggle-flag :colorpalette))
(let [value (read-string (dom/event->value event))]
(swap! local assoc :selected value :position 0))) container (mf/use-ref* nil)
(close [event] container-child (mf/use-ref* nil)]
(st/emit! (dw/toggle-flag :colorpalette)))]
(letfn [(select-coll [event]
(let [id (read-string (dom/event->value event))
selected (seek #(= id (:id %)) colls)]
(swap! local assoc :selected selected :position 0)))
(on-left-arrow-click [event]
(when (> offset 0)
(let [element (mf/ref-node container-child)]
(swap! local update :offset dec))))
(on-right-arrow-click [event]
(when (< offset invisible)
(let [element (mf/ref-node container-child)]
(swap! local update :offset inc))))
(on-scroll [event]
(if (pos? (.. event -nativeEvent -deltaY))
(on-right-arrow-click event)
(on-left-arrow-click event)))
(after-render []
(let [dom (mf/ref-node container)
width (.-clientWidth dom)]
(when (not= (:width @local) width)
(swap! local assoc :width width))))]
(mf/use-effect {:deps true :init after-render})
[:div.color-palette [:div.color-palette
[:div.color-palette-actions [:div.color-palette-actions
[:select.input-select {:on-change select-collection [:select.input-select {:on-change select-coll
:value (pr-str (:id selected-coll))} :default-value (pr-str (:id coll))}
(for [collection collections] (for [item colls]
[:option {:key (str (:id collection)) [:option {:key (:id item) :value (pr-str (:id item))}
:value (pr-str (:id collection))} (:name item)])]
(:name collection)])]
[:div.color-palette-buttons
[:div.btn-palette.edit.current i/pencil]
[:div.btn-palette.create i/close]]]
;; FIXME Scroll on click does not work #_[:div.color-palette-buttons
[:span.left-arrow {} [:div.btn-palette.edit.current i/pencil]
(when (> offset 0) [:div.btn-palette.create i/close]]]
{:on-click #(.scrollBy (dom/get-element "color-palette-inside") (- offset) 0)})
i/arrow-slide]
[:div.color-palette-content {:ref "container"} [:span.left-arrow {:on-click on-left-arrow-click} i/arrow-slide]
[:div.color-palette-inside {:id "color-palette-inside"
:ref "color-palette-inside" [:div.color-palette-content {:ref container :on-wheel on-scroll}
[:div.color-palette-inside {:ref container-child
:style {:position "relative" :style {:position "relative"
:width (str (* 86 (count (:colors coll))) "px")
:right (str (* 86 offset) "px")}} :right (str (* 86 offset) "px")}}
(for [color colors] (for [color (:colors coll)]
(-> (palette-item color) [:& palette-item {:color color :key color}])]]
(mx/with-key color)))]]
;; FIXME Scroll on click does not work [:span.right-arrow {:on-click on-right-arrow-click} i/arrow-slide]
[:span.right-arrow [:span.close-palette {:on-click close} i/close]])))
{:on-click (fn [event]
(when (< offset invisible)
(.scrollBy (dom/get-element "color-palette-inside") offset 0)))}
i/arrow-slide]
[:span.close-palette {:on-click close} (mf/defc colorpalette
i/close]]))) [props]
(let [colls (mf/deref collections-iref)]
(defn- colorpalette-init (mf/use-effect {:init #(st/emit! (udc/fetch-collections))})
[own] [:& palette {:colls (vals colls)}]))
(st/emit! (dc/fetch-collections))
own)
(mx/defc colorpalette
{:mixins [mx/static mx/reactive]
:init colorpalette-init}
[]
(let [flags (mx/react refs/flags)]
(when (contains? flags :colorpalette)
(palette))))

View file

@ -2,70 +2,43 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.colorpicker (ns uxbox.main.ui.workspace.colorpicker
(:require [lentes.core :as l] (:require
[potok.core :as ptk] [lentes.core :as l]
[uxbox.main.store :as st] [rumext.alpha :as mf]
[uxbox.main.refs :as refs] [uxbox.main.store :as st]
[uxbox.main.geom :as geom] [uxbox.main.ui.colorpicker :as cp]))
[uxbox.main.data.workspace :as udw]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.shapes :as uds]
[uxbox.builtins.icons :as i]
[uxbox.main.ui.lightbox :as lbx]
[uxbox.main.ui.colorpicker :as cp]
[uxbox.main.ui.workspace.recent-colors :refer [recent-colors]]
[uxbox.util.router :as rt]
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom]
[uxbox.util.data :refer [parse-int parse-float read-string]]))
(defn- focus-shape ;; --- Recent Colors Calc. Algorithm
[id]
(-> (l/in [:shapes id]) (defn- lookup-colors
[state]
(as-> {} $
(reduce (fn [acc shape]
(-> acc
(update (:fill-color shape) (fnil inc 0))
(update (:stroke-color shape) (fnil inc 0))))
$ (vals (:shapes state)))
(reverse (sort-by second $))
(map first $)
(remove nil? $)))
(def most-used-colors
(-> (l/lens lookup-colors)
(l/derive st/state))) (l/derive st/state)))
(mx/defcs shape-colorpicker ;; --- Color Picker Modal
{:mixins [mx/reactive mx/static]}
[own {:keys [x y shape attr] :as opts}]
(let [{:keys [id] :as shape} (mx/react (focus-shape shape))
left (- x 260)
top (- y 50)]
(letfn [(change-color [color]
(st/emit! (uds/update-attrs id {attr color})))]
[:div.colorpicker-tooltip
{:style {:left (str left "px")
:top (str top "px")}}
(cp/colorpicker (mf/defc colorpicker-modal
:theme :small [{:keys [x y default value page on-change] :as props}]
:value (get shape attr "#000000") [:div.colorpicker-tooltip
:on-change change-color) {:style {:left (str (- x 260) "px")
(recent-colors shape change-color)]))) :top (str (- y 50) "px")}}
[:& cp/colorpicker {:value (or value default)
:colors (into-array @most-used-colors)
:on-change on-change}]])
(mx/defcs page-colorpicker
{:mixins [mx/reactive mx/static]}
[own {:keys [x y attr default] :as opts}]
(let [{:keys [id metadata] :as page} (mx/react refs/selected-page)]
(letfn [(change-color [color]
(let [metadata (assoc metadata attr color)]
(st/emit! (udp/update-metadata id metadata))))]
[:div.colorpicker-tooltip
{:style {:left (str (- x 260) "px")
:top (str (- y 50) "px")}}
(cp/colorpicker
:theme :small
:value (get metadata attr default)
:on-change change-color)])))
(defmethod lbx/render-lightbox :workspace/shape-colorpicker
[params]
(shape-colorpicker params))
(defmethod lbx/render-lightbox :workspace/page-colorpicker
[params]
(page-colorpicker params))

View file

@ -92,7 +92,7 @@
(mx/defcs download-dialog (mx/defcs download-dialog
{:mixins [mx/static mx/reactive]} {:mixins [mx/static mx/reactive]}
[own] [own]
(let [project (mx/react refs/selected-project) #_(let [project (mx/react refs/selected-project)
pages (mx/react pages-ref) pages (mx/react pages-ref)
current (mx/react current-page-ref)] current (mx/react current-page-ref)]
(letfn [(on-close [event] (letfn [(on-close [event]

View file

@ -2,47 +2,39 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.drawarea (ns uxbox.main.ui.workspace.drawarea
"Draw interaction and component." "Draw interaction and component."
(:require [beicon.core :as rx] (:require
[potok.core :as ptk] [rumext.alpha :as mf]
[lentes.core :as l] [uxbox.main.data.workspace :as udw]
[rumext.core :as mx :include-macros true] [uxbox.main.data.workspace-drawing :as udwd]
[uxbox.main.store :as st] [uxbox.main.geom :as geom]
[uxbox.main.constants :as c] [uxbox.main.store :as st]
[uxbox.main.refs :as refs] [uxbox.main.streams :as streams]
[uxbox.main.streams :as streams] [uxbox.main.ui.shapes :as shapes]
[uxbox.main.workers :as uwrk] [uxbox.util.dom :as dom]
[uxbox.main.data.workspace :as udw] [uxbox.util.geom.path :as path]
[uxbox.main.data.shapes :as uds] [uxbox.util.geom.point :as gpt]))
[uxbox.main.ui.shapes :as shapes]
[uxbox.main.geom :as geom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.path :as path]
[uxbox.util.dom :as dom]))
;; --- Components ;; --- Components
(declare generic-draw-area) (declare generic-draw-area)
(declare path-draw-area) (declare path-draw-area)
(mx/defc draw-area (mf/defc draw-area
{:mixins [mx/static mx/reactive]} [{:keys [zoom shape modifiers] :as props}]
[zoom] (if (= (:type shape) :path)
(when-let [{:keys [id] :as shape} (mx/react refs/selected-drawing-shape)] [:& path-draw-area {:shape shape}]
(let [modifiers (mx/react (refs/selected-modifiers id))] [:& generic-draw-area {:shape (assoc shape :modifiers modifiers)
(if (= (:type shape) :path) :zoom zoom}]))
(path-draw-area shape)
(-> (assoc shape :modifiers modifiers)
(generic-draw-area zoom))))))
(mx/defc generic-draw-area (mf/defc generic-draw-area
[shape zoom] [{:keys [shape zoom]}]
(let [{:keys [x1 y1 width height]} (geom/selection-rect shape)] (let [{:keys [x1 y1 width height]} (geom/selection-rect shape)]
[:g {} [:g
(shapes/render-component shape) (shapes/render-shape shape)
[:rect.main {:x x1 :y y1 [:rect.main {:x x1 :y y1
:width width :width width
:height height :height height
@ -51,23 +43,24 @@
:fill "transparent" :fill "transparent"
:stroke-opacity "1"}}]])) :stroke-opacity "1"}}]]))
(mx/defc path-draw-area (mf/defc path-draw-area
[{:keys [segments] :as shape}] [{:keys [shape] :as props}]
(letfn [(on-click [event] (letfn [(on-click [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (udw/set-tooltip nil) (st/emit! (udw/set-tooltip nil)
(udw/close-drawing-path))) (udwd/close-drawing-path)))
(on-mouse-enter [event] (on-mouse-enter [event]
(st/emit! (udw/set-tooltip "Click to close the path"))) (st/emit! (udw/set-tooltip "Click to close the path")))
(on-mouse-leave [event] (on-mouse-leave [event]
(st/emit! (udw/set-tooltip nil)))] (st/emit! (udw/set-tooltip nil)))]
(when-let [{:keys [x y] :as segment} (first segments)] (when-let [{:keys [x y] :as segment} (first (:segments shape))]
[:g {} [:g
(shapes/render-component shape) (shapes/render-shape shape)
(when-not (:free shape) (when-not (:free shape)
[:circle.close-bezier {:cx x [:circle.close-bezier
:cy y {:cx x
:r 5 :cy y
:on-click on-click :r 5
:on-mouse-enter on-mouse-enter :on-click on-click
:on-mouse-leave on-mouse-leave}])]))) :on-mouse-enter on-mouse-enter
:on-mouse-leave on-mouse-leave}])])))

View file

@ -2,45 +2,17 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.grid (ns uxbox.main.ui.workspace.grid
(:require (:require
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]))
[uxbox.main.refs :as refs]))
;; --- Grid (Component) ;; --- Grid (Component)
(declare vertical-line)
(declare horizontal-line)
(mf/def grid
:mixins [mf/memo mf/reactive]
:render
(fn [own props]
(let [options (:metadata (mf/react refs/selected-page))
color (:grid-color options "#cccccc")
width c/viewport-width
height c/viewport-height
x-ticks (range (- 0 c/canvas-start-x)
(- width c/canvas-start-x)
(:grid-x-axis options 10))
y-ticks (range (- 0 c/canvas-start-x)
(- height c/canvas-start-x)
(:grid-y-axis options 10))
path (as-> [] $
(reduce (partial vertical-line height) $ x-ticks)
(reduce (partial horizontal-line width) $ y-ticks))]
[:g.grid {:style {:pointer-events "none"}}
[:path {:d (str/join " " path) :stroke color :opacity "0.3"}]])))
;; --- Helpers
(defn- horizontal-line (defn- horizontal-line
[width acc value] [width acc value]
(let [pos (+ value c/canvas-start-y)] (let [pos (+ value c/canvas-start-y)]
@ -50,3 +22,29 @@
[height acc value] [height acc value]
(let [pos (+ value c/canvas-start-y)] (let [pos (+ value c/canvas-start-y)]
(conj acc (str/format "M %s %s L %s %s" pos 0 pos height)))) (conj acc (str/format "M %s %s L %s %s" pos 0 pos height))))
(defn- make-grid-path
[metadata]
(let [width c/viewport-width
height c/viewport-height
x-ticks (range (- 0 c/canvas-start-x)
(- width c/canvas-start-x)
(:grid-x-axis metadata 10))
y-ticks (range (- 0 c/canvas-start-x)
(- height c/canvas-start-x)
(:grid-y-axis metadata 10))]
(as-> [] $
(reduce (partial vertical-line height) $ x-ticks)
(reduce (partial horizontal-line width) $ y-ticks)
(str/join " " $))))
(mf/defc grid
[{:keys [page] :as props}]
(let [metadata (:metadata page)
color (:grid-color metadata "#cccccc")
path (mf/use-memo {:deps #js [metadata]
:init #(make-grid-path metadata)})]
[:g.grid {:style {:pointer-events "none"}}
[:path {:d path :stroke color :opacity "0.3"}]]))

View file

@ -28,7 +28,7 @@
;; --- Zoom Widget ;; --- Zoom Widget
(mf/defc zoom-widget (mf/defc zoom-widget
{:wrap [mf/reactive*]} {:wrap [mf/wrap-reactive]}
[props] [props]
(let [zoom (mf/react refs/selected-zoom) (let [zoom (mf/react refs/selected-zoom)
increase #(st/emit! (dw/increase-zoom)) increase #(st/emit! (dw/increase-zoom))

View file

@ -15,6 +15,7 @@
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.shapes :as uds]
[uxbox.main.data.workspace :as udw] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace-drawing :as udwd]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.lightbox :as lbx]
[uxbox.util.data :refer [read-string jscoll->vec]] [uxbox.util.data :refer [read-string jscoll->vec]]
@ -56,7 +57,7 @@
:metadata {:width width :metadata {:width width
:height height} :height height}
:image id}] :image id}]
(st/emit! (udw/select-for-drawing shape)) (st/emit! (udwd/select-for-drawing shape))
(udl/close!))) (udl/close!)))
(on-files-selected [event] (on-files-selected [event]
(let [files (dom/get-event-files event) (let [files (dom/get-event-files event)
@ -98,7 +99,7 @@
:metadata {:width width :metadata {:width width
:height height} :height height}
:image id}] :image id}]
(st/emit! (udw/select-for-drawing shape)) (st/emit! (udwd/select-for-drawing shape))
(udl/close!)))] (udl/close!)))]
[:div.library-item {:key (str id) [:div.library-item {:key (str id)
:on-click on-click} :on-click on-click}

View file

@ -1,56 +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 <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.recent-colors
(:require [lentes.core :as l]
[potok.core :as ptk]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.data.workspace :as dw]
[uxbox.builtins.icons :as i]
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer (tr)]))
;; --- Helpers
(defn- count-color
[state shape prop]
(let [color (prop shape)]
(if (contains? state color)
(update state color inc)
(assoc state color 1))))
(defn- calculate-colors
[shapes]
(as-> {} $
(reduce #(count-color %1 %2 :fill-color) $ shapes)
(reduce #(count-color %1 %2 :stroke-color) $ shapes)
(remove nil? $)
(sort-by second (into [] $))
(take 5 (map first $))))
;; --- Component
(mx/defc recent-colors
{:mixins [mx/static mx/reactive]}
[{:keys [page id] :as shape} callback]
(let [shapes-by-id (mx/react refs/shapes-by-id)
shapes (->> (vals shapes-by-id)
(filter #(= (:page %) page)))
colors (calculate-colors shapes)]
[:div {}
[:span {} (tr "ds.recent-colors")]
[:div.row-flex {}
(for [color colors]
[:span.color-th {:style {:background-color color}
:key color
:on-click (partial callback color)}])
(for [i (range (- 5 (count colors)))]
[:span.color-th {:key (str "empty" i)}])
[:span.color-th.palette-th {} i/picker]]]))

View file

@ -2,39 +2,31 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.ruler (ns uxbox.main.ui.workspace.ruler
(:require [lentes.core :as l] (:require
[potok.core :as ptk] [rumext.alpha :as mf]
[beicon.core :as rx] [uxbox.main.constants :as c]
[uxbox.main.constants :as c] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as udw] [uxbox.main.store :as st]
[uxbox.main.refs :as refs] [uxbox.main.user-events :as uev]
[uxbox.main.streams :as streams] [uxbox.util.dom :as dom]
[uxbox.main.store :as st] [uxbox.util.geom.point :as gpt]
[uxbox.main.user-events :as uev] [uxbox.util.math :as mth]))
[uxbox.util.math :as mth]
[rumext.core :as mx :include-macros true]
[uxbox.util.geom.point :as gpt]
[uxbox.util.dom :as dom]))
(def ruler-points-ref (mf/defc ruler-text
(-> (l/key :ruler) [{:keys [zoom ruler] :as props}]
(l/derive refs/workspace))) (let [{:keys [start end]} ruler
distance (-> (gpt/distance (gpt/divide end zoom)
(mx/defc ruler-text (gpt/divide start zoom))
{:mixins [mx/static]}
[zoom [center pt]]
(let [distance (-> (gpt/distance (gpt/divide pt zoom)
(gpt/divide center zoom))
(mth/precision 2)) (mth/precision 2))
angle (-> (gpt/angle pt center) angle (-> (gpt/angle end start)
(mth/precision 2)) (mth/precision 2))
transform1 (str "translate(" (+ (:x pt) 35) "," (- (:y pt) 10) ")") transform1 (str "translate(" (+ (:x end) 35) "," (- (:y end) 10) ")")
transform2 (str "translate(" (+ (:x pt) 25) "," (- (:y pt) 30) ")")] transform2 (str "translate(" (+ (:x end) 25) "," (- (:y end) 30) ")")]
[:g {} [:g
[:rect {:fill "black" [:rect {:fill "black"
:fill-opacity "0.4" :fill-opacity "0.4"
:rx "3" :rx "3"
@ -49,24 +41,19 @@
[:tspan {:x "0" :y "20"} [:tspan {:x "0" :y "20"}
(str angle "°")]]])) (str angle "°")]]]))
(mx/defc ruler-line (mf/defc ruler-line
{:mixins [mx/static]} [{:keys [zoom ruler] :as props}]
[zoom [center pt]] (let [{:keys [start end]} ruler]
[:line {:x1 (:x center) [:line {:x1 (:x start)
:y1 (:y center) :y1 (:y start)
:x2 (:x pt) :x2 (:x end)
:y2 (:y pt) :y2 (:y end)
:style {:cursor "cell"} :style {:cursor "cell"}
:stroke-width "1" :stroke-width "1"
:stroke "red"}]) :stroke "red"}]))
(mx/defc ruler (mf/defc ruler
{:mixins [mx/static mx/reactive] [{:keys [ruler zoom] :as props}]
:will-unmount (fn [own]
(st/emit! ::uev/interrupt
(udw/clear-ruler))
own)}
[zoom]
(letfn [(on-mouse-down [event] (letfn [(on-mouse-down [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! ::uev/interrupt (st/emit! ::uev/interrupt
@ -74,7 +61,11 @@
(udw/start-ruler))) (udw/start-ruler)))
(on-mouse-up [event] (on-mouse-up [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! ::uev/interrupt))] (st/emit! ::uev/interrupt))
(on-unmount []
(st/emit! ::uev/interrupt
(udw/clear-ruler)))]
(mf/use-effect {:end on-unmount})
[:svg {:on-mouse-down on-mouse-down [:svg {:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up} :on-mouse-up on-mouse-up}
[:rect {:style {:fill "transparent" [:rect {:style {:fill "transparent"
@ -82,8 +73,8 @@
:cursor "cell"} :cursor "cell"}
:width c/viewport-width :width c/viewport-width
:height c/viewport-height}] :height c/viewport-height}]
(when-let [points (mx/react ruler-points-ref)] (when ruler
[:g {} [:g
(ruler-line zoom points) [:& ruler-line {:ruler ruler}]
(ruler-text zoom points)])])) [:& ruler-text {:ruler ruler :zoom zoom}]])]))

View file

@ -100,65 +100,57 @@
;; --- Horizontal Rule Ticks (Component) ;; --- Horizontal Rule Ticks (Component)
(mf/def horizontal-rule-ticks (mf/defc horizontal-rule-ticks
:mixins #{mf/memo} {:wrap [mf/wrap-memo]}
:render [{:keys [zoom]}]
(fn [own zoom] (let [zoom (or zoom 1)
(let [zoom (or zoom 1) path (reduce (partial make-vertical-tick zoom) [] +ticks+)]
path (reduce (partial make-vertical-tick zoom) [] +ticks+)] [:g
[:g [:path {:d (str/join " " path)}]
[:path {:d (str/join " " path)}] (for [tick +ticks+]
(for [tick +ticks+] [:& horizontal-text-label {:zoom zoom :value tick :key tick}])]))
[:& horizontal-text-label {:zoom zoom :value tick :key tick}])])))
;; --- Vertical Rule Ticks (Component) ;; --- Vertical Rule Ticks (Component)
(mf/def vertical-rule-ticks (mf/defc vertical-rule-ticks
:mixins #{mf/memo} {:wrap [mf/wrap-memo]}
:render [{:keys [zoom]}]
(fn [own zoom] (let [zoom (or zoom 1)
(let [zoom (or zoom 1) path (reduce (partial make-horizontal-tick zoom) [] +ticks+)]
path (reduce (partial make-horizontal-tick zoom) [] +ticks+)] [:g
[:g [:path {:d (str/join " " path)}]
[:path {:d (str/join " " path)}] (for [tick +ticks+]
(for [tick +ticks+] [:& vertical-text-label {:zoom zoom :value tick :key tick}])]))
[:& vertical-text-label {:zoom zoom :value tick :key tick}])])))
;; --- Horizontal Rule (Component) ;; --- Horizontal Rule (Component)
(mf/def horizontal-rule (mf/defc horizontal-rule
:mixins #{mf/memo mf/reactive} [{:keys [zoom] :as props}]
:render (let [scroll (mf/deref refs/workspace-scroll)
(fn [own props] scroll-x (:x scroll)
(let [scroll (mf/react refs/workspace-scroll) translate-x (- (- c/canvas-scroll-padding) (:x scroll))]
zoom (mf/react refs/selected-zoom) [:svg.horizontal-rule
scroll-x (:x scroll) {:width c/viewport-width
translate-x (- (- c/canvas-scroll-padding) (:x scroll))] :height 20}
[:svg.horizontal-rule [:rect {:height 20
{:width c/viewport-width :width c/viewport-width}]
:height 20} [:g {:transform (str "translate(" translate-x ", 0)")}
[:rect {:height 20 [:& horizontal-rule-ticks {:zoom zoom}]]]))
:width c/viewport-width}]
[:g {:transform (str "translate(" translate-x ", 0)")}
(horizontal-rule-ticks zoom)]])))
;; --- Vertical Rule (Component) ;; --- Vertical Rule (Component)
(mf/def vertical-rule (mf/defc vertical-rule
:mixins #{mf/memo mf/reactive} [{:keys [zoom] :as props}]
:render (let [scroll (mf/deref refs/workspace-scroll)
(fn [own props] scroll-y (:y scroll)
(let [scroll (mf/react refs/workspace-scroll) translate-y (- (- c/canvas-scroll-padding) (:y scroll))]
zoom (mf/react refs/selected-zoom) [:svg.vertical-rule
scroll-y (:y scroll) {:width 20
translate-y (- (- c/canvas-scroll-padding) (:y scroll))] :height c/viewport-height}
[:svg.vertical-rule
{:width 20
:height c/viewport-height}
[:g {:transform (str "translate(0, " translate-y ")")} [:g {:transform (str "translate(0, " translate-y ")")}
(vertical-rule-ticks zoom)] [:& vertical-rule-ticks {:zoom zoom}]]
[:rect {:x 0 [:rect {:x 0
:y 0 :y 0
:height 20 :height 20
:width 20}]]))) :width 20}]]))

View file

@ -15,6 +15,7 @@
[uxbox.util.geom.point :as gpt])) [uxbox.util.geom.point :as gpt]))
;; FIXME: revisit this ns in order to find a better location for its functions ;; FIXME: revisit this ns in order to find a better location for its functions
;; TODO: this need a good refactor (probably move to events with access to the state)
(defn set-scroll-position (defn set-scroll-position
[dom position] [dom position]
@ -25,8 +26,8 @@
[dom center] [dom center]
(let [viewport-width (.-offsetWidth dom) (let [viewport-width (.-offsetWidth dom)
viewport-height (.-offsetHeight dom) viewport-height (.-offsetHeight dom)
position-x (- (* (:x center) @refs/selected-zoom) (/ viewport-width 2)) position-x (- (* (:x center) 1 #_@refs/selected-zoom) (/ viewport-width 2))
position-y (- (* (:y center) @refs/selected-zoom) (/ viewport-height 2)) position-y (- (* (:y center) 1 #_@refs/selected-zoom) (/ viewport-height 2))
position (gpt/point position-x position-y)] position (gpt/point position-x position-y)]
(set-scroll-position dom position))) (set-scroll-position dom position)))

View file

@ -0,0 +1,299 @@
;; 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 <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.selection
"Multiple selection handlers component."
(:require
[beicon.core :as rx]
[lentes.core :as l]
[potok.core :as ptk]
[rumext.alpha :as mf]
[rumext.core :as mx]
[uxbox.main.constants :as c]
[uxbox.main.data.shapes :as uds]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.streams :as streams]
[uxbox.main.user-events :as uev]
[uxbox.main.workers :as uwrk]
[uxbox.util.dom :as dom]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]))
;; --- Refs & Constants
(def ^:private +circle-props+
{:r 6
:style {:fillOpacity "1"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"}
:fill "#31e6e0"
:stroke "#28c4d4"})
(defn- focus-selected-shapes
[state]
(let [selected (get-in state [:workspace :selected])]
(mapv #(get-in state [:shapes %]) selected)))
(def ^:private selected-shapes-ref
"A customized version of `refs/selected-shapes` that
additionally resolves the shapes to the real object
instead of just return a set of ids."
(-> (l/lens focus-selected-shapes)
(l/derive st/state)))
(def ^:private selected-modifers-ref
"A customized version of `refs/selected-modifiers`
that instead of focus to one concrete id, it focuses
on the whole map."
(-> (l/key :modifiers)
(l/derive refs/workspace)))
;; --- Resize Implementation
;; TODO: this function need to be refactored
;; (defrecord StartResizeSelected [vid ids shape]
;; ptk/WatchEvent
;; (watch [_ state stream]
;; (let [pid (get-in state [:workspace :current])
;; wst (get-in state [:workspace pid])
(defn- start-resize
[vid ids shape]
(prn "start-resize" vid ids shape)
(letfn [(on-resize [shape [point lock?]]
(let [result (geom/resize-shape vid shape point lock?)
scale (geom/calculate-scale-ratio shape result)
mtx (geom/generate-resize-matrix vid shape scale)
xfm (map #(udw/apply-temporal-resize % mtx))]
(apply st/emit! (sequence xfm ids))))
(on-end []
(apply st/emit! (map udw/apply-resize ids)))
;; Unifies the instantaneous proportion lock modifier
;; activated by Ctrl key and the shapes own proportion
;; lock flag that can be activated on element options.
(normalize-proportion-lock [[point ctrl?]]
(let [proportion-lock? (:proportion-lock shape)]
[point (or proportion-lock? ctrl?)]))
;; Applies alginment to point if it is currently
;; activated on the current workspace
(apply-grid-alignment [point]
(if @refs/selected-alignment
(uwrk/align-point point)
(rx/of point)))
;; Apply the current zoom factor to the point.
(apply-zoom [point]
(gpt/divide point @refs/selected-zoom))]
(let [shape (->> (geom/shape->rect-shape shape)
(geom/size))
stoper (->> streams/events
(rx/filter uev/mouse-up?)
(rx/take 1))
stream (->> streams/canvas-mouse-position
(rx/take-until stoper)
(rx/map apply-zoom)
(rx/mapcat apply-grid-alignment)
(rx/with-latest vector streams/mouse-position-ctrl)
(rx/map normalize-proportion-lock))]
(rx/subscribe stream (partial on-resize shape) nil on-end))))
;; --- Controls (Component)
(def ^:private handler-size-threshold
"The size in pixels that shape width or height
should reach in order to increase the handler
control pointer radius from 4 to 6."
60)
(mf/defc control-item
[{:keys [class on-click r cy cx] :as props}]
[:circle
{:class-name class
:on-mouse-down on-click
:r r
:style {:fillOpacity "1"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"}
:fill "#31e6e0"
:stroke "#28c4d4"
:cx cx
:cy cy}])
(mf/defc controls
[{:keys [shape zoom on-click] :as props}]
(let [{:keys [x1 y1 width height]} shape
radius (if (> (max width height) handler-size-threshold) 6.0 4.0)]
[:g.controls
[:rect.main {:x x1 :y y1
:width width
:height height
:stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom))
:style {:stroke "#333" :fill "transparent"
:stroke-opacity "1"}}]
[:& control-item {:class "top"
:on-click #(on-click :top %)
:r (/ radius zoom)
:cx (+ x1 (/ width 2))
:cy (- y1 2)}]
[:& control-item {:on-click #(on-click :right %)
:r (/ radius zoom)
:cy (+ y1 (/ height 2))
:cx (+ x1 width 1)
:class "right"}]
[:& control-item {:on-click #(on-click :bottom %)
:r (/ radius zoom)
:cx (+ x1 (/ width 2))
:cy (+ y1 height 2)
:class "bottom"}]
[:& control-item {:on-click #(on-click :left %)
:r (/ radius zoom)
:cy (+ y1 (/ height 2))
:cx (- x1 3)
:class "left"}]
[:& control-item {:on-click #(on-click :top-left %)
:r (/ radius zoom)
:cx x1
:cy y1
:class "top-left"}]
[:& control-item {:on-click #(on-click :top-right %)
:r (/ radius zoom)
:cx (+ x1 width)
:cy y1
:class "top-right"}]
[:& control-item {:on-click #(on-click :bottom-left %)
:r (/ radius zoom)
:cx x1
:cy (+ y1 height)
:class "bottom-left"}]
[:& control-item {:on-click #(on-click :bottom-right %)
:r (/ radius zoom)
:cx (+ x1 width)
:cy (+ y1 height)
:class "bottom-right"}]]))
;; --- Selection Handlers (Component)
;; (defn get-path-edition-stoper
;; [stream]
;; (letfn [(stoper-event? [{:keys [type shift] :as event}]
;; (and (uev/mouse-event? event) (= type :up)))]
;; (rx/merge
;; (rx/filter stoper-event? stream)
;; (->> stream
;; (rx/filter #(= % ::uev/interrupt))
;; (rx/take 1)))))
;; (defn start-path-edition
;; [shape-id index]
;; (letfn [(on-move [delta]
;; (st/emit! (uds/update-path shape-id index delta)))]
;; (let [stoper (get-path-edition-stoper streams/events)
;; stream (rx/take-until stoper streams/mouse-position-deltas)]
;; (when @refs/selected-alignment
;; (st/emit! (uds/initial-path-point-align shape-id index)))
;; (rx/subscribe stream on-move))))
;; (mx/defc path-edition-selection-handlers
;; [{:keys [id segments modifiers] :as shape} zoom]
;; (letfn [(on-click [index event]
;; (dom/stop-propagation event)
;; (start-path-edition id index))]
;; (let [{:keys [displacement]} modifiers
;; segments (if displacement
;; (map #(gpt/transform % displacement) segments)
;; segments)]
;; [:g.controls
;; (for [[index {:keys [x y]}] (map-indexed vector segments)]
;; [:circle {:cx x :cy y
;; :r (/ 6.0 zoom)
;; :key index
;; :on-click (partial on-click index)
;; :fill "#31e6e0"
;; :stroke "#28c4d4"
;; :style {:cursor "pointer"}}])])))
(mf/defc multiple-selection-handlers
[{:keys [shapes modifiers zoom] :as props}]
(let [shape (->> shapes
(map #(assoc % :modifiers (get modifiers (:id %))))
(map #(geom/selection-rect %))
(geom/shapes->rect-shape)
(geom/selection-rect))
on-click #(do (dom/stop-propagation %2)
(start-resize %1 (map :id shapes) shape))]
[:& controls {:shape shape
:zoom zoom
:on-click on-click}]))
(mf/defc single-selection-handlers
[{:keys [shape zoom] :as props}]
(let [on-click #(do (dom/stop-propagation %2)
(start-resize %1 #{(:id shape)} shape))
shape (geom/selection-rect shape)]
[:& controls {:shape shape :zoom zoom :on-click on-click}]))
;; (mx/defc text-edition-selection-handlers
;; {:mixins [mx/static]}
;; [{:keys [id] :as shape} zoom]
;; (let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)]
;; [:g.controls
;; [:rect.main {:x x1 :y y1
;; :width width
;; :height height
;; ;; :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom))
;; :style {:stroke "#333"
;; :stroke-width "0.5"
;; :stroke-opacity "0.5"
;; :fill "transparent"}}]]))
(defn- focus-shapes
[selected]
(mapv #(get-in @st/state [:shapes %]) selected))
(mf/defc selection-handlers
[{:keys [wst] :as props}]
(let [shapes (focus-shapes (:selected wst))
edition? (:edition wst)
modifiers (:modifiers wst)
zoom (:zoom wst 1)
num (count shapes)
{:keys [id type] :as shape} (first shapes)]
(cond
(zero? num)
nil
(> num 1)
[:& multiple-selection-handlers {:shapes shapes
:modifiers modifiers
:zoom zoom}]
;; (and (= type :text) edition?)
;; (-> (assoc shape :modifiers (get modifiers id))
;; (text-edition-selection-handlers zoom))
;; (= type :path)
;; (if (= edition? (:id shape))
;; (-> (assoc shape :modifiers (get modifiers id))
;; (path-edition-selection-handlers zoom))
;; (-> (assoc shape :modifiers (get modifiers id))
;; (single-selection-handlers zoom)))
:else
[:& single-selection-handlers
{:shape (assoc shape :modifiers (get modifiers id))
:zoom zoom}])))

View file

@ -11,7 +11,8 @@
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.main.data.workspace :as dw] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace-drawing :as udwd]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.shapes :as uds]
[uxbox.main.data.undo :as udu] [uxbox.main.data.undo :as udu]
[uxbox.main.data.history :as udh] [uxbox.main.data.history :as udh]
@ -26,39 +27,39 @@
;; --- Shortcuts ;; --- Shortcuts
(defonce +shortcuts+ (defonce +shortcuts+
{:shift+g #(st/emit! (dw/toggle-flag :grid)) {:shift+g #(st/emit! (udw/toggle-flag :grid))
:ctrl+g #(st/emit! (uds/group-selected)) :ctrl+g #(st/emit! (uds/group-selected))
:ctrl+shift+g #(st/emit! (uds/ungroup-selected)) :ctrl+shift+g #(st/emit! (uds/ungroup-selected))
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap)) :ctrl+shift+m #(st/emit! (udw/toggle-flag :sitemap))
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools)) :ctrl+shift+f #(st/emit! (udw/toggle-flag :drawtools))
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons)) :ctrl+shift+i #(st/emit! (udw/toggle-flag :icons))
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers)) :ctrl+shift+l #(st/emit! (udw/toggle-flag :layers))
:ctrl+0 #(st/emit! (dw/reset-zoom)) :ctrl+0 #(st/emit! (udw/reset-zoom))
:ctrl+r #(st/emit! (dw/toggle-flag :ruler)) :ctrl+r #(st/emit! (udw/toggle-flag :ruler))
:ctrl+d #(st/emit! (uds/duplicate-selected)) :ctrl+d #(st/emit! (uds/duplicate-selected))
:ctrl+c #(st/emit! (dw/copy-to-clipboard)) :ctrl+c #(st/emit! (udw/copy-to-clipboard))
:ctrl+v #(st/emit! (dw/paste-from-clipboard)) :ctrl+v #(st/emit! (udw/paste-from-clipboard))
:ctrl+shift+v #(udl/open! :clipboard) :ctrl+shift+v #(udl/open! :clipboard)
:ctrl+z #(st/emit! (udu/undo)) :ctrl+z #(st/emit! (udu/undo))
:ctrl+shift+z #(st/emit! (udu/redo)) :ctrl+shift+z #(st/emit! (udu/redo))
:ctrl+y #(st/emit! (udu/redo)) :ctrl+y #(st/emit! (udu/redo))
:ctrl+b #(st/emit! (dw/select-for-drawing wsd/+draw-tool-rect+)) :ctrl+b #(st/emit! (udwd/select-for-drawing wsd/+draw-tool-rect+))
:ctrl+e #(st/emit! (dw/select-for-drawing wsd/+draw-tool-circle+)) :ctrl+e #(st/emit! (udwd/select-for-drawing wsd/+draw-tool-circle+))
:ctrl+t #(st/emit! (dw/select-for-drawing wsd/+draw-tool-text+)) :ctrl+t #(st/emit! (udwd/select-for-drawing wsd/+draw-tool-text+))
:esc #(st/emit! (uds/deselect-all)) :esc #(st/emit! (udw/deselect-all))
:delete #(st/emit! (uds/delete-selected)) :delete #(st/emit! (udw/delete-selected))
:ctrl+up #(st/emit! (uds/move-selected-layer :up)) :ctrl+up #(st/emit! (udw/move-selected-layer :up))
:ctrl+down #(st/emit! (uds/move-selected-layer :down)) :ctrl+down #(st/emit! (udw/move-selected-layer :down))
:ctrl+shift+up #(st/emit! (uds/move-selected-layer :top)) :ctrl+shift+up #(st/emit! (udw/move-selected-layer :top))
:ctrl+shift+down #(st/emit! (uds/move-selected-layer :bottom)) :ctrl+shift+down #(st/emit! (udw/move-selected-layer :bottom))
:shift+up #(st/emit! (uds/move-selected :up :fast)) :shift+up #(st/emit! (udw/move-selected :up :fast))
:shift+down #(st/emit! (uds/move-selected :down :fast)) :shift+down #(st/emit! (udw/move-selected :down :fast))
:shift+right #(st/emit! (uds/move-selected :right :fast)) :shift+right #(st/emit! (udw/move-selected :right :fast))
:shift+left #(st/emit! (uds/move-selected :left :fast)) :shift+left #(st/emit! (udw/move-selected :left :fast))
:up #(st/emit! (uds/move-selected :up :std)) :up #(st/emit! (udw/move-selected :up :std))
:down #(st/emit! (uds/move-selected :down :std)) :down #(st/emit! (udw/move-selected :down :std))
:right #(st/emit! (uds/move-selected :right :std)) :right #(st/emit! (udw/move-selected :right :std))
:left #(st/emit! (uds/move-selected :left :std)) :left #(st/emit! (udw/move-selected :left :std))
}) })
;; --- Shortcuts Setup Functions ;; --- Shortcuts Setup Functions
@ -80,33 +81,10 @@
(events/unlistenByKey key) (events/unlistenByKey key)
(.clearKeyListener handler))))) (.clearKeyListener handler)))))
(defn- initialize (defn init
[] []
(let [stream (->> (rx/create watch-shortcuts) (let [stream (->> (rx/create watch-shortcuts)
(rx/pr-log "[debug]: shortcut:"))] (rx/pr-log "[debug]: shortcut:"))]
(rx/on-value stream (fn [event] (rx/on-value stream (fn [event]
(when-let [handler (get +shortcuts+ event)] (when-let [handler (get +shortcuts+ event)]
(handler)))))) (handler))))))
;; --- Helpers
;; (defn- move-selected
;; [dir speed]
;; (case speed
;; :std (st/emit! (uds/move-selected dir 1))
;; :fast (st/emit! (uds/move-selected dir 20))))
;; --- Mixin
(defn- init
[own]
(assoc own ::sub (initialize)))
(defn- will-unmount
[own]
(rx/cancel! (::sub own))
(dissoc own ::sub))
(def shortcuts-mixin
{:init init
:will-unmount will-unmount})

View file

@ -18,26 +18,31 @@
;; --- Left Sidebar (Component) ;; --- Left Sidebar (Component)
(mf/defc left-sidebar (mf/defc left-sidebar
[{:keys [flags page-id] :as props}] [{:keys [wst page] :as props}]
[:aside#settings-bar.settings-bar.settings-bar-left (let [{:keys [flags selected]} wst]
[:div.settings-bar-inside [:aside#settings-bar.settings-bar.settings-bar-left
(when (contains? flags :sitemap) [:div.settings-bar-inside
(sitemap-toolbox page-id)) (when (contains? flags :sitemap)
(when (contains? flags :document-history) [:& sitemap-toolbox {:page page}])
(history-toolbox page-id)) #_(when (contains? flags :document-history)
(when (contains? flags :layers) (history-toolbox page-id))
(layers-toolbox))]]) (when (contains? flags :layers)
[:& layers-toolbox {:page page
:selected selected}])]]))
;; --- Right Sidebar (Component) ;; --- Right Sidebar (Component)
(mf/defc right-sidebar (mf/defc right-sidebar
[{:keys [flags page-id] :as props}] [{:keys [wst page] :as props}]
[:aside#settings-bar.settings-bar (let [flags (:flags wst)
[:div.settings-bar-inside dtool (:drawing-tool wst)]
(when (contains? flags :drawtools) [:aside#settings-bar.settings-bar
(draw-toolbox flags)) [:div.settings-bar-inside
(when (contains? flags :element-options) (when (contains? flags :drawtools)
(options-toolbox)) [:& draw-toolbox {:flags flags :drawing-tool dtool}])
(when (contains? flags :icons) (when (contains? flags :element-options)
(icons-toolbox))]]) [:& options-toolbox {:page page
:selected (:selected wst)}])
(when (contains? flags :icons)
#_(icons-toolbox))]]))

View file

@ -2,34 +2,20 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.drawtools (ns uxbox.main.ui.workspace.sidebar.drawtools
(:require [lentes.core :as l] (:require
[potok.core :as ptk] [rumext.alpha :as mf]
[uxbox.main.store :as st] [uxbox.builtins.icons :as i]
[uxbox.main.refs :as refs] [uxbox.main.data.shapes :as uds]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as udw] [uxbox.main.data.workspace-drawing :as udwd]
[uxbox.main.user-events :as uev] [uxbox.main.store :as st]
[uxbox.builtins.icons :as i] [uxbox.main.user-events :as uev]
[uxbox.util.uuid :as uuid] [uxbox.util.i18n :refer (tr)]
[uxbox.util.i18n :refer (tr)] [uxbox.util.uuid :as uuid]))
[uxbox.util.router :as r]
[uxbox.util.data :refer (read-string)]
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom]))
;; --- Refs
(def ^:private drawing-shape-id-ref
"A focused vision of the drawing property
of the workspace status. This avoids
rerender the whole toolbox on each workspace
change."
(-> (l/key :drawing-tool)
(l/derive refs/workspace)))
;; --- Constants ;; --- Constants
@ -91,33 +77,32 @@
;; --- Draw Toolbox (Component) ;; --- Draw Toolbox (Component)
(mx/defc draw-toolbox (mf/defc draw-toolbox
{:mixins [mx/static mx/reactive]} {:wrap [mf/wrap-memo]}
[flags] [{:keys [flags drawing-tool] :as props}]
(let [drawing-tool (mx/react refs/selected-drawing-tool) (let [close #(st/emit! (udw/toggle-flag :drawtools))
close #(st/emit! (udw/toggle-flag :drawtools))
tools (->> (into [] +draw-tools+) tools (->> (into [] +draw-tools+)
(sort-by (comp :priority second))) (sort-by (comp :priority second)))
select-drawtool #(st/emit! ::uev/interrupt select-drawtool #(st/emit! ::uev/interrupt
(udw/deactivate-ruler) (udw/deactivate-ruler)
(udw/select-for-drawing %)) (udwd/select-for-drawing %))
toggle-ruler #(st/emit! (udw/select-for-drawing nil) toggle-ruler #(st/emit! (udwd/select-for-drawing nil)
(uds/deselect-all) (uds/deselect-all)
(udw/toggle-ruler))] (udw/toggle-ruler))]
[:div#form-tools.tool-window.drawing-tools {} [:div#form-tools.tool-window.drawing-tools
[:div.tool-window-bar {} [:div.tool-window-bar
[:div.tool-window-icon {} i/window] [:div.tool-window-icon i/window]
[:span {} (tr "ds.draw-tools")] [:span (tr "ds.draw-tools")]
[:div.tool-window-close {:on-click close} i/close]] [:div.tool-window-close {:on-click close} i/close]]
[:div.tool-window-content {} [:div.tool-window-content
(for [[i props] (map-indexed vector tools)] (for [[i props] (map-indexed vector tools)]
(let [selected? (= drawing-tool (:shape props))] (let [selected? (= drawing-tool (:shape props))]
[:div.tool-btn.tooltip.tooltip-hover [:div.tool-btn.tooltip.tooltip-hover
{:alt (tr (:help props)) {:alt (tr (:help props))
:class (when selected? "selected") :class (when selected? "selected")
:key (str i) :key i
:on-click (partial select-drawtool (:shape props))} :on-click (partial select-drawtool (:shape props))}
(:icon props)])) (:icon props)]))
[:div.tool-btn.tooltip.tooltip-hover [:div.tool-btn.tooltip.tooltip-hover

View file

@ -12,6 +12,7 @@
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.lenses :as ul] [uxbox.main.lenses :as ul]
[uxbox.main.data.workspace :as udw] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace-drawing :as udwd]
[uxbox.main.data.icons :as udi] [uxbox.main.data.icons :as udi]
[uxbox.main.ui.shapes.icon :as icon] [uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.dashboard.icons :as icons] [uxbox.main.ui.dashboard.icons :as icons]
@ -61,10 +62,10 @@
(letfn [(on-close [event] (letfn [(on-close [event]
(st/emit! (udw/toggle-flag :icons))) (st/emit! (udw/toggle-flag :icons)))
(on-select [icon event] (on-select [icon event]
(st/emit! (udw/select-for-drawing icon))) (st/emit! (udwd/select-for-drawing icon)))
(on-change [event] (on-change [event]
(let [value (read-string (dom/event->value event))] (let [value (read-string (dom/event->value event))]
(st/emit! (udw/select-for-drawing nil) (st/emit! (udwd/select-for-drawing nil)
(udw/select-icons-toolbox-collection value))))] (udw/select-icons-toolbox-collection value))))]
[:div#form-figures.tool-window [:div#form-figures.tool-window
[:div.tool-window-bar [:div.tool-window-bar

View file

@ -6,32 +6,28 @@
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.layers (ns uxbox.main.ui.workspace.sidebar.layers
(:require [lentes.core :as l] (:require
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.events :as events] [goog.events :as events]
[potok.core :as ptk] [lentes.core :as l]
[uxbox.main.store :as st] [potok.core :as ptk]
[uxbox.main.refs :as refs] [rumext.alpha :as mf]
[uxbox.main.data.workspace :as udw] [rumext.core :as mx]
[uxbox.main.data.shapes :as uds] [uxbox.builtins.icons :as i]
[uxbox.main.ui.shapes.icon :as icon] [uxbox.main.data.shapes :as uds]
[uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.refs :as refs]
[uxbox.util.data :refer (read-string classnames)] [uxbox.main.store :as st]
[uxbox.util.router :as r] [uxbox.main.ui.keyboard :as kbd]
[rumext.core :as mx] [uxbox.main.ui.shapes.icon :as icon]
[rumext.alpha :as mf] [uxbox.util.data :refer (read-string classnames)]
[uxbox.util.dom.dnd :as dnd] [uxbox.util.dom :as dom]
[uxbox.util.dom :as dom]) [uxbox.util.dom.dnd :as dnd]
[uxbox.util.router :as r])
(:import goog.events.EventType)) (:import goog.events.EventType))
;; --- Helpers ;; --- Helpers
(defn- focus-page
[id]
(-> (l/in [:pages id])
(l/derive st/state)))
(defn- select-shape (defn- select-shape
[selected item event] [selected item event]
(dom/prevent-default event) (dom/prevent-default event)
@ -42,18 +38,18 @@
nil nil
(.-ctrlKey event) (.-ctrlKey event)
(st/emit! (uds/select-shape id)) (st/emit! (udw/select-shape id))
(> (count selected) 1) (> (count selected) 1)
(st/emit! (uds/deselect-all) (st/emit! (udw/deselect-all)
(uds/select-shape id)) (udw/select-shape id))
(contains? selected id) (contains? selected id)
(st/emit! (uds/select-shape id)) (st/emit! (udw/select-shape id))
:else :else
(st/emit! (uds/deselect-all) (st/emit! (udw/deselect-all)
(uds/select-shape id))))) (udw/select-shape id)))))
(defn- toggle-visibility (defn- toggle-visibility
[selected item event] [selected item event]
@ -64,7 +60,7 @@
(st/emit! (uds/show-shape id)) (st/emit! (uds/show-shape id))
(st/emit! (uds/hide-shape id))) (st/emit! (uds/hide-shape id)))
(when (contains? selected id) (when (contains? selected id)
(st/emit! (uds/select-shape id))))) (st/emit! (udw/select-shape id)))))
(defn- toggle-blocking (defn- toggle-blocking
[item event] [item event]
@ -90,46 +86,45 @@
;; --- Shape Name (Component) ;; --- Shape Name (Component)
(mf/def shape-name (mf/defc layer-name
:mixins [mf/memo (mf/local)] [{:keys [shape] :as props}]
:render (let [local (mf/use-state {})
(fn [{:keys [::mf/local] :as own} {:keys [id] :as shape}] on-blur (fn [event]
(letfn [(on-blur [event] (let [target (dom/event->target event)
(let [target (dom/event->target event) parent (.-parentNode target)
parent (.-parentNode target) name (dom/get-value target)]
name (dom/get-value target)] (set! (.-draggable parent) true)
(set! (.-draggable parent) true) (st/emit! (uds/rename-shape (:id shape) name))
(st/emit! (uds/rename-shape id name)) (swap! local assoc :edition false)))
(swap! local assoc :edition false))) on-key-down (fn [event]
(on-key-down [event] (js/console.log event)
(js/console.log event) (when (kbd/enter? event)
(when (kbd/enter? event) (on-blur event)))
(on-blur event))) on-click (fn [event]
(on-click [event] (dom/prevent-default event)
(dom/prevent-default event) (let [parent (.-parentNode (.-target event))]
(let [parent (.-parentNode (.-target event))] (set! (.-draggable parent) false))
(set! (.-draggable parent) false)) (swap! local assoc :edition true))]
(swap! local assoc :edition true))] (if (:edition @local)
(if (:edition @local) [:input.element-name
[:input.element-name {:type "text"
{:type "text" :on-blur on-blur
:on-blur on-blur :on-key-down on-key-down
:on-key-down on-key-down :auto-focus true
:auto-focus true :default-value (:name shape "")}]
:default-value (:name shape "")}] [:span.element-name
[:span.element-name {:on-double-click on-click}
{:on-double-click on-click} (:name shape "")])))
(:name shape "")]))))
;; --- Layer Simple (Component) ;; --- Layer Simple (Component)
(mx/defcs layer-simple (mf/defc layer-item
{:mixins [mx/static (mx/local)]} [{:keys [shape selected] :as props}]
[{:keys [::mx/local]} item selected] (let [local (mf/use-state {})
(let [selected? (contains? selected (:id item)) selected? (contains? selected (:id shape))
select #(select-shape selected item %) select #(select-shape selected shape %)
toggle-visibility #(toggle-visibility selected item %) toggle-visibility #(toggle-visibility selected shape %)
toggle-blocking #(toggle-blocking item %) toggle-blocking #(toggle-blocking shape %)
li-classes (classnames li-classes (classnames
:selected selected? :selected selected?
:hide (:dragging @local)) :hide (:dragging @local))
@ -142,7 +137,7 @@
(letfn [(on-drag-start [event] (letfn [(on-drag-start [event]
(let [target (dom/event->target event)] (let [target (dom/event->target event)]
(dnd/set-allowed-effect! event "move") (dnd/set-allowed-effect! event "move")
(dnd/set-data! event (:id item)) (dnd/set-data! event (:id shape))
(dnd/set-image! event target 50 10) (dnd/set-image! event target 50 10)
(swap! local assoc :dragging true))) (swap! local assoc :dragging true)))
(on-drag-end [event] (on-drag-end [event]
@ -152,8 +147,8 @@
(let [id (dnd/get-data event) (let [id (dnd/get-data event)
over (:over @local)] over (:over @local)]
(case (:over @local) (case (:over @local)
:top (st/emit! (uds/drop-shape id (:id item) :before)) :top (st/emit! (uds/drop-shape id (:id shape) :before))
:bottom (st/emit! (uds/drop-shape id (:id item) :after))) :bottom (st/emit! (uds/drop-shape id (:id shape) :after)))
(swap! local assoc :dragging false :over nil))) (swap! local assoc :dragging false :over nil)))
(on-drag-over [event] (on-drag-over [event]
(dom/prevent-default event) (dom/prevent-default event)
@ -180,21 +175,21 @@
:on-drop on-drop :on-drop on-drop
:draggable true} :draggable true}
[:div.element-actions {} [:div.element-actions
[:div.toggle-element [:div.toggle-element
{:class (when-not (:hidden item) "selected") {:class (when-not (:hidden shape) "selected")
:on-click toggle-visibility} :on-click toggle-visibility}
i/eye] i/eye]
[:div.block-element [:div.block-element
{:class (when (:blocked item) "selected") {:class (when (:blocked shape) "selected")
:on-click toggle-blocking} :on-click toggle-blocking}
i/lock]] i/lock]]
[:div.element-icon (element-icon item)] [:div.element-icon (element-icon shape)]
(shape-name item)]]))) [:& layer-name {:shape shape}]]])))
;; --- Layer Group (Component) ;; --- Layer Group (Component)
(mx/defcs layer-group #_(mx/defcs layer-group
{:mixins [mx/static mx/reactive (mx/local)]} {:mixins [mx/static mx/reactive (mx/local)]}
[{:keys [::mx/local]} {:keys [id] :as item} selected] [{:keys [::mx/local]} {:keys [id] :as item} selected]
(let [selected? (contains? selected (:id item)) (let [selected? (contains? selected (:id item))
@ -284,40 +279,44 @@
;; --- Layers Tools (Buttons Component) ;; --- Layers Tools (Buttons Component)
(defn- allow-grouping? ;; (defn- allow-grouping?
"Check if the current situation allows grouping ;; "Check if the current situation allows grouping
of the currently selected shapes." ;; of the currently selected shapes."
[selected shapes-map] ;; [selected shapes-map]
(let [xform (comp (map shapes-map) ;; (let [xform (comp (map shapes-map)
(map :group)) ;; (map :group))
groups (into #{} xform selected)] ;; groups (into #{} xform selected)]
(= 1 (count groups)))) ;; (= 1 (count groups))))
(defn- allow-ungrouping? ;; (defn- allow-ungrouping?
"Check if the current situation allows ungrouping ;; "Check if the current situation allows ungrouping
of the currently selected shapes." ;; of the currently selected shapes."
[selected shapes-map] ;; [selected shapes-map]
(let [shapes (into #{} (map shapes-map) selected) ;; (let [shapes (into #{} (map shapes-map) selected)
groups (into #{} (map :group) shapes)] ;; groups (into #{} (map :group) shapes)]
(or (and (= 1 (count shapes)) ;; (or (and (= 1 (count shapes))
(= :group (:type (first shapes)))) ;; (= :group (:type (first shapes))))
(and (= 1 (count groups)) ;; (and (= 1 (count groups))
(not (nil? (first groups))))))) ;; (not (nil? (first groups)))))))
(mx/defc layers-tools (mf/defc layers-tools
"Layers widget options buttons." "Layers widget options buttons."
[selected shapes-map] [{:keys [selected shapes] :as props}]
(let [duplicate #(st/emit! (uds/duplicate-selected)) #_(let [duplicate #(st/emit! (uds/duplicate-selected))
group #(st/emit! (uds/group-selected)) group #(st/emit! (uds/group-selected))
ungroup #(st/emit! (uds/ungroup-selected)) ungroup #(st/emit! (uds/ungroup-selected))
delete #(st/emit! (uds/delete-selected)) delete #(st/emit! (udw/delete-selected))
allow-grouping? (allow-grouping? selected shapes-map) ;; allow-grouping? (allow-grouping? selected shapes)
allow-ungrouping? (allow-ungrouping? selected shapes-map) ;; allow-ungrouping? (allow-ungrouping? selected shapes)
;; NOTE: the grouping functionallity will be removed/replaced
;; with elements.
allow-ungrouping? false
allow-grouping? false
allow-duplicate? (= 1 (count selected)) allow-duplicate? (= 1 (count selected))
allow-deletion? (pos? (count selected))] allow-deletion? (pos? (count selected))]
[:div.layers-tools {} [:div.layers-tools
[:ul.layers-tools-content {} [:ul.layers-tools-content
[:li.clone-layer.tooltip.tooltip-top [:li.clone-layer.tooltip.tooltip-top
{:alt "Duplicate" {:alt "Duplicate"
:class (when-not allow-duplicate? "disable") :class (when-not allow-duplicate? "disable")
@ -341,25 +340,30 @@
;; --- Layers Toolbox (Component) ;; --- Layers Toolbox (Component)
(mx/defc layers-toolbox (mf/def layers-toolbox
{:mixins [mx/static mx/reactive]} :mixins [mx/static mx/reactive]
[]
(let [selected (mx/react refs/selected-shapes) :init
page (mx/react refs/selected-page) (fn [own {:keys [id]}]
shapes-map (mx/react refs/shapes-by-id) (assoc own ::shapes-ref (-> (l/key :shapes)
close #(st/emit! (udw/toggle-flag :layers)) (l/derive st/state))))
dragel (volatile! nil)]
[:div#layers.tool-window {} :render
[:div.tool-window-bar {} (fn [own {:keys [page selected] :as props}]
[:div.tool-window-icon {} i/layers] (let [shapes (mx/react (::shapes-ref own))
[:span {} "Layers"] close #(st/emit! (udw/toggle-flag :layers))
[:div.tool-window-close {:on-click close} i/close]] dragel (volatile! nil)]
[:div.tool-window-content {} [:div#layers.tool-window
[:ul.element-list {} [:div.tool-window-bar
(for [{:keys [id] :as shape} (map #(get shapes-map %) (:shapes page))] [:div.tool-window-icon i/layers]
(if (= (:type shape) :group) [:span "Layers"]
(-> (layer-group shape selected) [:div.tool-window-close {:on-click close} i/close]]
(mx/with-key id)) [:div.tool-window-content
(-> (layer-simple shape selected) [:ul.element-list
(mx/with-key id))))]] (for [id (:shapes page)]
(layers-tools selected shapes-map)])) (let [shape (get shapes id)]
[:& layer-item {:shape shape
:key id
:selected selected}]))]]
[:& layers-tools {:selected selected
:shapes shapes}]])))

View file

@ -8,8 +8,8 @@
(ns uxbox.main.ui.workspace.sidebar.options (ns uxbox.main.ui.workspace.sidebar.options
(:require (:require
[lentes.core :as l] [lentes.core :as l]
[potok.core :as ptk] [rumext.alpha :as mf]
[rumext.core :as mx :include-macros true] [rumext.core :as mx]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.shapes :as uds]
[uxbox.main.data.workspace :as udw] [uxbox.main.data.workspace :as udw]
@ -27,20 +27,18 @@
[uxbox.main.ui.workspace.sidebar.options.text :as options-text] [uxbox.main.ui.workspace.sidebar.options.text :as options-text]
[uxbox.util.data :as data] [uxbox.util.data :as data]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]))
[uxbox.util.router :as r]))
;; --- Constants ;; --- Constants
(def ^:private +menus-map+ (def ^:private +menus-map+
{:icon [::icon-measures ::fill ::stroke ::interactions] {:icon [::icon-measures ::fill ::stroke]
:rect [::rect-measures ::fill ::stroke ::interactions] :rect [::rect-measures ::fill ::stroke]
:path [::fill ::stroke ::interactions] :path [::fill ::stroke ::interactions]
:circle [::circle-measures ::fill ::stroke ::interactions] :circle [::circle-measures ::fill ::stroke]
:text [::fill ::text ::interactions] :text [::fill ::text]
:image [::image-measures ::interactions] :image [::image-measures]
:group [::fill ::stroke ::interactions] ::page [::page-measures ::page-grid-options]})
::page [::page-measures ::page-grid-options]})
(def ^:private +menus+ (def ^:private +menus+
[{:name "Size, position & rotation" [{:name "Size, position & rotation"
@ -89,45 +87,37 @@
;; --- Options ;; --- Options
(mx/defcs options (mf/defc shape-options
{:mixins [mx/static (mx/local)] [{:keys [sid] :as props}]
:key-fn #(pr-str (:id %1))} (let [shape-iref (mf/use-memo {:deps sid
[{:keys [::mx/local] :as own} shape] :init #(-> (l/in [:shapes sid])
(let [menus (get +menus-map+ (:type shape ::page)) (l/derive st/state))})
contained-in? (into #{} menus) shape (mf/deref shape-iref)
active (:menu @local (first menus))] menus (get +menus-map+ (:type shape))]
[:div {} [:div
(when (> (count menus) 1) (for [mid menus]
[:ul.element-icons {} (let [{:keys [comp] :as menu} (get +menus-by-id+ mid)]
(for [menu-id (get +menus-map+ (:type shape ::page))] [:& comp {:menu menu :shape shape :key mid}]))]))
(let [menu (get +menus-by-id+ menu-id)
selected? (= active menu-id)]
[:li#e-info {:on-click #(swap! local assoc :menu menu-id)
:key (str "menu-" (:id menu))
:class (when selected? "selected")}
(:icon menu)]))])
(when-let [menu (get +menus-by-id+ active)]
((:comp menu) menu shape))]))
(def selected-shape-ref (mf/defc page-options
(letfn [(getter [state] [{:keys [page] :as props}]
(let [selected (get-in state [:workspace :selected])] (let [menus (get +menus-map+ ::page)]
(when (= 1 (count selected)) [:div
(get-in state [:shapes (first selected)]))))] (for [mid menus]
(-> (l/lens getter) (let [{:keys [comp] :as menu} (get +menus-by-id+ mid)]
(l/derive st/state)))) [:& comp {:menu menu :page page :key mid}]))]))
(mx/defc options-toolbox (mf/defc options-toolbox
{:mixins [mx/static mx/reactive]} {:wrap [mf/wrap-memo]}
[] [{:keys [page selected] :as props}]
(let [shape (->> (mx/react selected-shape-ref) (let [close #(st/emit! (udw/toggle-flag :element-options))]
(merge shape-default-attrs)) [:div.elementa-options.tool-window
close #(st/emit! (udw/toggle-flag :element-options))] [:div.tool-window-bar
[:div.elementa-options.tool-window {} [:div.tool-window-icon i/options]
[:div.tool-window-bar {} [:span (tr "ds.element-options")]
[:div.tool-window-icon {} i/options]
[:span {} (tr "ds.element-options")]
[:div.tool-window-close {:on-click close} i/close]] [:div.tool-window-close {:on-click close} i/close]]
[:div.tool-window-content {} [:div.tool-window-content
[:div.element-options {} [:div.element-options
(options shape)]]])) (if (= (count selected) 1)
[:& shape-options {:sid (first selected)}]
[:& page-options {:page page}])]]]))

View file

@ -2,107 +2,117 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.options.circle-measures (ns uxbox.main.ui.workspace.sidebar.options.circle-measures
(:require [lentes.core :as l] (:require
[potok.core :as ptk] [rumext.alpha :as mf]
[rumext.core :as mx :include-macros true] [uxbox.builtins.icons :as i]
[uxbox.builtins.icons :as i] [uxbox.main.data.shapes :as uds]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as udw] [uxbox.main.geom :as geom]
[uxbox.main.geom :as geom] [uxbox.main.store :as st]
[uxbox.main.store :as st] [uxbox.util.data :refer (parse-int parse-float read-string)]
[uxbox.util.data :refer (parse-int parse-float read-string)] [uxbox.util.dom :as dom]
[uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt]
[uxbox.util.geom.point :as gpt] [uxbox.util.i18n :refer (tr)]
[uxbox.util.i18n :refer (tr)] [uxbox.util.math :refer (precision-or-0)]))
[uxbox.util.math :refer (precision-or-0)]
[uxbox.util.router :as r]))
(mx/defc circle-measures-menu (declare on-size-change)
{:mixins [mx/static]} (declare on-rotation-change)
[menu {:keys [id] :as shape}] (declare on-position-change)
(letfn [(on-size-change [attr event] (declare on-proportion-lock-change)
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)
props {attr value}]
(st/emit! (uds/update-dimensions sid props))))
(on-rotation-change [event]
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)]
(st/emit! (uds/update-rotation sid value))))
(on-pos-change [attr event]
(let [value (dom/event->value event)
value (parse-int value nil)
sid (:id shape)
point (gpt/point {attr value})]
(st/emit! (uds/update-position sid point))))
(on-proportion-lock-change [event]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions id))
(st/emit! (uds/lock-proportions id))))]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span "Size"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "Width"
:type "number"
:min "0"
:value (precision-or-0 (:rx shape 0) 2)
:on-change (partial on-size-change :rx)}]]
[:div.lock-size
{:class (when (:proportion-lock shape) "selected")
:on-click on-proportion-lock-change}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Height"
:type "number"
:min "0"
:value (precision-or-0 (:ry shape 0) 2)
:on-change (partial on-size-change :ry)}]]]
[:span "Position"] (mf/defc circle-measures-menu
[:div.row-flex [{:keys [menu shape] :as props}]
[:div.input-element.pixels [:div.element-set {:key (str (:id menu))}
[:input.input-text [:div.element-set-title (:name menu)]
{:placeholder "cx" [:div.element-set-content
:type "number" ;; SLIDEBAR FOR ROTATION AND OPACITY
:value (precision-or-0 (:cx shape 0) 2) [:span "Size"]
:on-change (partial on-pos-change :x)}]] [:div.row-flex
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "cy" {:placeholder "Width"
:type "number" :type "number"
:value (precision-or-0 (:cy shape 0) 2) :min "0"
:on-change (partial on-pos-change :y)}]]] :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)]
[:span "Rotation"] [:div.input-element.pixels
[:div.row-flex [:input.input-text
[:input.slidebar {:placeholder "Height"
{:type "range" :type "number"
:min 0 :min "0"
:max 360 :value (precision-or-0 (:ry shape 0) 2)
:value (:rotation shape 0) :on-change #(on-size-change % shape :ry)}]]]
:on-change on-rotation-change}]]
[:span "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 "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! (uds/update-dimensions sid props))))
(defn- on-rotation-change
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)]
(st/emit! (uds/update-rotation sid value))))
(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! (uds/update-position sid point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions (:id shape)))
(st/emit! (uds/lock-proportions (:id shape)))))
[: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"}}]]]]))

View file

@ -2,48 +2,43 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.options.fill (ns uxbox.main.ui.workspace.sidebar.options.fill
(:require [lentes.core :as l] (:require
[uxbox.util.i18n :refer (tr)] [rumext.alpha :as mf]
[uxbox.util.router :as r] [uxbox.builtins.icons :as i]
[potok.core :as ptk] [uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.shapes :as uds] [uxbox.main.ui.modal :as modal]
[uxbox.main.data.lightbox :as udl] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.builtins.icons :as i] [uxbox.util.data :refer [parse-float]]
[rumext.core :as mx :include-macros true] [uxbox.util.dom :as dom]
[uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]]))
[uxbox.util.data :refer (parse-int parse-float read-string)]
[uxbox.util.spec :refer (color?)]))
(mx/defc fill-menu (mf/defc fill-menu
{:mixins [mx/static]} [{:keys [menu shape]}]
[menu {:keys [id] :as shape}]
(letfn [(change-attrs [attrs] (letfn [(change-attrs [attrs]
(st/emit! (uds/update-attrs id attrs))) (st/emit! (udw/update-shape-attrs (:id shape) attrs)))
(on-color-change [event] (on-color-change [event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(when (color? value) (change-attrs {:fill-color value})))
(change-attrs {:fill-color value}))))
(on-opacity-change [event] (on-opacity-change [event]
(let [value (dom/event->value event) (let [value (dom/event->value event)
value (parse-float value 1) value (parse-float value 1)
value (/ value 10000)] value (/ value 10000)]
(change-attrs {:fill-opacity value}))) (change-attrs {:fill-opacity value})))
(on-color-picker-event [color]
(change-attrs {:fill-color color}))
(show-color-picker [event] (show-color-picker [event]
(let [x (.-clientX event) (let [x (.-clientX event)
y (.-clientY event) y (.-clientY event)
opts {:x x :y y props {:x x :y y
:shape (:id shape) :on-change #(change-attrs {:fill-color %})
:attr :fill-color :default "#ffffff"
:transparent? true}] :value (:fill-color shape)
(udl/open! :workspace/shape-colorpicker opts)))] :transparent? true}]
[:div.element-set {:key (str (:id menu))} (modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (:name menu)] [:div.element-set-title (:name menu)]
[:div.element-set-content [:div.element-set-content
@ -55,7 +50,7 @@
[:div.color-info [:div.color-info
[:input [:input
{:on-change on-color-change {:on-change on-color-change
:value (:fill-color shape)}]]] :value (:fill-color shape "")}]]]
;; SLIDEBAR FOR ROTATION AND OPACITY ;; SLIDEBAR FOR ROTATION AND OPACITY
[:span "Opacity"] [:span "Opacity"]
@ -64,6 +59,6 @@
{:type "range" {:type "range"
:min "0" :min "0"
:max "10000" :max "10000"
:value (* 10000 (:fill-opacity shape)) :value (str (* 10000 (:fill-opacity shape 1)))
:step "1" :step "1"
:on-change on-opacity-change}]]]])) :on-change on-opacity-change}]]]]))

View file

@ -2,109 +2,114 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.options.icon-measures (ns uxbox.main.ui.workspace.sidebar.options.icon-measures
(:require [lentes.core :as l] (:require
[potok.core :as ptk] [rumext.alpha :as mf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.util.i18n :refer [tr]] [uxbox.main.data.shapes :as uds]
[uxbox.util.router :as r] [uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st] [uxbox.main.geom :as geom]
[uxbox.main.data.workspace :as udw] [uxbox.main.store :as st]
[uxbox.main.data.shapes :as uds] [uxbox.util.data :refer [parse-int parse-float read-string]]
[uxbox.main.geom :as geom] [uxbox.util.dom :as dom]
[rumext.core :as mx :include-macros true] [uxbox.util.geom.point :as gpt]
[uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]]
[uxbox.util.geom.point :as gpt] [uxbox.util.math :refer [precision-or-0]]))
[uxbox.util.data :refer [parse-int parse-float read-string]]
[uxbox.util.math :refer [precision-or-0]]))
(mx/defc icon-measures-menu (declare on-size-change)
{:mixins [mx/static]} (declare on-rotation-change)
[menu shape] (declare on-position-change)
(letfn [(on-size-change [attr event] (declare on-proportion-lock-change)
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)
props {attr value}]
(st/emit! (uds/update-dimensions sid props))))
(on-rotation-change [event]
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)]
(st/emit! (uds/update-rotation sid value))))
(on-pos-change [attr event]
(let [value (dom/event->value event)
value (parse-int value nil)
sid (:id shape)
point (gpt/point {attr value})]
(st/emit! (uds/update-position sid point))))
(on-proportion-lock-change [event]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions (:id shape)))
(st/emit! (uds/lock-proportions (:id shape)))))]
(let [size (geom/size shape)]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span "Size"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "Width"
:type "number"
:min "0"
:value (precision-or-0 (:width size) 2)
:on-change (partial on-size-change :width)}]]
[:div.lock-size
{:class (when (:proportion-lock shape) "selected")
:on-click on-proportion-lock-change}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Height"
:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)
:on-change (partial on-size-change :height)}]]]
[:span "Position"] (mf/defc icon-measures-menu
[:div.row-flex [{:keys [menu shape] :as props}]
[:div.input-element.pixels (let [size (geom/size shape)]
[:input.input-text [:div.element-set {:key (str (:id menu))}
{:placeholder "X" [:div.element-set-title (:name menu)]
:type "number" [:div.element-set-content
:value (precision-or-0 (:x1 shape 0) 2) ;; SLIDEBAR FOR ROTATION AND OPACITY
:on-change (partial on-pos-change :x)}]] [:span "Size"]
[:div.input-element.pixels [:div.row-flex
[:input.input-text [:div.input-element.pixels
{:placeholder "Y" [:input.input-text {:placeholder "Width"
:type "number" :type "number"
:value (precision-or-0 (:y1 shape 0) 2) :min "0"
:on-change (partial on-pos-change :y)}]]] :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)]
[:span "Rotation"] [:div.input-element.pixels
[:div.row-flex [:input.input-text {:placeholder "Height"
[:input.slidebar :type "number"
{:type "range" :min "0"
:min 0 :value (precision-or-0 (:height size) 2)
:max 360 :on-change #(on-size-change % shape :height)}]]]
:value (:rotation shape 0)
:on-change on-rotation-change}]] [:span "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 "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}]]
[: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! (uds/update-dimensions sid props))))
(defn- on-rotation-change
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)]
(st/emit! (uds/update-rotation sid value))))
(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! (uds/update-position sid point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions (:id shape)))
(st/emit! (uds/lock-proportions (:id shape)))))
[: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"}}]
]]])))

View file

@ -6,119 +6,130 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.options.image-measures (ns uxbox.main.ui.workspace.sidebar.options.image-measures
(:require [lentes.core :as l] (:require
[uxbox.util.i18n :refer (tr)] [rumext.alpha :as mf]
[uxbox.util.router :as r] [uxbox.builtins.icons :as i]
[potok.core :as ptk] [uxbox.main.data.shapes :as uds]
[uxbox.main.store :as st] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as udw] [uxbox.main.geom :as geom]
[uxbox.main.data.shapes :as uds] [uxbox.main.store :as st]
[uxbox.builtins.icons :as i] [uxbox.util.data :refer (parse-int parse-float read-string)]
[uxbox.main.geom :as geom] [uxbox.util.dom :as dom]
[uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt]
[uxbox.util.geom.point :as gpt] [uxbox.util.i18n :refer (tr)]
[uxbox.util.data :refer (parse-int parse-float read-string)] [uxbox.util.math :refer (precision-or-0)]))
[uxbox.util.math :refer (precision-or-0)]
[rumext.core :as mx :include-macros true]))
(mx/defc image-measures-menu (declare on-size-change)
{:mixins [mx/static]} (declare on-rotation-change)
[menu {:keys [id] :as shape}] (declare on-opacity-change)
(letfn [(on-size-change [attr event] (declare on-position-change)
(let [value (dom/event->value event) (declare on-proportion-lock-change)
value (parse-int value 0)
props {attr value}]
(st/emit! (uds/update-dimensions id props))))
(on-rotation-change [event]
(let [value (dom/event->value event)
value (parse-int value 0)]
(st/emit! (uds/update-rotation id value))))
(on-opacity-change [event]
(let [value (dom/event->value event)
value (parse-float value 1)
value (/ value 10000)]
(st/emit! (uds/update-attrs id {:opacity value}))))
(on-pos-change [attr event]
(let [value (dom/event->value event)
value (parse-int value nil)
point (gpt/point {attr value})]
(st/emit! (uds/update-position id point))))
(on-proportion-lock-change [event]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions id))
(st/emit! (uds/lock-proportions id))))]
(let [size (geom/size shape)]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span "Size"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "Width"
:type "number"
:min "0"
:value (precision-or-0 (:width size) 2)
:on-change (partial on-size-change :width)}]]
[:div.lock-size
{:class (when (:proportion-lock shape) "selected")
:on-click on-proportion-lock-change}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Height"
:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)
:on-change (partial on-size-change :height)}]]]
[:span "Position"] (mf/defc image-measures-menu
[:div.row-flex [{:keys [menu shape] :as props}]
[:div.input-element.pixels (let [size (geom/size shape)]
[:input.input-text [:div.element-set
{:placeholder "X" [:div.element-set-title (:name menu)]
:type "number" [:div.element-set-content
:value (precision-or-0 (:x1 shape 0) 2) ;; SLIDEBAR FOR ROTATION AND OPACITY
:on-change (partial on-pos-change :x)}]] [:span "Size"]
[:div.input-element.pixels [:div.row-flex
[:input.input-text [:div.input-element.pixels
{:placeholder "Y" [:input.input-text
:type "number" {:placeholder "Width"
:value (precision-or-0 (:y1 shape 0) 2) :type "number"
:on-change (partial on-pos-change :y)}]]] :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 "Height"
:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)
:on-change #(on-size-change % shape :height)}]]]
;; [:span "Rotation"] [:span "Position"]
;; [:div.row-flex [:div.row-flex
;; [:input.slidebar [:div.input-element.pixels
;; {:type "range" [:input.input-text
;; :min 0 {:placeholder "X"
;; :max 360 :type "number"
;; :value (:rotation shape 0) :value (precision-or-0 (:x1 shape 0) 2)
;; :on-change on-rotation-change}]] :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)}]]]
;; [:div.row-flex ;; [:span "Rotation"]
;; [:div.input-element.degrees ;; [:div.row-flex
;; [:input.input-text ;; [:input.slidebar
;; {:placeholder "" ;; {:type "range"
;; :type "number" ;; :min 0
;; :min 0 ;; :max 360
;; :max 360 ;; :value (:rotation shape 0)
;; :value (precision-or-0 (:rotation shape 0) 2) ;; :on-change on-rotation-change}]]
;; :on-change on-rotation-change
;; }]] ;; [:div.row-flex
;; [:input.input-text ;; [:div.input-element.degrees
;; {:style {:visibility "hidden"}}]] ;; [: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 "Opacity"] [:span "Opacity"]
[:div.row-flex [:div.row-flex
[:input.slidebar [:input.slidebar
{:type "range" {:type "range"
:min "0" :min "0"
:max "10000" :max "10000"
:value (* 10000 (:opacity shape 1)) :value (* 10000 (:opacity shape 1))
:step "1" :step "1"
:on-change on-opacity-change}]] :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! (uds/update-dimensions (:id shape) props))))
(defn- on-rotation-change
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)]
(st/emit! (uds/update-rotation (:id shape) value))))
(defn- on-opacity-change
[event shape]
(let [value (dom/event->value event)
value (parse-float value 1)
value (/ value 10000)]
(st/emit! (uds/update-attrs (:id shape) {:opacity value}))))
(defn- on-position-change
[event shape attr]
(let [value (dom/event->value event)
value (parse-int value nil)
point (gpt/point {attr value})]
(st/emit! (uds/update-position (:id shape) point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions (:id shape)))
(st/emit! (uds/lock-proportions (:id shape)))))

View file

@ -7,8 +7,7 @@
(ns uxbox.main.ui.workspace.sidebar.options.interactions (ns uxbox.main.ui.workspace.sidebar.options.interactions
(:require (:require
[lentes.core :as l] [rumext.alpha :as mf]
[rumext.core :as mx :include-macros true]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.main.data.shapes :as uds] [uxbox.main.data.shapes :as uds]
@ -19,7 +18,6 @@
[uxbox.util.data :refer [read-string]] [uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as r]
[uxbox.util.spec :refer [color?]])) [uxbox.util.spec :refer [color?]]))
;; --- Helpers ;; --- Helpers
@ -53,11 +51,11 @@
;; :holdrelease "Hold release" ;; :holdrelease "Hold release"
(pr-str trigger))) (pr-str trigger)))
(mx/defc interactions-list (mf/defc interactions-list
[shape form-ref] [{:keys [shape form] :as props}]
(letfn [(on-edit [item event] (letfn [(on-edit [item event]
(dom/prevent-default event) (dom/prevent-default event)
(reset! form-ref item)) (reset! form item))
(delete [item] (delete [item]
(let [sid (:id shape) (let [sid (:id shape)
id (:id item)] id (:id item)]
@ -78,16 +76,17 @@
;; --- Trigger Input ;; --- Trigger Input
(mx/defc trigger-input (mf/defc trigger-input
[form-ref] [{:keys [form] :as props}]
(when-not (:trigger @form-ref) ;; (mf/use-effect
(swap! form-ref assoc :trigger :click)) ;; {:init #(when-not (:trigger @form) (swap! form assoc :trigger :click))
;; :deps true})
[:div [:div
[:span "Trigger"] [:span "Trigger"]
[:div.row-flex [:div.row-flex
[:select.input-select {:placeholder "Choose a trigger" [:select.input-select {:placeholder "Choose a trigger"
:on-change (partial on-change form-ref :trigger) :on-change (partial on-change form :trigger)
:value (pr-str (:trigger @form-ref))} :value (pr-str (:trigger @form))}
[:option {:value ":click"} "Click"] [:option {:value ":click"} "Click"]
[:option {:value ":doubleclick"} "Double-click"] [:option {:value ":doubleclick"} "Double-click"]
[:option {:value ":rightclick"} "Right-click"] [:option {:value ":rightclick"} "Right-click"]
@ -105,15 +104,15 @@
;; --- URL Input ;; --- URL Input
(mx/defc url-input (mf/defc url-input
[form-ref] [form]
[:div [:div
[:span "Url"] [:span "Url"]
[:div.row-flex [:div.row-flex
[:input.input-text [:input.input-text
{:placeholder "http://" {:placeholder "http://"
:on-change (partial on-change form-ref :url) :on-change (partial on-change form :url)
:value (:url @form-ref "") :value (:url @form "")
:type "url"}]]]) :type "url"}]]])
;; --- Elements Input ;; --- Elements Input
@ -129,16 +128,16 @@
(conj acc shape))))] (conj acc shape))))]
(reduce resolve-shape [] shapes)))) (reduce resolve-shape [] shapes))))
(mx/defc elements-input (mf/defc elements-input
[page form-ref] [{:keys [page-id form] :as props}]
(let [shapes (collect-shapes @st/state page)] (let [shapes (collect-shapes @st/state page-id)]
[:div [:div
[:span "Element"] [:span "Element"]
[:div.row-flex [:div.row-flex
[:select.input-select [:select.input-select
{:placeholder "Choose an element" {:placeholder "Choose an element"
:on-change (partial on-change form-ref :element) :on-change (partial on-change form :element)
:value (pr-str (:element @form-ref))} :value (pr-str (:element @form))}
[:option {:value "nil"} "---"] [:option {:value "nil"} "---"]
(for [shape shapes (for [shape shapes
:let [key (pr-str (:id shape))]] :let [key (pr-str (:id shape))]]
@ -146,11 +145,10 @@
;; --- Page Input ;; --- Page Input
(mx/defc pages-input (mf/defc pages-input
{:mixins [mx/reactive]}
[form-ref path] [form-ref path]
;; FIXME: react on ref ;; FIXME: react on ref
(let [pages (mx/react refs/selected-project-pages)] #_(let [pages (mx/react refs/selected-project-pages)]
(when (and (not (:page @form-ref)) (when (and (not (:page @form-ref))
(pos? (count pages))) (pos? (count pages)))
(swap! form-ref assoc :page (:id (first pages)))) (swap! form-ref assoc :page (:id (first pages))))
@ -166,124 +164,124 @@
;; --- Animation ;; --- Animation
(mx/defc animation-input (mf/defc animation-input
[form-ref] [{:keys [form] :as props}]
(when-not (:action @form-ref) (when-not (:action @form)
(swap! form-ref assoc :animation :none)) (swap! form assoc :animation :none))
[:div [:div
[:span "Animation"] [:span "Animation"]
[:div.row-flex [:div.row-flex
[:select.input-select [:select.input-select
{:placeholder "Animation" {:placeholder "Animation"
:on-change (partial on-change form-ref :animation) :on-change (partial on-change form :animation)
:value (pr-str (:animation @form-ref))} :value (pr-str (:animation @form))}
[:option {:value ":none"} "None"] [:option {:value ":none"} "None"]
[:option {:value ":fade"} "Fade"] [:option {:value ":fade"} "Fade"]
[:option {:value ":slide"} "Slide"]]]]) [:option {:value ":slide"} "Slide"]]]])
;; --- MoveTo Input ;; --- MoveTo Input
(mx/defc moveto-input (mf/defc moveto-input
[form-ref] [{:keys [form] :as props}]
(when-not (:moveto-x @form-ref) (when-not (:moveto-x @form)
(swap! form-ref assoc :moveto-x 0)) (swap! form assoc :moveto-x 0))
(when-not (:moveto-y @form-ref) (when-not (:moveto-y @form)
(swap! form-ref assoc :moveto-y 0)) (swap! form assoc :moveto-y 0))
[:div [:div
[:span "Move to position"] [:span "Move to position"]
[:div.row-flex [:div.row-flex
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "X" {:placeholder "X"
:on-change (partial on-change form-ref :moveto-x) :on-change (partial on-change form :moveto-x)
:type "number" :type "number"
:value (:moveto-x @form-ref "")}]] :value (:moveto-x @form "")}]]
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "Y" {:placeholder "Y"
:on-change (partial on-change form-ref :moveto-y) :on-change (partial on-change form :moveto-y)
:type "number" :type "number"
:value (:moveto-y @form-ref "")}]]]]) :value (:moveto-y @form "")}]]]])
;; --- MoveBy Input ;; --- MoveBy Input
(mx/defc moveby-input (mf/defc moveby-input
[form-ref] [{:keys [form] :as props}]
(when-not (:moveby-x @form-ref) (when-not (:moveby-x @form)
(swap! form-ref assoc :moveby-x 0)) (swap! form assoc :moveby-x 0))
(when-not (:moveby-y @form-ref) (when-not (:moveby-y @form)
(swap! form-ref assoc :moveby-y 0)) (swap! form assoc :moveby-y 0))
[:div [:div
[:span "Move to position"] [:span "Move to position"]
[:div.row-flex [:div.row-flex
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "X" {:placeholder "X"
:on-change (partial on-change form-ref :moveby-x) :on-change (partial on-change form :moveby-x)
:type "number" :type "number"
:value (:moveby-x @form-ref "")}]] :value (:moveby-x @form "")}]]
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "Y" {:placeholder "Y"
:on-change (partial on-change form-ref :moveby-y) :on-change (partial on-change form :moveby-y)
:type "number" :type "number"
:value (:moveby-y @form-ref "")}]]]]) :value (:moveby-y @form "")}]]]])
;; --- Opacity Input ;; --- Opacity Input
(mx/defc opacity-input (mf/defc opacity-input
[form-ref] [{:keys [form] :as props}]
(when-not (:opacity @form-ref) (when-not (:opacity @form)
(swap! form-ref assoc :opacity 100)) (swap! form assoc :opacity 100))
[:div [:div
[:span "Opacity"] [:span "Opacity"]
[:div.row-flex [:div.row-flex
[:div.input-element.percentail [:div.input-element.percentail
[:input.input-text [:input.input-text
{:placeholder "%" {:placeholder "%"
:on-change (partial on-change form-ref :opacity) :on-change (partial on-change form :opacity)
:min "0" :min "0"
:max "100" :max "100"
:type "number" :type "number"
:value (:opacity @form-ref "")}]]]]) :value (:opacity @form "")}]]]])
;; --- Rotate Input ;; --- Rotate Input
(mx/defc rotate-input ;; (mx/defc rotate-input
[form-ref] ;; [form]
[:div ;; [:div
[:span "Rotate (dg)"] ;; [:span "Rotate (dg)"]
[:div.row-flex ;; [:div.row-flex
[:div.input-element.degrees ;; [:div.input-element.degrees
[:input.input-text ;; [:input.input-text
{:placeholder "dg" ;; {:placeholder "dg"
:on-change (partial on-change form-ref :rotation) ;; :on-change (partial on-change form :rotation)
:type "number" ;; :type "number"
:value (:rotation @form-ref "")}]]]]) ;; :value (:rotation @form "")}]]]])
;; --- Resize Input ;; --- Resize Input
(mx/defc resize-input (mf/defc resize-input
[form-ref] [{:keys [form] :as props}]
[:div [:div
[:span "Resize"] [:span "Resize"]
[:div.row-flex [:div.row-flex
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "Width" {:placeholder "Width"
:on-change (partial on-change form-ref :resize-width) :on-change (partial on-change form :resize-width)
:type "number" :type "number"
:value (:resize-width @form-ref "")}]] :value (:resize-width @form "")}]]
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "Height" {:placeholder "Height"
:on-change (partial on-change form-ref :resize-height) :on-change (partial on-change form :resize-height)
:type "number" :type "number"
:value (:resize-height @form-ref "")}]]]]) :value (:resize-height @form "")}]]]])
;; --- Color Input ;; --- Color Input
(mx/defc colorpicker (mf/defc colorpicker
[{:keys [x y on-change value]}] [{:keys [x y on-change value]}]
(let [left (- x 260) (let [left (- x 260)
top (- y 50)] top (- y 50)]
@ -300,14 +298,14 @@
[params] [params]
(colorpicker params)) (colorpicker params))
(mx/defc color-input (mf/defc color-input
[form-ref] [{:keys [form] :as props}]
(when-not (:fill-color @form-ref) (when-not (:fill-color @form)
(swap! form-ref assoc :fill-color "#000000")) (swap! form assoc :fill-color "#000000"))
(when-not (:stroke-color @form-ref) (when-not (:stroke-color @form)
(swap! form-ref assoc :stroke-color "#000000")) (swap! form assoc :stroke-color "#000000"))
(letfn [(on-change [attr color] (letfn [(on-change [attr color]
(swap! form-ref assoc attr color)) (swap! form assoc attr color))
(on-change-fill-color [event] (on-change-fill-color [event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(when (color? value) (when (color? value)
@ -321,11 +319,11 @@
y (.-clientY event) y (.-clientY event)
opts {:x x :y y opts {:x x :y y
:on-change (partial on-change attr) :on-change (partial on-change attr)
:value (get @form-ref attr) :value (get @form attr)
:transparent? true}] :transparent? true}]
(udl/open! :interactions/colorpicker opts)))] (udl/open! :interactions/colorpicker opts)))]
(let [stroke-color (:stroke-color @form-ref) (let [stroke-color (:stroke-color @form)
fill-color (:fill-color @form-ref)] fill-color (:fill-color @form)]
[:div [:div
[:div.row-flex [:div.row-flex
[:div.column-half [:div.column-half
@ -351,17 +349,17 @@
;; --- Easing Input ;; --- Easing Input
(mx/defc easing-input (mf/defc easing-input
[form-ref] [{:keys [form] :as props}]
(when-not (:easing @form-ref) (when-not (:easing @form)
(swap! form-ref assoc :easing :linear)) (swap! form assoc :easing :linear))
[:div [:div
[:span "Easing"] [:span "Easing"]
[:div.row-flex [:div.row-flex
[:select.input-select [:select.input-select
{:placeholder "Easing" {:placeholder "Easing"
:on-change (partial on-change form-ref :easing) :on-change (partial on-change form :easing)
:value (pr-str (:easing @form-ref))} :value (pr-str (:easing @form))}
[:option {:value ":linear"} "Linear"] [:option {:value ":linear"} "Linear"]
[:option {:value ":easein"} "Ease in"] [:option {:value ":easein"} "Ease in"]
[:option {:value ":easeout"} "Ease out"] [:option {:value ":easeout"} "Ease out"]
@ -369,12 +367,12 @@
;; --- Duration Input ;; --- Duration Input
(mx/defc duration-input (mf/defc duration-input
[form-ref] [{:keys [form] :as props}]
(when-not (:duration @form-ref) (when-not (:duration @form)
(swap! form-ref assoc :duration 300)) (swap! form assoc :duration 300))
(when-not (:delay @form-ref) (when-not (:delay @form)
(swap! form-ref assoc :delay 0)) (swap! form assoc :delay 0))
[:div [:div
[:span "Duration | Delay"] [:span "Duration | Delay"]
[:div.row-flex [:div.row-flex
@ -382,21 +380,21 @@
[:input.input-text [:input.input-text
{:placeholder "Duration" {:placeholder "Duration"
:type "number" :type "number"
:on-change (partial on-change form-ref :duration) :on-change (partial on-change form :duration)
:value (pr-str (:duration @form-ref))}]] :value (pr-str (:duration @form))}]]
[:div.input-element.miliseconds [:div.input-element.miliseconds
[:input.input-text {:placeholder "Delay" [:input.input-text {:placeholder "Delay"
:type "number" :type "number"
:on-change (partial on-change form-ref :delay) :on-change (partial on-change form :delay)
:value (pr-str (:delay @form-ref))}]]]]) :value (pr-str (:delay @form))}]]]])
;; --- Action Input ;; --- Action Input
(mx/defc action-input (mf/defc action-input
[page form-ref] [{:keys [shape form] :as props}]
(when-not (:action @form-ref) ;; (when-not (:action @form)
(swap! form-ref assoc :action :show)) ;; (swap! form assoc :action :show))
(let [form @form-ref (let [form-data (deref form)
simple? #{:gotourl :gotopage} simple? #{:gotourl :gotopage}
elements? (complement simple?) elements? (complement simple?)
animation? #{:show :hide :toggle} animation? #{:show :hide :toggle}
@ -406,8 +404,8 @@
[:div.row-flex [:div.row-flex
[:select.input-select [:select.input-select
{:placeholder "Choose an action" {:placeholder "Choose an action"
:on-change (partial on-change form-ref :action [:trigger]) :on-change (partial on-change form :action [:trigger])
:value (pr-str (:action form))} :value (pr-str (:action form-data))}
[:option {:value ":show"} "Show"] [:option {:value ":show"} "Show"]
[:option {:value ":hide"} "Hide"] [:option {:value ":hide"} "Hide"]
[:option {:value ":toggle"} "Toggle"] [:option {:value ":toggle"} "Toggle"]
@ -422,47 +420,49 @@
#_[:option {:value ":goback"} "Go back"] #_[:option {:value ":goback"} "Go back"]
[:option {:value ":scrolltoelement"} "Scroll to element"]]] [:option {:value ":scrolltoelement"} "Scroll to element"]]]
(case (:action form) (case (:action form-data)
:gotourl (url-input form-ref) :gotourl [:& url-input {:form form}]
:gotopage (pages-input form-ref) ;; :gotopage (pages-input form)
:color (color-input form-ref) :color [:& color-input {:form form}]
;; :rotate (rotate-input form-ref) ;; :rotate (rotate-input form)
:size (resize-input form-ref) :size [:& resize-input {:form form}]
:moveto (moveto-input form-ref) :moveto [:& moveto-input {:form form}]
:moveby (moveby-input form-ref) :moveby [:& moveby-input {:form form}]
:opacity (opacity-input form-ref) :opacity [:& opacity-input {:form form}]
nil) nil)
(when (elements? (:action form)) (when (elements? (:action form-data))
(elements-input page form-ref)) [:& elements-input {:page-id (:page shape)
:form form}])
(when (and (animation? (:action form)) (when (and (animation? (:action form-data))
(:element @form-ref)) (:element form-data))
(animation-input form-ref)) [:& 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}]])]))
(when (or (not= (:animation form :none) :none)
(and (only-easing? (:action form))
(:element form)))
(list (easing-input form-ref)
(duration-input form-ref)))
]))
;; --- Form ;; --- Form
(mx/defc interactions-form (mf/defc interactions-form
[shape form-ref] [{:keys [shape form] :as props}]
(letfn [(on-submit [event] (letfn [(on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(let [shape-id (:id shape) (let [sid (:id shape)
data (deref form-ref)] data (deref form)]
(st/emit! (uds/update-interaction shape-id data)) (st/emit! (uds/update-interaction sid data))
(reset! form-ref nil))) (reset! form nil)))
(on-cancel [event] (on-cancel [event]
(dom/prevent-default event) (dom/prevent-default event)
(reset! form-ref nil))] (reset! form nil))]
[:form {:on-submit on-submit} [:form {:on-submit on-submit}
(trigger-input form-ref) [:& trigger-input {:form form}]
(action-input (:page shape) form-ref) [:& action-input {:shape shape :form form}]
[:div.row-flex [:div.row-flex
[:input.btn-primary.btn-small.save-btn [:input.btn-primary.btn-small.save-btn
{:value "Save" :type "submit"}] {:value "Save" :type "submit"}]
@ -471,23 +471,24 @@
;; --- Interactions Menu ;; --- Interactions Menu
(mx/defcs interactions-menu (def +initial-form+
{:mixins [mx/static (mx/local)]} {:trigger :click
[own menu shape] :action :show})
(let [local (::mx/local own)
form-ref (l/derive (l/key :form) local) (mf/defc interactions-menu
interactions (:interactions shape) [{:keys [menu shape] :as props}]
create-interaction #(reset! form-ref {})] (let [form (mf/use-state nil)
interactions (:interactions shape)]
[:div.element-set {:key (str (:id menu))} [:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)] [:div.element-set-title (:name menu)]
[:div.element-set-content [:div.element-set-content
(if @form-ref (if form
(interactions-form shape form-ref) [:& interactions-form {:form form :shape shape}]
[:div [:div
(interactions-list shape form-ref) [:& interactions-list {:form form :shape shape}]
[:input.btn-primary.btn-small [:input.btn-primary.btn-small
{:value "New interaction" {:value "New interaction"
:on-click create-interaction :on-click #(reset! form +initial-form+)
:type "button"}]])]])) :type "button"}]])]]))
;; --- Not implemented stuff ;; --- Not implemented stuff

View file

@ -7,57 +7,55 @@
(ns uxbox.main.ui.workspace.sidebar.options.page (ns uxbox.main.ui.workspace.sidebar.options.page
"Page options menu entries." "Page options menu entries."
(:require [lentes.core :as l] (:require
[potok.core :as ptk] [cuerdas.core :as str]
[cuerdas.core :as str] [rumext.alpha :as mf]
[uxbox.main.store :as st] [uxbox.builtins.icons :as i]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.refs :as refs] [uxbox.main.data.pages :as udp]
[uxbox.main.data.pages :as udp] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as udw] [uxbox.main.refs :as refs]
[uxbox.main.data.lightbox :as udl] [uxbox.main.store :as st]
[uxbox.builtins.icons :as i] [uxbox.main.ui.modal :as modal]
[uxbox.main.ui.workspace.colorpicker] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[rumext.core :as mx :include-macros true] [uxbox.util.data :refer [parse-int]]
[uxbox.util.data :refer [parse-int]] [uxbox.util.dom :as dom]
[uxbox.util.spec :refer [color?]] [uxbox.util.spec :refer [color?]]))
[uxbox.util.dom :as dom]))
(mx/defcs measures-menu (mf/defc measures-menu
{:mixins [mx/static mx/reactive]} [{:keys [menu page] :as props}]
[own menu] (let [metadata (:metadata page)
(let [{:keys [id metadata] :as page} (mx/react refs/selected-page)
metadata (merge c/page-metadata metadata)] metadata (merge c/page-metadata metadata)]
(letfn [(on-size-change [attr] (letfn [(on-size-change [event attr]
(when-let [value (-> (mx/ref-node own (name attr)) (let [value (-> (dom/event->value event)
(dom/get-value) (parse-int nil))]
(parse-int nil))]
(st/emit! (->> (assoc metadata attr value) (st/emit! (->> (assoc metadata attr value)
(udp/update-metadata id))))) (udp/update-metadata (:id page))))))
(on-color-change [] (change-color [color]
(when-let [value (-> (mx/ref-node own "color") (st/emit! (->> (assoc metadata :background color)
(dom/get-value) (udp/update-metadata (:id page)))))
(#(if (color? %) % nil)))]
(->> (assoc metadata :background value)
(udp/update-metadata id)
(st/emit!))))
(on-name-change [] (on-color-change [event]
(when-let [value (-> (mx/ref-node own "name") (let [value (dom/event->value event)]
(dom/get-value) (change-color value)))
(str/trim))]
(on-name-change [event]
(let [value (-> (dom/event->value event)
(str/trim))]
(st/emit! (->> (assoc page :name value) (st/emit! (->> (assoc page :name value)
(udp/update-page id))))) (udp/update-page (:id page))))))
(show-color-picker [event] (show-color-picker [event]
(let [x (.-clientX event) (let [x (.-clientX event)
y (.-clientY event) y (.-clientY event)
opts {:x x :y y props {:x x :y y
:default "#ffffff" :default "#ffffff"
:transparent? true :value (:background metadata)
:attr :background}] :transparent? true
(udl/open! :workspace/page-colorpicker opts)))] :on-change change-color}]
(modal/show! colorpicker-modal props)))]
[:div.element-set [:div.element-set
[:div.element-set-title (:name menu)] [:div.element-set-title (:name menu)]
[:div.element-set-content [:div.element-set-content
@ -66,7 +64,6 @@
[:div.input-element [:div.input-element
[:input.input-text [:input.input-text
{:type "text" {:type "text"
:ref "name"
:on-change on-name-change :on-change on-name-change
:value (str (:name page)) :value (str (:name page))
:placeholder "page name"}]]] :placeholder "page name"}]]]
@ -76,15 +73,13 @@
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:type "number" {:type "number"
:ref "width" :on-change #(on-size-change % :width)
:on-change #(on-size-change :width)
:value (str (:width metadata)) :value (str (:width metadata))
:placeholder "width"}]] :placeholder "width"}]]
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:type "number" {:type "number"
:ref "height" :on-change #(on-size-change % :height)
:on-change #(on-size-change :height)
:value (str (:height metadata)) :value (str (:height metadata))
:placeholder "height"}]]] :placeholder "height"}]]]
@ -96,43 +91,39 @@
[:div.color-info [:div.color-info
[:input [:input
{:on-change on-color-change {:on-change on-color-change
:ref "color"
:value (:background metadata)}]]]]]))) :value (:background metadata)}]]]]])))
(mx/defcs grid-options-menu (mf/defc grid-options-menu
{:mixins [mx/static mx/reactive]} [{:keys [menu page] :as props}]
[own menu] (let [metadata (:metadata page)
(let [{:keys [id metadata] :as page} (mx/react refs/selected-page)
metadata (merge c/page-metadata metadata)] metadata (merge c/page-metadata metadata)]
(letfn [(on-x-change [] (letfn [(on-x-change [event]
(when-let [value (-> (mx/ref-node own "x-axis") (let [value (-> (dom/event->value event)
(dom/get-value) (parse-int nil))]
(parse-int nil))] (st/emit! (->> (assoc metadata :grid-x-axis value)
(st/emit! (udp/update-metadata (:id page))))))
(->> (assoc metadata :grid-x-axis value) (on-y-change [event]
(udw/update-metadata id))))) (let [value (-> (dom/event->value event)
(on-y-change [] (parse-int nil))]
(when-let [value (-> (mx/ref-node own "y-axis") (st/emit! (->> (assoc metadata :grid-y-axis value)
(dom/get-value) (udp/update-metadata (:id page))))))
(parse-int nil))]
(st/emit! (change-color [color]
(->> (assoc metadata :grid-y-axis value) (st/emit! (->> (assoc metadata :grid-color color)
(udw/update-metadata id))))) (udp/update-metadata (:id page)))))
(on-color-change [] (on-color-change [event]
(when-let [value (-> (mx/ref-node own "color") (let [value (dom/event->value event)]
(dom/get-value) (change-color value)))
(#(if (color? %) % nil)))]
(->> (assoc metadata :grid-color value)
(udp/update-metadata id)
(st/emit!))))
(show-color-picker [event] (show-color-picker [event]
(let [x (.-clientX event) (let [x (.-clientX event)
y (.-clientY event) y (.-clientY event)
opts {:x x :y y props {:x x :y y
:transparent? true :transparent? true
:default "#cccccc" :default "#cccccc"
:attr :grid-color}] :attr :grid-color
(udl/open! :workspace/page-colorpicker opts)))] :on-change change-color}]
(modal/show! colorpicker-modal props)))]
[:div.element-set [:div.element-set
[:div.element-set-title (:name menu)] [:div.element-set-title (:name menu)]
[:div.element-set-content [:div.element-set-content
@ -141,14 +132,12 @@
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:type "number" {:type "number"
:ref "x-axis"
:value (:grid-x-axis metadata) :value (:grid-x-axis metadata)
:on-change on-x-change :on-change on-x-change
:placeholder "x"}]] :placeholder "x"}]]
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:type "number" {:type "number"
:ref "y-axis"
:value (:grid-y-axis metadata) :value (:grid-y-axis metadata)
:on-change on-y-change :on-change on-y-change
:placeholder "y"}]]] :placeholder "y"}]]]
@ -160,5 +149,4 @@
[:div.color-info [:div.color-info
[:input [:input
{:on-change on-color-change {:on-change on-color-change
:ref "color"
:value (:grid-color metadata "#cccccc")}]]]]]))) :value (:grid-color metadata "#cccccc")}]]]]])))

View file

@ -6,101 +6,104 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.options.rect-measures (ns uxbox.main.ui.workspace.sidebar.options.rect-measures
(:require [lentes.core :as l] (:require
[uxbox.util.i18n :refer [tr]] [rumext.alpha :as mf]
[uxbox.util.router :as r] [uxbox.builtins.icons :as i]
[potok.core :as ptk] [uxbox.main.data.shapes :as uds]
[uxbox.main.store :as st] [uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as udw] [uxbox.main.geom :as geom]
[uxbox.main.data.shapes :as uds] [uxbox.main.store :as st]
[uxbox.builtins.icons :as i] [uxbox.util.data :refer [parse-int parse-float read-string]]
[rumext.core :as mx :include-macros true] [uxbox.util.dom :as dom]
[uxbox.main.geom :as geom] [uxbox.util.geom.point :as gpt]
[uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]]
[uxbox.util.geom.point :as gpt] [uxbox.util.math :refer [precision-or-0]]))
[uxbox.util.data :refer [parse-int parse-float read-string]]
[uxbox.util.math :refer [precision-or-0]]))
(mx/defc rect-measures-menu (declare on-size-change)
{:mixins [mx/static]} (declare on-rotation-change)
[menu {:keys [id] :as shape}] (declare on-position-change)
(letfn [(on-size-change [event attr] (declare on-proportion-lock-change)
(let [value (-> (dom/event->value event)
(parse-int 0))]
(st/emit! (uds/update-dimensions id {attr value}))))
(on-rotation-change [event]
(let [value (-> (dom/event->value event)
(parse-int 0))]
(st/emit! (uds/update-rotation id value))))
(on-pos-change [event attr]
(let [value (-> (dom/event->value event)
(parse-int nil))
point (gpt/point {attr value})]
(st/emit! (uds/update-position id point))))
(on-proportion-lock-change [event]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions id))
(st/emit! (uds/lock-proportions id))))]
(let [size (geom/size shape)]
[:div.element-set
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span "Size"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "Width"
:type "number"
:min "0"
:value (precision-or-0 (:width size) 2)
:on-change #(on-size-change % :width)}]]
[:div.lock-size
{:class (when (:proportion-lock shape) "selected")
:on-click on-proportion-lock-change}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Height"
:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)
:on-change #(on-size-change % :height)}]]]
[:span "Position"] (mf/defc rect-measures-menu
[:div.row-flex [{:keys [menu shape] :as props}]
[:div.input-element.pixels (let [size (geom/size shape)]
[:input.input-text [:div.element-set
{:placeholder "x" [:div.element-set-title (:name menu)]
:type "number" [:div.element-set-content
:value (precision-or-0 (:x1 shape 0) 2) ;; SLIDEBAR FOR ROTATION AND OPACITY
:on-change #(on-pos-change % :x)}]] [:span "Size"]
[:div.input-element.pixels [:div.row-flex
[:input.input-text [:div.input-element.pixels
{:placeholder "y" [:input.input-text {:placeholder "Width"
:type "number" :type "number"
:value (precision-or-0 (:y1 shape 0) 2) :min "0"
:on-change #(on-pos-change % :y)}]]] :value (precision-or-0 (:width size) 2)
:on-change #(on-size-change % shape :width)}]]
[:span "Rotation"] [:div.lock-size {:class (when (:proportion-lock shape) "selected")
[:div.row-flex :on-click #(on-proportion-lock-change % shape)}
[:input.slidebar (if (:proportion-lock shape) i/lock i/unlock)]
{:type "range"
:min 0
:max 360
:value (:rotation shape 0)
:on-change on-rotation-change}]]
[:div.row-flex [:div.input-element.pixels
[:div.input-element.degrees [:input.input-text {:placeholder "Height"
[:input.input-text :type "number"
{:placeholder "" :min "0"
:type "number" :value (precision-or-0 (:height size) 2)
:min 0 :on-change #(on-size-change % shape :height)}]]]
:max 360
:value (precision-or-0 (:rotation shape "0") 2) [:span "Position"]
:on-change on-rotation-change [:div.row-flex
}]] [:div.input-element.pixels
[:input.input-text [:input.input-text {:placeholder "x"
{:style {:visibility "hidden"}}] :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 "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! (uds/update-dimensions (:id shape) {attr value}))))
(defn- on-rotation-change
[event shape]
(let [value (-> (dom/event->value event)
(parse-int 0))]
(st/emit! (uds/update-rotation (:id shape) value))))
(defn- on-position-change
[event shape attr]
(let [value (-> (dom/event->value event)
(parse-int nil))
point (gpt/point {attr value})]
(st/emit! (uds/update-position (:id shape) point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (uds/unlock-proportions (:id shape)))
(st/emit! (uds/lock-proportions (:id shape)))))

View file

@ -2,61 +2,41 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.options.stroke (ns uxbox.main.ui.workspace.sidebar.options.stroke
(:require [lentes.core :as l] (:require
[uxbox.util.i18n :refer (tr)] [rumext.alpha :as mf]
[uxbox.util.router :as r] [uxbox.builtins.icons :as i]
[potok.core :as ptk] [uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.shapes :as uds] [uxbox.main.ui.modal :as modal]
[uxbox.main.data.lightbox :as udl] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.builtins.icons :as i] [uxbox.util.data :refer [parse-int parse-float read-string]]
[rumext.core :as mx :include-macros true] [uxbox.util.dom :as dom]
[uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]]
[uxbox.util.data :refer (parse-int parse-float read-string)] [uxbox.util.math :refer [precision-or-0]]))
[uxbox.util.math :refer (precision-or-0)]
[uxbox.util.spec :refer (color?)]))
(mx/defcs stroke-menu (declare on-width-change)
{:mixins [mx/static (mx/local)]} (declare on-opacity-change)
[{:keys [::mx/local]} menu {:keys [id] :as shape}] (declare on-stroke-style-change)
(letfn [(on-width-change [event] (declare on-stroke-color-change)
(let [value (-> (dom/event->value event) (declare on-border-change)
(parse-float 1))] (declare show-color-picker)
(st/emit! (uds/update-attrs id {:stroke-width value}))))
(on-opacity-change [event] (mf/defc stroke-menu
(let [value (-> (dom/event->value event) [{:keys [menu shape] :as props}]
(parse-float 1) (let [local (mf/use-state {})
(/ 10000))] on-border-lock #(swap! local update :border-lock not)
(st/emit! (uds/update-attrs id {:stroke-opacity value})))) on-stroke-style-change #(on-stroke-style-change % shape)
(on-stroke-style-change [event] on-width-change #(on-width-change % shape)
(let [value (-> (dom/event->value event) on-stroke-color-change #(on-stroke-color-change % shape)
(read-string))] on-border-change-rx #(on-border-change % shape local :rx)
(st/emit! (uds/update-attrs id {:stroke-style value})))) on-border-change-ry #(on-border-change % shape local :ry)
(on-stroke-color-change [event] on-opacity-change #(on-opacity-change % shape)
(let [value (dom/event->value event)] show-color-picker #(show-color-picker % shape)]
(when (color? value) [:div.element-set
(st/emit! (uds/update-attrs id {:stroke-color value})))))
(on-border-change [event attr]
(let [value (-> (dom/event->value event)
(parse-int nil))]
(if (:border-lock @local)
(st/emit! (uds/update-attrs id {:rx value :ry value}))
(st/emit! (uds/update-attrs id {attr value})))))
(on-border-proportion-lock [event]
(swap! local update :border-lock not))
(show-color-picker [event]
(let [x (.-clientX event)
y (.-clientY event)
opts {:x x :y y
:shape (:id shape)
:attr :stroke-color
:transparent? true}]
(udl/open! :workspace/shape-colorpicker opts)))]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)] [:div.element-set-title (:name menu)]
[:div.element-set-content [:div.element-set-content
[:span "Style"] [:span "Style"]
@ -94,17 +74,17 @@
{:placeholder "rx" {:placeholder "rx"
:type "number" :type "number"
:value (precision-or-0 (:rx shape 0) 2) :value (precision-or-0 (:rx shape 0) 2)
:on-change #(on-border-change % :rx)}]] :on-change on-border-change-rx}]]
[:div.lock-size [:div.lock-size
{:class (when (:border-lock @local) "selected") {:class (when (:border-lock @local) "selected")
:on-click on-border-proportion-lock} :on-click on-border-lock}
i/lock] i/lock]
[:div.input-element.pixels [:div.input-element.pixels
[:input.input-text [:input.input-text
{:placeholder "ry" {:placeholder "ry"
:type "number" :type "number"
:value (precision-or-0 (:ry shape 0) 2) :value (precision-or-0 (:ry shape 0) 2)
:on-change #(on-border-change % :ry)}]]] :on-change on-border-change-ry}]]]
[:span "Opacity"] [:span "Opacity"]
[:div.row-flex [:div.row-flex
@ -112,6 +92,50 @@
{:type "range" {:type "range"
:min "0" :min "0"
:max "10000" :max "10000"
:value (* 10000 (:stroke-opacity shape)) :value (* 10000 (:stroke-opacity shape 1))
:step "1" :step "1"
:on-change on-opacity-change}]]]])) :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)))

View file

@ -104,13 +104,33 @@
;; TODO: refactor this to not use global refs ;; TODO: refactor this to not use global refs
(defn- pages-selector
[project-id]
(let [get-order #(get-in % [:metadata :order])]
(fn [state]
;; NOTE: this function will be executed on every state change
;; when we are on workspace page, that is ok but we need to
;; think in a better approach (maybe materialize the result
;; after pages fetching...)
(->> (vals (:pages state))
(filter #(= project-id (:project %)))
(sort-by get-order)))))
(mf/def sitemap-toolbox (mf/def sitemap-toolbox
:mixins [mf/memo mf/reactive] :mixins [mf/memo mf/reactive]
:init
(fn [own {:keys [page] :as props}]
(assoc own
::project-ref (-> (l/in [:projects (:project page)])
(l/derive st/state))
::pages-ref (-> (l/lens (pages-selector (:project page)))
(l/derive st/state))))
:render :render
(fn [own current-page-id] (fn [own {:keys [page] :as props}]
(let [project (mf/react refs/selected-project) (let [project (mf/react (::project-ref own))
pages (mf/react refs/selected-project-pages) pages (mf/react (::pages-ref own))
create #(udl/open! :page-form {:page {:project (:id project)}}) create #(udl/open! :page-form {:page {:project (:id project)}})
close #(st/emit! (dw/toggle-flag :sitemap)) close #(st/emit! (dw/toggle-flag :sitemap))
deletable? (> (count pages) 1)] deletable? (> (count pages) 1)]
@ -124,9 +144,9 @@
[:span (:name project)] [:span (:name project)]
[:div.add-page {:on-click create} i/close]] [:div.add-page {:on-click create} i/close]]
[:ul.element-list [:ul.element-list
(for [page pages] (for [item pages]
(let [selected? (= (:id page) current-page-id)] (let [selected? (= (:id item) (:id page))]
[:& page-item {:page page [:& page-item {:page item
:deletable? deletable? :deletable? deletable?
:selected? selected? :selected? selected?
:key (:id page)}]))]]]))) :key (:id item)}]))]]])))

View file

@ -0,0 +1,224 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.viewport
(:require
[goog.events :as events]
[rumext.alpha :as mf]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace-drawing :as udwd]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.workspace.canvas :refer [canvas]]
[uxbox.main.ui.workspace.grid :refer [grid]]
[uxbox.main.ui.workspace.ruler :refer [ruler]]
[uxbox.main.user-events :as uev]
[uxbox.util.data :refer [parse-int]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt])
(:import goog.events.EventType))
;; --- Coordinates Widget
(mf/def coordinates
:mixins [mf/reactive mf/memo]
:render
(fn [own {:keys [zoom] :as props}]
(let [coords (some-> (mf/react refs/canvas-mouse-position)
(gpt/divide zoom)
(gpt/round 0))]
[:ul.coordinates
[:span {:alt "x"}
(str "X: " (:x coords "-"))]
[:span {:alt "y"}
(str "Y: " (:y coords "-"))]])))
;; --- Cursor tooltip
(defn- get-shape-tooltip
"Return the shape tooltip text"
[shape]
(case (:type shape)
:icon "Click to place the Icon"
:image "Click to place the Image"
:rect "Drag to draw a Box"
:text "Drag to draw a Text Box"
:path "Click to draw a Path"
:circle "Drag to draw a Circle"
nil))
(mf/defc cursor-tooltip
{:wrap [mf/wrap-memo]}
[{:keys [tooltip]}]
(let [coords (mf/deref refs/window-mouse-position)]
[:span.cursor-tooltip
{:style
{:position "fixed"
:left (str (+ (:x coords) 5) "px")
:top (str (- (:y coords) 25) "px")}}
tooltip]))
;; --- Selection Rect
(mf/defc selrect
{:wrap [mf/wrap-memo]}
[{rect :value}]
(when rect
(let [{:keys [x1 y1 width height]} (geom/size rect)]
[:rect.selection-rect
{:x x1
:y y1
:width width
:height height}])))
;; --- Viewport
(mf/def viewport
:init
(fn [own props]
(assoc own ::viewport (mf/create-ref)))
:did-mount
(fn [own]
(letfn [(translate-point-to-viewport [pt]
(let [viewport (mf/ref-node (::viewport own))
brect (.getBoundingClientRect viewport)
brect (gpt/point (parse-int (.-left brect))
(parse-int (.-top brect)))]
(gpt/subtract pt brect)))
(translate-point-to-canvas [pt]
(let [viewport (mf/ref-node (::viewport own))]
(when-let [canvas (dom/get-element-by-class "page-canvas" viewport)]
(let [brect (.getBoundingClientRect canvas)
bbox (.getBBox canvas)
brect (gpt/point (parse-int (.-left brect))
(parse-int (.-top brect)))
bbox (gpt/point (.-x bbox) (.-y bbox))]
(-> (gpt/add pt bbox)
(gpt/subtract brect))))))
(on-key-down [event]
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/keyboard-event :down key ctrl? shift?))
(when (kbd/space? event)
(st/emit! (udw/start-viewport-positioning)))))
(on-key-up [event]
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(when (kbd/space? event)
(st/emit! (udw/stop-viewport-positioning)))
(st/emit! (uev/keyboard-event :up key ctrl? shift?))))
(on-mousemove [event]
(let [wpt (gpt/point (.-clientX event)
(.-clientY event))
vpt (translate-point-to-viewport wpt)
cpt (translate-point-to-canvas wpt)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
event {:ctrl ctrl?
:shift shift?
:window-coords wpt
:viewport-coords vpt
:canvas-coords cpt}]
(st/emit! (uev/pointer-event wpt vpt cpt ctrl? shift?))))]
(let [key1 (events/listen js/document EventType.MOUSEMOVE on-mousemove)
key2 (events/listen js/document EventType.KEYDOWN on-key-down)
key3 (events/listen js/document EventType.KEYUP on-key-up)]
(assoc own
::key1 key1
::key2 key2
::key3 key3))))
:will-unmount
(fn [own]
(events/unlistenByKey (::key1 own))
(events/unlistenByKey (::key2 own))
(events/unlistenByKey (::key3 own))
(dissoc own ::key1 ::key2 ::key3))
:render
(fn [own {:keys [page wst] :as props}]
(let [{:keys [drawing-tool tooltip zoom flags]} wst
tooltip (or tooltip (get-shape-tooltip drawing-tool))
zoom (or zoom 1)]
(letfn [(on-mouse-down [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :down ctrl? shift?)))
(if drawing-tool
(st/emit! (udwd/start-drawing drawing-tool))
(st/emit! ::uev/interrupt (udw/start-selrect))))
(on-context-menu [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :context-menu ctrl? shift?))))
(on-mouse-up [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :up ctrl? shift?))))
(on-click [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :click ctrl? shift?))))
(on-double-click [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (uev/mouse-event :double-click ctrl? shift?))))]
[:*
[:& coordinates {:zoom zoom}]
[:div.tooltip-container
(when tooltip
[:& cursor-tooltip {:tooltip tooltip}])]
[:svg.viewport {:width (* c/viewport-width zoom)
:height (* c/viewport-height zoom)
:ref (::viewport own)
:class (when drawing-tool "drawing")
:on-context-menu on-context-menu
:on-click on-click
:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
(when page
[:& canvas {:page page :wst wst}])
(if (contains? flags :grid)
[:& grid {:page page}])]
(when (contains? flags :ruler)
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
[:& selrect {:value (:selrect wst)}]]]))))

View file

@ -91,7 +91,7 @@
ptk/EffectEvent ptk/EffectEvent
(effect [_ state stream] (effect [_ state stream]
(let [router (:router state)] (let [router (:router state)]
(prn "Navigate:" id params qparams "| Match:" (resolve-url router id params qparams)) ;; (prn "Navigate:" id params qparams "| Match:" (resolve-url router id params qparams))
(navigate! router id params qparams)))) (navigate! router id params qparams))))
(defn nav (defn nav

View file

@ -56,9 +56,9 @@
(l/derive st/state))) (l/derive st/state)))
(mf/defc app (mf/defc app
{:wrap [mf/reactive*]} {:wrap [mf/wrap-reactive]}
[] []
(let [route (mf/deref route-ref)] (let [route (mf/react route-ref)]
(case (get-in route [:data :name]) (case (get-in route [:data :name])
:view/notfound (notfound-page) :view/notfound (notfound-page)
:view/viewer (let [{:keys [token id]} (get-in route [:params :path])] :view/viewer (let [{:keys [token id]} (get-in route [:params :path])]

View file

@ -34,9 +34,9 @@
;; --- Component ;; --- Component
(mf/defc viewer-page (mf/defc viewer-page
{:wrap [mf/reactive*]} {:wrap [mf/wrap-reactive]}
[{:keys [token id]}] [{:keys [token id]}]
(let [{:keys [project pages flags]} (mf/deref state-ref)] (let [{:keys [project pages flags]} (mf/react state-ref)]
(mf/use-effect (mf/use-effect
{:init #(st/emit! (dv/initialize token))}) {:init #(st/emit! (dv/initialize token))})
(when (seq pages) (when (seq pages)

View file

@ -13,7 +13,7 @@
;; --- Background (Component) ;; --- Background (Component)
(mf/defc background (mf/defc background
{:wrap [mf/memo*]} {:wrap [mf/wrap-memo]}
[{:keys [background] :as metadata}] [{:keys [background] :as metadata}]
[:rect [:rect
{:x 0 :y 0 {:x 0 :y 0
@ -26,7 +26,7 @@
(declare shape) (declare shape)
(mf/defc canvas (mf/defc canvas
{:wrap [mf/memo*]} {:wrap [mf/wrap-memo]}
[{:keys [page] :as props}] [{:keys [page] :as props}]
(let [{:keys [metadata id]} page (let [{:keys [metadata id]} page
{:keys [width height]} metadata] {:keys [width height]} metadata]

8
frontend/vendor/.babelrc vendored Normal file
View file

@ -0,0 +1,8 @@
{
"presets": [
["@babel/preset-env", {"targets": {"browsers": "last 1 Chrome versions"}, "modules": false}],
["@babel/preset-react"]
],
"plugins": ["@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-class-properties"]
}

View file

@ -1,4 +0,0 @@
# License
date-fns is licensed under the [MIT license](http://kossnocorp.mit-license.org).
Read more about MIT at [TLDRLegal](https://tldrlegal.com/license/mit-license).

View file

@ -1 +0,0 @@
1.30.1

1702
frontend/vendor/datefns/datefns.bundle.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,9 +0,0 @@
Current version: 1.28.0
Build browserified bundle:
./node_modules/browserify/bin/cmd.js -s dateFns -e src/index.js -o datefns.js
Minified bundle:
./node_modules/uglify-js/bin/uglifyjs datefns.js -m -o datefns.min.js

View file

@ -5,11 +5,15 @@
{:file "jszip/jszip.js" {:file "jszip/jszip.js"
:file-min "jszip/jszip.min.js" :file-min "jszip/jszip.min.js"
:provides ["vendor.jszip"]} :provides ["vendor.jszip"]}
{:file "datefns/datefns.js" {:file "datefns/datefns.bundle.js"
:file-min "datefns/datefns.min.js" :file-min "datefns/datefns.bundle.min.js"
:provides ["vendor.datefns"]} :provides ["vendor.datefns"]}
] {:file "react-color/react-color.bundle.js"
:file-min "react-color/react-color.bundle.min.js"
:requires ["cljsjs.react"]
:provides ["vendor.react-color"]}]
:externs ["main.externs.js" :externs ["main.externs.js"
"snapsvg/externs.js" "snapsvg/externs.js"
"jszip/externs.js" "jszip/externs.js"
"react-color/externs.js"
"datefns/externs.js"]} "datefns/externs.js"]}

4440
frontend/vendor/package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load diff

34
frontend/vendor/package.json vendored Normal file
View file

@ -0,0 +1,34 @@
{
"name": "uxbox-vendor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "NODE_ENV=production rollup -c",
"minify:react-color": "terser react-color/react-color.bundle.js -c -m -o react-color/react-color.bundle.min.js",
"minify:datefns": "terser datefns/datefns.bundle.js -c -m -o datefns/datefns.bundle.min.js",
"dist": "npm run build && npm run minify:react-color && npm run minify:datefns"
},
"author": "",
"license": "MPL2",
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-export-default-from": "^7.5.2",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"rollup": "^1.12.3",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^5.0.0",
"terser": "^4.1.2"
},
"dependencies": {
"date-fns": "^1.30.1",
"react-color": "^2.17.3",
"snapsvg": "^0.5.1"
}
}

View file

@ -0,0 +1,2 @@
var ChromePicker = {};
var SketchPicker = {};

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
import SketchPicker from "react-color/lib/components/sketch/Sketch";
if (typeof self !== "undefined") { init(self); }
else if (typeof global !== "undefined") { init(global); }
else if (typeof window !== "undefined") { init(window); }
else { throw new Error("unsupported execution environment"); }
function init(g) {
g.SketchPicker = SketchPicker;
}

51
frontend/vendor/rollup.config.js vendored Normal file
View file

@ -0,0 +1,51 @@
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import globals from 'rollup-plugin-node-globals';
import builtins from 'rollup-plugin-node-builtins';
const plugins = [
babel({
exclude: 'node_modules/**',
sourceMap: false
}),
resolve({
mainFields: ['module', 'main'],
// preferBuiltins: false,
browser: true
}),
commonjs({
include: 'node_modules/**', // Default: undefined
// if true then uses of `global` won't be dealt with by this plugin
ignoreGlobal: false, // Default: false
sourceMap: false, // Default: true
}),
globals(),
builtins(),
];
export default [{
input: "./react-color/react-color.js",
external: ["react", "react-dom"],
output: {
globals: {
"react": "React",
"react-dom": "ReactDOM"
},
compact: true,
file: './react-color/react-color.bundle.js',
format: 'iife',
},
plugins: plugins
}, {
input: "./datefns/datefns.js",
output: {
compact: true,
file: './datefns/datefns.bundle.js',
format: 'iife',
},
plugins: plugins
}];