♻️ 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

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