mirror of
https://github.com/penpot/penpot.git
synced 2025-05-16 08:46:11 +02:00
♻️ mainly workspace refactor
This commit is contained in:
parent
4e382d456f
commit
212ae89c50
85 changed files with 18494 additions and 6609 deletions
|
@ -1,11 +1,7 @@
|
|||
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
|
||||
org.clojure/clojure {:mvn/version "1.10.1"}
|
||||
funcool/promesa {:mvn/version "2.0.1"}
|
||||
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"}
|
||||
|
||||
environ/environ {:mvn/version "1.1.0"}
|
||||
|
@ -13,8 +9,10 @@
|
|||
|
||||
funcool/beicon {:mvn/version "5.0.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/promesa {:mvn/version "2.0.1"}
|
||||
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
|
||||
}
|
||||
:paths ["src" "vendor" "resources"]
|
||||
:aliases
|
||||
|
|
92
frontend/package-lock.json
generated
92
frontend/package-lock.json
generated
|
@ -11,9 +11,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
||||
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
|
@ -313,18 +313,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz",
|
||||
"integrity": "sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==",
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz",
|
||||
"integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browserslist": "^4.6.1",
|
||||
"caniuse-lite": "^1.0.30000971",
|
||||
"browserslist": "^4.6.3",
|
||||
"caniuse-lite": "^1.0.30000980",
|
||||
"chalk": "^2.4.2",
|
||||
"normalize-range": "^0.1.2",
|
||||
"num2fraction": "^1.2.2",
|
||||
"postcss": "^7.0.16",
|
||||
"postcss-value-parser": "^3.3.1"
|
||||
"postcss": "^7.0.17",
|
||||
"postcss-value-parser": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"aws-sign2": {
|
||||
|
@ -481,14 +481,14 @@
|
|||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.3.tgz",
|
||||
"integrity": "sha512-CNBqTCq22RKM8wKJNowcqihHJ4SkI8CGeK7KOR9tPboXUuS5Zk5lQgzzTbs4oxD8x+6HUshZUa2OyNI9lR93bQ==",
|
||||
"version": "4.6.6",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz",
|
||||
"integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30000975",
|
||||
"electron-to-chromium": "^1.3.164",
|
||||
"node-releases": "^1.1.23"
|
||||
"caniuse-lite": "^1.0.30000984",
|
||||
"electron-to-chromium": "^1.3.191",
|
||||
"node-releases": "^1.1.25"
|
||||
}
|
||||
},
|
||||
"buffer-equal": {
|
||||
|
@ -550,9 +550,9 @@
|
|||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30000979",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000979.tgz",
|
||||
"integrity": "sha512-gcu45yfq3B7Y+WB05fOMfr0EiSlq+1u+m6rPHyJli/Wy3PVQNGaU7VA4bZE5qw+AU2UVOBR/N5g1bzADUqdvFw==",
|
||||
"version": "1.0.30000988",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000988.tgz",
|
||||
"integrity": "sha512-lPj3T8poYrRc/bniW5SQPND3GRtSrQdUM/R4mCYTbZxyi3jQiggLvZH4+BYUuX0t4TXjU+vMM7KFDQg+rSzZUQ==",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
|
@ -973,9 +973,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.183",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.183.tgz",
|
||||
"integrity": "sha512-WbKCYs7yAFOfpuoa2pK5kbOngriUtlPC+8mcQW5L/686wv04w7hYXfw5ScDrsl9kixFw1SPsALEob5V/gtlDxw==",
|
||||
"version": "1.3.207",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.207.tgz",
|
||||
"integrity": "sha512-RIgAnfqbjZNECBLjslfy4cIYvcPl3GAXmnENrcoo0TZ8fGkyEEAealAbO7MoevW4xYUPe+e68cWAj6eP0DmMHw==",
|
||||
"dev": true
|
||||
},
|
||||
"end-of-stream": {
|
||||
|
@ -2198,9 +2198,9 @@
|
|||
}
|
||||
},
|
||||
"gulp-match": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.0.3.tgz",
|
||||
"integrity": "sha1-kcfA1/Kb7NZgbVfYCn+Hdqh6uo4=",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz",
|
||||
"integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimatch": "^3.0.3"
|
||||
|
@ -2874,9 +2874,9 @@
|
|||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
|
@ -3164,9 +3164,9 @@
|
|||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.24",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.24.tgz",
|
||||
"integrity": "sha512-wym2jptfuKowMmkZsfCSTsn8qAVo8zm+UiQA6l5dNqUcpfChZSnS/vbbpOeXczf+VdPhutxh+99lWHhdd6xKzg==",
|
||||
"version": "1.1.26",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.26.tgz",
|
||||
"integrity": "sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^5.3.0"
|
||||
|
@ -3612,9 +3612,9 @@
|
|||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz",
|
||||
"integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-hrtime": {
|
||||
|
@ -4140,9 +4140,9 @@
|
|||
}
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.12",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
|
||||
"integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
|
||||
"version": "0.5.13",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
|
||||
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
|
@ -4187,9 +4187,9 @@
|
|||
}
|
||||
},
|
||||
"spdx-license-ids": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
|
||||
"integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
|
||||
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"split-string": {
|
||||
|
@ -4353,9 +4353,9 @@
|
|||
}
|
||||
},
|
||||
"ternary-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.0.1.tgz",
|
||||
"integrity": "sha1-Bk5Im0tb9gumpre8fy9cJ07Pgmk=",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz",
|
||||
"integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"duplexify": "^3.5.0",
|
||||
|
@ -4789,9 +4789,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<section id="app" tabindex="1"></section>
|
||||
<section id="lightbox"></section>
|
||||
<section id="loader"></section>
|
||||
<section id="modal"></section>
|
||||
<script src="{{& jsfile}}"></script>
|
||||
<script>uxbox.main.init()</script>
|
||||
</body>
|
||||
|
|
|
@ -97,7 +97,6 @@
|
|||
display: flex;
|
||||
transition: all .6s ease;
|
||||
width: 100%;
|
||||
overflow-x: scroll;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,6 @@
|
|||
}
|
||||
|
||||
.colorpicker-tooltip {
|
||||
background: $primary-ui-bg;
|
||||
border-radius: $br-small;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui :as ui]
|
||||
[uxbox.main.ui.lightbox :refer [lightbox]]
|
||||
[uxbox.main.ui.modal :refer [modal]]
|
||||
[uxbox.main.ui.loader :refer [loader]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.html.history :as html-history]
|
||||
|
@ -70,6 +71,7 @@
|
|||
|
||||
(mf/mount (ui/app) (dom/get-element "app"))
|
||||
(mf/mount (lightbox) (dom/get-element "lightbox"))
|
||||
(mf/mount (mf/element modal) (dom/get-element "modal"))
|
||||
(mf/mount (loader) (dom/get-element "loader"))
|
||||
|
||||
(on-navigate router cpath)))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.data.colors
|
||||
(:require
|
||||
|
@ -23,25 +23,14 @@
|
|||
(declare persist-collections)
|
||||
(declare collections-fetched?)
|
||||
|
||||
(defrecord Initialize [type id]
|
||||
(defrecord Initialize []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [type (or type :own)
|
||||
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))))
|
||||
(assoc-in state [:dashboard :colors] {:selected #{}})))
|
||||
|
||||
(defn initialize
|
||||
[type id]
|
||||
(prn "colors$initialize" type id)
|
||||
(Initialize. type id))
|
||||
[]
|
||||
(Initialize.))
|
||||
|
||||
;; --- Collections Fetched
|
||||
|
||||
|
@ -142,9 +131,7 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [type (get-in state [:dashboard :colors :type])]
|
||||
(rx/of (persist-collections)
|
||||
(rt/nav :dashboard/colors nil {:type type})))))
|
||||
(rx/of (persist-collections))))
|
||||
|
||||
(defn delete-collection
|
||||
[id]
|
||||
|
@ -152,20 +139,19 @@
|
|||
|
||||
;; --- Replace Color
|
||||
|
||||
(defrecord ReplaceColor [id from to]
|
||||
(defrecord AddColor [coll-id color]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [replacer #(-> (disj % from) (conj to))]
|
||||
(update-in state [:colors-collections id :colors] (fnil replacer #{}))))
|
||||
(update-in state [:colors-collections coll-id :colors] set/union #{color}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(rx/of (persist-collections))))
|
||||
|
||||
(defn replace-color
|
||||
(defn add-color
|
||||
"Add or replace color in a collection."
|
||||
[{:keys [id from to] :as params}]
|
||||
(ReplaceColor. id from to))
|
||||
[coll-id color]
|
||||
(AddColor. coll-id color))
|
||||
|
||||
;; --- Remove Color
|
||||
|
||||
|
@ -247,15 +233,17 @@
|
|||
(or (uuid? to) (nil? 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
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [id selected]} (get-in state [:dashboard :colors])]
|
||||
(rx/of (remove-colors id selected)
|
||||
#(assoc-in % [:dashboard :colors :selected] #{})))))
|
||||
(rx/of (remove-colors coll-id colors))))
|
||||
|
||||
(defn delete-selected-colors
|
||||
[]
|
||||
(DeleteSelectedColors.))
|
||||
(defn delete-colors
|
||||
[coll-id colors]
|
||||
(DeleteColors. coll-id colors))
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
|
||||
(s/def ::grid-x-axis number?)
|
||||
(s/def ::grid-y-axis number?)
|
||||
(s/def ::grid-color us/color?)
|
||||
(s/def ::background us/color?)
|
||||
(s/def ::grid-color string?)
|
||||
(s/def ::background string?)
|
||||
(s/def ::background-opacity number?)
|
||||
(s/def ::grid-alignment boolean?)
|
||||
(s/def ::width number?)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.data.shapes
|
||||
(:require [cljs.spec.alpha :as s :include-macros true]
|
||||
(:require [cljs.spec.alpha :as s]
|
||||
[lentes.core :as l]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
|
@ -55,6 +55,14 @@
|
|||
(s/def ::y1 number?)
|
||||
(s/def ::x2 number?)
|
||||
(s/def ::y2 number?)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::page uuid?)
|
||||
(s/def ::type #{:rect
|
||||
:group
|
||||
:path
|
||||
:circle
|
||||
:image
|
||||
:text})
|
||||
|
||||
(s/def ::attributes
|
||||
(s/keys :opt-un [::fill-color
|
||||
|
@ -80,24 +88,12 @@
|
|||
::blocked
|
||||
::locked]))
|
||||
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::page uuid?)
|
||||
(s/def ::type #{:rect
|
||||
:group
|
||||
:path
|
||||
:circle
|
||||
:image
|
||||
:text})
|
||||
|
||||
(s/def ::shape
|
||||
(s/merge (s/keys ::req-un [::id ::page ::type]) ::attributes))
|
||||
|
||||
(s/def ::rect-like-shape
|
||||
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))
|
||||
|
||||
(s/def ::direction #{:up :down :right :left})
|
||||
(s/def ::speed #{:std :fast})
|
||||
|
||||
;; --- Shapes CRUD
|
||||
|
||||
(deftype AddShape [data]
|
||||
|
@ -105,8 +101,8 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (geom/setup-proportions data)
|
||||
page (get-in state [:workspace :page])]
|
||||
(impl/assoc-shape-to-page state shape page))))
|
||||
page-id (get-in state [:workspace :current])]
|
||||
(impl/assoc-shape-to-page state shape page-id))))
|
||||
|
||||
(defn add-shape
|
||||
[data]
|
||||
|
@ -141,31 +137,6 @@
|
|||
{:pre [(uuid? id) (string? 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
|
||||
|
||||
(deftype UpdateShapeRotation [id rotation]
|
||||
|
@ -201,69 +172,6 @@
|
|||
{:pre [(uuid? id) (us/valid? ::update-dimensions-opts 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
|
||||
|
||||
(deftype UpdateShapePosition [id point]
|
||||
|
@ -294,7 +202,7 @@
|
|||
;; --- Update Shape Attrs
|
||||
|
||||
(declare UpdateAttrs)
|
||||
|
||||
;; TODO: moved
|
||||
(deftype UpdateAttrs [id attrs]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -496,52 +404,6 @@
|
|||
(keyword? 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
|
||||
|
||||
(deftype UpdateInteraction [shape interaction]
|
||||
|
@ -583,6 +445,10 @@
|
|||
{:pre [(uuid? id) (number? index) (gpt/point? delta)]}
|
||||
(UpdatePath. id index delta))
|
||||
|
||||
(def ^:private canvas-coords
|
||||
(gpt/point c/canvas-start-x
|
||||
c/canvas-start-y))
|
||||
|
||||
(deftype InitialPathPointAlign [id index]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
|
@ -624,6 +490,7 @@
|
|||
|
||||
;; --- Events (implicit) (for selected)
|
||||
|
||||
;; NOTE: moved to workspace
|
||||
(deftype DeselectAll []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -684,91 +551,4 @@
|
|||
[]
|
||||
(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))
|
||||
|
|
|
@ -301,13 +301,13 @@
|
|||
acc))
|
||||
|
||||
(defn match-by-selrect
|
||||
[state page selrect]
|
||||
[state page-id selrect]
|
||||
(let [xf (comp (map #(get-in state [:shapes %]))
|
||||
(remove :hidden)
|
||||
(remove :blocked)
|
||||
(map geom/selection-rect))
|
||||
match (partial try-match-shape xf selrect)
|
||||
shapes (get-in state [:pages page :shapes])]
|
||||
shapes (get-in state [:pages page-id :shapes])]
|
||||
(reduce match #{} (sequence xf shapes))))
|
||||
|
||||
(defn group-shapes
|
||||
|
|
|
@ -7,42 +7,50 @@
|
|||
(ns uxbox.main.data.workspace
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.store :as st]
|
||||
[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.workspace.scroll :as wscroll]
|
||||
[uxbox.main.data.workspace.drawing :as wdrawing]
|
||||
[uxbox.main.data.workspace.selrect :as wselrect]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.shapes-impl :as simpl]
|
||||
;; [uxbox.main.data.workspace.drawing :as wdrawing]
|
||||
[uxbox.main.data.workspace.ruler :as wruler]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.main.data.workspace.scroll :as wscroll]
|
||||
[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.main.geom :as geom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.time :as dt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[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
|
||||
|
||||
(def start-viewport-positioning wscroll/start-viewport-positioning)
|
||||
(def stop-viewport-positioning wscroll/stop-viewport-positioning)
|
||||
(def start-drawing wdrawing/start-drawing)
|
||||
(def close-drawing-path wdrawing/close-drawing-path)
|
||||
(def select-for-drawing wdrawing/select-for-drawing)
|
||||
(def start-selrect wselrect/start-selrect)
|
||||
;; (def start-drawing wdrawing/start-drawing)
|
||||
;; (def close-drawing-path wdrawing/close-drawing-path)
|
||||
;; (def select-for-drawing wdrawing/select-for-drawing)
|
||||
(def start-ruler wruler/start-ruler)
|
||||
(def clear-ruler wruler/clear-ruler)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; General workspace events
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- Initialize Workspace
|
||||
|
||||
(declare initialize-alignment)
|
||||
|
@ -50,24 +58,18 @@
|
|||
(defrecord Initialize [project-id page-id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [default-flags #{:sitemap :drawtools :layers :element-options :rules}]
|
||||
(if (:workspace state)
|
||||
(update state :workspace merge
|
||||
{:project project-id
|
||||
:page page-id
|
||||
:selected #{}
|
||||
:drawing nil
|
||||
:drawing-tool nil
|
||||
:tooltip nil})
|
||||
(assoc state :workspace
|
||||
{:project project-id
|
||||
:zoom 1
|
||||
:page page-id
|
||||
:flags default-flags
|
||||
:selected #{}
|
||||
:drawing nil
|
||||
:drawing-tool nil
|
||||
:tooltip nil}))))
|
||||
(let [default-flags #{:sitemap :drawtools :layers :element-options :rules}
|
||||
initial-workspace {:project-id project-id
|
||||
:page-id page-id
|
||||
:zoom 1
|
||||
:flags default-flags
|
||||
:selected #{}
|
||||
:drawing nil
|
||||
:drawing-tool nil
|
||||
:tooltip nil}]
|
||||
(-> state
|
||||
(update-in [:workspace page-id] #(if (nil? %) initial-workspace %))
|
||||
(assoc-in [:workspace :current] page-id))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -104,7 +106,8 @@
|
|||
(defrecord SetTooltip [text]
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[text]
|
||||
|
@ -112,30 +115,33 @@
|
|||
|
||||
;; --- Workspace Flags
|
||||
|
||||
(deftype ActivateFlag [flag]
|
||||
(defrecord ActivateFlag [flag]
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[flag]
|
||||
{:pre [(keyword? flag)]}
|
||||
(ActivateFlag. flag))
|
||||
|
||||
(deftype DeactivateFlag [flag]
|
||||
(defrecord DeactivateFlag [flag]
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[flag]
|
||||
{:pre [(keyword? flag)]}
|
||||
(DeactivateFlag. flag))
|
||||
|
||||
(deftype ToggleFlag [flag]
|
||||
(defrecord ToggleFlag [flag]
|
||||
ptk/WatchEvent
|
||||
(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)
|
||||
(rx/of (deactivate-flag flag))
|
||||
(rx/of (activate-flag flag))))))
|
||||
|
@ -146,7 +152,7 @@
|
|||
|
||||
;; --- Workspace Ruler
|
||||
|
||||
(deftype ActivateRuler []
|
||||
(defrecord ActivateRuler []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (set-tooltip "Drag to use the ruler")
|
||||
|
@ -156,7 +162,7 @@
|
|||
[]
|
||||
(ActivateRuler.))
|
||||
|
||||
(deftype DeactivateRuler []
|
||||
(defrecord DeactivateRuler []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (set-tooltip nil)
|
||||
|
@ -166,10 +172,11 @@
|
|||
[]
|
||||
(DeactivateRuler.))
|
||||
|
||||
(deftype ToggleRuler []
|
||||
(defrecord ToggleRuler []
|
||||
ptk/WatchEvent
|
||||
(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)
|
||||
(rx/of (deactivate-ruler))
|
||||
(rx/of (activate-ruler))))))
|
||||
|
@ -180,10 +187,11 @@
|
|||
|
||||
;; --- Icons Toolbox
|
||||
|
||||
(deftype SelectIconsToolboxCollection [id]
|
||||
(defrecord SelectIconsToolboxCollection [id]
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
(watch [_ state stream]
|
||||
|
@ -194,11 +202,7 @@
|
|||
{:pre [(or (nil? id) (uuid? id))]}
|
||||
(SelectIconsToolboxCollection. id))
|
||||
|
||||
(deftype InitializeIconsToolbox []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
state)
|
||||
|
||||
(defrecord InitializeIconsToolbox []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(letfn [(get-first-with-icons [colls]
|
||||
|
@ -215,7 +219,8 @@
|
|||
|
||||
;; Only perform the autoselection if it is not
|
||||
;; previously already selected by the user.
|
||||
(when-not (contains? (:workspace state) :icons-toolbox)
|
||||
;; TODO
|
||||
#_(when-not (contains? (:workspace state) :icons-toolbox)
|
||||
(->> stream
|
||||
(rx/filter udi/collections-fetched?)
|
||||
(rx/take 1)
|
||||
|
@ -230,7 +235,8 @@
|
|||
(defrecord CopyToClipboard []
|
||||
ptk/UpdateEvent
|
||||
(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)
|
||||
:created-at (dt/now)
|
||||
:items selected}
|
||||
|
@ -251,13 +257,13 @@
|
|||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page (get-in state [:workspace :page])
|
||||
(let [page-id (get-in state [:workspace :current])
|
||||
selected (if (nil? id)
|
||||
(first (:clipboard state))
|
||||
(->> (:clipboard state)
|
||||
(filter #(= id (:id %)))
|
||||
(first)))]
|
||||
(shimpl/duplicate-shapes state (:items selected) page))))
|
||||
(simpl/duplicate-shapes state (:items selected) page-id))))
|
||||
|
||||
(defn paste-from-clipboard
|
||||
"Copy selected shapes to clipboard."
|
||||
|
@ -266,34 +272,37 @@
|
|||
|
||||
;; --- Zoom Management
|
||||
|
||||
(deftype IncreaseZoom []
|
||||
(defrecord IncreaseZoom []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [increase #(nth c/zoom-levels
|
||||
(+ (index-of c/zoom-levels %) 1)
|
||||
(last c/zoom-levels))]
|
||||
(update-in state [:workspace :zoom] (fnil increase 1)))))
|
||||
(last c/zoom-levels))
|
||||
page-id (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace page-id :zoom] (fnil increase 1)))))
|
||||
|
||||
(defn increase-zoom
|
||||
[]
|
||||
(IncreaseZoom.))
|
||||
|
||||
(deftype DecreaseZoom []
|
||||
(defrecord DecreaseZoom []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [decrease #(nth c/zoom-levels
|
||||
(- (index-of c/zoom-levels %) 1)
|
||||
(first c/zoom-levels))]
|
||||
(update-in state [:workspace :zoom] (fnil decrease 1)))))
|
||||
(first c/zoom-levels))
|
||||
page-id (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace page-id :zoom] (fnil decrease 1)))))
|
||||
|
||||
(defn decrease-zoom
|
||||
[]
|
||||
(DecreaseZoom.))
|
||||
|
||||
(deftype ResetZoom []
|
||||
(defrecord ResetZoom []
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[]
|
||||
|
@ -323,6 +332,434 @@
|
|||
{:pre [(uuid? 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
|
||||
|
||||
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
|
@ -43,8 +42,9 @@
|
|||
(deftype StartRuler []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pos (get-in state [:workspace :pointer :viewport])]
|
||||
(assoc-in state [:workspace :ruler] [pos pos])))
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
pos (get-in state [:workspace :pointer :viewport])]
|
||||
(assoc-in state [:workspace pid :ruler] {:start pos :end pos})))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -65,13 +65,15 @@
|
|||
(deftype UpdateRuler [point ctrl?]
|
||||
ptk/UpdateEvent
|
||||
(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?
|
||||
(assoc-in state [:workspace :ruler] [start point])
|
||||
(let [end (-> (gpt/subtract point start)
|
||||
(assoc-in state [:workspace pid :ruler :end] point)
|
||||
(let [start (get-in state [:workspace pid :ruler :start])
|
||||
end (-> (gpt/subtract point start)
|
||||
(align-position)
|
||||
(gpt/add start))]
|
||||
(assoc-in state [:workspace :ruler] [start end]))))))
|
||||
(assoc-in state [:workspace pid :ruler :end] end))))))
|
||||
|
||||
(defn update-ruler
|
||||
[point ctrl?]
|
||||
|
@ -84,7 +86,8 @@
|
|||
(deftype ClearRuler []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace dissoc :ruler)))
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid] dissoc :ruler))))
|
||||
|
||||
(defn clear-ruler
|
||||
[]
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[potok.core :as ptk]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
|
|
|
@ -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))))
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; 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."
|
||||
(:require [beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
|
@ -14,6 +14,7 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.main.user-events :as uev]
|
||||
|
@ -30,13 +31,14 @@
|
|||
(deftype SelectForDrawing [shape]
|
||||
ptk/UpdateEvent
|
||||
(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)
|
||||
(= shape current))
|
||||
(update state :workspace dissoc :drawing :drawing-tool)
|
||||
(update state :workspace assoc
|
||||
:drawing shape
|
||||
:drawing-tool shape)))))
|
||||
(update-in state [:workspace pid] dissoc :drawing :drawing-tool)
|
||||
(update-in state [:workspace pid] assoc
|
||||
:drawing shape
|
||||
:drawing-tool shape)))))
|
||||
|
||||
(defn select-for-drawing
|
||||
[shape]
|
||||
|
@ -48,7 +50,8 @@
|
|||
(deftype ClearDrawingState []
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[]
|
||||
|
@ -89,12 +92,13 @@
|
|||
(deftype InitializeDrawing [point]
|
||||
ptk/UpdateEvent
|
||||
(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)
|
||||
:y1 (:y point)
|
||||
:x2 (+ (:x point) 2)
|
||||
:y2 (+ (:y point) 2)})]
|
||||
(assoc-in state [:workspace :drawing] shape))))
|
||||
(assoc-in state [:workspace pid :drawing] shape))))
|
||||
|
||||
(defn initialize-drawing
|
||||
[point]
|
||||
|
@ -106,13 +110,14 @@
|
|||
(deftype UpdateDrawing [position lock?]
|
||||
ptk/UpdateEvent
|
||||
(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/size))
|
||||
result (geom/resize-shape :bottom-right shape position lock?)
|
||||
scale (geom/calculate-scale-ratio shape result)
|
||||
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
|
||||
[position lock?]
|
||||
|
@ -124,15 +129,17 @@
|
|||
(deftype FinishDrawing []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [id] :as shape} (get-in state [:workspace :drawing])
|
||||
resize-mtx (get-in state [:workspace :modifiers id :resize])
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
{:keys [id] :as shape} (get-in state [:workspace pid :drawing])
|
||||
resize-mtx (get-in state [:workspace pid :modifiers id :resize])
|
||||
shape (cond-> shape
|
||||
resize-mtx (geom/transform resize-mtx))]
|
||||
(prn "finish-drawing" shape)
|
||||
(if-not shape
|
||||
(rx/empty)
|
||||
(rx/of (clear-drawing-state)
|
||||
(uds/add-shape shape)
|
||||
(uds/select-first-shape)
|
||||
(udw/select-first-shape)
|
||||
::uev/interrupt)))))
|
||||
|
||||
(defn finish-drawing
|
||||
|
@ -144,7 +151,8 @@
|
|||
(deftype FinishPathDrawing []
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[]
|
||||
|
@ -155,7 +163,8 @@
|
|||
(deftype InsertDrawingPathPoint [point]
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[point]
|
||||
|
@ -167,10 +176,11 @@
|
|||
(deftype UpdateDrawingPathPoint [index point]
|
||||
ptk/UpdateEvent
|
||||
(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)]
|
||||
(cond-> state
|
||||
exists? (assoc-in [:workspace :drawing :segments index] point)))))
|
||||
exists? (assoc-in [:workspace pid :drawing :segments index] point)))))
|
||||
|
||||
(defn update-drawing-path-point
|
||||
[index point]
|
||||
|
@ -182,7 +192,8 @@
|
|||
(deftype CloseDrawingPath []
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
(watch [_ state stream]
|
||||
|
@ -197,7 +208,8 @@
|
|||
(deftype SimplifyDrawingPath [tolerance]
|
||||
ptk/UpdateEvent
|
||||
(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
|
||||
[tolerance]
|
||||
|
@ -282,7 +294,7 @@
|
|||
:y2 (+ y (/ 200 proportion))}
|
||||
shape (geom/setup shape props)]
|
||||
(st/emit! (uds/add-shape shape)
|
||||
(uds/select-first-shape)
|
||||
(udw/select-first-shape)
|
||||
(select-for-drawing nil)
|
||||
::uev/interrupt)))
|
||||
|
||||
|
@ -298,7 +310,7 @@
|
|||
:y2 (+ y height)}
|
||||
shape (geom/setup shape props)]
|
||||
(st/emit! (uds/add-shape shape)
|
||||
(uds/select-first-shape)
|
||||
(udw/select-first-shape)
|
||||
(select-for-drawing nil)
|
||||
::uev/interrupt)))
|
||||
|
||||
|
@ -401,5 +413,3 @@
|
|||
|
||||
(rx/subscribe points on-point)
|
||||
(rx/subscribe stream on-draw nil on-finish))))
|
||||
|
||||
|
|
@ -71,7 +71,6 @@
|
|||
"ds.your-libraries-title" "YOUR LIBRARIES"
|
||||
"ds.default-library-title" "Unnamed Collection (%s)"
|
||||
|
||||
"ds.recent-colors" "Recent colors"
|
||||
"ds.element-options" "Element options"
|
||||
"ds.draw-tools" "Draw tools"
|
||||
"ds.sitemap" "Sitemap"
|
||||
|
|
|
@ -71,7 +71,6 @@
|
|||
"ds.your-libraries-title" "VOS LIBRAIRIES"
|
||||
"ds.default-library-title" "Collection sans nom (%s)"
|
||||
|
||||
"ds.recent-colors" "Couleurs récentes"
|
||||
"ds.element-options" "Options d'élément"
|
||||
"ds.draw-tools" "Outils de dessin"
|
||||
"ds.sitemap" "Plan du site"
|
||||
|
@ -169,5 +168,5 @@
|
|||
"errors.network" "Impossible de se connecter au serveur principal."
|
||||
"errors.generic" "Quelque chose c'est mal passé."
|
||||
"errors.conflict" "Conflit sur la sauvegarde des données, actualisez et réessayez."
|
||||
|
||||
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2017-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.refs
|
||||
"A collection of derived refs."
|
||||
|
@ -11,6 +11,8 @@
|
|||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.store :as st]))
|
||||
|
||||
;; TODO: move inside workspaces because this is workspace only refs
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn resolve-project
|
||||
|
@ -32,35 +34,19 @@
|
|||
(filter #(= project (:project %)))
|
||||
(sort-by get-order))))
|
||||
|
||||
(def workspace
|
||||
(-> (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
|
||||
(def ^:deprecated selected-page
|
||||
"Ref to the current selected page."
|
||||
(-> (l/lens resolve-page)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; DEPRECATED
|
||||
(def selected-page-id
|
||||
"Ref to the current selected page id."
|
||||
(-> (l/key :id)
|
||||
(l/derive selected-page)))
|
||||
;; --- NOT DEPRECATED
|
||||
|
||||
(def workspace
|
||||
(letfn [(selector [state]
|
||||
(let [id (get-in state [:workspace :current])]
|
||||
(get-in state [:workspace id])))]
|
||||
(-> (l/lens selector)
|
||||
(l/derive st/state))))
|
||||
|
||||
(def selected-shapes
|
||||
(-> (l/key :selected)
|
||||
|
@ -74,10 +60,6 @@
|
|||
(-> (l/key :flags)
|
||||
(l/derive workspace)))
|
||||
|
||||
(def shapes-by-id
|
||||
(-> (l/key :shapes)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def selected-zoom
|
||||
(-> (l/key :zoom)
|
||||
(l/derive workspace)))
|
||||
|
@ -109,31 +91,40 @@
|
|||
(l/derive workspace)))
|
||||
|
||||
(defn alignment-activated?
|
||||
[state]
|
||||
(let [{:keys [flags]} (:workspace state)]
|
||||
(and (contains? flags :grid-indexed)
|
||||
(contains? flags :grid-snap))))
|
||||
[flags]
|
||||
(and (contains? flags :grid-indexed)
|
||||
(contains? flags :grid-snap)))
|
||||
|
||||
(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)))
|
||||
|
||||
(def canvas-mouse-position
|
||||
(-> (l/in [:pointer :canvas])
|
||||
(l/derive workspace)))
|
||||
(-> (l/key :canvas)
|
||||
(l/derive mouse-position)))
|
||||
|
||||
(def viewport-mouse-position
|
||||
(-> (l/in [:pointer :viewport])
|
||||
(l/derive workspace)))
|
||||
(-> (l/key :viewport)
|
||||
(l/derive mouse-position)))
|
||||
|
||||
(def window-mouse-position
|
||||
(-> (l/in [:pointer :window])
|
||||
(l/derive workspace)))
|
||||
(-> (l/key :window)
|
||||
(l/derive mouse-position)))
|
||||
|
||||
(def workspace-scroll
|
||||
(-> (l/key :scroll)
|
||||
(l/derive workspace)))
|
||||
|
||||
(-> (l/in [:workspace :scroll])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def shapes-by-id
|
||||
(-> (l/key :shapes)
|
||||
(l/derive st/state)))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
(def page-id-ref-s (rx/from-atom refs/selected-page-id))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(defn- user-interaction-event?
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.auth :as auth]
|
||||
[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.shapes]
|
||||
[uxbox.main.ui.workspace :refer [workspace]]
|
||||
[uxbox.main.ui.workspace :refer [workspace-page]]
|
||||
[uxbox.util.data :refer [parse-int uuid-str?]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.html.history :as html-history]
|
||||
|
@ -106,18 +104,20 @@
|
|||
(:settings/profile
|
||||
:settings/password
|
||||
:settings/notifications)
|
||||
(mf/element settings/settings {:route route})
|
||||
(mf/element settings/settings #js {:route route})
|
||||
|
||||
(:dashboard/projects
|
||||
:dashboard/icons
|
||||
:dashboard/images
|
||||
:dashboard/colors)
|
||||
(mf/element dashboard/dashboard {:route route})
|
||||
(mf/element dashboard/dashboard #js {:route route})
|
||||
|
||||
:workspace/page
|
||||
(let [project (uuid (get-in route [:params :path :project]))
|
||||
page (uuid (get-in route [:params :path :page]))]
|
||||
[:& workspace {:project project :page page :key page}])
|
||||
(let [project-id (uuid (get-in route [:params :path :project]))
|
||||
page-id (uuid (get-in route [:params :path :page]))]
|
||||
[:& workspace-page {:project-id project-id
|
||||
:page-id page-id
|
||||
:key page-id}])
|
||||
|
||||
nil
|
||||
))))
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
" the projects will be periodicaly wiped."]])
|
||||
|
||||
(mf/defc login-form
|
||||
{:wrap [mf/reactive*]}
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[]
|
||||
(let [data (mf/react form-data)
|
||||
valid? (fm/valid? ::login-form data)]
|
||||
|
|
|
@ -2,202 +2,19 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.colorpicker
|
||||
(:require [lentes.core :as l]
|
||||
[goog.events :as events]
|
||||
[uxbox.util.forms :as sc]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.data :as data]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.color :as color])
|
||||
(:import goog.events.EventType))
|
||||
(:require
|
||||
[goog.object :as gobj]
|
||||
[rumext.alpha :as mf]
|
||||
[vendor.react-color]))
|
||||
|
||||
;; --- 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"}]]]])))
|
||||
|
|
|
@ -6,22 +6,22 @@
|
|||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.confirm
|
||||
(:require [uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.lightbox :as lbx]))
|
||||
(:require
|
||||
[uxbox.builtins.icons :as i]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(mx/defc confirm-dialog
|
||||
(mf/defc confirm-dialog
|
||||
[{:keys [on-accept on-cancel hint] :as ctx}]
|
||||
(letfn [(accept [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!)
|
||||
(modal/hide!)
|
||||
(on-accept (dissoc ctx :on-accept :on-cancel)))
|
||||
(cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!)
|
||||
(modal/hide!)
|
||||
(when on-cancel
|
||||
(on-cancel (dissoc ctx :on-accept :on-cancel))))]
|
||||
[:div.lightbox-body.confirm-dialog
|
||||
|
@ -38,9 +38,7 @@
|
|||
:value (tr "ds.confirm-cancel")
|
||||
:on-click cancel}]]
|
||||
[:a.close {:href "#"
|
||||
:on-click #(do (dom/prevent-default %)
|
||||
(udl/close!))} i/close]]))
|
||||
|
||||
(defmethod lbx/render-lightbox :confirm
|
||||
[context]
|
||||
(confirm-dialog context))
|
||||
:on-click #(do
|
||||
(dom/prevent-default %)
|
||||
(modal/hide!))}
|
||||
i/close]]))
|
||||
|
|
|
@ -19,19 +19,23 @@
|
|||
(uuid-str? id) (uuid id)
|
||||
:else nil)
|
||||
type (when (str/alpha? type) (keyword type))]
|
||||
{:section (:name data)
|
||||
:type type
|
||||
:id id}))
|
||||
[(:name data) type id]))
|
||||
|
||||
(mf/defc dashboard
|
||||
{:wrap [mf/memo*]}
|
||||
[{:keys [route] :as props}]
|
||||
(let [{:keys [section] :as props} (parse-route route)]
|
||||
(let [[section type id] (parse-route route)]
|
||||
[:main.dashboard-main
|
||||
(messages-widget)
|
||||
[:& header props]
|
||||
[:& header {:section section}]
|
||||
(case section
|
||||
:dashboard/icons (mf/element icons/icons-page props)
|
||||
:dashboard/images (mf/element images/images-page props)
|
||||
:dashboard/projects (mf/element projects/projects-page props)
|
||||
:dashboard/colors (mf/element colors/colors-page props))]))
|
||||
:dashboard/icons
|
||||
[:& icons/icons-page {:type type :id id}]
|
||||
|
||||
:dashboard/images
|
||||
[:& images/images-page {:type type :id id}]
|
||||
|
||||
:dashboard/projects
|
||||
[:& projects/projects-page]
|
||||
|
||||
:dashboard/colors
|
||||
[:& colors/colors-page {:type type :id id}])]))
|
||||
|
|
|
@ -2,215 +2,208 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.colors
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[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.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.lightbox :as lbx]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.color :refer [hex->rgb]]
|
||||
[uxbox.util.data :refer [seek]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as t :refer [tr]]
|
||||
[uxbox.util.lens :as ul]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def collections-ref
|
||||
(def collections-iref
|
||||
(-> (l/key :colors-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def opts-ref
|
||||
(-> (l/in [:dashboard :colors])
|
||||
(def selected-colors-iref
|
||||
(-> (l/in [:dashboard :colors :selected])
|
||||
(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
|
||||
|
||||
(mf/def page-title
|
||||
:mixins [(mf/local) mf/memo mf/reactive]
|
||||
(mf/defc page-title
|
||||
[{: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
|
||||
(fn [{:keys [::mf/local] :as own}
|
||||
{:keys [id] :as coll}]
|
||||
(let [own? (= :own (:type coll))
|
||||
edit? (:edit @local)]
|
||||
(letfn [(save []
|
||||
(let [dom (mx/ref-node own "input")
|
||||
name (dom/get-inner-text dom)]
|
||||
(st/emit! (dc/rename-collection id (str/trim name)))
|
||||
(swap! local assoc :edit false)))
|
||||
(cancel []
|
||||
(swap! local assoc :edit false))
|
||||
(edit []
|
||||
(swap! local assoc :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)))
|
||||
(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]])]))))
|
||||
(on-delete []
|
||||
(modal/show! confirm-dialog {: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
|
||||
:dangerouslySetInnerHTML {"__html" (: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
|
||||
|
||||
(mf/def nav-item
|
||||
:mixins [(mf/local) mf/memo]
|
||||
|
||||
:render
|
||||
(fn [{:keys [::mf/local] :as own}
|
||||
{:keys [id type name ::selected?] :as coll}]
|
||||
(let [colors (count (:colors coll))
|
||||
editable? (= type :own)]
|
||||
(letfn [(on-click [event]
|
||||
(let [type (or type :own)]
|
||||
(st/emit! (rt/navigate :dashboard/colors nil {:type type :id id}))))
|
||||
(on-input-change [event]
|
||||
(mf/defc nav-item
|
||||
[{:keys [coll selected?] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
{:keys [id type name]} coll
|
||||
colors (count (:colors coll))
|
||||
editable? (= type :own)]
|
||||
(letfn [(on-click [event]
|
||||
(let [type (or type :own)]
|
||||
(st/emit! (rt/nav :dashboard/colors nil {:type type :id id}))))
|
||||
(on-input-change [event]
|
||||
(let [value (dom/get-target event)
|
||||
value (dom/get-value value)]
|
||||
(swap! local assoc :name value)))
|
||||
(on-cancel [event]
|
||||
(swap! local dissoc :name)
|
||||
(swap! local dissoc :edit))
|
||||
(on-double-click [event]
|
||||
(when editable?
|
||||
(swap! local assoc :edit true)))
|
||||
(on-input-keyup [event]
|
||||
(when (k/enter? event)
|
||||
(let [value (dom/get-target event)
|
||||
value (dom/get-value value)]
|
||||
(st/emit! (dc/rename-collection id (str/trim (:name @local))))
|
||||
(swap! local assoc :edit false))))]
|
||||
[:li {:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:class-name (when selected? "current")}
|
||||
(if (:edit @local)
|
||||
[:div
|
||||
[:input.element-title
|
||||
{:value (if (:name @local) (:name @local) name)
|
||||
:on-change on-input-change
|
||||
:on-key-down on-input-keyup}]
|
||||
[:span.close {:on-click on-cancel} i/close]]
|
||||
[:span.element-title name])
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c colors))]]))))
|
||||
(on-cancel [event]
|
||||
(swap! local dissoc :name)
|
||||
(swap! local dissoc :edit))
|
||||
(on-double-click [event]
|
||||
(when editable?
|
||||
(swap! local assoc :edit true)))
|
||||
(on-input-keyup [event]
|
||||
(when (k/enter? event)
|
||||
(let [value (dom/get-target event)
|
||||
value (dom/get-value value)]
|
||||
(st/emit! (dc/rename-collection id (str/trim (:name @local))))
|
||||
(swap! local assoc :edit false))))]
|
||||
[:li {:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:class-name (when selected? "current")}
|
||||
(if (:edit @local)
|
||||
[:div
|
||||
[:input.element-title
|
||||
{:value (if (:name @local) (:name @local) name)
|
||||
:on-change on-input-change
|
||||
:on-key-down on-input-keyup}]
|
||||
[:span.close {:on-click on-cancel} i/close]]
|
||||
[:span.element-title name])
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c colors))]])))
|
||||
|
||||
(mf/def nav
|
||||
:mixins [mf/memo mf/reactive]
|
||||
|
||||
:render
|
||||
(fn [own {:keys [id type] :as props}]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
colls (mf/react collections-ref)
|
||||
select-tab (fn [type]
|
||||
(if-let [coll (->> (vals colls)
|
||||
(filter #(= type (:type %)))
|
||||
(sort-by :created-at)
|
||||
(first))]
|
||||
(st/emit! (rt/nav :dashboard/colors nil {:type type :id (:id coll)}))
|
||||
(st/emit! (rt/nav :dashboard/colors nil {:type type}))))]
|
||||
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
(tr "ds.your-colors-title")]
|
||||
[: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?))))]]])))
|
||||
(mf/defc nav
|
||||
[{:keys [id type colls selected-coll] :as props}]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
select-tab #(st/emit! (rt/nav :dashboard/colors nil {:type %}))]
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
(tr "ds.your-colors-title")]
|
||||
[: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 [item colls]
|
||||
(let [selected? (= (:id item) (:id selected-coll))]
|
||||
[:& nav-item {:coll item :selected? selected? :key (:id item)}]))]]]))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mx/defc grid-form
|
||||
[coll-id]
|
||||
[:div.grid-item.small-item.add-project
|
||||
{:on-click #(udl/open! :color-form {:coll coll-id})}
|
||||
[:span (tr "ds.color-new")]])
|
||||
(mf/defc grid-form
|
||||
[{:keys [id] :as props}]
|
||||
(letfn [(on-submit [val]
|
||||
(st/emit! (dc/add-color id val))
|
||||
(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
|
||||
:mixins [mf/reactive mf/memo]
|
||||
(mf/defc grid-options-tooltip
|
||||
[{: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
|
||||
(fn [own {:keys [selected on-select title]}]
|
||||
{:pre [(uuid? selected)
|
||||
(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}]
|
||||
(mf/defc grid-options
|
||||
[{:keys [id type coll selected] :as props}]
|
||||
(let [local (mf/use-state {})]
|
||||
(letfn [(delete [event]
|
||||
(st/emit! (dc/delete-selected-colors)))
|
||||
(st/emit! (dc/delete-colors id selected)))
|
||||
(on-delete [event]
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(modal/show! confirm-dialog {:on-accept delete}))
|
||||
(on-toggle-copy [event]
|
||||
(swap! local update :show-copy-tooltip not)
|
||||
(swap! local assoc :show-move-tooltip false))
|
||||
|
@ -237,17 +230,17 @@
|
|||
{:alt (tr "ds.multiselect-bar.copy")
|
||||
:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.copy-to-library")
|
||||
:on-select on-copy}))
|
||||
[:& grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.copy-to-library")
|
||||
:on-select on-copy}])
|
||||
i/copy]
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt (tr "ds.multiselect-bar.move")
|
||||
:on-click on-toggle-move}
|
||||
(when (:show-move-tooltip @local)
|
||||
(grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.move-to-library")
|
||||
:on-select on-move}))
|
||||
[:& grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.move-to-library")
|
||||
:on-select on-move}])
|
||||
i/move]
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt (tr "ds.multiselect-bar.delete")
|
||||
|
@ -260,114 +253,76 @@
|
|||
{:alt (tr "ds.multiselect-bar.copy")
|
||||
:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.copy-to-library")
|
||||
:on-select on-copy}))
|
||||
[:& grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.copy-to-library")
|
||||
:on-select on-copy}])
|
||||
i/organize]])])))
|
||||
|
||||
(mf/def grid-item
|
||||
:key-fn :color
|
||||
:mixins [mf/memo]
|
||||
(mf/defc grid-item
|
||||
[{:keys [color selected?] :as props}]
|
||||
(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
|
||||
(fn [own {:keys [color selected?] :as props}]
|
||||
(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)))]])))
|
||||
(mf/defc grid
|
||||
[{:keys [id type coll selected] :as props}]
|
||||
(let [{:keys [colors]} coll
|
||||
editable? (= :own type)
|
||||
colors (->> (remove nil? colors)
|
||||
(sort-by identity))]
|
||||
[:div.dashboard-grid-content
|
||||
[:div.dashboard-grid-row
|
||||
(when (and editable? id)
|
||||
[:& grid-form {:id id}])
|
||||
(for [color colors]
|
||||
(let [selected? (contains? selected color)]
|
||||
[:& grid-item {:color color :selected? selected? :key color}]))]]))
|
||||
|
||||
(mf/def grid
|
||||
:mixins [mf/memo]
|
||||
|
||||
:render
|
||||
(fn [own {:keys [selected ::coll] :as props}]
|
||||
(let [{:keys [id type colors]} coll
|
||||
editable? (or (= :own type) (nil? id))
|
||||
colors (->> (remove nil? colors)
|
||||
(sort-by identity))]
|
||||
[:div.dashboard-grid-content
|
||||
[: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))])))
|
||||
(mf/defc content
|
||||
[{:keys [id type coll] :as props}]
|
||||
(let [selected (mf/deref selected-colors-iref)]
|
||||
[:section.dashboard-grid.library
|
||||
[:& page-title {:coll coll}]
|
||||
[:& grid {:coll coll :id id :type type :selected selected}]
|
||||
(when (seq selected)
|
||||
[:& grid-options {:id id :type type
|
||||
:selected selected
|
||||
:coll coll}])]))
|
||||
|
||||
;; --- Colors Page
|
||||
|
||||
(mf/def colors-page
|
||||
:key-fn identity
|
||||
:mixins #{mf/memo mf/reactive}
|
||||
(mf/defc colors-page
|
||||
[{:keys [id type] :as props}]
|
||||
(let [type (or type :own)
|
||||
|
||||
:init
|
||||
(fn [own props]
|
||||
(let [{:keys [type id]} (::mf/props own)]
|
||||
(st/emit! (dc/initialize type id))
|
||||
own))
|
||||
colls (mf/deref collections-iref)
|
||||
colls (cond->> (vals colls)
|
||||
(= type :own) (filter #(= :own (:type %)))
|
||||
(= type :builtin) (filter #(= :builtin (:type %)))
|
||||
true (sort-by :created-at))
|
||||
selected-coll (if id
|
||||
(seek #(= id (:id %)) colls)
|
||||
(first colls))
|
||||
id (:id selected-coll)]
|
||||
|
||||
:render
|
||||
(fn [own {:keys [type] :as props}]
|
||||
(let [type (or type :own)
|
||||
props (assoc props :type type)]
|
||||
[:section.dashboard-content
|
||||
(nav props)
|
||||
(content props)])))
|
||||
(mf/use-effect {:init #(st/emit! (dc/initialize)) :deps #js [id type]})
|
||||
(mf/use-effect {:init #(st/emit! (dc/fetch-collections))})
|
||||
|
||||
;; --- 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))
|
||||
|
|
|
@ -73,9 +73,9 @@
|
|||
(l/lens #(-> % vals count)))
|
||||
(l/derive st/state))))
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [ordering (:order props :created)
|
||||
filtering (:filter props "")
|
||||
(fn [own {:keys [opts] :as props}]
|
||||
(let [ordering (:order opts :created)
|
||||
filtering (:filter opts "")
|
||||
num-projects (mf/react (::num-projects own))]
|
||||
(letfn [(on-term-change [event]
|
||||
(let [term (-> (dom/get-target event)
|
||||
|
@ -119,7 +119,7 @@
|
|||
[{:keys [project] :as props}]
|
||||
(let [url (mf/use-state nil)]
|
||||
(mf/use-effect
|
||||
{:watch (:page-id project)
|
||||
{:deps #js [(:page-id project)]
|
||||
:init (fn []
|
||||
(when-let [page-id (:page-id project)]
|
||||
(let [svg (exports/render-page page-id)
|
||||
|
@ -141,6 +141,7 @@
|
|||
;; --- Grid Item
|
||||
|
||||
(mf/defc grid-item
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [project] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
on-navigate #(st/emit! (udp/go-to (:id project)))
|
||||
|
@ -188,9 +189,11 @@
|
|||
;; --- Grid
|
||||
|
||||
(mf/defc grid
|
||||
{:wrap [mf/reactive*]}
|
||||
[{:keys [order filter] :or {order :created filter ""} :as props}]
|
||||
(let [projects (->> (vals (mf/deref projects-ref))
|
||||
[{:keys [opts] :as props}]
|
||||
(let [order (:order opts :created)
|
||||
filter (:filter opts "")
|
||||
projects (mf/deref projects-ref)
|
||||
projects (->> (vals projects)
|
||||
(filter-projects-by filter)
|
||||
(sort-projects-by order))
|
||||
on-click #(do
|
||||
|
@ -208,13 +211,10 @@
|
|||
;; --- Projects Page
|
||||
|
||||
(mf/defc projects-page
|
||||
{:wrap [mf/reactive*]}
|
||||
[props]
|
||||
(let [opts (mf/deref opts-ref)
|
||||
props (merge opts props)]
|
||||
(mf/use-effect
|
||||
{:init #(st/emit! (udp/initialize))})
|
||||
[_]
|
||||
(mf/use-effect
|
||||
{:init #(st/emit! (udp/initialize))})
|
||||
(let [opts (mf/deref opts-ref)]
|
||||
[:section.dashboard-content
|
||||
[:& menu props]
|
||||
[:& grid props]]))
|
||||
|
||||
[:& menu {:opts opts}]
|
||||
[:& grid {:opts opts}]]))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(ns uxbox.main.ui.lightbox
|
||||
"DEPRECATED: should be replaced by uxbox.main.ui.modal"
|
||||
(:require
|
||||
[goog.events :as events]
|
||||
[lentes.core :as l]
|
||||
|
|
56
frontend/src/uxbox/main/ui/modal.cljs
Normal file
56
frontend/src/uxbox/main/ui/modal.cljs
Normal 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)}]))
|
||||
|
||||
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
[uxbox.main.ui.settings.profile :as profile]))
|
||||
|
||||
(mf/defc settings
|
||||
{:wrap [mf/memo*]}
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [route] :as props}]
|
||||
(let [section (get-in route [:data :name])]
|
||||
[:main.dashboard-main
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
;; --- Profile Photo Form
|
||||
|
||||
(mf/defc profile-photo-form
|
||||
{:wrap [mf/reactive*]}
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[]
|
||||
(letfn [(on-change [event]
|
||||
(let [target (dom/get-target event)
|
||||
|
|
|
@ -5,7 +5,55 @@
|
|||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(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)
|
||||
|
|
|
@ -2,43 +2,15 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.common
|
||||
(:require [lentes.core :as l]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
[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)))
|
||||
(:require
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Events
|
||||
|
||||
|
@ -48,27 +20,24 @@
|
|||
drawing? @refs/selected-drawing-tool]
|
||||
(when-not (:blocked shape)
|
||||
(cond
|
||||
(or drawing?
|
||||
(and group (:locked (geom/resolve-parent shape))))
|
||||
drawing?
|
||||
nil
|
||||
|
||||
(and (not selected?) (empty? selected))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (uds/select-shape id))
|
||||
(start-move))
|
||||
(st/emit! (udw/select-shape id)
|
||||
(udw/start-move-selected)))
|
||||
|
||||
(and (not selected?) (not (empty? selected)))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(if (kbd/shift? event)
|
||||
(st/emit! (uds/select-shape id))
|
||||
(do
|
||||
(st/emit! (uds/deselect-all)
|
||||
(uds/select-shape id))
|
||||
(start-move))))
|
||||
|
||||
(st/emit! (udw/select-shape id))
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id)
|
||||
(udw/start-move-selected))))
|
||||
:else
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(start-move))))))
|
||||
(st/emit! (udw/start-move-selected)))))))
|
||||
|
|
|
@ -2,24 +2,25 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.group
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.main.ui.shapes.rect :as rect]
|
||||
[uxbox.main.ui.shapes.circle :as circle]
|
||||
[uxbox.main.ui.shapes.text :as text]
|
||||
[uxbox.main.ui.shapes.path :as path]
|
||||
[uxbox.main.ui.shapes.image :as image]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[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]))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
|
|
|
@ -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))))))
|
|
@ -49,7 +49,7 @@
|
|||
(l/derive st/state)))
|
||||
|
||||
(mf/defc user
|
||||
{:wrap [mf/reactive*]}
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[_]
|
||||
(let [open (mf/use-state false)
|
||||
profile (mf/react profile-ref)
|
||||
|
|
|
@ -22,14 +22,14 @@
|
|||
[uxbox.main.ui.confirm]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[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.download]
|
||||
[uxbox.main.ui.workspace.header :refer [header]]
|
||||
[uxbox.main.ui.workspace.images]
|
||||
[uxbox.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
||||
[uxbox.main.ui.workspace.scroll :as scroll]
|
||||
[uxbox.main.ui.workspace.shortcuts :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.history :refer [history-dialog]]
|
||||
[uxbox.main.user-events :as uev]
|
||||
|
@ -47,10 +47,10 @@
|
|||
(st/emit! (uev/scroll-event (gpt/point left top)))))
|
||||
|
||||
(defn- on-wheel
|
||||
[own event]
|
||||
[event canvas]
|
||||
(when (kbd/ctrl? event)
|
||||
(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)
|
||||
mouse-point @refs/viewport-mouse-position]
|
||||
(dom/prevent-default event)
|
||||
|
@ -60,80 +60,90 @@
|
|||
(st/emit! (dw/increase-zoom)))
|
||||
(scroll/scroll-to-point dom mouse-point scroll-position))))
|
||||
|
||||
(mf/def workspace
|
||||
:mixins #{mf/reactive
|
||||
shortcuts-mixin}
|
||||
(defn- subscibe
|
||||
[canvas page]
|
||||
(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
|
||||
(fn [own {:keys [project page] :as props}]
|
||||
(st/emit! (dw/initialize project page))
|
||||
(fn [own {:keys [project-id page-id] :as props}]
|
||||
(st/emit! (dw/initialize project-id page-id))
|
||||
(assoc own
|
||||
::canvas (mf/create-ref)
|
||||
::page-ref (-> (l/in [:pages page])
|
||||
(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))
|
||||
|
||||
::page-ref (-> (l/in [:pages page-id])
|
||||
(l/derive st/state))
|
||||
::workspace-ref (-> (l/in [:workspace page-id])
|
||||
(l/derive st/state))))
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [flags (mf/deref refs/flags)
|
||||
page (mf/deref (::page-ref own))
|
||||
;; project-id (get props :project)
|
||||
;; page-id (get props :page)
|
||||
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)}])]])))
|
||||
(let [wst (mf/react (::workspace-ref own))
|
||||
page (mf/react (::page-ref own))]
|
||||
(when page
|
||||
[:& workspace {:page page :wst wst}]))))
|
||||
|
|
|
@ -7,30 +7,12 @@
|
|||
|
||||
(ns uxbox.main.ui.workspace.canvas
|
||||
(: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]
|
||||
[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.streams :as streams]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[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.grid :refer [grid]]
|
||||
[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.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
[uxbox.util.geom.point :as gpt])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
|
@ -46,235 +28,26 @@
|
|||
:height "100%"
|
||||
: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
|
||||
|
||||
(mf/def canvas
|
||||
:mixins [mf/memo mf/reactive]
|
||||
:render
|
||||
(fn [own {:keys [page zoom] :as props}]
|
||||
(let [{:keys [metadata id]} page
|
||||
width (:width metadata)
|
||||
height (:height metadata)]
|
||||
[:svg.page-canvas {:x c/canvas-start-x
|
||||
:y c/canvas-start-y
|
||||
:ref (str "canvas" id)
|
||||
:width width
|
||||
:height height}
|
||||
(background metadata)
|
||||
[:svg.page-layout
|
||||
[:g.main
|
||||
(for [item (reverse (:shapes page))]
|
||||
(-> (uus/shape item)
|
||||
(mf/with-key (str item))))
|
||||
(selection-handlers)
|
||||
(draw-area zoom)]]])))
|
||||
|
||||
;; --- 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)]]))))
|
||||
(mf/defc canvas
|
||||
[{:keys [page wst] :as props}]
|
||||
(let [{:keys [metadata id]} page
|
||||
zoom (:zoom wst 1) ;; NOTE: maybe forward wst to draw-area
|
||||
width (:width metadata)
|
||||
height (:height metadata)]
|
||||
[:svg.page-canvas {:x c/canvas-start-x
|
||||
:y c/canvas-start-y
|
||||
:width width
|
||||
:height height}
|
||||
[:& background metadata]
|
||||
[:svg.page-layout
|
||||
[:g.main
|
||||
(for [item (reverse (:shapes page))]
|
||||
(-> (uus/shape item)
|
||||
(mf/with-key (str item))))
|
||||
[:& selection-handlers {:wst wst}]
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])]]]))
|
||||
|
|
|
@ -6,121 +6,115 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.colorpalette
|
||||
(:require [beicon.core :as rx]
|
||||
[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.main.data.shapes :as uds]
|
||||
[uxbox.main.data.colors :as dc]
|
||||
[uxbox.main.ui.dashboard.colors :refer (collections-ref)]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.lens :as ul]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.color :refer (hex->rgb)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.colors :as udc]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.color :refer [hex->rgb]]
|
||||
[uxbox.util.data :refer [read-string seek]]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(defn- get-selected-collection
|
||||
[local collections]
|
||||
(if-let [selected (:selected @local)]
|
||||
(first (filter #(= selected (:id %)) collections))
|
||||
(first (filter #(and (:id %) (> (count (:colors %)) 0)) collections))))
|
||||
;; --- Refs
|
||||
|
||||
(mx/defc palette-item
|
||||
{:mixins [mx/static]}
|
||||
[color]
|
||||
(def collections-iref
|
||||
(-> (l/key :colors-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Components
|
||||
|
||||
(mf/defc palette-item
|
||||
[{:keys [color] :as props}]
|
||||
(letfn [(select-color [event]
|
||||
(let [attrs (if (kbd/shift? event)
|
||||
{:stroke-color color}
|
||||
{:fill-color color})]
|
||||
(st/emit! (uds/update-selected-shapes-attrs attrs))))]
|
||||
(st/emit! (udw/update-selected-shapes-attrs attrs))))]
|
||||
(let [rgb-vec (hex->rgb color)
|
||||
rgb-color (apply str "" (interpose ", " rgb-vec))]
|
||||
[:div.color-cell {:key (str color)
|
||||
:on-click select-color}
|
||||
[:span.color {:style {:background color}}]
|
||||
[:span.color-text {} 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))
|
||||
[:span.color-text color]
|
||||
[:span.color-text rgb-color]])))
|
||||
|
||||
(defn- document-width
|
||||
[]
|
||||
(.. js/document -documentElement -clientWidth))
|
||||
|
||||
(mx/defcs palette
|
||||
{:mixins [mx/static mx/reactive (mx/local)]
|
||||
:after-render palette-after-render}
|
||||
[{:keys [::mx/local] :as own}]
|
||||
(let [collections (->> (mx/react collections-ref)
|
||||
(vals)
|
||||
(filter :id)
|
||||
(sort-by :name))
|
||||
{:keys [colors] :as selected-coll} (get-selected-collection local collections)
|
||||
(mf/defc palette
|
||||
[{:keys [colls] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
colls (->> colls
|
||||
(filter :id)
|
||||
(sort-by :name))
|
||||
coll (or (:selected @local)
|
||||
(first colls))
|
||||
|
||||
width (:width @local (* (document-width) 0.84))
|
||||
offset (:offset @local 0)
|
||||
visible (/ width 86)
|
||||
invisible (- (count colors) visible)]
|
||||
(letfn [(select-collection [event]
|
||||
(let [value (read-string (dom/event->value event))]
|
||||
(swap! local assoc :selected value :position 0)))
|
||||
(close [event]
|
||||
(st/emit! (dw/toggle-flag :colorpalette)))]
|
||||
invisible (- (count (:colors coll)) visible)
|
||||
close #(st/emit! (udw/toggle-flag :colorpalette))
|
||||
|
||||
container (mf/use-ref* nil)
|
||||
container-child (mf/use-ref* nil)]
|
||||
|
||||
(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-actions
|
||||
[:select.input-select {:on-change select-collection
|
||||
:value (pr-str (:id selected-coll))}
|
||||
(for [collection collections]
|
||||
[:option {:key (str (:id collection))
|
||||
:value (pr-str (:id collection))}
|
||||
(:name collection)])]
|
||||
[:div.color-palette-buttons
|
||||
[:div.btn-palette.edit.current i/pencil]
|
||||
[:div.btn-palette.create i/close]]]
|
||||
[:select.input-select {:on-change select-coll
|
||||
:default-value (pr-str (:id coll))}
|
||||
(for [item colls]
|
||||
[:option {:key (:id item) :value (pr-str (:id item))}
|
||||
(:name item)])]
|
||||
|
||||
;; FIXME Scroll on click does not work
|
||||
[:span.left-arrow {}
|
||||
(when (> offset 0)
|
||||
{:on-click #(.scrollBy (dom/get-element "color-palette-inside") (- offset) 0)})
|
||||
i/arrow-slide]
|
||||
#_[:div.color-palette-buttons
|
||||
[:div.btn-palette.edit.current i/pencil]
|
||||
[:div.btn-palette.create i/close]]]
|
||||
|
||||
[:div.color-palette-content {:ref "container"}
|
||||
[:div.color-palette-inside {:id "color-palette-inside"
|
||||
:ref "color-palette-inside"
|
||||
[:span.left-arrow {:on-click on-left-arrow-click} i/arrow-slide]
|
||||
|
||||
[:div.color-palette-content {:ref container :on-wheel on-scroll}
|
||||
[:div.color-palette-inside {:ref container-child
|
||||
:style {:position "relative"
|
||||
:width (str (* 86 (count (:colors coll))) "px")
|
||||
:right (str (* 86 offset) "px")}}
|
||||
(for [color colors]
|
||||
(-> (palette-item color)
|
||||
(mx/with-key color)))]]
|
||||
(for [color (:colors coll)]
|
||||
[:& palette-item {:color color :key color}])]]
|
||||
|
||||
;; FIXME Scroll on click does not work
|
||||
[:span.right-arrow
|
||||
{:on-click (fn [event]
|
||||
(when (< offset invisible)
|
||||
(.scrollBy (dom/get-element "color-palette-inside") offset 0)))}
|
||||
i/arrow-slide]
|
||||
[:span.right-arrow {:on-click on-right-arrow-click} i/arrow-slide]
|
||||
[:span.close-palette {:on-click close} i/close]])))
|
||||
|
||||
[:span.close-palette {:on-click close}
|
||||
i/close]])))
|
||||
|
||||
(defn- colorpalette-init
|
||||
[own]
|
||||
(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))))
|
||||
(mf/defc colorpalette
|
||||
[props]
|
||||
(let [colls (mf/deref collections-iref)]
|
||||
(mf/use-effect {:init #(st/emit! (udc/fetch-collections))})
|
||||
[:& palette {:colls (vals colls)}]))
|
||||
|
|
|
@ -2,70 +2,43 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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
|
||||
(:require [lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.geom :as geom]
|
||||
[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]]))
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.colorpicker :as cp]))
|
||||
|
||||
(defn- focus-shape
|
||||
[id]
|
||||
(-> (l/in [:shapes id])
|
||||
;; --- Recent Colors Calc. Algorithm
|
||||
|
||||
(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)))
|
||||
|
||||
(mx/defcs shape-colorpicker
|
||||
{: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")}}
|
||||
;; --- Color Picker Modal
|
||||
|
||||
(cp/colorpicker
|
||||
:theme :small
|
||||
:value (get shape attr "#000000")
|
||||
:on-change change-color)
|
||||
(recent-colors shape change-color)])))
|
||||
(mf/defc colorpicker-modal
|
||||
[{:keys [x y default value page on-change] :as props}]
|
||||
[:div.colorpicker-tooltip
|
||||
{:style {:left (str (- x 260) "px")
|
||||
: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))
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
(mx/defcs download-dialog
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own]
|
||||
(let [project (mx/react refs/selected-project)
|
||||
#_(let [project (mx/react refs/selected-project)
|
||||
pages (mx/react pages-ref)
|
||||
current (mx/react current-page-ref)]
|
||||
(letfn [(on-close [event]
|
||||
|
|
|
@ -2,47 +2,39 @@
|
|||
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.drawarea
|
||||
"Draw interaction and component."
|
||||
(:require [beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[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.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[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]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace-drawing :as udwd]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.streams :as streams]
|
||||
[uxbox.main.ui.shapes :as shapes]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.path :as path]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
;; --- Components
|
||||
|
||||
(declare generic-draw-area)
|
||||
(declare path-draw-area)
|
||||
|
||||
(mx/defc draw-area
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[zoom]
|
||||
(when-let [{:keys [id] :as shape} (mx/react refs/selected-drawing-shape)]
|
||||
(let [modifiers (mx/react (refs/selected-modifiers id))]
|
||||
(if (= (:type shape) :path)
|
||||
(path-draw-area shape)
|
||||
(-> (assoc shape :modifiers modifiers)
|
||||
(generic-draw-area zoom))))))
|
||||
(mf/defc draw-area
|
||||
[{:keys [zoom shape modifiers] :as props}]
|
||||
(if (= (:type shape) :path)
|
||||
[:& path-draw-area {:shape shape}]
|
||||
[:& generic-draw-area {:shape (assoc shape :modifiers modifiers)
|
||||
:zoom zoom}]))
|
||||
|
||||
(mx/defc generic-draw-area
|
||||
[shape zoom]
|
||||
(mf/defc generic-draw-area
|
||||
[{:keys [shape zoom]}]
|
||||
(let [{:keys [x1 y1 width height]} (geom/selection-rect shape)]
|
||||
[:g {}
|
||||
(shapes/render-component shape)
|
||||
[:g
|
||||
(shapes/render-shape shape)
|
||||
[:rect.main {:x x1 :y y1
|
||||
:width width
|
||||
:height height
|
||||
|
@ -51,23 +43,24 @@
|
|||
:fill "transparent"
|
||||
:stroke-opacity "1"}}]]))
|
||||
|
||||
(mx/defc path-draw-area
|
||||
[{:keys [segments] :as shape}]
|
||||
(mf/defc path-draw-area
|
||||
[{:keys [shape] :as props}]
|
||||
(letfn [(on-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (udw/set-tooltip nil)
|
||||
(udw/close-drawing-path)))
|
||||
(udwd/close-drawing-path)))
|
||||
(on-mouse-enter [event]
|
||||
(st/emit! (udw/set-tooltip "Click to close the path")))
|
||||
(on-mouse-leave [event]
|
||||
(st/emit! (udw/set-tooltip nil)))]
|
||||
(when-let [{:keys [x y] :as segment} (first segments)]
|
||||
[:g {}
|
||||
(shapes/render-component shape)
|
||||
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
||||
[:g
|
||||
(shapes/render-shape shape)
|
||||
(when-not (:free shape)
|
||||
[:circle.close-bezier {:cx x
|
||||
:cy y
|
||||
:r 5
|
||||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}])])))
|
||||
[:circle.close-bezier
|
||||
{:cx x
|
||||
:cy y
|
||||
:r 5
|
||||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}])])))
|
||||
|
|
|
@ -2,45 +2,17 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.grid
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.refs :as refs]))
|
||||
[uxbox.main.constants :as c]))
|
||||
|
||||
;; --- 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
|
||||
[width acc value]
|
||||
(let [pos (+ value c/canvas-start-y)]
|
||||
|
@ -50,3 +22,29 @@
|
|||
[height acc value]
|
||||
(let [pos (+ value c/canvas-start-y)]
|
||||
(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"}]]))
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
;; --- Zoom Widget
|
||||
|
||||
(mf/defc zoom-widget
|
||||
{:wrap [mf/reactive*]}
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[props]
|
||||
(let [zoom (mf/react refs/selected-zoom)
|
||||
increase #(st/emit! (dw/increase-zoom))
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace-drawing :as udwd]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.data :refer [read-string jscoll->vec]]
|
||||
|
@ -56,7 +57,7 @@
|
|||
:metadata {:width width
|
||||
:height height}
|
||||
:image id}]
|
||||
(st/emit! (udw/select-for-drawing shape))
|
||||
(st/emit! (udwd/select-for-drawing shape))
|
||||
(udl/close!)))
|
||||
(on-files-selected [event]
|
||||
(let [files (dom/get-event-files event)
|
||||
|
@ -98,7 +99,7 @@
|
|||
:metadata {:width width
|
||||
:height height}
|
||||
:image id}]
|
||||
(st/emit! (udw/select-for-drawing shape))
|
||||
(st/emit! (udwd/select-for-drawing shape))
|
||||
(udl/close!)))]
|
||||
[:div.library-item {:key (str id)
|
||||
:on-click on-click}
|
||||
|
|
|
@ -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]]]))
|
||||
|
|
@ -2,39 +2,31 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.ruler
|
||||
(:require [lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.util.math :as mth]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.math :as mth]))
|
||||
|
||||
(def ruler-points-ref
|
||||
(-> (l/key :ruler)
|
||||
(l/derive refs/workspace)))
|
||||
|
||||
(mx/defc ruler-text
|
||||
{:mixins [mx/static]}
|
||||
[zoom [center pt]]
|
||||
(let [distance (-> (gpt/distance (gpt/divide pt zoom)
|
||||
(gpt/divide center zoom))
|
||||
(mf/defc ruler-text
|
||||
[{:keys [zoom ruler] :as props}]
|
||||
(let [{:keys [start end]} ruler
|
||||
distance (-> (gpt/distance (gpt/divide end zoom)
|
||||
(gpt/divide start zoom))
|
||||
(mth/precision 2))
|
||||
angle (-> (gpt/angle pt center)
|
||||
angle (-> (gpt/angle end start)
|
||||
(mth/precision 2))
|
||||
transform1 (str "translate(" (+ (:x pt) 35) "," (- (:y pt) 10) ")")
|
||||
transform2 (str "translate(" (+ (:x pt) 25) "," (- (:y pt) 30) ")")]
|
||||
[:g {}
|
||||
transform1 (str "translate(" (+ (:x end) 35) "," (- (:y end) 10) ")")
|
||||
transform2 (str "translate(" (+ (:x end) 25) "," (- (:y end) 30) ")")]
|
||||
[:g
|
||||
[:rect {:fill "black"
|
||||
:fill-opacity "0.4"
|
||||
:rx "3"
|
||||
|
@ -49,24 +41,19 @@
|
|||
[:tspan {:x "0" :y "20"}
|
||||
(str angle "°")]]]))
|
||||
|
||||
(mx/defc ruler-line
|
||||
{:mixins [mx/static]}
|
||||
[zoom [center pt]]
|
||||
[:line {:x1 (:x center)
|
||||
:y1 (:y center)
|
||||
:x2 (:x pt)
|
||||
:y2 (:y pt)
|
||||
:style {:cursor "cell"}
|
||||
:stroke-width "1"
|
||||
:stroke "red"}])
|
||||
(mf/defc ruler-line
|
||||
[{:keys [zoom ruler] :as props}]
|
||||
(let [{:keys [start end]} ruler]
|
||||
[:line {:x1 (:x start)
|
||||
:y1 (:y start)
|
||||
:x2 (:x end)
|
||||
:y2 (:y end)
|
||||
:style {:cursor "cell"}
|
||||
:stroke-width "1"
|
||||
:stroke "red"}]))
|
||||
|
||||
(mx/defc ruler
|
||||
{:mixins [mx/static mx/reactive]
|
||||
:will-unmount (fn [own]
|
||||
(st/emit! ::uev/interrupt
|
||||
(udw/clear-ruler))
|
||||
own)}
|
||||
[zoom]
|
||||
(mf/defc ruler
|
||||
[{:keys [ruler zoom] :as props}]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! ::uev/interrupt
|
||||
|
@ -74,7 +61,11 @@
|
|||
(udw/start-ruler)))
|
||||
(on-mouse-up [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
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:rect {:style {:fill "transparent"
|
||||
|
@ -82,8 +73,8 @@
|
|||
:cursor "cell"}
|
||||
:width c/viewport-width
|
||||
:height c/viewport-height}]
|
||||
(when-let [points (mx/react ruler-points-ref)]
|
||||
[:g {}
|
||||
(ruler-line zoom points)
|
||||
(ruler-text zoom points)])]))
|
||||
(when ruler
|
||||
[:g
|
||||
[:& ruler-line {:ruler ruler}]
|
||||
[:& ruler-text {:ruler ruler :zoom zoom}]])]))
|
||||
|
||||
|
|
|
@ -100,65 +100,57 @@
|
|||
|
||||
;; --- Horizontal Rule Ticks (Component)
|
||||
|
||||
(mf/def horizontal-rule-ticks
|
||||
:mixins #{mf/memo}
|
||||
:render
|
||||
(fn [own zoom]
|
||||
(let [zoom (or zoom 1)
|
||||
path (reduce (partial make-vertical-tick zoom) [] +ticks+)]
|
||||
[:g
|
||||
[:path {:d (str/join " " path)}]
|
||||
(for [tick +ticks+]
|
||||
[:& horizontal-text-label {:zoom zoom :value tick :key tick}])])))
|
||||
(mf/defc horizontal-rule-ticks
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [zoom]}]
|
||||
(let [zoom (or zoom 1)
|
||||
path (reduce (partial make-vertical-tick zoom) [] +ticks+)]
|
||||
[:g
|
||||
[:path {:d (str/join " " path)}]
|
||||
(for [tick +ticks+]
|
||||
[:& horizontal-text-label {:zoom zoom :value tick :key tick}])]))
|
||||
|
||||
;; --- Vertical Rule Ticks (Component)
|
||||
|
||||
(mf/def vertical-rule-ticks
|
||||
:mixins #{mf/memo}
|
||||
:render
|
||||
(fn [own zoom]
|
||||
(let [zoom (or zoom 1)
|
||||
path (reduce (partial make-horizontal-tick zoom) [] +ticks+)]
|
||||
[:g
|
||||
[:path {:d (str/join " " path)}]
|
||||
(for [tick +ticks+]
|
||||
[:& vertical-text-label {:zoom zoom :value tick :key tick}])])))
|
||||
(mf/defc vertical-rule-ticks
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [zoom]}]
|
||||
(let [zoom (or zoom 1)
|
||||
path (reduce (partial make-horizontal-tick zoom) [] +ticks+)]
|
||||
[:g
|
||||
[:path {:d (str/join " " path)}]
|
||||
(for [tick +ticks+]
|
||||
[:& vertical-text-label {:zoom zoom :value tick :key tick}])]))
|
||||
|
||||
;; --- Horizontal Rule (Component)
|
||||
|
||||
(mf/def horizontal-rule
|
||||
:mixins #{mf/memo mf/reactive}
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [scroll (mf/react refs/workspace-scroll)
|
||||
zoom (mf/react refs/selected-zoom)
|
||||
scroll-x (:x scroll)
|
||||
translate-x (- (- c/canvas-scroll-padding) (:x scroll))]
|
||||
[:svg.horizontal-rule
|
||||
{:width c/viewport-width
|
||||
:height 20}
|
||||
[:rect {:height 20
|
||||
:width c/viewport-width}]
|
||||
[:g {:transform (str "translate(" translate-x ", 0)")}
|
||||
(horizontal-rule-ticks zoom)]])))
|
||||
(mf/defc horizontal-rule
|
||||
[{:keys [zoom] :as props}]
|
||||
(let [scroll (mf/deref refs/workspace-scroll)
|
||||
scroll-x (:x scroll)
|
||||
translate-x (- (- c/canvas-scroll-padding) (:x scroll))]
|
||||
[:svg.horizontal-rule
|
||||
{:width c/viewport-width
|
||||
:height 20}
|
||||
[:rect {:height 20
|
||||
:width c/viewport-width}]
|
||||
[:g {:transform (str "translate(" translate-x ", 0)")}
|
||||
[:& horizontal-rule-ticks {:zoom zoom}]]]))
|
||||
|
||||
;; --- Vertical Rule (Component)
|
||||
|
||||
(mf/def vertical-rule
|
||||
:mixins #{mf/memo mf/reactive}
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [scroll (mf/react refs/workspace-scroll)
|
||||
zoom (mf/react refs/selected-zoom)
|
||||
scroll-y (:y scroll)
|
||||
translate-y (- (- c/canvas-scroll-padding) (:y scroll))]
|
||||
[:svg.vertical-rule
|
||||
{:width 20
|
||||
:height c/viewport-height}
|
||||
(mf/defc vertical-rule
|
||||
[{:keys [zoom] :as props}]
|
||||
(let [scroll (mf/deref refs/workspace-scroll)
|
||||
scroll-y (:y scroll)
|
||||
translate-y (- (- c/canvas-scroll-padding) (:y scroll))]
|
||||
[:svg.vertical-rule
|
||||
{:width 20
|
||||
:height c/viewport-height}
|
||||
|
||||
[:g {:transform (str "translate(0, " translate-y ")")}
|
||||
(vertical-rule-ticks zoom)]
|
||||
[:rect {:x 0
|
||||
:y 0
|
||||
:height 20
|
||||
:width 20}]])))
|
||||
[:g {:transform (str "translate(0, " translate-y ")")}
|
||||
[:& vertical-rule-ticks {:zoom zoom}]]
|
||||
[:rect {:x 0
|
||||
:y 0
|
||||
:height 20
|
||||
:width 20}]]))
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
;; 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
|
||||
[dom position]
|
||||
|
@ -25,8 +26,8 @@
|
|||
[dom center]
|
||||
(let [viewport-width (.-offsetWidth dom)
|
||||
viewport-height (.-offsetHeight dom)
|
||||
position-x (- (* (:x center) @refs/selected-zoom) (/ viewport-width 2))
|
||||
position-y (- (* (:y center) @refs/selected-zoom) (/ viewport-height 2))
|
||||
position-x (- (* (:x center) 1 #_@refs/selected-zoom) (/ viewport-width 2))
|
||||
position-y (- (* (:y center) 1 #_@refs/selected-zoom) (/ viewport-height 2))
|
||||
position (gpt/point position-x position-y)]
|
||||
(set-scroll-position dom position)))
|
||||
|
||||
|
|
299
frontend/src/uxbox/main/ui/workspace/selection.cljs
Normal file
299
frontend/src/uxbox/main/ui/workspace/selection.cljs
Normal 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}])))
|
|
@ -11,7 +11,8 @@
|
|||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[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.undo :as udu]
|
||||
[uxbox.main.data.history :as udh]
|
||||
|
@ -26,39 +27,39 @@
|
|||
;; --- 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+shift+g #(st/emit! (uds/ungroup-selected))
|
||||
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap))
|
||||
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools))
|
||||
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons))
|
||||
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers))
|
||||
:ctrl+0 #(st/emit! (dw/reset-zoom))
|
||||
:ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
||||
:ctrl+shift+m #(st/emit! (udw/toggle-flag :sitemap))
|
||||
:ctrl+shift+f #(st/emit! (udw/toggle-flag :drawtools))
|
||||
:ctrl+shift+i #(st/emit! (udw/toggle-flag :icons))
|
||||
:ctrl+shift+l #(st/emit! (udw/toggle-flag :layers))
|
||||
:ctrl+0 #(st/emit! (udw/reset-zoom))
|
||||
:ctrl+r #(st/emit! (udw/toggle-flag :ruler))
|
||||
:ctrl+d #(st/emit! (uds/duplicate-selected))
|
||||
:ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
||||
:ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
||||
:ctrl+c #(st/emit! (udw/copy-to-clipboard))
|
||||
:ctrl+v #(st/emit! (udw/paste-from-clipboard))
|
||||
:ctrl+shift+v #(udl/open! :clipboard)
|
||||
:ctrl+z #(st/emit! (udu/undo))
|
||||
:ctrl+shift+z #(st/emit! (udu/redo))
|
||||
:ctrl+y #(st/emit! (udu/redo))
|
||||
:ctrl+b #(st/emit! (dw/select-for-drawing wsd/+draw-tool-rect+))
|
||||
:ctrl+e #(st/emit! (dw/select-for-drawing wsd/+draw-tool-circle+))
|
||||
:ctrl+t #(st/emit! (dw/select-for-drawing wsd/+draw-tool-text+))
|
||||
:esc #(st/emit! (uds/deselect-all))
|
||||
:delete #(st/emit! (uds/delete-selected))
|
||||
:ctrl+up #(st/emit! (uds/move-selected-layer :up))
|
||||
:ctrl+down #(st/emit! (uds/move-selected-layer :down))
|
||||
:ctrl+shift+up #(st/emit! (uds/move-selected-layer :top))
|
||||
:ctrl+shift+down #(st/emit! (uds/move-selected-layer :bottom))
|
||||
:shift+up #(st/emit! (uds/move-selected :up :fast))
|
||||
:shift+down #(st/emit! (uds/move-selected :down :fast))
|
||||
:shift+right #(st/emit! (uds/move-selected :right :fast))
|
||||
:shift+left #(st/emit! (uds/move-selected :left :fast))
|
||||
:up #(st/emit! (uds/move-selected :up :std))
|
||||
:down #(st/emit! (uds/move-selected :down :std))
|
||||
:right #(st/emit! (uds/move-selected :right :std))
|
||||
:left #(st/emit! (uds/move-selected :left :std))
|
||||
:ctrl+b #(st/emit! (udwd/select-for-drawing wsd/+draw-tool-rect+))
|
||||
:ctrl+e #(st/emit! (udwd/select-for-drawing wsd/+draw-tool-circle+))
|
||||
:ctrl+t #(st/emit! (udwd/select-for-drawing wsd/+draw-tool-text+))
|
||||
:esc #(st/emit! (udw/deselect-all))
|
||||
:delete #(st/emit! (udw/delete-selected))
|
||||
:ctrl+up #(st/emit! (udw/move-selected-layer :up))
|
||||
:ctrl+down #(st/emit! (udw/move-selected-layer :down))
|
||||
:ctrl+shift+up #(st/emit! (udw/move-selected-layer :top))
|
||||
:ctrl+shift+down #(st/emit! (udw/move-selected-layer :bottom))
|
||||
:shift+up #(st/emit! (udw/move-selected :up :fast))
|
||||
:shift+down #(st/emit! (udw/move-selected :down :fast))
|
||||
:shift+right #(st/emit! (udw/move-selected :right :fast))
|
||||
:shift+left #(st/emit! (udw/move-selected :left :fast))
|
||||
:up #(st/emit! (udw/move-selected :up :std))
|
||||
:down #(st/emit! (udw/move-selected :down :std))
|
||||
:right #(st/emit! (udw/move-selected :right :std))
|
||||
:left #(st/emit! (udw/move-selected :left :std))
|
||||
})
|
||||
|
||||
;; --- Shortcuts Setup Functions
|
||||
|
@ -80,33 +81,10 @@
|
|||
(events/unlistenByKey key)
|
||||
(.clearKeyListener handler)))))
|
||||
|
||||
(defn- initialize
|
||||
(defn init
|
||||
[]
|
||||
(let [stream (->> (rx/create watch-shortcuts)
|
||||
(rx/pr-log "[debug]: shortcut:"))]
|
||||
(rx/on-value stream (fn [event]
|
||||
(when-let [handler (get +shortcuts+ event)]
|
||||
(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})
|
||||
|
|
|
@ -18,26 +18,31 @@
|
|||
;; --- Left Sidebar (Component)
|
||||
|
||||
(mf/defc left-sidebar
|
||||
[{:keys [flags page-id] :as props}]
|
||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
(sitemap-toolbox page-id))
|
||||
(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
(layers-toolbox))]])
|
||||
[{:keys [wst page] :as props}]
|
||||
(let [{:keys [flags selected]} wst]
|
||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page
|
||||
:selected selected}])]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
(mf/defc right-sidebar
|
||||
[{:keys [flags page-id] :as props}]
|
||||
[:aside#settings-bar.settings-bar
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :drawtools)
|
||||
(draw-toolbox flags))
|
||||
(when (contains? flags :element-options)
|
||||
(options-toolbox))
|
||||
(when (contains? flags :icons)
|
||||
(icons-toolbox))]])
|
||||
[{:keys [wst page] :as props}]
|
||||
(let [flags (:flags wst)
|
||||
dtool (:drawing-tool wst)]
|
||||
[:aside#settings-bar.settings-bar
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :drawtools)
|
||||
[:& draw-toolbox {:flags flags :drawing-tool dtool}])
|
||||
(when (contains? flags :element-options)
|
||||
[:& options-toolbox {:page page
|
||||
:selected (:selected wst)}])
|
||||
(when (contains? flags :icons)
|
||||
#_(icons-toolbox))]]))
|
||||
|
||||
|
|
|
@ -2,34 +2,20 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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
|
||||
(:require [lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[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)))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace-drawing :as udwd]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Constants
|
||||
|
||||
|
@ -91,33 +77,32 @@
|
|||
|
||||
;; --- Draw Toolbox (Component)
|
||||
|
||||
(mx/defc draw-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[flags]
|
||||
(let [drawing-tool (mx/react refs/selected-drawing-tool)
|
||||
close #(st/emit! (udw/toggle-flag :drawtools))
|
||||
(mf/defc draw-toolbox
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [flags drawing-tool] :as props}]
|
||||
(let [close #(st/emit! (udw/toggle-flag :drawtools))
|
||||
tools (->> (into [] +draw-tools+)
|
||||
(sort-by (comp :priority second)))
|
||||
|
||||
select-drawtool #(st/emit! ::uev/interrupt
|
||||
(udw/deactivate-ruler)
|
||||
(udw/select-for-drawing %))
|
||||
toggle-ruler #(st/emit! (udw/select-for-drawing nil)
|
||||
(udwd/select-for-drawing %))
|
||||
toggle-ruler #(st/emit! (udwd/select-for-drawing nil)
|
||||
(uds/deselect-all)
|
||||
(udw/toggle-ruler))]
|
||||
|
||||
[:div#form-tools.tool-window.drawing-tools {}
|
||||
[:div.tool-window-bar {}
|
||||
[:div.tool-window-icon {} i/window]
|
||||
[:span {} (tr "ds.draw-tools")]
|
||||
[:div#form-tools.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content {}
|
||||
[:div.tool-window-content
|
||||
(for [[i props] (map-indexed vector tools)]
|
||||
(let [selected? (= drawing-tool (:shape props))]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr (:help props))
|
||||
:class (when selected? "selected")
|
||||
:key (str i)
|
||||
:key i
|
||||
:on-click (partial select-drawtool (:shape props))}
|
||||
(:icon props)]))
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace-drawing :as udwd]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.main.ui.dashboard.icons :as icons]
|
||||
|
@ -61,10 +62,10 @@
|
|||
(letfn [(on-close [event]
|
||||
(st/emit! (udw/toggle-flag :icons)))
|
||||
(on-select [icon event]
|
||||
(st/emit! (udw/select-for-drawing icon)))
|
||||
(st/emit! (udwd/select-for-drawing icon)))
|
||||
(on-change [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))))]
|
||||
[:div#form-figures.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
|
|
@ -6,32 +6,28 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.layers
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.data :refer (read-string classnames)]
|
||||
[uxbox.util.router :as r]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.dom.dnd :as dnd]
|
||||
[uxbox.util.dom :as dom])
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
[lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[rumext.alpha :as mf]
|
||||
[rumext.core :as mx]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.util.data :refer (read-string classnames)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.dom.dnd :as dnd]
|
||||
[uxbox.util.router :as r])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn- focus-page
|
||||
[id]
|
||||
(-> (l/in [:pages id])
|
||||
(l/derive st/state)))
|
||||
|
||||
(defn- select-shape
|
||||
[selected item event]
|
||||
(dom/prevent-default event)
|
||||
|
@ -42,18 +38,18 @@
|
|||
nil
|
||||
|
||||
(.-ctrlKey event)
|
||||
(st/emit! (uds/select-shape id))
|
||||
(st/emit! (udw/select-shape id))
|
||||
|
||||
(> (count selected) 1)
|
||||
(st/emit! (uds/deselect-all)
|
||||
(uds/select-shape id))
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id))
|
||||
|
||||
(contains? selected id)
|
||||
(st/emit! (uds/select-shape id))
|
||||
(st/emit! (udw/select-shape id))
|
||||
|
||||
:else
|
||||
(st/emit! (uds/deselect-all)
|
||||
(uds/select-shape id)))))
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id)))))
|
||||
|
||||
(defn- toggle-visibility
|
||||
[selected item event]
|
||||
|
@ -64,7 +60,7 @@
|
|||
(st/emit! (uds/show-shape id))
|
||||
(st/emit! (uds/hide-shape id)))
|
||||
(when (contains? selected id)
|
||||
(st/emit! (uds/select-shape id)))))
|
||||
(st/emit! (udw/select-shape id)))))
|
||||
|
||||
(defn- toggle-blocking
|
||||
[item event]
|
||||
|
@ -90,46 +86,45 @@
|
|||
|
||||
;; --- Shape Name (Component)
|
||||
|
||||
(mf/def shape-name
|
||||
:mixins [mf/memo (mf/local)]
|
||||
:render
|
||||
(fn [{:keys [::mf/local] :as own} {:keys [id] :as shape}]
|
||||
(letfn [(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
parent (.-parentNode target)
|
||||
name (dom/get-value target)]
|
||||
(set! (.-draggable parent) true)
|
||||
(st/emit! (uds/rename-shape id name))
|
||||
(swap! local assoc :edition false)))
|
||||
(on-key-down [event]
|
||||
(js/console.log event)
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-click [event]
|
||||
(dom/prevent-default event)
|
||||
(let [parent (.-parentNode (.-target event))]
|
||||
(set! (.-draggable parent) false))
|
||||
(swap! local assoc :edition true))]
|
||||
(if (:edition @local)
|
||||
[:input.element-name
|
||||
{:type "text"
|
||||
:on-blur on-blur
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
:default-value (:name shape "")}]
|
||||
[:span.element-name
|
||||
{:on-double-click on-click}
|
||||
(:name shape "")]))))
|
||||
(mf/defc layer-name
|
||||
[{:keys [shape] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
on-blur (fn [event]
|
||||
(let [target (dom/event->target event)
|
||||
parent (.-parentNode target)
|
||||
name (dom/get-value target)]
|
||||
(set! (.-draggable parent) true)
|
||||
(st/emit! (uds/rename-shape (:id shape) name))
|
||||
(swap! local assoc :edition false)))
|
||||
on-key-down (fn [event]
|
||||
(js/console.log event)
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
on-click (fn [event]
|
||||
(dom/prevent-default event)
|
||||
(let [parent (.-parentNode (.-target event))]
|
||||
(set! (.-draggable parent) false))
|
||||
(swap! local assoc :edition true))]
|
||||
(if (:edition @local)
|
||||
[:input.element-name
|
||||
{:type "text"
|
||||
:on-blur on-blur
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
:default-value (:name shape "")}]
|
||||
[:span.element-name
|
||||
{:on-double-click on-click}
|
||||
(:name shape "")])))
|
||||
|
||||
;; --- Layer Simple (Component)
|
||||
|
||||
(mx/defcs layer-simple
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[{:keys [::mx/local]} item selected]
|
||||
(let [selected? (contains? selected (:id item))
|
||||
select #(select-shape selected item %)
|
||||
toggle-visibility #(toggle-visibility selected item %)
|
||||
toggle-blocking #(toggle-blocking item %)
|
||||
(mf/defc layer-item
|
||||
[{:keys [shape selected] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
selected? (contains? selected (:id shape))
|
||||
select #(select-shape selected shape %)
|
||||
toggle-visibility #(toggle-visibility selected shape %)
|
||||
toggle-blocking #(toggle-blocking shape %)
|
||||
li-classes (classnames
|
||||
:selected selected?
|
||||
:hide (:dragging @local))
|
||||
|
@ -142,7 +137,7 @@
|
|||
(letfn [(on-drag-start [event]
|
||||
(let [target (dom/event->target event)]
|
||||
(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)
|
||||
(swap! local assoc :dragging true)))
|
||||
(on-drag-end [event]
|
||||
|
@ -152,8 +147,8 @@
|
|||
(let [id (dnd/get-data event)
|
||||
over (:over @local)]
|
||||
(case (:over @local)
|
||||
:top (st/emit! (uds/drop-shape id (:id item) :before))
|
||||
:bottom (st/emit! (uds/drop-shape id (:id item) :after)))
|
||||
:top (st/emit! (uds/drop-shape id (:id shape) :before))
|
||||
:bottom (st/emit! (uds/drop-shape id (:id shape) :after)))
|
||||
(swap! local assoc :dragging false :over nil)))
|
||||
(on-drag-over [event]
|
||||
(dom/prevent-default event)
|
||||
|
@ -180,21 +175,21 @@
|
|||
:on-drop on-drop
|
||||
:draggable true}
|
||||
|
||||
[:div.element-actions {}
|
||||
[:div.element-actions
|
||||
[:div.toggle-element
|
||||
{:class (when-not (:hidden item) "selected")
|
||||
{:class (when-not (:hidden shape) "selected")
|
||||
:on-click toggle-visibility}
|
||||
i/eye]
|
||||
[:div.block-element
|
||||
{:class (when (:blocked item) "selected")
|
||||
{:class (when (:blocked shape) "selected")
|
||||
:on-click toggle-blocking}
|
||||
i/lock]]
|
||||
[:div.element-icon (element-icon item)]
|
||||
(shape-name item)]])))
|
||||
[:div.element-icon (element-icon shape)]
|
||||
[:& layer-name {:shape shape}]]])))
|
||||
|
||||
;; --- Layer Group (Component)
|
||||
|
||||
(mx/defcs layer-group
|
||||
#_(mx/defcs layer-group
|
||||
{:mixins [mx/static mx/reactive (mx/local)]}
|
||||
[{:keys [::mx/local]} {:keys [id] :as item} selected]
|
||||
(let [selected? (contains? selected (:id item))
|
||||
|
@ -284,40 +279,44 @@
|
|||
|
||||
;; --- Layers Tools (Buttons Component)
|
||||
|
||||
(defn- allow-grouping?
|
||||
"Check if the current situation allows grouping
|
||||
of the currently selected shapes."
|
||||
[selected shapes-map]
|
||||
(let [xform (comp (map shapes-map)
|
||||
(map :group))
|
||||
groups (into #{} xform selected)]
|
||||
(= 1 (count groups))))
|
||||
;; (defn- allow-grouping?
|
||||
;; "Check if the current situation allows grouping
|
||||
;; of the currently selected shapes."
|
||||
;; [selected shapes-map]
|
||||
;; (let [xform (comp (map shapes-map)
|
||||
;; (map :group))
|
||||
;; groups (into #{} xform selected)]
|
||||
;; (= 1 (count groups))))
|
||||
|
||||
(defn- allow-ungrouping?
|
||||
"Check if the current situation allows ungrouping
|
||||
of the currently selected shapes."
|
||||
[selected shapes-map]
|
||||
(let [shapes (into #{} (map shapes-map) selected)
|
||||
groups (into #{} (map :group) shapes)]
|
||||
(or (and (= 1 (count shapes))
|
||||
(= :group (:type (first shapes))))
|
||||
(and (= 1 (count groups))
|
||||
(not (nil? (first groups)))))))
|
||||
;; (defn- allow-ungrouping?
|
||||
;; "Check if the current situation allows ungrouping
|
||||
;; of the currently selected shapes."
|
||||
;; [selected shapes-map]
|
||||
;; (let [shapes (into #{} (map shapes-map) selected)
|
||||
;; groups (into #{} (map :group) shapes)]
|
||||
;; (or (and (= 1 (count shapes))
|
||||
;; (= :group (:type (first shapes))))
|
||||
;; (and (= 1 (count groups))
|
||||
;; (not (nil? (first groups)))))))
|
||||
|
||||
(mx/defc layers-tools
|
||||
(mf/defc layers-tools
|
||||
"Layers widget options buttons."
|
||||
[selected shapes-map]
|
||||
(let [duplicate #(st/emit! (uds/duplicate-selected))
|
||||
[{:keys [selected shapes] :as props}]
|
||||
#_(let [duplicate #(st/emit! (uds/duplicate-selected))
|
||||
group #(st/emit! (uds/group-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-ungrouping? (allow-ungrouping? selected shapes-map)
|
||||
;; allow-grouping? (allow-grouping? selected shapes)
|
||||
;; 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-deletion? (pos? (count selected))]
|
||||
[:div.layers-tools {}
|
||||
[:ul.layers-tools-content {}
|
||||
[:div.layers-tools
|
||||
[:ul.layers-tools-content
|
||||
[:li.clone-layer.tooltip.tooltip-top
|
||||
{:alt "Duplicate"
|
||||
:class (when-not allow-duplicate? "disable")
|
||||
|
@ -341,25 +340,30 @@
|
|||
|
||||
;; --- Layers Toolbox (Component)
|
||||
|
||||
(mx/defc layers-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [selected (mx/react refs/selected-shapes)
|
||||
page (mx/react refs/selected-page)
|
||||
shapes-map (mx/react refs/shapes-by-id)
|
||||
close #(st/emit! (udw/toggle-flag :layers))
|
||||
dragel (volatile! nil)]
|
||||
[:div#layers.tool-window {}
|
||||
[:div.tool-window-bar {}
|
||||
[:div.tool-window-icon {} i/layers]
|
||||
[:span {} "Layers"]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content {}
|
||||
[:ul.element-list {}
|
||||
(for [{:keys [id] :as shape} (map #(get shapes-map %) (:shapes page))]
|
||||
(if (= (:type shape) :group)
|
||||
(-> (layer-group shape selected)
|
||||
(mx/with-key id))
|
||||
(-> (layer-simple shape selected)
|
||||
(mx/with-key id))))]]
|
||||
(layers-tools selected shapes-map)]))
|
||||
(mf/def layers-toolbox
|
||||
:mixins [mx/static mx/reactive]
|
||||
|
||||
:init
|
||||
(fn [own {:keys [id]}]
|
||||
(assoc own ::shapes-ref (-> (l/key :shapes)
|
||||
(l/derive st/state))))
|
||||
|
||||
:render
|
||||
(fn [own {:keys [page selected] :as props}]
|
||||
(let [shapes (mx/react (::shapes-ref own))
|
||||
close #(st/emit! (udw/toggle-flag :layers))
|
||||
dragel (volatile! nil)]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/layers]
|
||||
[:span "Layers"]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:ul.element-list
|
||||
(for [id (:shapes page)]
|
||||
(let [shape (get shapes id)]
|
||||
[:& layer-item {:shape shape
|
||||
:key id
|
||||
:selected selected}]))]]
|
||||
[:& layers-tools {:selected selected
|
||||
:shapes shapes}]])))
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
(ns uxbox.main.ui.workspace.sidebar.options
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[rumext.alpha :as mf]
|
||||
[rumext.core :as mx]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
|
@ -27,20 +27,18 @@
|
|||
[uxbox.main.ui.workspace.sidebar.options.text :as options-text]
|
||||
[uxbox.util.data :as data]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as r]))
|
||||
[uxbox.util.i18n :refer [tr]]))
|
||||
|
||||
;; --- Constants
|
||||
|
||||
(def ^:private +menus-map+
|
||||
{:icon [::icon-measures ::fill ::stroke ::interactions]
|
||||
:rect [::rect-measures ::fill ::stroke ::interactions]
|
||||
:path [::fill ::stroke ::interactions]
|
||||
:circle [::circle-measures ::fill ::stroke ::interactions]
|
||||
:text [::fill ::text ::interactions]
|
||||
:image [::image-measures ::interactions]
|
||||
:group [::fill ::stroke ::interactions]
|
||||
::page [::page-measures ::page-grid-options]})
|
||||
{:icon [::icon-measures ::fill ::stroke]
|
||||
:rect [::rect-measures ::fill ::stroke]
|
||||
:path [::fill ::stroke ::interactions]
|
||||
:circle [::circle-measures ::fill ::stroke]
|
||||
:text [::fill ::text]
|
||||
:image [::image-measures]
|
||||
::page [::page-measures ::page-grid-options]})
|
||||
|
||||
(def ^:private +menus+
|
||||
[{:name "Size, position & rotation"
|
||||
|
@ -89,45 +87,37 @@
|
|||
|
||||
;; --- Options
|
||||
|
||||
(mx/defcs options
|
||||
{:mixins [mx/static (mx/local)]
|
||||
:key-fn #(pr-str (:id %1))}
|
||||
[{:keys [::mx/local] :as own} shape]
|
||||
(let [menus (get +menus-map+ (:type shape ::page))
|
||||
contained-in? (into #{} menus)
|
||||
active (:menu @local (first menus))]
|
||||
[:div {}
|
||||
(when (> (count menus) 1)
|
||||
[:ul.element-icons {}
|
||||
(for [menu-id (get +menus-map+ (:type shape ::page))]
|
||||
(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))]))
|
||||
(mf/defc shape-options
|
||||
[{:keys [sid] :as props}]
|
||||
(let [shape-iref (mf/use-memo {:deps sid
|
||||
:init #(-> (l/in [:shapes sid])
|
||||
(l/derive st/state))})
|
||||
shape (mf/deref shape-iref)
|
||||
menus (get +menus-map+ (:type shape))]
|
||||
[:div
|
||||
(for [mid menus]
|
||||
(let [{:keys [comp] :as menu} (get +menus-by-id+ mid)]
|
||||
[:& comp {:menu menu :shape shape :key mid}]))]))
|
||||
|
||||
(def selected-shape-ref
|
||||
(letfn [(getter [state]
|
||||
(let [selected (get-in state [:workspace :selected])]
|
||||
(when (= 1 (count selected))
|
||||
(get-in state [:shapes (first selected)]))))]
|
||||
(-> (l/lens getter)
|
||||
(l/derive st/state))))
|
||||
(mf/defc page-options
|
||||
[{:keys [page] :as props}]
|
||||
(let [menus (get +menus-map+ ::page)]
|
||||
[:div
|
||||
(for [mid menus]
|
||||
(let [{:keys [comp] :as menu} (get +menus-by-id+ mid)]
|
||||
[:& comp {:menu menu :page page :key mid}]))]))
|
||||
|
||||
(mx/defc options-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [shape (->> (mx/react selected-shape-ref)
|
||||
(merge shape-default-attrs))
|
||||
close #(st/emit! (udw/toggle-flag :element-options))]
|
||||
[:div.elementa-options.tool-window {}
|
||||
[:div.tool-window-bar {}
|
||||
[:div.tool-window-icon {} i/options]
|
||||
[:span {} (tr "ds.element-options")]
|
||||
(mf/defc options-toolbox
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [page selected] :as props}]
|
||||
(let [close #(st/emit! (udw/toggle-flag :element-options))]
|
||||
[:div.elementa-options.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/options]
|
||||
[:span (tr "ds.element-options")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content {}
|
||||
[:div.element-options {}
|
||||
(options shape)]]]))
|
||||
[:div.tool-window-content
|
||||
[:div.element-options
|
||||
(if (= (count selected) 1)
|
||||
[:& shape-options {:sid (first selected)}]
|
||||
[:& page-options {:page page}])]]]))
|
||||
|
|
|
@ -2,107 +2,117 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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
|
||||
(:require [lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.math :refer (precision-or-0)]
|
||||
[uxbox.util.router :as r]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.math :refer (precision-or-0)]))
|
||||
|
||||
(mx/defc circle-measures-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(letfn [(on-size-change [attr event]
|
||||
(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)}]]]
|
||||
(declare on-size-change)
|
||||
(declare on-rotation-change)
|
||||
(declare on-position-change)
|
||||
(declare on-proportion-lock-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 (partial on-pos-change :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "cy"
|
||||
:type "number"
|
||||
:value (precision-or-0 (:cy shape 0) 2)
|
||||
:on-change (partial on-pos-change :y)}]]]
|
||||
(mf/defc circle-measures-menu
|
||||
[{:keys [menu shape] :as props}]
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
;; SLIDEBAR FOR ROTATION AND OPACITY
|
||||
[:span "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 #(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.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min 0
|
||||
:max 360
|
||||
:value (:rotation shape 0)
|
||||
:on-change on-rotation-change}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min "0"
|
||||
:value (precision-or-0 (:ry shape 0) 2)
|
||||
:on-change #(on-size-change % shape :ry)}]]]
|
||||
|
||||
[: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"}}]]]]))
|
||||
|
|
|
@ -2,48 +2,43 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.fill
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
[uxbox.util.spec :refer (color?)]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.util.data :refer [parse-float]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :refer [tr]]))
|
||||
|
||||
(mx/defc fill-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(mf/defc fill-menu
|
||||
[{:keys [menu shape]}]
|
||||
(letfn [(change-attrs [attrs]
|
||||
(st/emit! (uds/update-attrs id attrs)))
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) attrs)))
|
||||
(on-color-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(when (color? value)
|
||||
(change-attrs {:fill-color value}))))
|
||||
(change-attrs {:fill-color value})))
|
||||
(on-opacity-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-float value 1)
|
||||
value (/ value 10000)]
|
||||
(change-attrs {:fill-opacity value})))
|
||||
(on-color-picker-event [color]
|
||||
(change-attrs {:fill-color color}))
|
||||
(show-color-picker [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:shape (:id shape)
|
||||
:attr :fill-color
|
||||
:transparent? true}]
|
||||
(udl/open! :workspace/shape-colorpicker opts)))]
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
props {:x x :y y
|
||||
:on-change #(change-attrs {:fill-color %})
|
||||
:default "#ffffff"
|
||||
:value (:fill-color shape)
|
||||
:transparent? true}]
|
||||
(modal/show! colorpicker-modal props)))]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
|
||||
|
@ -55,7 +50,7 @@
|
|||
[:div.color-info
|
||||
[:input
|
||||
{:on-change on-color-change
|
||||
:value (:fill-color shape)}]]]
|
||||
:value (:fill-color shape "")}]]]
|
||||
|
||||
;; SLIDEBAR FOR ROTATION AND OPACITY
|
||||
[:span "Opacity"]
|
||||
|
@ -64,6 +59,6 @@
|
|||
{:type "range"
|
||||
:min "0"
|
||||
:max "10000"
|
||||
:value (* 10000 (:fill-opacity shape))
|
||||
:value (str (* 10000 (:fill-opacity shape 1)))
|
||||
:step "1"
|
||||
:on-change on-opacity-change}]]]]))
|
||||
|
|
|
@ -2,109 +2,114 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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
|
||||
(:require [lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.geom :as geom]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.data :refer [parse-int parse-float read-string]]
|
||||
[uxbox.util.math :refer [precision-or-0]]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer [parse-int parse-float read-string]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.math :refer [precision-or-0]]))
|
||||
|
||||
(mx/defc icon-measures-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu shape]
|
||||
(letfn [(on-size-change [attr event]
|
||||
(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)}]]]
|
||||
(declare on-size-change)
|
||||
(declare on-rotation-change)
|
||||
(declare on-position-change)
|
||||
(declare on-proportion-lock-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 (partial on-pos-change :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:type "number"
|
||||
:value (precision-or-0 (:y1 shape 0) 2)
|
||||
:on-change (partial on-pos-change :y)}]]]
|
||||
(mf/defc icon-measures-menu
|
||||
[{:keys [menu shape] :as props}]
|
||||
(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 #(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.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min 0
|
||||
:max 360
|
||||
:value (:rotation shape 0)
|
||||
:on-change on-rotation-change}]]
|
||||
[: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 "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"}}]
|
||||
]]])))
|
||||
|
|
|
@ -6,119 +6,130 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.image-measures
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
[uxbox.util.math :refer (precision-or-0)]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.math :refer (precision-or-0)]))
|
||||
|
||||
(mx/defc image-measures-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(letfn [(on-size-change [attr event]
|
||||
(let [value (dom/event->value event)
|
||||
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)}]]]
|
||||
(declare on-size-change)
|
||||
(declare on-rotation-change)
|
||||
(declare on-opacity-change)
|
||||
(declare on-position-change)
|
||||
(declare on-proportion-lock-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 (partial on-pos-change :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:type "number"
|
||||
:value (precision-or-0 (:y1 shape 0) 2)
|
||||
:on-change (partial on-pos-change :y)}]]]
|
||||
(mf/defc image-measures-menu
|
||||
[{:keys [menu shape] :as props}]
|
||||
(let [size (geom/size shape)]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
;; SLIDEBAR FOR ROTATION AND OPACITY
|
||||
[:span "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 % 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"]
|
||||
;; [:div.row-flex
|
||||
;; [:input.slidebar
|
||||
;; {:type "range"
|
||||
;; :min 0
|
||||
;; :max 360
|
||||
;; :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)}]]]
|
||||
|
||||
;; [:div.row-flex
|
||||
;; [:div.input-element.degrees
|
||||
;; [:input.input-text
|
||||
;; {:placeholder ""
|
||||
;; :type "number"
|
||||
;; :min 0
|
||||
;; :max 360
|
||||
;; :value (precision-or-0 (:rotation shape 0) 2)
|
||||
;; :on-change on-rotation-change
|
||||
;; }]]
|
||||
;; [:input.input-text
|
||||
;; {:style {:visibility "hidden"}}]]
|
||||
;; [:span "Rotation"]
|
||||
;; [:div.row-flex
|
||||
;; [:input.slidebar
|
||||
;; {:type "range"
|
||||
;; :min 0
|
||||
;; :max 360
|
||||
;; :value (:rotation shape 0)
|
||||
;; :on-change on-rotation-change}]]
|
||||
|
||||
;; [:div.row-flex
|
||||
;; [:div.input-element.degrees
|
||||
;; [:input.input-text
|
||||
;; {:placeholder ""
|
||||
;; :type "number"
|
||||
;; :min 0
|
||||
;; :max 360
|
||||
;; :value (precision-or-0 (:rotation shape 0) 2)
|
||||
;; :on-change on-rotation-change
|
||||
;; }]]
|
||||
;; [:input.input-text
|
||||
;; {:style {:visibility "hidden"}}]]
|
||||
|
||||
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min "0"
|
||||
:max "10000"
|
||||
:value (* 10000 (:opacity shape 1))
|
||||
:step "1"
|
||||
:on-change on-opacity-change}]]
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min "0"
|
||||
:max "10000"
|
||||
:value (* 10000 (:opacity shape 1))
|
||||
:step "1"
|
||||
:on-change #(on-opacity-change % shape)}]]]]))
|
||||
|
||||
]])))
|
||||
(defn- on-size-change
|
||||
[event shape attr]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
props {attr value}]
|
||||
(st/emit! (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)))))
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.interactions
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
|
@ -19,7 +18,6 @@
|
|||
[uxbox.util.data :refer [read-string]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.spec :refer [color?]]))
|
||||
|
||||
;; --- Helpers
|
||||
|
@ -53,11 +51,11 @@
|
|||
;; :holdrelease "Hold release"
|
||||
(pr-str trigger)))
|
||||
|
||||
(mx/defc interactions-list
|
||||
[shape form-ref]
|
||||
(mf/defc interactions-list
|
||||
[{:keys [shape form] :as props}]
|
||||
(letfn [(on-edit [item event]
|
||||
(dom/prevent-default event)
|
||||
(reset! form-ref item))
|
||||
(reset! form item))
|
||||
(delete [item]
|
||||
(let [sid (:id shape)
|
||||
id (:id item)]
|
||||
|
@ -78,16 +76,17 @@
|
|||
|
||||
;; --- Trigger Input
|
||||
|
||||
(mx/defc trigger-input
|
||||
[form-ref]
|
||||
(when-not (:trigger @form-ref)
|
||||
(swap! form-ref assoc :trigger :click))
|
||||
(mf/defc trigger-input
|
||||
[{:keys [form] :as props}]
|
||||
;; (mf/use-effect
|
||||
;; {:init #(when-not (:trigger @form) (swap! form assoc :trigger :click))
|
||||
;; :deps true})
|
||||
[:div
|
||||
[:span "Trigger"]
|
||||
[:div.row-flex
|
||||
[:select.input-select {:placeholder "Choose a trigger"
|
||||
:on-change (partial on-change form-ref :trigger)
|
||||
:value (pr-str (:trigger @form-ref))}
|
||||
:on-change (partial on-change form :trigger)
|
||||
:value (pr-str (:trigger @form))}
|
||||
[:option {:value ":click"} "Click"]
|
||||
[:option {:value ":doubleclick"} "Double-click"]
|
||||
[:option {:value ":rightclick"} "Right-click"]
|
||||
|
@ -105,15 +104,15 @@
|
|||
|
||||
;; --- URL Input
|
||||
|
||||
(mx/defc url-input
|
||||
[form-ref]
|
||||
(mf/defc url-input
|
||||
[form]
|
||||
[:div
|
||||
[:span "Url"]
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "http://"
|
||||
:on-change (partial on-change form-ref :url)
|
||||
:value (:url @form-ref "")
|
||||
:on-change (partial on-change form :url)
|
||||
:value (:url @form "")
|
||||
:type "url"}]]])
|
||||
|
||||
;; --- Elements Input
|
||||
|
@ -129,16 +128,16 @@
|
|||
(conj acc shape))))]
|
||||
(reduce resolve-shape [] shapes))))
|
||||
|
||||
(mx/defc elements-input
|
||||
[page form-ref]
|
||||
(let [shapes (collect-shapes @st/state page)]
|
||||
(mf/defc elements-input
|
||||
[{:keys [page-id form] :as props}]
|
||||
(let [shapes (collect-shapes @st/state page-id)]
|
||||
[:div
|
||||
[:span "Element"]
|
||||
[:div.row-flex
|
||||
[:select.input-select
|
||||
{:placeholder "Choose an element"
|
||||
:on-change (partial on-change form-ref :element)
|
||||
:value (pr-str (:element @form-ref))}
|
||||
:on-change (partial on-change form :element)
|
||||
:value (pr-str (:element @form))}
|
||||
[:option {:value "nil"} "---"]
|
||||
(for [shape shapes
|
||||
:let [key (pr-str (:id shape))]]
|
||||
|
@ -146,11 +145,10 @@
|
|||
|
||||
;; --- Page Input
|
||||
|
||||
(mx/defc pages-input
|
||||
{:mixins [mx/reactive]}
|
||||
(mf/defc pages-input
|
||||
[form-ref path]
|
||||
;; 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))
|
||||
(pos? (count pages)))
|
||||
(swap! form-ref assoc :page (:id (first pages))))
|
||||
|
@ -166,124 +164,124 @@
|
|||
|
||||
;; --- Animation
|
||||
|
||||
(mx/defc animation-input
|
||||
[form-ref]
|
||||
(when-not (:action @form-ref)
|
||||
(swap! form-ref assoc :animation :none))
|
||||
(mf/defc animation-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:action @form)
|
||||
(swap! form assoc :animation :none))
|
||||
[:div
|
||||
[:span "Animation"]
|
||||
[:div.row-flex
|
||||
[:select.input-select
|
||||
{:placeholder "Animation"
|
||||
:on-change (partial on-change form-ref :animation)
|
||||
:value (pr-str (:animation @form-ref))}
|
||||
:on-change (partial on-change form :animation)
|
||||
:value (pr-str (:animation @form))}
|
||||
[:option {:value ":none"} "None"]
|
||||
[:option {:value ":fade"} "Fade"]
|
||||
[:option {:value ":slide"} "Slide"]]]])
|
||||
|
||||
;; --- MoveTo Input
|
||||
|
||||
(mx/defc moveto-input
|
||||
[form-ref]
|
||||
(when-not (:moveto-x @form-ref)
|
||||
(swap! form-ref assoc :moveto-x 0))
|
||||
(when-not (:moveto-y @form-ref)
|
||||
(swap! form-ref assoc :moveto-y 0))
|
||||
(mf/defc moveto-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:moveto-x @form)
|
||||
(swap! form assoc :moveto-x 0))
|
||||
(when-not (:moveto-y @form)
|
||||
(swap! form assoc :moveto-y 0))
|
||||
[:div
|
||||
[:span "Move to position"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "X"
|
||||
:on-change (partial on-change form-ref :moveto-x)
|
||||
:on-change (partial on-change form :moveto-x)
|
||||
:type "number"
|
||||
:value (:moveto-x @form-ref "")}]]
|
||||
:value (:moveto-x @form "")}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:on-change (partial on-change form-ref :moveto-y)
|
||||
:on-change (partial on-change form :moveto-y)
|
||||
:type "number"
|
||||
:value (:moveto-y @form-ref "")}]]]])
|
||||
:value (:moveto-y @form "")}]]]])
|
||||
|
||||
;; --- MoveBy Input
|
||||
|
||||
(mx/defc moveby-input
|
||||
[form-ref]
|
||||
(when-not (:moveby-x @form-ref)
|
||||
(swap! form-ref assoc :moveby-x 0))
|
||||
(when-not (:moveby-y @form-ref)
|
||||
(swap! form-ref assoc :moveby-y 0))
|
||||
(mf/defc moveby-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:moveby-x @form)
|
||||
(swap! form assoc :moveby-x 0))
|
||||
(when-not (:moveby-y @form)
|
||||
(swap! form assoc :moveby-y 0))
|
||||
[:div
|
||||
[:span "Move to position"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "X"
|
||||
:on-change (partial on-change form-ref :moveby-x)
|
||||
:on-change (partial on-change form :moveby-x)
|
||||
:type "number"
|
||||
:value (:moveby-x @form-ref "")}]]
|
||||
:value (:moveby-x @form "")}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:on-change (partial on-change form-ref :moveby-y)
|
||||
:on-change (partial on-change form :moveby-y)
|
||||
:type "number"
|
||||
:value (:moveby-y @form-ref "")}]]]])
|
||||
:value (:moveby-y @form "")}]]]])
|
||||
|
||||
;; --- Opacity Input
|
||||
|
||||
(mx/defc opacity-input
|
||||
[form-ref]
|
||||
(when-not (:opacity @form-ref)
|
||||
(swap! form-ref assoc :opacity 100))
|
||||
(mf/defc opacity-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:opacity @form)
|
||||
(swap! form assoc :opacity 100))
|
||||
[:div
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.percentail
|
||||
[:input.input-text
|
||||
{:placeholder "%"
|
||||
:on-change (partial on-change form-ref :opacity)
|
||||
:on-change (partial on-change form :opacity)
|
||||
:min "0"
|
||||
:max "100"
|
||||
:type "number"
|
||||
:value (:opacity @form-ref "")}]]]])
|
||||
:value (:opacity @form "")}]]]])
|
||||
|
||||
;; --- Rotate Input
|
||||
|
||||
(mx/defc rotate-input
|
||||
[form-ref]
|
||||
[:div
|
||||
[:span "Rotate (dg)"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.degrees
|
||||
[:input.input-text
|
||||
{:placeholder "dg"
|
||||
:on-change (partial on-change form-ref :rotation)
|
||||
:type "number"
|
||||
:value (:rotation @form-ref "")}]]]])
|
||||
;; (mx/defc rotate-input
|
||||
;; [form]
|
||||
;; [:div
|
||||
;; [:span "Rotate (dg)"]
|
||||
;; [:div.row-flex
|
||||
;; [:div.input-element.degrees
|
||||
;; [:input.input-text
|
||||
;; {:placeholder "dg"
|
||||
;; :on-change (partial on-change form :rotation)
|
||||
;; :type "number"
|
||||
;; :value (:rotation @form "")}]]]])
|
||||
|
||||
;; --- Resize Input
|
||||
|
||||
(mx/defc resize-input
|
||||
[form-ref]
|
||||
(mf/defc resize-input
|
||||
[{:keys [form] :as props}]
|
||||
[:div
|
||||
[:span "Resize"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Width"
|
||||
:on-change (partial on-change form-ref :resize-width)
|
||||
:on-change (partial on-change form :resize-width)
|
||||
:type "number"
|
||||
:value (:resize-width @form-ref "")}]]
|
||||
:value (:resize-width @form "")}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Height"
|
||||
:on-change (partial on-change form-ref :resize-height)
|
||||
:on-change (partial on-change form :resize-height)
|
||||
:type "number"
|
||||
:value (:resize-height @form-ref "")}]]]])
|
||||
:value (:resize-height @form "")}]]]])
|
||||
|
||||
;; --- Color Input
|
||||
|
||||
(mx/defc colorpicker
|
||||
(mf/defc colorpicker
|
||||
[{:keys [x y on-change value]}]
|
||||
(let [left (- x 260)
|
||||
top (- y 50)]
|
||||
|
@ -300,14 +298,14 @@
|
|||
[params]
|
||||
(colorpicker params))
|
||||
|
||||
(mx/defc color-input
|
||||
[form-ref]
|
||||
(when-not (:fill-color @form-ref)
|
||||
(swap! form-ref assoc :fill-color "#000000"))
|
||||
(when-not (:stroke-color @form-ref)
|
||||
(swap! form-ref assoc :stroke-color "#000000"))
|
||||
(mf/defc color-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:fill-color @form)
|
||||
(swap! form assoc :fill-color "#000000"))
|
||||
(when-not (:stroke-color @form)
|
||||
(swap! form assoc :stroke-color "#000000"))
|
||||
(letfn [(on-change [attr color]
|
||||
(swap! form-ref assoc attr color))
|
||||
(swap! form assoc attr color))
|
||||
(on-change-fill-color [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(when (color? value)
|
||||
|
@ -321,11 +319,11 @@
|
|||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:on-change (partial on-change attr)
|
||||
:value (get @form-ref attr)
|
||||
:value (get @form attr)
|
||||
:transparent? true}]
|
||||
(udl/open! :interactions/colorpicker opts)))]
|
||||
(let [stroke-color (:stroke-color @form-ref)
|
||||
fill-color (:fill-color @form-ref)]
|
||||
(let [stroke-color (:stroke-color @form)
|
||||
fill-color (:fill-color @form)]
|
||||
[:div
|
||||
[:div.row-flex
|
||||
[:div.column-half
|
||||
|
@ -351,17 +349,17 @@
|
|||
|
||||
;; --- Easing Input
|
||||
|
||||
(mx/defc easing-input
|
||||
[form-ref]
|
||||
(when-not (:easing @form-ref)
|
||||
(swap! form-ref assoc :easing :linear))
|
||||
(mf/defc easing-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:easing @form)
|
||||
(swap! form assoc :easing :linear))
|
||||
[:div
|
||||
[:span "Easing"]
|
||||
[:div.row-flex
|
||||
[:select.input-select
|
||||
{:placeholder "Easing"
|
||||
:on-change (partial on-change form-ref :easing)
|
||||
:value (pr-str (:easing @form-ref))}
|
||||
:on-change (partial on-change form :easing)
|
||||
:value (pr-str (:easing @form))}
|
||||
[:option {:value ":linear"} "Linear"]
|
||||
[:option {:value ":easein"} "Ease in"]
|
||||
[:option {:value ":easeout"} "Ease out"]
|
||||
|
@ -369,12 +367,12 @@
|
|||
|
||||
;; --- Duration Input
|
||||
|
||||
(mx/defc duration-input
|
||||
[form-ref]
|
||||
(when-not (:duration @form-ref)
|
||||
(swap! form-ref assoc :duration 300))
|
||||
(when-not (:delay @form-ref)
|
||||
(swap! form-ref assoc :delay 0))
|
||||
(mf/defc duration-input
|
||||
[{:keys [form] :as props}]
|
||||
(when-not (:duration @form)
|
||||
(swap! form assoc :duration 300))
|
||||
(when-not (:delay @form)
|
||||
(swap! form assoc :delay 0))
|
||||
[:div
|
||||
[:span "Duration | Delay"]
|
||||
[:div.row-flex
|
||||
|
@ -382,21 +380,21 @@
|
|||
[:input.input-text
|
||||
{:placeholder "Duration"
|
||||
:type "number"
|
||||
:on-change (partial on-change form-ref :duration)
|
||||
:value (pr-str (:duration @form-ref))}]]
|
||||
:on-change (partial on-change form :duration)
|
||||
:value (pr-str (:duration @form))}]]
|
||||
[:div.input-element.miliseconds
|
||||
[:input.input-text {:placeholder "Delay"
|
||||
:type "number"
|
||||
:on-change (partial on-change form-ref :delay)
|
||||
:value (pr-str (:delay @form-ref))}]]]])
|
||||
:on-change (partial on-change form :delay)
|
||||
:value (pr-str (:delay @form))}]]]])
|
||||
|
||||
;; --- Action Input
|
||||
|
||||
(mx/defc action-input
|
||||
[page form-ref]
|
||||
(when-not (:action @form-ref)
|
||||
(swap! form-ref assoc :action :show))
|
||||
(let [form @form-ref
|
||||
(mf/defc action-input
|
||||
[{:keys [shape form] :as props}]
|
||||
;; (when-not (:action @form)
|
||||
;; (swap! form assoc :action :show))
|
||||
(let [form-data (deref form)
|
||||
simple? #{:gotourl :gotopage}
|
||||
elements? (complement simple?)
|
||||
animation? #{:show :hide :toggle}
|
||||
|
@ -406,8 +404,8 @@
|
|||
[:div.row-flex
|
||||
[:select.input-select
|
||||
{:placeholder "Choose an action"
|
||||
:on-change (partial on-change form-ref :action [:trigger])
|
||||
:value (pr-str (:action form))}
|
||||
:on-change (partial on-change form :action [:trigger])
|
||||
:value (pr-str (:action form-data))}
|
||||
[:option {:value ":show"} "Show"]
|
||||
[:option {:value ":hide"} "Hide"]
|
||||
[:option {:value ":toggle"} "Toggle"]
|
||||
|
@ -422,47 +420,49 @@
|
|||
#_[:option {:value ":goback"} "Go back"]
|
||||
[:option {:value ":scrolltoelement"} "Scroll to element"]]]
|
||||
|
||||
(case (:action form)
|
||||
:gotourl (url-input form-ref)
|
||||
:gotopage (pages-input form-ref)
|
||||
:color (color-input form-ref)
|
||||
;; :rotate (rotate-input form-ref)
|
||||
:size (resize-input form-ref)
|
||||
:moveto (moveto-input form-ref)
|
||||
:moveby (moveby-input form-ref)
|
||||
:opacity (opacity-input form-ref)
|
||||
(case (:action form-data)
|
||||
:gotourl [:& url-input {:form form}]
|
||||
;; :gotopage (pages-input form)
|
||||
:color [:& color-input {:form form}]
|
||||
;; :rotate (rotate-input form)
|
||||
:size [:& resize-input {:form form}]
|
||||
:moveto [:& moveto-input {:form form}]
|
||||
:moveby [:& moveby-input {:form form}]
|
||||
:opacity [:& opacity-input {:form form}]
|
||||
nil)
|
||||
|
||||
(when (elements? (:action form))
|
||||
(elements-input page form-ref))
|
||||
(when (elements? (:action form-data))
|
||||
[:& elements-input {:page-id (:page shape)
|
||||
:form form}])
|
||||
|
||||
(when (and (animation? (:action form))
|
||||
(:element @form-ref))
|
||||
(animation-input form-ref))
|
||||
(when (and (animation? (:action form-data))
|
||||
(:element form-data))
|
||||
[:& animation-input {:form form}])
|
||||
|
||||
(when (or (not= (:animation form-data :none) :none)
|
||||
(and (only-easing? (:action form-data))
|
||||
(:element form-data)))
|
||||
[:*
|
||||
[:& easing-input {:form form}]
|
||||
[:& duration-input {:form form}]])]))
|
||||
|
||||
(when (or (not= (:animation form :none) :none)
|
||||
(and (only-easing? (:action form))
|
||||
(:element form)))
|
||||
(list (easing-input form-ref)
|
||||
(duration-input form-ref)))
|
||||
]))
|
||||
|
||||
;; --- Form
|
||||
|
||||
(mx/defc interactions-form
|
||||
[shape form-ref]
|
||||
(mf/defc interactions-form
|
||||
[{:keys [shape form] :as props}]
|
||||
(letfn [(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(let [shape-id (:id shape)
|
||||
data (deref form-ref)]
|
||||
(st/emit! (uds/update-interaction shape-id data))
|
||||
(reset! form-ref nil)))
|
||||
(let [sid (:id shape)
|
||||
data (deref form)]
|
||||
(st/emit! (uds/update-interaction sid data))
|
||||
(reset! form nil)))
|
||||
(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(reset! form-ref nil))]
|
||||
(reset! form nil))]
|
||||
[:form {:on-submit on-submit}
|
||||
(trigger-input form-ref)
|
||||
(action-input (:page shape) form-ref)
|
||||
[:& trigger-input {:form form}]
|
||||
[:& action-input {:shape shape :form form}]
|
||||
[:div.row-flex
|
||||
[:input.btn-primary.btn-small.save-btn
|
||||
{:value "Save" :type "submit"}]
|
||||
|
@ -471,23 +471,24 @@
|
|||
|
||||
;; --- Interactions Menu
|
||||
|
||||
(mx/defcs interactions-menu
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[own menu shape]
|
||||
(let [local (::mx/local own)
|
||||
form-ref (l/derive (l/key :form) local)
|
||||
interactions (:interactions shape)
|
||||
create-interaction #(reset! form-ref {})]
|
||||
(def +initial-form+
|
||||
{:trigger :click
|
||||
:action :show})
|
||||
|
||||
(mf/defc interactions-menu
|
||||
[{:keys [menu shape] :as props}]
|
||||
(let [form (mf/use-state nil)
|
||||
interactions (:interactions shape)]
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
(if @form-ref
|
||||
(interactions-form shape form-ref)
|
||||
(if form
|
||||
[:& interactions-form {:form form :shape shape}]
|
||||
[:div
|
||||
(interactions-list shape form-ref)
|
||||
[:& interactions-list {:form form :shape shape}]
|
||||
[:input.btn-primary.btn-small
|
||||
{:value "New interaction"
|
||||
:on-click create-interaction
|
||||
:on-click #(reset! form +initial-form+)
|
||||
:type "button"}]])]]))
|
||||
|
||||
;; --- Not implemented stuff
|
||||
|
|
|
@ -7,57 +7,55 @@
|
|||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.page
|
||||
"Page options menu entries."
|
||||
(:require [lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.workspace.colorpicker]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.data :refer [parse-int]]
|
||||
[uxbox.util.spec :refer [color?]]
|
||||
[uxbox.util.dom :as dom]))
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.util.data :refer [parse-int]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.spec :refer [color?]]))
|
||||
|
||||
(mx/defcs measures-menu
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own menu]
|
||||
(let [{:keys [id metadata] :as page} (mx/react refs/selected-page)
|
||||
(mf/defc measures-menu
|
||||
[{:keys [menu page] :as props}]
|
||||
(let [metadata (:metadata page)
|
||||
metadata (merge c/page-metadata metadata)]
|
||||
(letfn [(on-size-change [attr]
|
||||
(when-let [value (-> (mx/ref-node own (name attr))
|
||||
(dom/get-value)
|
||||
(parse-int nil))]
|
||||
(letfn [(on-size-change [event attr]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int nil))]
|
||||
(st/emit! (->> (assoc metadata attr value)
|
||||
(udp/update-metadata id)))))
|
||||
(udp/update-metadata (:id page))))))
|
||||
|
||||
(on-color-change []
|
||||
(when-let [value (-> (mx/ref-node own "color")
|
||||
(dom/get-value)
|
||||
(#(if (color? %) % nil)))]
|
||||
(->> (assoc metadata :background value)
|
||||
(udp/update-metadata id)
|
||||
(st/emit!))))
|
||||
(change-color [color]
|
||||
(st/emit! (->> (assoc metadata :background color)
|
||||
(udp/update-metadata (:id page)))))
|
||||
|
||||
(on-name-change []
|
||||
(when-let [value (-> (mx/ref-node own "name")
|
||||
(dom/get-value)
|
||||
(str/trim))]
|
||||
(on-color-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(change-color value)))
|
||||
|
||||
(on-name-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(str/trim))]
|
||||
(st/emit! (->> (assoc page :name value)
|
||||
(udp/update-page id)))))
|
||||
(udp/update-page (:id page))))))
|
||||
|
||||
(show-color-picker [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:default "#ffffff"
|
||||
:transparent? true
|
||||
:attr :background}]
|
||||
(udl/open! :workspace/page-colorpicker opts)))]
|
||||
props {:x x :y y
|
||||
:default "#ffffff"
|
||||
:value (:background metadata)
|
||||
:transparent? true
|
||||
:on-change change-color}]
|
||||
(modal/show! colorpicker-modal props)))]
|
||||
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
|
@ -66,7 +64,6 @@
|
|||
[:div.input-element
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:ref "name"
|
||||
:on-change on-name-change
|
||||
:value (str (:name page))
|
||||
:placeholder "page name"}]]]
|
||||
|
@ -76,15 +73,13 @@
|
|||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:type "number"
|
||||
:ref "width"
|
||||
:on-change #(on-size-change :width)
|
||||
:on-change #(on-size-change % :width)
|
||||
:value (str (:width metadata))
|
||||
:placeholder "width"}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:type "number"
|
||||
:ref "height"
|
||||
:on-change #(on-size-change :height)
|
||||
:on-change #(on-size-change % :height)
|
||||
:value (str (:height metadata))
|
||||
:placeholder "height"}]]]
|
||||
|
||||
|
@ -96,43 +91,39 @@
|
|||
[:div.color-info
|
||||
[:input
|
||||
{:on-change on-color-change
|
||||
:ref "color"
|
||||
:value (:background metadata)}]]]]])))
|
||||
|
||||
(mx/defcs grid-options-menu
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own menu]
|
||||
(let [{:keys [id metadata] :as page} (mx/react refs/selected-page)
|
||||
(mf/defc grid-options-menu
|
||||
[{:keys [menu page] :as props}]
|
||||
(let [metadata (:metadata page)
|
||||
metadata (merge c/page-metadata metadata)]
|
||||
(letfn [(on-x-change []
|
||||
(when-let [value (-> (mx/ref-node own "x-axis")
|
||||
(dom/get-value)
|
||||
(parse-int nil))]
|
||||
(st/emit!
|
||||
(->> (assoc metadata :grid-x-axis value)
|
||||
(udw/update-metadata id)))))
|
||||
(on-y-change []
|
||||
(when-let [value (-> (mx/ref-node own "y-axis")
|
||||
(dom/get-value)
|
||||
(parse-int nil))]
|
||||
(st/emit!
|
||||
(->> (assoc metadata :grid-y-axis value)
|
||||
(udw/update-metadata id)))))
|
||||
(on-color-change []
|
||||
(when-let [value (-> (mx/ref-node own "color")
|
||||
(dom/get-value)
|
||||
(#(if (color? %) % nil)))]
|
||||
(->> (assoc metadata :grid-color value)
|
||||
(udp/update-metadata id)
|
||||
(st/emit!))))
|
||||
(letfn [(on-x-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int nil))]
|
||||
(st/emit! (->> (assoc metadata :grid-x-axis value)
|
||||
(udp/update-metadata (:id page))))))
|
||||
(on-y-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int nil))]
|
||||
(st/emit! (->> (assoc metadata :grid-y-axis value)
|
||||
(udp/update-metadata (:id page))))))
|
||||
|
||||
(change-color [color]
|
||||
(st/emit! (->> (assoc metadata :grid-color color)
|
||||
(udp/update-metadata (:id page)))))
|
||||
(on-color-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(change-color value)))
|
||||
|
||||
(show-color-picker [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:transparent? true
|
||||
:default "#cccccc"
|
||||
:attr :grid-color}]
|
||||
(udl/open! :workspace/page-colorpicker opts)))]
|
||||
props {:x x :y y
|
||||
:transparent? true
|
||||
:default "#cccccc"
|
||||
:attr :grid-color
|
||||
:on-change change-color}]
|
||||
(modal/show! colorpicker-modal props)))]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
|
@ -141,14 +132,12 @@
|
|||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:type "number"
|
||||
:ref "x-axis"
|
||||
:value (:grid-x-axis metadata)
|
||||
:on-change on-x-change
|
||||
:placeholder "x"}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:type "number"
|
||||
:ref "y-axis"
|
||||
:value (:grid-y-axis metadata)
|
||||
:on-change on-y-change
|
||||
:placeholder "y"}]]]
|
||||
|
@ -160,5 +149,4 @@
|
|||
[:div.color-info
|
||||
[:input
|
||||
{:on-change on-color-change
|
||||
:ref "color"
|
||||
:value (:grid-color metadata "#cccccc")}]]]]])))
|
||||
|
|
|
@ -6,101 +6,104 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.rect-measures
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as r]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.data :refer [parse-int parse-float read-string]]
|
||||
[uxbox.util.math :refer [precision-or-0]]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer [parse-int parse-float read-string]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.math :refer [precision-or-0]]))
|
||||
|
||||
(mx/defc rect-measures-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(letfn [(on-size-change [event attr]
|
||||
(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)}]]]
|
||||
(declare on-size-change)
|
||||
(declare on-rotation-change)
|
||||
(declare on-position-change)
|
||||
(declare on-proportion-lock-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-pos-change % :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "y"
|
||||
:type "number"
|
||||
:value (precision-or-0 (:y1 shape 0) 2)
|
||||
:on-change #(on-pos-change % :y)}]]]
|
||||
(mf/defc rect-measures-menu
|
||||
[{:keys [menu shape] :as props}]
|
||||
(let [size (geom/size shape)]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
;; SLIDEBAR FOR ROTATION AND OPACITY
|
||||
[:span "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 % shape :width)}]]
|
||||
|
||||
[:span "Rotation"]
|
||||
[:div.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min 0
|
||||
:max 360
|
||||
:value (:rotation shape 0)
|
||||
:on-change on-rotation-change}]]
|
||||
[: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.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"}}]
|
||||
]]])))
|
||||
[: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 "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 % 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)))))
|
||||
|
|
|
@ -2,61 +2,41 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.stroke
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
[uxbox.util.math :refer (precision-or-0)]
|
||||
[uxbox.util.spec :refer (color?)]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.util.data :refer [parse-int parse-float read-string]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.math :refer [precision-or-0]]))
|
||||
|
||||
(mx/defcs stroke-menu
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[{:keys [::mx/local]} menu {:keys [id] :as shape}]
|
||||
(letfn [(on-width-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-float 1))]
|
||||
(st/emit! (uds/update-attrs id {:stroke-width value}))))
|
||||
(on-opacity-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-float 1)
|
||||
(/ 10000))]
|
||||
(st/emit! (uds/update-attrs id {:stroke-opacity value}))))
|
||||
(on-stroke-style-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(read-string))]
|
||||
(st/emit! (uds/update-attrs id {:stroke-style value}))))
|
||||
(on-stroke-color-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(when (color? value)
|
||||
(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))}
|
||||
(declare on-width-change)
|
||||
(declare on-opacity-change)
|
||||
(declare on-stroke-style-change)
|
||||
(declare on-stroke-color-change)
|
||||
(declare on-border-change)
|
||||
(declare show-color-picker)
|
||||
|
||||
(mf/defc stroke-menu
|
||||
[{:keys [menu shape] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
on-border-lock #(swap! local update :border-lock not)
|
||||
on-stroke-style-change #(on-stroke-style-change % shape)
|
||||
on-width-change #(on-width-change % shape)
|
||||
on-stroke-color-change #(on-stroke-color-change % shape)
|
||||
on-border-change-rx #(on-border-change % shape local :rx)
|
||||
on-border-change-ry #(on-border-change % shape local :ry)
|
||||
on-opacity-change #(on-opacity-change % shape)
|
||||
show-color-picker #(show-color-picker % shape)]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
[:span "Style"]
|
||||
|
@ -94,17 +74,17 @@
|
|||
{:placeholder "rx"
|
||||
:type "number"
|
||||
:value (precision-or-0 (:rx shape 0) 2)
|
||||
:on-change #(on-border-change % :rx)}]]
|
||||
:on-change on-border-change-rx}]]
|
||||
[:div.lock-size
|
||||
{:class (when (:border-lock @local) "selected")
|
||||
:on-click on-border-proportion-lock}
|
||||
:on-click on-border-lock}
|
||||
i/lock]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "ry"
|
||||
:type "number"
|
||||
:value (precision-or-0 (:ry shape 0) 2)
|
||||
:on-change #(on-border-change % :ry)}]]]
|
||||
:on-change on-border-change-ry}]]]
|
||||
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
|
@ -112,6 +92,50 @@
|
|||
{:type "range"
|
||||
:min "0"
|
||||
:max "10000"
|
||||
:value (* 10000 (:stroke-opacity shape))
|
||||
:value (* 10000 (:stroke-opacity shape 1))
|
||||
:step "1"
|
||||
:on-change on-opacity-change}]]]]))
|
||||
|
||||
(defn- on-width-change
|
||||
[event shape]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-float 1))]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-width value}))))
|
||||
|
||||
(defn- on-opacity-change
|
||||
[event shape]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-float 1)
|
||||
(/ 10000))]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-opacity value}))))
|
||||
|
||||
(defn- on-stroke-style-change
|
||||
[event shape]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(read-string))]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-style value}))))
|
||||
|
||||
(defn- on-stroke-color-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color value}))))
|
||||
|
||||
(defn- on-border-change
|
||||
[event shape local attr]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int nil))
|
||||
id (:id shape)]
|
||||
(if (:border-lock @local)
|
||||
(st/emit! (udw/update-shape-attrs id {:rx value :ry value}))
|
||||
(st/emit! (udw/update-shape-attrs id {attr value})))))
|
||||
|
||||
(defn- show-color-picker
|
||||
[event shape]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
props {:x x :y y
|
||||
:default "#ffffff"
|
||||
:value (:stroke-color shape)
|
||||
:on-change #(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color %}))
|
||||
:transparent? true}]
|
||||
(modal/show! colorpicker-modal props)))
|
||||
|
|
|
@ -104,13 +104,33 @@
|
|||
|
||||
;; 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
|
||||
: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
|
||||
(fn [own current-page-id]
|
||||
(let [project (mf/react refs/selected-project)
|
||||
pages (mf/react refs/selected-project-pages)
|
||||
(fn [own {:keys [page] :as props}]
|
||||
(let [project (mf/react (::project-ref own))
|
||||
pages (mf/react (::pages-ref own))
|
||||
create #(udl/open! :page-form {:page {:project (:id project)}})
|
||||
close #(st/emit! (dw/toggle-flag :sitemap))
|
||||
deletable? (> (count pages) 1)]
|
||||
|
@ -124,9 +144,9 @@
|
|||
[:span (:name project)]
|
||||
[:div.add-page {:on-click create} i/close]]
|
||||
[:ul.element-list
|
||||
(for [page pages]
|
||||
(let [selected? (= (:id page) current-page-id)]
|
||||
[:& page-item {:page page
|
||||
(for [item pages]
|
||||
(let [selected? (= (:id item) (:id page))]
|
||||
[:& page-item {:page item
|
||||
:deletable? deletable?
|
||||
:selected? selected?
|
||||
:key (:id page)}]))]]])))
|
||||
:key (:id item)}]))]]])))
|
||||
|
|
224
frontend/src/uxbox/main/ui/workspace/viewport.cljs
Normal file
224
frontend/src/uxbox/main/ui/workspace/viewport.cljs
Normal 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)}]]]))))
|
|
@ -91,7 +91,7 @@
|
|||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(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))))
|
||||
|
||||
(defn nav
|
||||
|
|
|
@ -56,9 +56,9 @@
|
|||
(l/derive st/state)))
|
||||
|
||||
(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])
|
||||
:view/notfound (notfound-page)
|
||||
:view/viewer (let [{:keys [token id]} (get-in route [:params :path])]
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
;; --- Component
|
||||
|
||||
(mf/defc viewer-page
|
||||
{:wrap [mf/reactive*]}
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[{:keys [token id]}]
|
||||
(let [{:keys [project pages flags]} (mf/deref state-ref)]
|
||||
(let [{:keys [project pages flags]} (mf/react state-ref)]
|
||||
(mf/use-effect
|
||||
{:init #(st/emit! (dv/initialize token))})
|
||||
(when (seq pages)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
;; --- Background (Component)
|
||||
|
||||
(mf/defc background
|
||||
{:wrap [mf/memo*]}
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [background] :as metadata}]
|
||||
[:rect
|
||||
{:x 0 :y 0
|
||||
|
@ -26,7 +26,7 @@
|
|||
(declare shape)
|
||||
|
||||
(mf/defc canvas
|
||||
{:wrap [mf/memo*]}
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [page] :as props}]
|
||||
(let [{:keys [metadata id]} page
|
||||
{:keys [width height]} metadata]
|
||||
|
|
8
frontend/vendor/.babelrc
vendored
Normal file
8
frontend/vendor/.babelrc
vendored
Normal 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"]
|
||||
}
|
4
frontend/vendor/datefns/LICENSE.md
vendored
4
frontend/vendor/datefns/LICENSE.md
vendored
|
@ -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).
|
1
frontend/vendor/datefns/VERSION
vendored
1
frontend/vendor/datefns/VERSION
vendored
|
@ -1 +0,0 @@
|
|||
1.30.1
|
1702
frontend/vendor/datefns/datefns.bundle.js
vendored
Normal file
1702
frontend/vendor/datefns/datefns.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
frontend/vendor/datefns/datefns.bundle.min.js
vendored
Normal file
1
frontend/vendor/datefns/datefns.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3402
frontend/vendor/datefns/datefns.js
vendored
3402
frontend/vendor/datefns/datefns.js
vendored
File diff suppressed because it is too large
Load diff
4
frontend/vendor/datefns/datefns.min.js
vendored
4
frontend/vendor/datefns/datefns.min.js
vendored
File diff suppressed because one or more lines are too long
9
frontend/vendor/datefns/notes.md
vendored
9
frontend/vendor/datefns/notes.md
vendored
|
@ -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
|
10
frontend/vendor/deps.cljs
vendored
10
frontend/vendor/deps.cljs
vendored
|
@ -5,11 +5,15 @@
|
|||
{:file "jszip/jszip.js"
|
||||
:file-min "jszip/jszip.min.js"
|
||||
:provides ["vendor.jszip"]}
|
||||
{:file "datefns/datefns.js"
|
||||
:file-min "datefns/datefns.min.js"
|
||||
{:file "datefns/datefns.bundle.js"
|
||||
:file-min "datefns/datefns.bundle.min.js"
|
||||
: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"
|
||||
"snapsvg/externs.js"
|
||||
"jszip/externs.js"
|
||||
"react-color/externs.js"
|
||||
"datefns/externs.js"]}
|
||||
|
|
4440
frontend/vendor/package-lock.json
generated
vendored
Normal file
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
34
frontend/vendor/package.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
2
frontend/vendor/react-color/externs.js
vendored
Normal file
2
frontend/vendor/react-color/externs.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
var ChromePicker = {};
|
||||
var SketchPicker = {};
|
9179
frontend/vendor/react-color/react-color.bundle.js
vendored
Normal file
9179
frontend/vendor/react-color/react-color.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
frontend/vendor/react-color/react-color.bundle.min.js
vendored
Normal file
1
frontend/vendor/react-color/react-color.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
frontend/vendor/react-color/react-color.js
vendored
Normal file
11
frontend/vendor/react-color/react-color.js
vendored
Normal 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
51
frontend/vendor/rollup.config.js
vendored
Normal 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
|
||||
}];
|
Loading…
Add table
Add a link
Reference in a new issue