♻️ mainly workspace refactor

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

View file

@ -1,11 +1,7 @@
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,131 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.workspace.selrect
"Workspace selection rect."
(:require [beicon.core :as rx]
[potok.core :as ptk]
[uxbox.main.store :as st]
[uxbox.main.constants :as c]
[uxbox.main.refs :as refs]
[uxbox.main.streams :as streams]
[uxbox.main.data.shapes :as uds]
[uxbox.main.user-events :as uev]
[uxbox.util.geom.point :as gpt]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare stop-selrect)
(declare update-selrect)
(declare get-selection-stoper)
(declare selection->rect)
(declare translate-to-canvas)
;; --- Start Selrect
(deftype StartSelrect []
ptk/UpdateEvent
(update [_ state]
(let [position @refs/viewport-mouse-position
selection {::start position
::stop position}]
(assoc-in state [:workspace :selrect] (selection->rect selection))))
ptk/WatchEvent
(watch [_ state stream]
(let [stoper (get-selection-stoper stream)]
;; NOTE: the `viewport-mouse-position` can be derived from `stream`
;; but it used from `streams/` ns just for convenience
(rx/concat
(->> streams/viewport-mouse-position
(rx/take-until stoper)
(rx/map update-selrect))
(rx/just (stop-selrect))))))
(defn start-selrect
[]
(StartSelrect.))
;; --- Update Selrect
(deftype UpdateSelrect [position]
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace :selrect ::stop] position)
(update-in [:workspace :selrect] selection->rect))))
(defn update-selrect
[position]
{:pre [(gpt/point? position)]}
(UpdateSelrect. position))
;; --- Clear Selrect
(deftype ClearSelrect []
ptk/UpdateEvent
(update [_ state]
(update state :workspace dissoc :selrect)))
(defn clear-selrect
[]
(ClearSelrect.))
;; --- Stop Selrect
(deftype StopSelrect []
ptk/WatchEvent
(watch [_ state stream]
(let [rect (-> (get-in state [:workspace :selrect])
(translate-to-canvas))]
(rx/of
(clear-selrect)
(uds/deselect-all)
(uds/select-shapes-by-selrect rect)))))
(defn stop-selrect
[]
(StopSelrect.))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Selection Rect Implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- selection->rect
[data]
(let [start (::start data)
stop (::stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
end-x (max (:x start) (:x stop))
end-y (max (:y start) (:y stop))]
(assoc data
:x1 start-x
:y1 start-y
:x2 end-x
:y2 end-y
:type :rect)))
(defn- get-selection-stoper
[stream]
(->> (rx/merge (rx/filter #(= % ::uev/interrupt) stream)
(rx/filter uev/mouse-up? stream))
(rx/take 1)))
(defn- translate-to-canvas
"Translate the given rect to the canvas coordinates system."
[rect]
(let [zoom @refs/selected-zoom
startx (* c/canvas-start-x zoom)
starty (* c/canvas-start-y zoom)]
(assoc rect
:x1 (/ (- (:x1 rect) startx) zoom)
:y1 (/ (- (:y1 rect) starty) zoom)
:x2 (/ (- (:x2 rect) startx) zoom)
:y2 (/ (- (:y2 rect) starty) zoom))))

View file

@ -4,7 +4,7 @@
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(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))))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,56 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.recent-colors
(:require [lentes.core :as l]
[potok.core :as ptk]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.data.workspace :as dw]
[uxbox.builtins.icons :as i]
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer (tr)]))
;; --- Helpers
(defn- count-color
[state shape prop]
(let [color (prop shape)]
(if (contains? state color)
(update state color inc)
(assoc state color 1))))
(defn- calculate-colors
[shapes]
(as-> {} $
(reduce #(count-color %1 %2 :fill-color) $ shapes)
(reduce #(count-color %1 %2 :stroke-color) $ shapes)
(remove nil? $)
(sort-by second (into [] $))
(take 5 (map first $))))
;; --- Component
(mx/defc recent-colors
{:mixins [mx/static mx/reactive]}
[{:keys [page id] :as shape} callback]
(let [shapes-by-id (mx/react refs/shapes-by-id)
shapes (->> (vals shapes-by-id)
(filter #(= (:page %) page)))
colors (calculate-colors shapes)]
[:div {}
[:span {} (tr "ds.recent-colors")]
[:div.row-flex {}
(for [color colors]
[:span.color-th {:style {:background-color color}
:key color
:on-click (partial callback color)}])
(for [i (range (- 5 (count colors)))]
[:span.color-th {:key (str "empty" i)}])
[:span.color-th.palette-th {} i/picker]]]))

View file

@ -2,39 +2,31 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; 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}]])]))

View file

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

View file

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

View file

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

View file

@ -11,7 +11,8 @@
[potok.core :as ptk]
[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})

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -91,7 +91,7 @@
ptk/EffectEvent
(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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -1 +0,0 @@
1.30.1

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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

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

View file

@ -5,11 +5,15 @@
{:file "jszip/jszip.js"
:file-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

File diff suppressed because it is too large Load diff

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

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

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

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

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