mirror of
https://github.com/penpot/penpot.git
synced 2025-07-24 14:07:16 +02:00
Move all files under frontend directory.
This commit is contained in:
parent
92b45b2d05
commit
e21798f1ed
603 changed files with 10 additions and 31 deletions
16
frontend/src/uxbox/main/ui/auth.cljs
Normal file
16
frontend/src/uxbox/main/ui/auth.cljs
Normal file
|
@ -0,0 +1,16 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.auth
|
||||
(:require [uxbox.main.ui.auth.login :as login]
|
||||
[uxbox.main.ui.auth.register :as register]
|
||||
[uxbox.main.ui.auth.recovery-request :as recovery-request]
|
||||
[uxbox.main.ui.auth.recovery :as recovery]))
|
||||
|
||||
(def login-page login/login-page)
|
||||
(def register-page register/register-page)
|
||||
(def recovery-page recovery/recovery-page)
|
||||
(def recovery-request-page recovery-request/recovery-request-page)
|
81
frontend/src/uxbox/main/ui/auth/login.cljs
Normal file
81
frontend/src/uxbox/main/ui/auth/login.cljs
Normal file
|
@ -0,0 +1,81 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.auth.login
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.data.messages :as udm]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.navigation :as nav]))
|
||||
|
||||
(def form-data (forms/focus-data :login st/state))
|
||||
(def set-value! (partial forms/set-value! :login))
|
||||
|
||||
(defn- login-page-will-mount
|
||||
[own]
|
||||
(when @st/auth-ref
|
||||
(rt/go :dashboard/projects))
|
||||
own)
|
||||
|
||||
(def +login-form+
|
||||
{:email [forms/required forms/string]
|
||||
:password [forms/required forms/string]})
|
||||
|
||||
(mx/defc login-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
valid? (forms/valid? data +login-form+)]
|
||||
(letfn [(on-change [event field]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (da/login {:username (:email data)
|
||||
:password (:password data)})))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
:ref "email"
|
||||
:value (:email data "")
|
||||
:on-change #(on-change % :email)
|
||||
:placeholder "Email or Username"
|
||||
:type "text"}]
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:ref "password"
|
||||
:value (:password data "")
|
||||
:on-change #(on-change % :password)
|
||||
:placeholder "Password"
|
||||
:type "password"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value "Continue"
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
[:a {:on-click #(rt/go :auth/recovery-request)} "Forgot your password?"]
|
||||
[:a {:on-click #(rt/go :auth/register)} "Don't have an account?"]]]])))
|
||||
|
||||
(mx/defc login-page
|
||||
{:mixins [mx/static]
|
||||
:will-mount login-page-will-mount}
|
||||
[]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(uum/messages)
|
||||
[:a i/logo]
|
||||
(login-form)]])
|
77
frontend/src/uxbox/main/ui/auth/recovery.cljs
Normal file
77
frontend/src/uxbox/main/ui/auth/recovery.cljs
Normal file
|
@ -0,0 +1,77 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.auth.recovery
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as udm]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.navigation :as nav]))
|
||||
|
||||
;; --- Recovery Form
|
||||
|
||||
(def form-data (forms/focus-data :recovery st/state))
|
||||
(def set-value! (partial forms/set-value! :recovery))
|
||||
|
||||
(def +recovery-form+
|
||||
{:password [forms/required forms/string]})
|
||||
|
||||
(mx/defc recovery-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[token]
|
||||
(let [data (merge (mx/react form-data)
|
||||
{:token token})
|
||||
valid? (forms/valid? data +recovery-form+)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (uda/recovery data)
|
||||
(forms/clear :recovery)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:value (:password data "")
|
||||
:on-change (partial on-change :password)
|
||||
:placeholder "Password"
|
||||
:type "password"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value "Recover password"
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
[:a {:on-click #(rt/go :auth/login)} "Go back!"]]]])))
|
||||
|
||||
;; --- Recovery Page
|
||||
|
||||
(defn- recovery-page-will-mount
|
||||
[own]
|
||||
(let [[token] (:rum/args own)]
|
||||
(rs/emit! (uda/validate-recovery-token token))
|
||||
own))
|
||||
|
||||
(mx/defc recovery-page
|
||||
{:mixins [mx/static]
|
||||
:will-mount recovery-page-will-mount
|
||||
:will-unmount (forms/cleaner-fn :recovery)}
|
||||
[token]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(uum/messages)
|
||||
[:a i/logo]
|
||||
(recovery-form token)]])
|
67
frontend/src/uxbox/main/ui/auth/recovery_request.cljs
Normal file
67
frontend/src/uxbox/main/ui/auth/recovery_request.cljs
Normal file
|
@ -0,0 +1,67 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.auth.recovery-request
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as udm]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.navigation :as nav]))
|
||||
|
||||
(def form-data (forms/focus-data :recovery-request st/state))
|
||||
(def set-value! (partial forms/set-value! :recovery-request))
|
||||
|
||||
(def +recovery-request-form+
|
||||
{:username [forms/required forms/string]})
|
||||
|
||||
(mx/defc recovery-request-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
valid? (forms/valid? data +recovery-request-form+)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (uda/recovery-request data)
|
||||
(forms/clear :recovery-request)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "username"
|
||||
:value (:username data "")
|
||||
:on-change (partial on-change :username)
|
||||
:placeholder "username or email address"
|
||||
:type "text"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value "Recover password"
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
[:a {:on-click #(rt/go :auth/login)} "Go back!"]]]])))
|
||||
|
||||
;; --- Recovery Request Page
|
||||
|
||||
(mx/defc recovery-request-page
|
||||
{:mixins [mx/static]
|
||||
:will-unmount (forms/cleaner-fn :recovery-request)}
|
||||
[]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(uum/messages)
|
||||
[:a i/logo]
|
||||
(recovery-request-form)]])
|
108
frontend/src/uxbox/main/ui/auth/register.cljs
Normal file
108
frontend/src/uxbox/main/ui/auth/register.cljs
Normal file
|
@ -0,0 +1,108 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.auth.register
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as udm]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.navigation :as nav]))
|
||||
|
||||
;; --- Register Form
|
||||
|
||||
(def form-data (forms/focus-data :register st/state))
|
||||
(def form-errors (forms/focus-errors :register st/state))
|
||||
(def set-value! (partial forms/set-value! :register))
|
||||
(def set-error! (partial forms/set-error! :register))
|
||||
|
||||
(def +register-form+
|
||||
{:username [forms/required forms/string]
|
||||
:fullname [forms/required forms/string]
|
||||
:email [forms/required forms/email]
|
||||
:password [forms/required forms/string]})
|
||||
|
||||
(mx/defc register-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
errors (mx/react form-errors)
|
||||
valid? (forms/valid? data +register-form+)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(on-error [{:keys [type code] :as payload}]
|
||||
(case code
|
||||
:uxbox.services.users/email-already-exists
|
||||
(set-error! :email "Email already exists")
|
||||
:uxbox.services.users/username-already-exists
|
||||
(set-error! :username "Username already exists")))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (uda/register data on-error)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "fullname"
|
||||
:value (:fullname data "")
|
||||
:on-change (partial on-change :fullname)
|
||||
:placeholder "Full Name"
|
||||
:type "text"}]
|
||||
(forms/input-error errors :fullname)
|
||||
|
||||
[:input.input-text
|
||||
{:name "username"
|
||||
:value (:username data "")
|
||||
:on-change (partial on-change :username)
|
||||
:placeholder "Username"
|
||||
:type "text"}]
|
||||
(forms/input-error errors :username)
|
||||
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
:ref "email"
|
||||
:value (:email data "")
|
||||
:on-change (partial on-change :email)
|
||||
:placeholder "Email"
|
||||
:type "text"}]
|
||||
(forms/input-error errors :email)
|
||||
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:ref "password"
|
||||
:value (:password data "")
|
||||
:on-change (partial on-change :password)
|
||||
:placeholder "Password"
|
||||
:type "password"}]
|
||||
(forms/input-error errors :password)
|
||||
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value "Register"
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
;; [:a {:on-click #(rt/go :auth/recover-password)} "Forgot your password?"]
|
||||
[:a {:on-click #(rt/go :auth/login)} "Already have an account?"]]]])))
|
||||
|
||||
;; --- Register Page
|
||||
|
||||
(mx/defc register-page
|
||||
{:mixins [mx/static]}
|
||||
[own]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(uum/messages)
|
||||
[:a i/logo]
|
||||
(register-form)]])
|
224
frontend/src/uxbox/main/ui/colorpicker.cljs
Normal file
224
frontend/src/uxbox/main/ui/colorpicker.cljs
Normal file
|
@ -0,0 +1,224 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.colorpicker
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[goog.events :as events]
|
||||
[uxbox.util.forms :as sc]
|
||||
[uxbox.util.mixins :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))
|
||||
|
||||
;; --- Picker Box
|
||||
|
||||
(defn- picker-box-render
|
||||
[own]
|
||||
(html
|
||||
[: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)"}]]))
|
||||
|
||||
(def picker-box
|
||||
(mx/component
|
||||
{:render picker-box-render
|
||||
:name "picker-box"
|
||||
:mixins []}))
|
||||
|
||||
;; --- Slider Box
|
||||
|
||||
(defn slider-box-render
|
||||
[own]
|
||||
(html
|
||||
[: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})
|
||||
|
||||
(def slider-box
|
||||
(mx/component
|
||||
{:render slider-box-render
|
||||
:name "slider-box"
|
||||
:mixins []}))
|
||||
|
||||
;; --- 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 event]
|
||||
(let [event (.-nativeEvent event)
|
||||
my (.-offsetY event)
|
||||
h (* (/ my (:s-height dimensions)) 360)]
|
||||
(swap! local assoc :color [(+ h 15) 1 255])))
|
||||
|
||||
(defn- colorpicker-render
|
||||
[own & {:keys [value on-change theme]
|
||||
:or {value "#d4edfb" theme :default}}]
|
||||
(let [local (:rum/local own)
|
||||
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 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))))]
|
||||
(html
|
||||
[: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)]
|
||||
[: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)}
|
||||
(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"}]]]]))))
|
||||
|
||||
|
||||
|
||||
(def colorpicker
|
||||
(mx/component
|
||||
{:render colorpicker-render
|
||||
:name "colorpicker"
|
||||
:mixins [mx/static (mx/local)]}))
|
53
frontend/src/uxbox/main/ui/confirm.cljs
Normal file
53
frontend/src/uxbox/main/ui/confirm.cljs
Normal file
|
@ -0,0 +1,53 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.confirm
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.lightbox :as lbx]))
|
||||
|
||||
(defn- confirm-dialog-render
|
||||
[own {:keys [on-accept on-cancel hint] :as ctx}]
|
||||
(letfn [(accept [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!)
|
||||
(on-accept (dissoc ctx :on-accept :on-cancel)))
|
||||
(cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!)
|
||||
(when on-cancel
|
||||
(on-cancel (dissoc ctx :on-accept :on-cancel))))]
|
||||
(html
|
||||
[:div.lightbox-body.confirm-dialog
|
||||
[:h3 "Are you sure?"]
|
||||
(if hint
|
||||
[:span hint])
|
||||
[:div.row-flex
|
||||
[:input.btn-success.btn-small
|
||||
{:type "button"
|
||||
:value "Ok"
|
||||
:on-click accept}]
|
||||
[:input.btn-delete.btn-small
|
||||
{:type "button"
|
||||
:value "Cancel"
|
||||
:on-click cancel}]]
|
||||
[:a.close {:href "#"
|
||||
:on-click #(do (dom/prevent-default %)
|
||||
(udl/close!))} i/close]])))
|
||||
|
||||
(def confirm-dialog
|
||||
(mx/component
|
||||
{:render confirm-dialog-render
|
||||
:name "confirm-dialog"
|
||||
:mixins []}))
|
||||
|
||||
(defmethod lbx/render-lightbox :confirm
|
||||
[context]
|
||||
(confirm-dialog context))
|
12
frontend/src/uxbox/main/ui/dashboard.cljs
Normal file
12
frontend/src/uxbox/main/ui/dashboard.cljs
Normal file
|
@ -0,0 +1,12 @@
|
|||
(ns uxbox.main.ui.dashboard
|
||||
(:require [uxbox.main.ui.dashboard.projects :as projects]
|
||||
;; [uxbox.main.ui.dashboard.elements :as elements]
|
||||
[uxbox.main.ui.dashboard.icons :as icons]
|
||||
[uxbox.main.ui.dashboard.images :as images]
|
||||
[uxbox.main.ui.dashboard.colors :as colors]))
|
||||
|
||||
(def projects-page projects/projects-page)
|
||||
;; (def elements-page elements/elements-page)
|
||||
(def icons-page icons/icons-page)
|
||||
(def images-page images/images-page)
|
||||
(def colors-page colors/colors-page)
|
370
frontend/src/uxbox/main/ui/dashboard/colors.cljs
Normal file
370
frontend/src/uxbox/main/ui/dashboard/colors.cljs
Normal file
|
@ -0,0 +1,370 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.colors
|
||||
(:require [cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.data.colors :as dc]
|
||||
[uxbox.main.data.dashboard :as dd]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.dashboard.header :refer (header)]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.keyboard :as k]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.color :refer (hex->rgb)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.util.lens :as ul]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rstore :as rs]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def dashboard-ref
|
||||
(-> (l/in [:dashboard :colors])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def collections-ref
|
||||
(-> (l/key :colors-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Page Title
|
||||
|
||||
(mx/defcs page-title
|
||||
{:mixins [(mx/local {}) mx/static mx/reactive]}
|
||||
[own {:keys [id] :as coll}]
|
||||
(let [local (:rum/local own)
|
||||
dashboard (mx/react dashboard-ref)
|
||||
own? (= :own (:type coll))
|
||||
edit? (:edit @local)]
|
||||
(letfn [(save []
|
||||
(let [dom (mx/ref-node own "input")
|
||||
name (dom/get-inner-text dom)]
|
||||
(rs/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 []
|
||||
(rs/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 "Storage")]))]
|
||||
(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
|
||||
|
||||
(mx/defc nav-item
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id type name] :as coll} selected?]
|
||||
(letfn [(on-click [event]
|
||||
(let [type (or type :own)]
|
||||
(rs/emit! (dc/select-collection type id))))]
|
||||
(let [colors (count (:colors coll))]
|
||||
[:li {:on-click on-click
|
||||
:class-name (when selected? "current")}
|
||||
[:span.element-title
|
||||
(if coll name "Storage")]
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c colors))]])))
|
||||
|
||||
(def ^:private storage-num-colors-ref
|
||||
(-> (comp (l/in [:colors-collections nil :colors])
|
||||
(l/lens count))
|
||||
(l/derive st/state)))
|
||||
|
||||
(mx/defc nav-item-storage
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[selected?]
|
||||
(let [num-colors (mx/react storage-num-colors-ref)
|
||||
on-click #(rs/emit! (dc/select-collection :own nil))]
|
||||
[:li {:on-click on-click :class (when selected? "current")}
|
||||
[:span.element-title "Storage"]
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c num-colors))]]))
|
||||
|
||||
(mx/defc nav-section
|
||||
{:mixins [mx/static]}
|
||||
[type selected colls]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
colls (cond->> (vals colls)
|
||||
own? (filter #(= :own (:type %)))
|
||||
builtin? (filter #(= :builtin (:type %)))
|
||||
own? (sort-by :created-at)
|
||||
builtin? (sort-by :created-at))]
|
||||
[:ul.library-elements
|
||||
(when own?
|
||||
[:li
|
||||
[:a.btn-primary
|
||||
{:on-click #(rs/emit! (dc/create-collection))}
|
||||
"+ New library"]])
|
||||
(when own?
|
||||
(nav-item-storage (nil? selected)))
|
||||
(for [coll colls
|
||||
:let [selected? (= (:id coll) selected)
|
||||
key (str (:id coll))]]
|
||||
(-> (nav-item coll selected?)
|
||||
(mx/with-key key)))]))
|
||||
|
||||
(mx/defc nav
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id type] :as state} colls]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)]
|
||||
(letfn [(select-tab [type]
|
||||
(if (= type :own)
|
||||
(rs/emit! (dc/select-collection type))
|
||||
(let [coll (->> (map second colls)
|
||||
(filter #(= type (:type %)))
|
||||
(sort-by :created-at)
|
||||
(first))]
|
||||
(if coll
|
||||
(rs/emit! (dc/select-collection type (:id coll)))
|
||||
(rs/emit! (dc/select-collection type))))))]
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
"YOUR COLORS"]
|
||||
[:li {:class-name (when builtin? "current")
|
||||
:on-click (partial select-tab :builtin)}
|
||||
"COLORS STORE"]]
|
||||
(nav-section type id colls)]])))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mx/defc grid-form
|
||||
[coll-id]
|
||||
[:div.grid-item.small-item.add-project
|
||||
{:on-click #(udl/open! :color-form {:coll coll-id})}
|
||||
[:span "+ New color"]])
|
||||
|
||||
(mx/defc grid-options-tooltip
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[& {:keys [selected on-select title]}]
|
||||
{:pre [(uuid? selected)
|
||||
(fn? on-select)
|
||||
(string? title)]}
|
||||
(let [colls (mx/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]
|
||||
[:li [:a {:href "#" :on-click #(on-select % nil)} "Storage"]]
|
||||
(for [coll colls
|
||||
:let [id (:id coll)
|
||||
name (:name coll)]]
|
||||
[:li {:key (str id)}
|
||||
[:a {:on-click #(on-select % id)} name]])]))
|
||||
|
||||
(mx/defcs grid-options
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[own {:keys [type id] :as coll}]
|
||||
(let [editable? (or (= type :own) (nil? id))
|
||||
local (:rum/local own)]
|
||||
(letfn [(delete [event]
|
||||
(rs/emit! (dc/delete-selected-colors)))
|
||||
(on-delete [event]
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(on-toggle-copy [event]
|
||||
(swap! local update :show-copy-tooltip not)
|
||||
(swap! local assoc :show-move-tooltip false))
|
||||
(on-toggle-move [event]
|
||||
(swap! local update :show-move-tooltip not)
|
||||
(swap! local assoc :show-copy-tooltip false))
|
||||
(on-copy [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(rs/emit! (dc/copy-selected selected)))
|
||||
(on-move [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(rs/emit! (dc/move-selected id selected)))]
|
||||
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
(if editable?
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-copy :alt "Copy"}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Copy to library"
|
||||
:on-select on-copy))
|
||||
|
||||
i/copy]
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-move :alt "Move"}
|
||||
(when (:show-move-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Move to library"
|
||||
:on-select on-move))
|
||||
i/move]
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt "Delete"
|
||||
:on-click on-delete}
|
||||
i/trash]]
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Copy to library"
|
||||
:on-select on-copy))
|
||||
i/organize]])])))
|
||||
|
||||
(mx/defc grid-item
|
||||
[color selected?]
|
||||
(let [color-rgb (hex->rgb color)]
|
||||
(letfn [(toggle-selection [event]
|
||||
(rs/emit! (dc/toggle-color-selection color)))
|
||||
(toggle-selection-shifted [event]
|
||||
(when (k/shift? event)
|
||||
(toggle-selection event)))]
|
||||
[:div.grid-item.small-item.project-th
|
||||
{:on-click toggle-selection-shifted}
|
||||
[:span.color-swatch {:style {:background-color color}}]
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
:id color
|
||||
:on-click toggle-selection
|
||||
:checked selected?}]
|
||||
[:label {:for color}]]
|
||||
[:span.color-data color]
|
||||
[:span.color-data (apply str "RGB " (interpose ", " color-rgb))]])))
|
||||
|
||||
(mx/defc grid
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id type colors] :as coll} selected]
|
||||
(let [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 id))
|
||||
(for [color colors
|
||||
:let [selected? (contains? selected color)]]
|
||||
(-> (grid-item color selected?)
|
||||
(mx/with-key (str color))))]]))
|
||||
|
||||
(mx/defc content
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [selected] :as state} coll]
|
||||
[:section.dashboard-grid.library
|
||||
(page-title coll)
|
||||
(grid coll selected)
|
||||
(when (and (seq selected))
|
||||
(grid-options coll))])
|
||||
|
||||
;; --- Colors Page
|
||||
|
||||
(defn- colors-page-will-mount
|
||||
[own]
|
||||
(let [[type id] (:rum/args own)]
|
||||
(rs/emit! (dc/initialize type id))
|
||||
own))
|
||||
|
||||
(defn- colors-page-did-remount
|
||||
[old-own own]
|
||||
(let [[old-type old-id] (:rum/args old-own)
|
||||
[new-type new-id] (:rum/args own)]
|
||||
(when (or (not= old-type new-type)
|
||||
(not= old-id new-id))
|
||||
(rs/emit! (dc/initialize new-type new-id)))
|
||||
own))
|
||||
|
||||
(mx/defc colors-page
|
||||
{:will-mount colors-page-will-mount
|
||||
:did-remount colors-page-did-remount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[_ _]
|
||||
(let [state (mx/react dashboard-ref)
|
||||
colls (mx/react collections-ref)
|
||||
coll (get colls (:id state))]
|
||||
[:main.dashboard-main
|
||||
(uum/messages)
|
||||
(header)
|
||||
[:section.dashboard-content
|
||||
(nav state colls)
|
||||
(content state coll)]]))
|
||||
|
||||
;; --- Colors Lightbox (Component)
|
||||
|
||||
(mx/defcs color-lightbox
|
||||
{:mixins [(mx/local {}) mx/static]}
|
||||
[own {:keys [coll color] :as params}]
|
||||
(let [local (:rum/local own)]
|
||||
(letfn [(on-submit [event]
|
||||
(let [params {:id coll
|
||||
:from color
|
||||
:to (:hex @local)}]
|
||||
(rs/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 "New color"]
|
||||
[:form
|
||||
[:div.row-flex.center
|
||||
(colorpicker
|
||||
:value (or (:hex @local) color "#00ccff")
|
||||
:on-change #(swap! local assoc :hex %))]
|
||||
|
||||
[:input#project-btn.btn-primary
|
||||
{:value "+ Add color"
|
||||
:on-click on-submit
|
||||
:type "button"}]]
|
||||
[:a.close {:on-click on-close} i/close]])))
|
||||
|
||||
(defmethod lbx/render-lightbox :color-form
|
||||
[params]
|
||||
(color-lightbox params))
|
211
frontend/src/uxbox/main/ui/dashboard/elements.cljs
Normal file
211
frontend/src/uxbox/main/ui/dashboard/elements.cljs
Normal file
|
@ -0,0 +1,211 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.elements
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.dashboard :as dd]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.dashboard.header :refer (header)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Page Title
|
||||
|
||||
;; (defn page-title-render
|
||||
;; []
|
||||
;; (html
|
||||
;; [:div.dashboard-title
|
||||
;; [:h2 "Element library name"]
|
||||
;; [:div.edition
|
||||
;; [:span i/pencil]
|
||||
;; [:span i/trash]]]))
|
||||
|
||||
;; (def ^:private page-title
|
||||
;; (mx/component
|
||||
;; {:render page-title-render
|
||||
;; :name "page-title"
|
||||
;; :mixins [mx/static]}))
|
||||
|
||||
;; ;; --- Grid
|
||||
|
||||
;; (defn grid-render
|
||||
;; [own]
|
||||
;; (html
|
||||
;; [:div.dashboard-grid-content
|
||||
;; [:div.dashboard-grid-row
|
||||
;; [:div.grid-item.add-project
|
||||
;; {on-click #(udl/open! :new-element)}
|
||||
;; [:span "+ New element"]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]
|
||||
;; [:div.grid-item.project-th
|
||||
;; [:span.grid-item-image i/image]
|
||||
;; [:h3 "Custom element"]
|
||||
;; [:div.project-th-actions
|
||||
;; [:div.project-th-icon.edit i/pencil]
|
||||
;; [:div.project-th-icon.delete i/trash]]]]]))
|
||||
|
||||
;; (def ^:private grid
|
||||
;; (mx/component
|
||||
;; {:render grid-render
|
||||
;; :name "grid"
|
||||
;; :mixins [mx/static]}))
|
||||
|
||||
;; --- Elements Page
|
||||
|
||||
;; (defn elements-page-render
|
||||
;; [own]
|
||||
;; (html
|
||||
;; [:main.dashboard-main
|
||||
;; (header)
|
||||
;; [:section.dashboard-content
|
||||
;; (ui.library-bar/library-bar)
|
||||
;; [:section.dashboard-grid.library
|
||||
;; (page-title)
|
||||
;; (grid)]]]))
|
||||
|
||||
;; (defn elements-page-will-mount
|
||||
;; [own]
|
||||
;; (rs/emit! (dd/initialize :dashboard/elements))
|
||||
;; own)
|
||||
|
||||
;; (defn elements-page-did-remount
|
||||
;; [old-state state]
|
||||
;; (rs/emit! (dd/initialize :dashboard/elements))
|
||||
;; state)
|
||||
|
||||
;; (def elements-page
|
||||
;; (mx/component
|
||||
;; {:render elements-page-render
|
||||
;; :will-mount elements-page-will-mount
|
||||
;; :did-remount elements-page-did-remount
|
||||
;; :name "elements-page"
|
||||
;; :mixins [mx/static]}))
|
||||
|
||||
;; --- New Element Lightbox (TODO)
|
||||
|
||||
;; (defn- new-element-lightbox-render
|
||||
;; [own]
|
||||
;; (html
|
||||
;; ;;------Element lightbox
|
||||
|
||||
;; ;;[:div.lightbox-body
|
||||
;; ;;[:h3 "New element"]
|
||||
;; ;;[:div.row-flex
|
||||
;; ;;[:div.lightbox-big-btn
|
||||
;; ;;[:span.big-svg i/shapes]
|
||||
;; ;;[:span.text "Go to workspace"]]
|
||||
;; ;;[:div.lightbox-big-btn
|
||||
;; ;;[:span.big-svg.upload i/exit]
|
||||
;; ;;[:span.text "Upload file"]]]
|
||||
;; ;;[:a.close {:href "#"
|
||||
;; ;;:on-click #(do (dom/prevent-default %)
|
||||
;; ;;(udl/close!))}
|
||||
;; ;;i/close]]
|
||||
|
||||
;; ;;------Upload image lightbox
|
||||
|
||||
;; ;;[:div.lightbox-body
|
||||
;; ;;[:h3 "Import image"]
|
||||
;; ;;[:div.row-flex
|
||||
;; ;;[:div.lightbox-big-btn
|
||||
;; ;;[:span.big-svg i/image]
|
||||
;; ;;[:span.text "Select from library"]]
|
||||
;; ;;[:div.lightbox-big-btn
|
||||
;; ;;[:span.big-svg.upload i/exit]
|
||||
;; ;;[:span.text "Upload file"]]]
|
||||
;; ;;[:a.close {:href "#"
|
||||
;; ;;:on-click #(do (dom/prevent-default %)
|
||||
;; ;;(udl/close!))}
|
||||
;; ;;i/close]]
|
||||
|
||||
;; ;;------Upload image library lightbox
|
||||
|
||||
;; ))
|
||||
|
||||
;; (def ^:private new-element-lightbox
|
||||
;; (mx/component
|
||||
;; {:render new-element-lightbox-render
|
||||
;; :name "new-element-lightbox"}))
|
||||
|
||||
;; (defmethod lbx/render-lightbox :new-element
|
||||
;; [_]
|
||||
;; (new-element-lightbox))
|
54
frontend/src/uxbox/main/ui/dashboard/header.cljs
Normal file
54
frontend/src/uxbox/main/ui/dashboard/header.cljs
Normal file
|
@ -0,0 +1,54 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.header
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.users :as ui.u]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rstore :as rs]))
|
||||
|
||||
(def header-ref
|
||||
(-> (l/key :dashboard)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mx/defc header-link
|
||||
[section content]
|
||||
(let [link (r/route-for section)]
|
||||
[:a {:href (str "/#" link)} content]))
|
||||
|
||||
(mx/defc header
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [local (mx/react header-ref)
|
||||
projects? (= (:section local) :dashboard/projects)
|
||||
elements? (= (:section local) :dashboard/elements)
|
||||
icons? (= (:section local) :dashboard/icons)
|
||||
images? (= (:section local) :dashboard/images)
|
||||
colors? (= (:section local) :dashboard/colors)]
|
||||
[:header#main-bar.main-bar
|
||||
[:div.main-logo
|
||||
(header-link :dashboard/projects i/logo)]
|
||||
[:ul.main-nav
|
||||
[:li {:class (when projects? "current")}
|
||||
(header-link :dashboard/projects (tr "ds.projects"))]
|
||||
#_[:li {:class (when elements? "current")}
|
||||
(header-link :dashboard/elements (tr "ds.elements"))]
|
||||
[:li {:class (when icons? "current")}
|
||||
(header-link :dashboard/icons (tr "ds.icons"))]
|
||||
[:li {:class (when images? "current")}
|
||||
(header-link :dashboard/images (tr "ds.images"))]
|
||||
[:li {:class (when colors? "current")}
|
||||
(header-link :dashboard/colors (tr "ds.colors"))]]
|
||||
(ui.u/user)]))
|
||||
|
||||
|
479
frontend/src/uxbox/main/ui/dashboard/icons.cljs
Normal file
479
frontend/src/uxbox/main/ui/dashboard/icons.cljs
Normal file
|
@ -0,0 +1,479 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.icons
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.icons :as di]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.dashboard.header :refer (header)]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.datetime :as dt]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as sc]
|
||||
[uxbox.util.lens :as ul]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Helpers & Constants
|
||||
|
||||
(def +ordering-options+
|
||||
{:name "ds.project-ordering.by-name"
|
||||
:created "ds.project-ordering.by-creation-date"})
|
||||
|
||||
(defn- sort-icons-by
|
||||
[ordering icons]
|
||||
(case ordering
|
||||
:name (sort-by :name icons)
|
||||
:created (reverse (sort-by :created-at icons))
|
||||
icons))
|
||||
|
||||
(defn- contains-term?
|
||||
[phrase term]
|
||||
{:pre [(string? phrase)
|
||||
(string? term)]}
|
||||
(let [term (name term)]
|
||||
(str/includes? (str/lower phrase) (str/trim (str/lower term)))))
|
||||
|
||||
(defn- filter-icons-by
|
||||
[term icons]
|
||||
(if (str/blank? term)
|
||||
icons
|
||||
(filter #(contains-term? (:name %) term) icons)))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private dashboard-ref
|
||||
(-> (l/in [:dashboard :icons])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private collections-ref
|
||||
(-> (l/key :icons-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private icons-ref
|
||||
(-> (l/key :icons)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Page Title
|
||||
|
||||
(mx/defcs page-title
|
||||
{:mixins [(mx/local {}) mx/static mx/reactive]}
|
||||
[own {:keys [id] :as coll}]
|
||||
(let [local (:rum/local own)
|
||||
dashboard (mx/react dashboard-ref)
|
||||
own? (= :own (:type coll))
|
||||
edit? (:edit @local)]
|
||||
(letfn [(on-save [e]
|
||||
(let [dom (mx/ref-node own "input")
|
||||
name (.-innerText dom)]
|
||||
(rs/emit! (di/rename-collection id (str/trim name)))
|
||||
(swap! local assoc :edit false)))
|
||||
(on-cancel [e]
|
||||
(swap! local assoc :edit false))
|
||||
(on-edit [e]
|
||||
(swap! local assoc :edit true))
|
||||
(on-input-keydown [e]
|
||||
(cond
|
||||
(kbd/esc? e) (on-cancel e)
|
||||
(kbd/enter? e)
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(on-save e))))
|
||||
(delete []
|
||||
(rs/emit! (di/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 on-cancel} i/close]]
|
||||
(if own?
|
||||
[:span.dashboard-title-field
|
||||
{:on-double-click on-edit}
|
||||
(:name coll)]
|
||||
[:span.dashboard-title-field
|
||||
(:name coll "Storage")]))]
|
||||
(when (and own? coll)
|
||||
[:div.edition
|
||||
(if edit?
|
||||
[:span {:on-click on-save} i/save]
|
||||
[:span {:on-click on-edit} i/pencil])
|
||||
[:span {:on-click on-delete} i/trash]])])))
|
||||
|
||||
;; --- Nav
|
||||
|
||||
(defn react-count-icons
|
||||
[id]
|
||||
(->> (mx/react icons-ref)
|
||||
(vals)
|
||||
(filter #(= id (:collection %)))
|
||||
(count)))
|
||||
|
||||
(mx/defc nav-item
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id type name num-icons] :as coll} selected?]
|
||||
(letfn [(on-click [event]
|
||||
(let [type (or type :own)]
|
||||
(rs/emit! (di/select-collection type id))))]
|
||||
(let [num-icons (or num-icons (react-count-icons id))]
|
||||
[:li {:on-click on-click
|
||||
:class-name (when selected? "current")}
|
||||
[:span.element-title
|
||||
(if coll name "Storage")]
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c num-icons))]])))
|
||||
|
||||
(mx/defc nav-section
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[type selected colls]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
colls (cond->> (vals colls)
|
||||
own? (filter #(= :own (:type %)))
|
||||
builtin? (filter #(= :builtin (:type %))))
|
||||
colls (sort-by :name colls)]
|
||||
[:ul.library-elements
|
||||
(when own?
|
||||
[:li
|
||||
[:a.btn-primary
|
||||
{:on-click #(rs/emit! (di/create-collection))}
|
||||
"+ New collection"]])
|
||||
(when own?
|
||||
(nav-item nil (nil? selected)))
|
||||
(for [coll colls
|
||||
:let [selected? (= (:id coll) selected)
|
||||
key (str (:id coll))]]
|
||||
(-> (nav-item coll selected?)
|
||||
(mx/with-key key)))]))
|
||||
|
||||
(mx/defc nav
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [type id] :as state} colls]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)]
|
||||
(letfn [(select-tab [type]
|
||||
(if (= type :builtin)
|
||||
(let [colls (->> (map second colls)
|
||||
(filter #(= :builtin (:type %)))
|
||||
(sort-by :name))]
|
||||
(rs/emit! (di/select-collection type (:id (first colls)))))
|
||||
(rs/emit! (di/select-collection type))))]
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
"YOUR ICONS"]
|
||||
[:li {:class-name (when builtin? "current")
|
||||
:on-click (partial select-tab :builtin)}
|
||||
"ICONS STORE"]]
|
||||
|
||||
(nav-section type id colls)]])))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mx/defcs grid-form
|
||||
{:mixins [mx/static]}
|
||||
[own coll-id]
|
||||
(letfn [(forward-click [event]
|
||||
(dom/click (mx/ref-node own "file-input")))
|
||||
(on-file-selected [event]
|
||||
(let [files (dom/get-event-files event)]
|
||||
(rs/emit! (di/create-icons coll-id files))))]
|
||||
[:div.grid-item.small-item.add-project {:on-click forward-click}
|
||||
[:span "+ New icon"]
|
||||
[:input.upload-image-input
|
||||
{:style {:display "none"}
|
||||
:multiple true
|
||||
:ref "file-input"
|
||||
:value ""
|
||||
:accept "image/svg+xml"
|
||||
:type "file"
|
||||
:on-change on-file-selected}]]))
|
||||
|
||||
(mx/defc grid-options-tooltip
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[& {:keys [selected on-select title]}]
|
||||
{:pre [(uuid? selected)
|
||||
(fn? on-select)
|
||||
(string? title)]}
|
||||
(let [colls (mx/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]
|
||||
[:li [:a {:href "#" :on-click #(on-select % nil)} "Storage"]]
|
||||
(for [coll colls
|
||||
:let [id (:id coll)
|
||||
name (:name coll)]]
|
||||
[:li {:key (str id)}
|
||||
[:a {:on-click #(on-select % id)} name]])]))
|
||||
|
||||
(mx/defcs grid-options
|
||||
{:mixins [(mx/local) mx/static]}
|
||||
[own {:keys [type id] :as coll} selected]
|
||||
(let [editable? (or (= type :own) (nil? coll))
|
||||
local (:rum/local own)]
|
||||
(letfn [(delete []
|
||||
(rs/emit! (di/delete-selected)))
|
||||
(on-delete [event]
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(on-toggle-copy [event]
|
||||
(swap! local update :show-copy-tooltip not))
|
||||
(on-toggle-move [event]
|
||||
(swap! local update :show-move-tooltip not))
|
||||
(on-copy [selected]
|
||||
(println "copy from" id "to" selected)
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(rs/emit! (di/copy-selected selected)))
|
||||
(on-move [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(rs/emit! (di/move-selected selected)))
|
||||
(on-rename [event]
|
||||
(let [selected (first selected)]
|
||||
(rs/emit! (di/update-opts :edition selected))))]
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
(if editable?
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-copy :alt "Copy"}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Copy to library"
|
||||
:on-select on-copy))
|
||||
i/copy]
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-move :alt "Move"}
|
||||
(when (:show-move-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Move to library"
|
||||
:on-select on-move))
|
||||
i/move]
|
||||
(when (= 1 (count selected))
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt "Rename"
|
||||
:on-click on-rename}
|
||||
i/pencil])
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt "Delete"
|
||||
:on-click on-delete}
|
||||
i/trash]]
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Copy to library"
|
||||
:on-select on-copy))
|
||||
i/organize]])])))
|
||||
|
||||
(mx/defc grid-item
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id created-at] :as icon} selected? edition?]
|
||||
(letfn [(toggle-selection [event]
|
||||
(rs/emit! (di/toggle-icon-selection id)))
|
||||
(toggle-selection-shifted [event]
|
||||
(when (kbd/shift? event)
|
||||
(toggle-selection event)))
|
||||
(on-key-down [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(rs/emit! (di/update-opts :edition false)
|
||||
(di/rename-icon id name))))
|
||||
(on-edit [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (di/update-opts :edition id)))]
|
||||
[:div.grid-item.small-item.project-th
|
||||
{:on-click toggle-selection-shifted
|
||||
:id (str "grid-item-" id)}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
:id (:id icon)
|
||||
:on-click toggle-selection
|
||||
:checked selected?}]
|
||||
[:label {:for (:id icon)}]]
|
||||
[:span.grid-item-image
|
||||
(icon/icon-svg icon)]
|
||||
[:div.item-info
|
||||
(if edition?
|
||||
[:input.element-name {:type "text"
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-click on-edit
|
||||
:default-value (:name icon)}]
|
||||
[:h3 {:on-double-click on-edit}
|
||||
(:name icon)])
|
||||
(str "Uploaded at " (dt/format created-at "L"))]]))
|
||||
|
||||
(mx/defc grid
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [selected edition id type] :as state}]
|
||||
(let [editable? (or (= type :own) (nil? id))
|
||||
ordering (:order state :name)
|
||||
filtering (:filter state "")
|
||||
icons (mx/react icons-ref)
|
||||
icons (->> (vals icons)
|
||||
(filter #(= id (:collection %)))
|
||||
(filter-icons-by filtering)
|
||||
(sort-icons-by ordering))]
|
||||
[:div.dashboard-grid-content
|
||||
[:div.dashboard-grid-row
|
||||
(when editable? (grid-form id))
|
||||
(for [icon icons
|
||||
:let [id (:id icon)
|
||||
edition? (= edition id)
|
||||
selected? (contains? selected id)]]
|
||||
(-> (grid-item icon selected? edition?)
|
||||
(mx/with-key (str id))))]]))
|
||||
|
||||
(mx/defc content
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [selected] :as state} coll]
|
||||
[:section.dashboard-grid.library
|
||||
(page-title coll)
|
||||
(grid state)
|
||||
(when (seq selected)
|
||||
(grid-options coll selected))])
|
||||
|
||||
;; --- Menu
|
||||
|
||||
(mx/defc menu
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[state {:keys [id num-icons] :as coll}]
|
||||
(let [ordering (:order state :name)
|
||||
filtering (:filter state "")
|
||||
num-icons (or num-icons (react-count-icons id))]
|
||||
(letfn [(on-term-change [event]
|
||||
(let [term (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(rs/emit! (di/update-opts :filter term))))
|
||||
(on-ordering-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (read-string value)]
|
||||
(rs/emit! (di/update-opts :order value))))
|
||||
(on-clear [event]
|
||||
(rs/emit! (di/update-opts :filter "")))]
|
||||
[:section.dashboard-bar.library-gap
|
||||
[:div.dashboard-info
|
||||
|
||||
;; Counter
|
||||
[:span.dashboard-icons (tr "ds.num-icons" (t/c num-icons))]
|
||||
|
||||
;; Sorting
|
||||
[:divi
|
||||
[:span (tr "ds.project-ordering")]
|
||||
[:select.input-select
|
||||
{:on-change on-ordering-change
|
||||
:value (pr-str ordering)}
|
||||
(for [[key value] (seq +ordering-options+)
|
||||
:let [ovalue (pr-str key)
|
||||
olabel (tr value)]]
|
||||
[:option {:key ovalue :value ovalue} olabel])]]
|
||||
;; Search
|
||||
[:form.dashboard-search
|
||||
[:input.input-text
|
||||
{:key :icons-search-box
|
||||
:type "text"
|
||||
:on-change on-term-change
|
||||
:auto-focus true
|
||||
:placeholder (tr "ds.project-search.placeholder")
|
||||
:value (or filtering "")}]
|
||||
[:div.clear-search {:on-click on-clear} i/close]]]])))
|
||||
|
||||
;; --- Icons Page
|
||||
|
||||
(defn- icons-page-will-mount
|
||||
[own]
|
||||
(let [[type id] (:rum/args own)]
|
||||
(rs/emit! (di/initialize type id))
|
||||
own))
|
||||
|
||||
(defn- icons-page-did-remount
|
||||
[old-own own]
|
||||
(let [[old-type old-id] (:rum/args old-own)
|
||||
[new-type new-id] (:rum/args own)]
|
||||
(when (or (not= old-type new-type)
|
||||
(not= old-id new-id))
|
||||
(rs/emit! (di/initialize new-type new-id)))
|
||||
own))
|
||||
|
||||
(mx/defc icons-page
|
||||
{:will-mount icons-page-will-mount
|
||||
:did-remount icons-page-did-remount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [state (mx/react dashboard-ref)
|
||||
colls (mx/react collections-ref)
|
||||
coll (get colls (:id state))]
|
||||
[:main.dashboard-main
|
||||
(header)
|
||||
[:section.dashboard-content
|
||||
(nav state colls)
|
||||
(menu state coll)
|
||||
(content state coll)]]))
|
||||
|
||||
;; --- New Icon Lightbox (TODO)
|
||||
|
||||
;; (defn- new-icon-lightbox-render
|
||||
;; [own]
|
||||
;; (html
|
||||
;; [:div.lightbox-body
|
||||
;; [:h3 "New icon"]
|
||||
;; [:div.row-flex
|
||||
;; [:div.lightbox-big-btn
|
||||
;; [:span.big-svg i/shapes]
|
||||
;; [:span.text "Go to workspace"]
|
||||
;; ]
|
||||
;; [:div.lightbox-big-btn
|
||||
;; [:span.big-svg.upload i/exit]
|
||||
;; [:span.text "Upload file"]
|
||||
;; ]
|
||||
;; ]
|
||||
;; [:a.close {:href "#"
|
||||
;; :on-click #(do (dom/prevent-default %)
|
||||
;; (udl/close!))}
|
||||
;; i/close]]))
|
||||
|
||||
;; (def new-icon-lightbox
|
||||
;; (mx/component
|
||||
;; {:render new-icon-lightbox-render
|
||||
;; :name "new-icon-lightbox"}))
|
||||
|
||||
;; (defmethod lbx/render-lightbox :new-icon
|
||||
;; [_]
|
||||
;; (new-icon-lightbox))
|
452
frontend/src/uxbox/main/ui/dashboard/images.cljs
Normal file
452
frontend/src/uxbox/main/ui/dashboard/images.cljs
Normal file
|
@ -0,0 +1,452 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.images
|
||||
(:require [cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.images :as di]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.dashboard.header :refer (header)]
|
||||
[uxbox.util.datetime :as dt]
|
||||
[uxbox.util.data :as data :refer (read-string)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Helpers & Constants
|
||||
|
||||
(def +ordering-options+
|
||||
{:name "ds.project-ordering.by-name"
|
||||
:created "ds.project-ordering.by-creation-date"})
|
||||
|
||||
(defn- sort-images-by
|
||||
[ordering images]
|
||||
(case ordering
|
||||
:name (sort-by :name images)
|
||||
:created (reverse (sort-by :created-at images))
|
||||
images))
|
||||
|
||||
(defn- contains-term?
|
||||
[phrase term]
|
||||
(let [term (name term)]
|
||||
(str/includes? (str/lower phrase) (str/trim (str/lower term)))))
|
||||
|
||||
(defn- filter-images-by
|
||||
[term images]
|
||||
(if (str/blank? term)
|
||||
images
|
||||
(filter #(contains-term? (:name %) term) images)))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private dashboard-ref
|
||||
(-> (l/in [:dashboard :images])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private collections-ref
|
||||
(-> (l/key :images-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private images-ref
|
||||
(-> (l/key :images)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private uploading?-ref
|
||||
(-> (l/key :uploading)
|
||||
(l/derive dashboard-ref)))
|
||||
|
||||
;; --- Page Title
|
||||
|
||||
(mx/defcs page-title
|
||||
{:mixins [(mx/local {}) mx/static mx/reactive]}
|
||||
[own {:keys [id] :as coll}]
|
||||
(let [local (:rum/local own)
|
||||
dashboard (mx/react dashboard-ref)
|
||||
own? (= :own (:type coll))
|
||||
edit? (:edit @local)]
|
||||
(letfn [(on-save [e]
|
||||
(let [dom (mx/ref-node own "input")
|
||||
name (dom/get-inner-text dom)]
|
||||
(rs/emit! (di/rename-collection id (str/trim name)))
|
||||
(swap! local assoc :edit false)))
|
||||
(on-cancel [e]
|
||||
(swap! local assoc :edit false))
|
||||
(on-edit [e]
|
||||
(swap! local assoc :edit true))
|
||||
(on-input-keydown [e]
|
||||
(cond
|
||||
(kbd/esc? e) (on-cancel e)
|
||||
(kbd/enter? e)
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(on-save e))))
|
||||
(delete []
|
||||
(rs/emit! (di/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 on-cancel} i/close]]
|
||||
(if own?
|
||||
[:span.dashboard-title-field
|
||||
{:on-double-click on-edit}
|
||||
(:name coll)]
|
||||
[:span.dashboard-title-field
|
||||
(:name coll "Storage")]))]
|
||||
(when (and own? coll)
|
||||
[:div.edition
|
||||
(if edit?
|
||||
[:span {:on-click on-save} i/save]
|
||||
[:span {:on-click on-edit} i/pencil])
|
||||
[:span {:on-click on-delete} i/trash]])])))
|
||||
|
||||
;; --- Nav
|
||||
|
||||
(defn react-count-images
|
||||
[id]
|
||||
(->> (mx/react images-ref)
|
||||
(vals)
|
||||
(filter #(= id (:collection %)))
|
||||
(count)))
|
||||
|
||||
(mx/defc nav-item
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id type name] :as coll} selected?]
|
||||
(letfn [(on-click [event]
|
||||
(let [type (or type :own)]
|
||||
(rs/emit! (di/select-collection type id))))]
|
||||
(let [num-images (react-count-images id)]
|
||||
[:li {:on-click on-click
|
||||
:class-name (when selected? "current")}
|
||||
[:span.element-title
|
||||
(if coll name "Storage")]
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c num-images))]])))
|
||||
|
||||
(mx/defc nav-section
|
||||
{:mixins [mx/static]}
|
||||
[type selected colls]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
collections (cond->> (vals colls)
|
||||
own? (filter #(= :own (:type %)))
|
||||
builtin? (filter #(= :builtin (:type %)))
|
||||
own? (sort-by :name))]
|
||||
[:ul.library-elements
|
||||
(when own?
|
||||
[:li
|
||||
[:a.btn-primary
|
||||
{:on-click #(rs/emit! (di/create-collection))}
|
||||
"+ New library"]])
|
||||
(when own?
|
||||
(nav-item nil (nil? selected)))
|
||||
(for [coll collections
|
||||
:let [selected? (= (:id coll) selected)
|
||||
key (str (:id coll))]]
|
||||
(-> (nav-item coll selected?)
|
||||
(mx/with-key key)))]))
|
||||
|
||||
(mx/defc nav
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [type id] :as state} colls]
|
||||
(let [own? (= type :own)
|
||||
builtin? (= type :builtin)]
|
||||
(letfn [(select-tab [type]
|
||||
(if own?
|
||||
(rs/emit! (di/select-collection type))
|
||||
(let [coll (->> (map second colls)
|
||||
(filter #(= type (:type %)))
|
||||
(sort-by :name)
|
||||
(first))]
|
||||
(if coll
|
||||
(rs/emit! (di/select-collection type (:id coll)))
|
||||
(rs/emit! (di/select-collection type))))))]
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
"YOUR IMAGES"]
|
||||
[:li {:class-name (when builtin? "current")
|
||||
:on-click (partial select-tab :builtin)}
|
||||
"IMAGES STORE"]]
|
||||
|
||||
(nav-section type id colls)]])))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mx/defcs grid-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own coll-id]
|
||||
(letfn [(forward-click [event]
|
||||
(dom/click (mx/ref-node own "file-input")))
|
||||
(on-file-selected [event]
|
||||
(let [files (dom/get-event-files event)]
|
||||
(rs/emit! (di/create-images coll-id files))))]
|
||||
(let [uploading? (mx/react uploading?-ref)]
|
||||
[:div.grid-item.add-project {:on-click forward-click}
|
||||
(if uploading?
|
||||
[:div i/loader-pencil]
|
||||
[:span "+ New image"])
|
||||
[:input.upload-image-input
|
||||
{:style {:display "none"}
|
||||
:multiple true
|
||||
:ref "file-input"
|
||||
:value ""
|
||||
:accept "image/jpeg,image/png"
|
||||
:type "file"
|
||||
:on-change on-file-selected}]])))
|
||||
|
||||
(mx/defc grid-options-tooltip
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[& {:keys [selected on-select title]}]
|
||||
{:pre [(uuid? selected)
|
||||
(fn? on-select)
|
||||
(string? title)]}
|
||||
(let [colls (mx/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]
|
||||
[:li [:a {:href "#" :on-click #(on-select % nil)} "Storage"]]
|
||||
(for [coll colls
|
||||
:let [id (:id coll)
|
||||
name (:name coll)]]
|
||||
[:li {:key (str id)}
|
||||
[:a {:on-click #(on-select % id)} name]])]))
|
||||
|
||||
(mx/defcs grid-options
|
||||
{:mixins [(mx/local) mx/static]}
|
||||
[own {:keys [type id] :as coll} selected]
|
||||
(let [editable? (or (= type :own) (nil? coll))
|
||||
local (:rum/local own)]
|
||||
(letfn [(delete []
|
||||
(rs/emit! (di/delete-selected)))
|
||||
(on-delete [event]
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(on-toggle-copy [event]
|
||||
(swap! local update :show-copy-tooltip not))
|
||||
(on-toggle-move [event]
|
||||
(swap! local update :show-move-tooltip not))
|
||||
(on-copy [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(rs/emit! (di/copy-selected selected)))
|
||||
(on-move [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(rs/emit! (di/move-selected selected)))
|
||||
(on-rename [event]
|
||||
(let [selected (first selected)]
|
||||
(rs/emit! (di/update-opts :edition selected))))]
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
(if editable?
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-copy :alt "Copy"}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Copy to library"
|
||||
:on-select on-copy))
|
||||
i/copy]
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-move :alt "Move"}
|
||||
(when (:show-move-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Move to library"
|
||||
:on-select on-move))
|
||||
i/move]
|
||||
(when (= 1 (count selected))
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt "Rename"
|
||||
:on-click on-rename}
|
||||
i/pencil])
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt "Delete"
|
||||
:on-click on-delete}
|
||||
i/trash]]
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
(grid-options-tooltip :selected id
|
||||
:title "Copy to library"
|
||||
:on-select on-copy))
|
||||
i/organize]])])))
|
||||
|
||||
(mx/defc grid-item
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id created-at] :as image} selected? edition?]
|
||||
(letfn [(toggle-selection [event]
|
||||
(rs/emit! (di/toggle-image-selection id)))
|
||||
(toggle-selection-shifted [event]
|
||||
(when (kbd/shift? event)
|
||||
(toggle-selection event)))
|
||||
(on-key-down [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(rs/emit! (di/update-opts :edition false)
|
||||
(di/rename-image id name))))
|
||||
(on-edit [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (di/update-opts :edition id)))]
|
||||
[:div.grid-item.images-th
|
||||
{:on-click toggle-selection-shifted}
|
||||
[:div.grid-item-th
|
||||
{:style {:background-image (str "url('" (:thumbnail image) "')")}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
:id (:id image)
|
||||
:on-click toggle-selection
|
||||
:checked selected?}]
|
||||
[:label {:for (:id image)}]]]
|
||||
[:div.item-info
|
||||
(if edition?
|
||||
[:input.element-name {:type "text"
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-click on-edit
|
||||
:default-value (:name image)}]
|
||||
[:h3 {:on-double-click on-edit}
|
||||
(:name image)])
|
||||
[:span.date
|
||||
(str "Uploaded at " (dt/format created-at "L"))]]]))
|
||||
|
||||
(mx/defc grid
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id type selected edition] :as state}]
|
||||
(let [editable? (or (= type :own) (nil? id))
|
||||
ordering (:order state :name)
|
||||
filtering (:filter state "")
|
||||
images (mx/react images-ref)
|
||||
images (->> (vals images)
|
||||
(filter #(= id (:collection %)))
|
||||
(filter-images-by filtering)
|
||||
(sort-images-by ordering))]
|
||||
[:div.dashboard-grid-content
|
||||
[:div.dashboard-grid-row
|
||||
(when editable? (grid-form id))
|
||||
(for [image images
|
||||
:let [id (:id image)
|
||||
edition? (= edition id)
|
||||
selected? (contains? selected id)]]
|
||||
(-> (grid-item image selected? edition?)
|
||||
(mx/with-key (str id))))]]))
|
||||
|
||||
(mx/defc content
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [selected] :as state} coll]
|
||||
[:section.dashboard-grid.library
|
||||
(page-title coll)
|
||||
(grid state)
|
||||
(when (seq selected)
|
||||
(grid-options coll selected))])
|
||||
|
||||
;; --- Menu
|
||||
|
||||
(mx/defc menu
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[coll]
|
||||
(let [state (mx/react dashboard-ref)
|
||||
ordering (:order state :name)
|
||||
filtering (:filter state "")
|
||||
icount (count (:images coll))]
|
||||
(letfn [(on-term-change [event]
|
||||
(let [term (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(rs/emit! (di/update-opts :filter term))))
|
||||
(on-ordering-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (read-string value)]
|
||||
(rs/emit! (di/update-opts :order value))))
|
||||
(on-clear [event]
|
||||
(rs/emit! (di/update-opts :filter "")))]
|
||||
[:section.dashboard-bar.library-gap
|
||||
[:div.dashboard-info
|
||||
|
||||
;; Counter
|
||||
[:span.dashboard-images (tr "ds.num-images" (t/c icount))]
|
||||
|
||||
;; Sorting
|
||||
[:div
|
||||
[:span (tr "ds.project-ordering")]
|
||||
[:select.input-select
|
||||
{:on-change on-ordering-change
|
||||
:value (pr-str ordering)}
|
||||
(for [[key value] (seq +ordering-options+)
|
||||
:let [ovalue (pr-str key)
|
||||
olabel (tr value)]]
|
||||
[:option {:key ovalue :value ovalue} olabel])]]
|
||||
;; Search
|
||||
[:form.dashboard-search
|
||||
[:input.input-text
|
||||
{:key :images-search-box
|
||||
:type "text"
|
||||
:on-change on-term-change
|
||||
:auto-focus true
|
||||
:placeholder (tr "ds.project-search.placeholder")
|
||||
:value (or filtering "")}]
|
||||
[:div.clear-search {:on-click on-clear} i/close]]]])))
|
||||
|
||||
;; --- Images Page
|
||||
|
||||
(defn- images-page-will-mount
|
||||
[own]
|
||||
(let [[type id] (:rum/args own)]
|
||||
(rs/emit! (di/initialize type id))
|
||||
own))
|
||||
|
||||
(defn- images-page-did-remount
|
||||
[old-own own]
|
||||
(let [[old-type old-id] (:rum/args old-own)
|
||||
[new-type new-id] (:rum/args own)]
|
||||
(when (or (not= old-type new-type)
|
||||
(not= old-id new-id))
|
||||
(rs/emit! (di/initialize new-type new-id)))
|
||||
own))
|
||||
|
||||
(mx/defc images-page
|
||||
{:will-mount images-page-will-mount
|
||||
:did-remount images-page-did-remount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[_ _]
|
||||
(let [state (mx/react dashboard-ref)
|
||||
colls (mx/react collections-ref)
|
||||
coll (get colls (:id state))]
|
||||
[:main.dashboard-main
|
||||
(header)
|
||||
[:section.dashboard-content
|
||||
(nav state colls)
|
||||
(menu coll)
|
||||
(content state coll)]]))
|
364
frontend/src/uxbox/main/ui/dashboard/projects.cljs
Normal file
364
frontend/src/uxbox/main/ui/dashboard/projects.cljs
Normal file
|
@ -0,0 +1,364 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.projects
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.projects :as udp]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.dashboard.header :refer (header)]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.exports :as exports]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.datetime :as dt]))
|
||||
|
||||
;; --- Helpers & Constants
|
||||
|
||||
(def ^:private +project-defaults+
|
||||
{:name ""
|
||||
:width 1920
|
||||
:height 1080
|
||||
:layout "desktop"})
|
||||
|
||||
(def +layouts+
|
||||
{"mobile"
|
||||
{:name "Mobile"
|
||||
:id "mobile"
|
||||
:width 320
|
||||
:height 480}
|
||||
"tablet"
|
||||
{:name "Tablet"
|
||||
:id "tablet"
|
||||
:width 1024
|
||||
:height 768}
|
||||
"notebook"
|
||||
{:name "Notebook"
|
||||
:id "notebook"
|
||||
:width 1366
|
||||
:height 768}
|
||||
"desktop"
|
||||
{:name "Desktop"
|
||||
:id "desktop"
|
||||
:width 1920
|
||||
:height 1080}})
|
||||
|
||||
(def +ordering-options+
|
||||
{:name "ds.project-ordering.by-name"
|
||||
:created "ds.project-ordering.by-creation-date"})
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def projects-map-ref
|
||||
(-> (l/key :projects)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def dashboard-ref
|
||||
(-> (l/in [:dashboard :projects])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def project-ordering-ref
|
||||
(-> (l/key :project-order)
|
||||
(l/derive dashboard-ref)))
|
||||
|
||||
(def project-filtering-ref
|
||||
(-> (l/in [:project-filter])
|
||||
(l/derive dashboard-ref)))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn sort-projects-by
|
||||
[ordering projs]
|
||||
(case ordering
|
||||
:name (sort-by :name projs)
|
||||
:created (reverse (sort-by :created-at projs))
|
||||
projs))
|
||||
|
||||
(defn contains-term?
|
||||
[phrase term]
|
||||
(let [term (name term)]
|
||||
(str/includes? (str/lower phrase) (str/trim (str/lower term)))))
|
||||
|
||||
(defn filter-projects-by
|
||||
[term projs]
|
||||
(if (str/blank? term)
|
||||
projs
|
||||
(filter #(contains-term? (:name %) term) projs)))
|
||||
|
||||
;; --- Menu (Filter & Sort)
|
||||
|
||||
(mx/defc menu
|
||||
{:mixins [mx/static]}
|
||||
[state projects]
|
||||
(let [ordering (:order state :created)
|
||||
filtering (:filter state "")
|
||||
count (count projects)]
|
||||
(letfn [(on-term-change [event]
|
||||
(let [term (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(rs/emit! (udp/update-opts :filter term))))
|
||||
(on-ordering-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (read-string value)]
|
||||
(rs/emit! (udp/update-opts :order value))))
|
||||
(on-clear [event]
|
||||
(rs/emit! (udp/update-opts :filter "")))]
|
||||
[:section.dashboard-bar
|
||||
[:div.dashboard-info
|
||||
|
||||
;; Counter
|
||||
[:span.dashboard-images (tr "ds.num-projects" (t/c count))]
|
||||
|
||||
;; Sorting
|
||||
[:div
|
||||
[:span (tr "ds.project-ordering")]
|
||||
[:select.input-select
|
||||
{:on-change on-ordering-change
|
||||
:value (pr-str ordering)}
|
||||
(for [[key value] (seq +ordering-options+)
|
||||
:let [ovalue (pr-str key)
|
||||
olabel (tr value)]]
|
||||
[:option {:key ovalue :value ovalue} olabel])]]
|
||||
;; Search
|
||||
[:form.dashboard-search
|
||||
[:input.input-text
|
||||
{:key :images-search-box
|
||||
:type "text"
|
||||
:on-change on-term-change
|
||||
:auto-focus true
|
||||
:placeholder (tr "ds.project-search.placeholder")
|
||||
:value (or filtering "")}]
|
||||
[:div.clear-search {:on-click on-clear} i/close]]]])))
|
||||
|
||||
;; --- Grid Item Thumbnail
|
||||
|
||||
(defn- grid-item-thumbnail-will-mount
|
||||
[own]
|
||||
(let [[project] (:rum/args own)
|
||||
svg (exports/render-page* (:page-id project))
|
||||
url (some-> svg
|
||||
(blob/create "image/svg+xml")
|
||||
(blob/create-uri))]
|
||||
(assoc own ::url url)))
|
||||
|
||||
(defn- grid-item-thumbnail-will-unmount
|
||||
[own]
|
||||
(let [url (::url own)]
|
||||
(when url (blob/revoke-uri url))
|
||||
own))
|
||||
|
||||
(mx/defcs grid-item-thumbnail
|
||||
{:mixins [mx/static]
|
||||
:will-mount grid-item-thumbnail-will-mount
|
||||
:will-unmount grid-item-thumbnail-will-unmount}
|
||||
[own project]
|
||||
(if-let [url (::url own)]
|
||||
[:div.grid-item-th
|
||||
{:style {:background-image (str "url('" url "')")}}]
|
||||
[:div.grid-item-th
|
||||
{:style {:background-image "url('/images/project-placeholder.svg')"}}]))
|
||||
|
||||
;; --- Grid Item
|
||||
|
||||
(mx/defcs grid-item
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[{:keys [rum/local] :as own} project]
|
||||
(letfn [(on-navigate [event]
|
||||
(rs/emit! (udp/go-to (:id project))))
|
||||
(delete []
|
||||
(rs/emit! (udp/delete-project project)))
|
||||
(on-delete [event]
|
||||
(dom/stop-propagation event)
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(on-key-down [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)
|
||||
id (:id project)]
|
||||
(swap! local assoc :edition false)
|
||||
(rs/emit! (udp/rename-project id name))))
|
||||
(on-edit [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(swap! local assoc :edition true))]
|
||||
[:div.grid-item.project-th {:on-click on-navigate}
|
||||
(grid-item-thumbnail project)
|
||||
[:div.item-info
|
||||
(if (:edition @local)
|
||||
[:input {:type "text"
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-click on-edit
|
||||
:default-value (:name project)}]
|
||||
[:h3 (:name project)])
|
||||
[:span.date
|
||||
(str "Updated " (dt/timeago (:modified-at project)))]]
|
||||
[:div.project-th-actions
|
||||
[:div.project-th-icon.pages
|
||||
i/page
|
||||
[:span (:total-pages project)]]
|
||||
#_[:div.project-th-icon.comments
|
||||
i/chat
|
||||
[:span "0"]]
|
||||
[:div.project-th-icon.edit
|
||||
{:on-click on-edit}
|
||||
i/pencil]
|
||||
[:div.project-th-icon.delete
|
||||
{:on-click on-delete}
|
||||
i/trash]]]))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mx/defc grid
|
||||
{:mixins [mx/static]}
|
||||
[state projects]
|
||||
(let [ordering (:order state :created)
|
||||
filtering (:filter state "")
|
||||
projects (->> (vals projects)
|
||||
(filter-projects-by filtering)
|
||||
(sort-projects-by ordering))]
|
||||
(letfn [(on-click [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/open! :new-project))]
|
||||
[:section.dashboard-grid
|
||||
[:h2 "Your projects"]
|
||||
[:div.dashboard-grid-content
|
||||
[:div.dashboard-grid-row
|
||||
[:div.grid-item.add-project
|
||||
{:on-click on-click}
|
||||
[:span "+ New project"]]
|
||||
(for [item projects]
|
||||
(-> (grid-item item)
|
||||
(mx/with-key (:id item))))]]])))
|
||||
|
||||
;; --- Projects Page
|
||||
|
||||
(defn projects-page-will-mount
|
||||
[own]
|
||||
(rs/emit! (udp/initialize))
|
||||
own)
|
||||
|
||||
(defn projects-page-did-remount
|
||||
[old-own own]
|
||||
(rs/emit! (udp/initialize))
|
||||
own)
|
||||
|
||||
(mx/defc projects-page
|
||||
{:will-mount projects-page-will-mount
|
||||
:did-remount projects-page-did-remount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [state (mx/react dashboard-ref)
|
||||
projects-map (mx/react projects-map-ref)]
|
||||
[:main.dashboard-main
|
||||
(uum/messages)
|
||||
(header)
|
||||
[:section.dashboard-content
|
||||
(menu state projects-map)
|
||||
(grid state projects-map)]]))
|
||||
|
||||
;; --- Lightbox: Layout input
|
||||
|
||||
(mx/defc layout-input
|
||||
[local layout-id]
|
||||
(let [layout (get-in +layouts+ [layout-id])
|
||||
id (:id layout)
|
||||
name (:name layout)
|
||||
width (:width layout)
|
||||
height (:height layout)]
|
||||
[:div
|
||||
[:input
|
||||
{:type "radio"
|
||||
:key id
|
||||
:id id
|
||||
:name "project-layout"
|
||||
:value name
|
||||
:checked (= layout-id (:layout @local))
|
||||
:on-change #(swap! local merge {:layout layout-id
|
||||
:width width
|
||||
:height height})}]
|
||||
[:label {:value (:name @local) :for id} name]]))
|
||||
|
||||
;; --- Lightbox: Layout selector
|
||||
|
||||
(mx/defc layout-selector
|
||||
[local]
|
||||
[:div.input-radio.radio-primary
|
||||
(layout-input local "mobile")
|
||||
(layout-input local "tablet")
|
||||
(layout-input local "notebook")
|
||||
(layout-input local "desktop")])
|
||||
|
||||
;; -- New Project Lightbox
|
||||
|
||||
(mx/defcs new-project-lightbox
|
||||
{:mixins [(mx/local +project-defaults+)]}
|
||||
[own]
|
||||
(let [local (:rum/local own)
|
||||
name (:name @local)
|
||||
width (:width @local)
|
||||
height (:height @local)]
|
||||
[:div.lightbox-body
|
||||
[:h3 "New project"]
|
||||
[:form {:on-submit (constantly nil)}
|
||||
[:input#project-name.input-text
|
||||
{:placeholder "New project name"
|
||||
:type "text"
|
||||
:value name
|
||||
:auto-focus true
|
||||
:on-change #(swap! local assoc :name (.-value (.-target %)))}]
|
||||
[:div.project-size
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder "Width"
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value width
|
||||
:on-change #(swap! local assoc :width (.-value (.-target %)))}]
|
||||
[:a.toggle-layout
|
||||
{:href "#"
|
||||
:on-click #(swap! local assoc :width width :height height)}
|
||||
i/toggle]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value height
|
||||
:on-change #(swap! local assoc :height (.-value (.-target %)))}]]
|
||||
;; Layout selector
|
||||
(layout-selector local)
|
||||
;; Submit
|
||||
(when-not (empty? (str/trim name))
|
||||
[:input#project-btn.btn-primary
|
||||
{:value "Go go go!"
|
||||
:on-click #(do
|
||||
(dom/prevent-default %)
|
||||
(rs/emit! (udp/create-project @local))
|
||||
(udl/close!))
|
||||
|
||||
:type "submit"}])]
|
||||
[:a.close {:href "#"
|
||||
:on-click #(do (dom/prevent-default %)
|
||||
(udl/close!))}
|
||||
i/close]]))
|
||||
|
||||
(defmethod lbx/render-lightbox :new-project
|
||||
[_]
|
||||
(new-project-lightbox))
|
758
frontend/src/uxbox/main/ui/icons.cljs
Normal file
758
frontend/src/uxbox/main/ui/icons.cljs
Normal file
File diff suppressed because one or more lines are too long
18
frontend/src/uxbox/main/ui/keyboard.cljs
Normal file
18
frontend/src/uxbox/main/ui/keyboard.cljs
Normal file
|
@ -0,0 +1,18 @@
|
|||
(ns uxbox.main.ui.keyboard)
|
||||
|
||||
(defn is-keycode?
|
||||
[keycode]
|
||||
(fn [e]
|
||||
(= (.-keyCode e) keycode)))
|
||||
|
||||
(defn ctrl?
|
||||
[event]
|
||||
(.-ctrlKey event))
|
||||
|
||||
(defn shift?
|
||||
[event]
|
||||
(.-shiftKey event))
|
||||
|
||||
(def esc? (is-keycode? 27))
|
||||
(def enter? (is-keycode? 13))
|
||||
(def space? (is-keycode? 32))
|
69
frontend/src/uxbox/main/ui/lightbox.cljs
Normal file
69
frontend/src/uxbox/main/ui/lightbox.cljs
Normal file
|
@ -0,0 +1,69 @@
|
|||
(ns uxbox.main.ui.lightbox
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.keyboard :as k]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (classnames)]
|
||||
[goog.events :as events])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Lentes
|
||||
|
||||
(def ^:private lightbox-ref
|
||||
(-> (l/key :lightbox)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Lightbox (Component)
|
||||
|
||||
(defmulti render-lightbox :name)
|
||||
(defmethod render-lightbox :default [_] nil)
|
||||
|
||||
(defn- on-esc-clicked
|
||||
[event]
|
||||
(when (k/esc? event)
|
||||
(udl/close!)
|
||||
(dom/stop-propagation event)))
|
||||
|
||||
(defn- on-out-clicked
|
||||
[own event]
|
||||
(let [parent (mx/ref-node own "parent")
|
||||
current (dom/get-target event)]
|
||||
(when (dom/equals? parent current)
|
||||
(udl/close!))))
|
||||
|
||||
(defn- lightbox-will-mount
|
||||
[own]
|
||||
(let [key (events/listen js/document
|
||||
EventType.KEYDOWN
|
||||
on-esc-clicked)]
|
||||
(assoc own ::key key)))
|
||||
|
||||
(defn- lightbox-will-umount
|
||||
[own]
|
||||
(events/unlistenByKey (::key own))
|
||||
(dissoc own ::key))
|
||||
|
||||
(defn- lightbox-render
|
||||
[own]
|
||||
(let [data (mx/react lightbox-ref)
|
||||
classes (classnames
|
||||
:hide (nil? data)
|
||||
:transparent (:transparent? data))]
|
||||
(html
|
||||
[:div.lightbox
|
||||
{:class classes
|
||||
:ref "parent"
|
||||
:on-click (partial on-out-clicked own)}
|
||||
(render-lightbox data)])))
|
||||
|
||||
(def lightbox
|
||||
(mx/component
|
||||
{:name "lightbox"
|
||||
:render lightbox-render
|
||||
:will-mount lightbox-will-mount
|
||||
:will-unmount lightbox-will-umount
|
||||
:mixins [mx/reactive]}))
|
38
frontend/src/uxbox/main/ui/loader.cljs
Normal file
38
frontend/src/uxbox/main/ui/loader.cljs
Normal file
|
@ -0,0 +1,38 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.loader
|
||||
(:require [sablono.core :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.shapes]))
|
||||
|
||||
;; --- Error Handling
|
||||
|
||||
(defn- on-error
|
||||
[error]
|
||||
;; Disable loader in case of error.
|
||||
(reset! st/loader false))
|
||||
|
||||
(rs/add-error-watcher :loader on-error)
|
||||
|
||||
;; --- Component
|
||||
|
||||
(defn loader-render
|
||||
[own]
|
||||
(when (mx/react st/loader)
|
||||
(html
|
||||
[:div.loader-content i/loader])))
|
||||
|
||||
(def loader
|
||||
(mx/component
|
||||
{:render loader-render
|
||||
:name "loader"
|
||||
:mixins [mx/reactive mx/static]}))
|
||||
|
91
frontend/src/uxbox/main/ui/messages.cljs
Normal file
91
frontend/src/uxbox/main/ui/messages.cljs
Normal file
|
@ -0,0 +1,91 @@
|
|||
(ns uxbox.main.ui.messages
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.messages :as udm]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.data :refer (classnames)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Lenses
|
||||
|
||||
(def ^:private message-ref
|
||||
(-> (l/key :message)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Notification Component
|
||||
|
||||
(defn notification-render
|
||||
[own {:keys [type] :as message}]
|
||||
(let [classes (classnames :error (= type :error)
|
||||
:info (= type :info)
|
||||
:hide-message (= (:state message) :hide)
|
||||
:quick true)
|
||||
close #(udm/close!)]
|
||||
(html
|
||||
[:div.message {:class classes}
|
||||
[:div.message-body
|
||||
[:span.close {:on-click close}
|
||||
i/close]
|
||||
[:span (:content message)]]])))
|
||||
|
||||
(def ^:private notification-box
|
||||
(mx/component
|
||||
{:render notification-render
|
||||
:name "notification"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- Dialog Component
|
||||
|
||||
(defn dialog-render
|
||||
[own {:keys [on-accept on-cancel] :as message}]
|
||||
(let [classes (classnames :info true
|
||||
:hide-message (= (:state message) :hide))]
|
||||
(letfn [(accept [event]
|
||||
(dom/prevent-default event)
|
||||
(on-accept)
|
||||
(ts/schedule 0 udm/close!))
|
||||
|
||||
(cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(when on-cancel
|
||||
(on-cancel))
|
||||
(ts/schedule 0 udm/close!))]
|
||||
(html
|
||||
[:div.message {:class classes}
|
||||
[:div.message-body
|
||||
[:span.close {:on-click cancel} i/close]
|
||||
[:span (:content message)]
|
||||
[:div.message-action
|
||||
[:a.btn-transparent.btn-small
|
||||
{:on-click accept}
|
||||
"Accept"]
|
||||
[:a.btn-transparent.btn-small
|
||||
{:on-click cancel}
|
||||
"Cancel"]]]]))))
|
||||
|
||||
(def ^:private dialog-box
|
||||
(mx/component
|
||||
{:render dialog-render
|
||||
:name "dialog"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- Main Component (entry point)
|
||||
|
||||
(defn messages-render
|
||||
[own]
|
||||
(let [message (mx/react message-ref)]
|
||||
(case (:type message)
|
||||
:error (notification-box message)
|
||||
:info (notification-box message)
|
||||
:dialog (dialog-box message)
|
||||
nil)))
|
||||
|
||||
(def messages
|
||||
(mx/component
|
||||
{:render messages-render
|
||||
:name "messages"
|
||||
:mixins [mx/static mx/reactive]}))
|
11
frontend/src/uxbox/main/ui/navigation.cljs
Normal file
11
frontend/src/uxbox/main/ui/navigation.cljs
Normal file
|
@ -0,0 +1,11 @@
|
|||
(ns uxbox.main.ui.navigation
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[goog.events :as events]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(defn link
|
||||
"Given an href and a component, return a link component that will navigate
|
||||
to the given URI withour reloading the page."
|
||||
[href component]
|
||||
(html
|
||||
[:a {:href (str "/#" href)} component]))
|
24
frontend/src/uxbox/main/ui/settings.cljs
Normal file
24
frontend/src/uxbox/main/ui/settings.cljs
Normal file
|
@ -0,0 +1,24 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.settings.profile :as profile]
|
||||
[uxbox.main.ui.settings.password :as password]
|
||||
[uxbox.main.ui.settings.notifications :as notifications]
|
||||
[uxbox.main.ui.dashboard.header :refer (header)]))
|
||||
|
||||
(def profile-page profile/profile-page)
|
||||
(def password-page password/password-page)
|
||||
(def notifications-page notifications/notifications-page)
|
57
frontend/src/uxbox/main/ui/settings/header.cljs
Normal file
57
frontend/src/uxbox/main/ui/settings/header.cljs
Normal file
|
@ -0,0 +1,57 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings.header
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.users :refer (user)]
|
||||
[uxbox.util.mixins :as mx :include-macros true]))
|
||||
|
||||
(def ^:private section-ref
|
||||
(-> (l/in [:route :id])
|
||||
(l/derive st/state)))
|
||||
|
||||
(defn- header-link
|
||||
[section content]
|
||||
(let [link (r/route-for section)]
|
||||
(html
|
||||
[:a {:href (str "/#" link)} content])))
|
||||
|
||||
(defn header-render
|
||||
[own]
|
||||
(let [section (mx/react section-ref)
|
||||
profile? (= section :settings/profile)
|
||||
password? (= section :settings/password)
|
||||
notifications? (= section :settings/notifications)]
|
||||
(println "header-render" section)
|
||||
(html
|
||||
[:header#main-bar.main-bar
|
||||
[:div.main-logo
|
||||
(header-link :dashboard/projects i/logo)]
|
||||
[:ul.main-nav
|
||||
[:li {:class (when profile? "current")}
|
||||
(header-link :settings/profile (tr "settings.profile"))]
|
||||
[:li {:class (when password? "current")}
|
||||
(header-link :settings/password (tr "settings.password"))]
|
||||
[:li {:class (when notifications? "current")}
|
||||
(header-link :settings/notifications (tr "settings.notifications"))]]
|
||||
(user)])))
|
||||
|
||||
(def header
|
||||
(mx/component
|
||||
{:render header-render
|
||||
:name "header"
|
||||
:mixins [rum/static
|
||||
mx/reactive]}))
|
42
frontend/src/uxbox/main/ui/settings/notifications.cljs
Normal file
42
frontend/src/uxbox/main/ui/settings/notifications.cljs
Normal file
|
@ -0,0 +1,42 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings.notifications
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.settings.header :refer (header)]))
|
||||
|
||||
(defn notifications-page-render
|
||||
[own]
|
||||
(html
|
||||
[:main.dashboard-main
|
||||
(header)
|
||||
[:section.dashboard-content.user-settings
|
||||
[:section.user-settings-content
|
||||
[:span.user-settings-label "Prototype notifications"]
|
||||
[:p "Get a roll up of prototype changes in your inbox."]
|
||||
[:div.input-radio.radio-primary
|
||||
[:input {:type "radio" :id "notification-1" :name "notification-1" :value "none"}]
|
||||
[:label {:for "notification-1" :value "None"} "None"]
|
||||
[:input {:type "radio" :id "notification-2" :name "notification-2" :value "every-hour"}]
|
||||
[:label {:for "notification-2" :value "Every hour"} "Every hour"]
|
||||
[:input {:type "radio" :id "notification-3" :name "notification-3" :value "every-day"}]
|
||||
[:label {:for "notification-3" :value "Every day"} "Every day"]]
|
||||
[:input.btn-primary {:type "submit" :value "Update settings"}]
|
||||
]]]))
|
||||
|
||||
(def notifications-page
|
||||
(mx/component
|
||||
{:render notifications-page-render
|
||||
:name "notifications-page"
|
||||
:mixins [mx/static]}))
|
86
frontend/src/uxbox/main/ui/settings/password.cljs
Normal file
86
frontend/src/uxbox/main/ui/settings/password.cljs
Normal file
|
@ -0,0 +1,86 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings.password
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.settings.header :refer (header)]))
|
||||
|
||||
(def form-data (forms/focus-data :profile-password st/state))
|
||||
(def form-errors (forms/focus-errors :profile-password st/state))
|
||||
(def set-value! (partial forms/set-value! :profile-password))
|
||||
(def set-errors! (partial forms/set-errors! :profile-password))
|
||||
|
||||
(def +password-form+
|
||||
[[:password-1 forms/required forms/string [forms/min-len 6]]
|
||||
[:password-2 forms/required forms/string
|
||||
[forms/identical-to :password-1 :message "errors.form.password-not-match"]]
|
||||
[:old-password forms/required forms/string]])
|
||||
|
||||
(mx/defc password-form
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
errors (mx/react form-errors)
|
||||
valid? (forms/valid? data +password-form+)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(on-submit [event]
|
||||
(println "on-submit" data)
|
||||
#_(rs/emit! (udu/update-password form)))]
|
||||
(println "password-form" data)
|
||||
[:form.password-form
|
||||
[:span.user-settings-label "Change password"]
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (forms/error-class errors :old-password)
|
||||
:value (:old-password data "")
|
||||
:on-change (partial on-change :old-password)
|
||||
:placeholder "Old password"}]
|
||||
(forms/input-error errors :old-password)
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (forms/error-class errors :password-1)
|
||||
:value (:password-1 data "")
|
||||
:on-change (partial on-change :password-1)
|
||||
:placeholder "New password"}]
|
||||
(forms/input-error errors :password-1)
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (forms/error-class errors :password-2)
|
||||
:value (:password-2 data "")
|
||||
:on-change (partial on-change :password-2)
|
||||
:placeholder "Confirm password"}]
|
||||
(forms/input-error errors :password-2)
|
||||
[:input.btn-primary
|
||||
{:type "button"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:on-click on-submit
|
||||
:value "Update settings"}]])))
|
||||
|
||||
;; --- Password Page
|
||||
|
||||
(mx/defc password-page
|
||||
{:mixins [mx/static]}
|
||||
[]
|
||||
[:main.dashboard-main
|
||||
(header)
|
||||
(uum/messages)
|
||||
[:section.dashboard-content.user-settings
|
||||
[:section.user-settings-content
|
||||
(password-form)]]])
|
151
frontend/src/uxbox/main/ui/settings/profile.cljs
Normal file
151
frontend/src/uxbox/main/ui/settings/profile.cljs
Normal file
|
@ -0,0 +1,151 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings.profile
|
||||
(:require [cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.interop :refer (iterable->seq)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.settings.header :refer (header)]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.data.users :as udu]))
|
||||
|
||||
(def form-data (forms/focus-data :profile st/state))
|
||||
(def form-errors (forms/focus-errors :profile st/state))
|
||||
(def set-value! (partial forms/set-value! :profile))
|
||||
(def set-error! (partial forms/set-error! :profile))
|
||||
|
||||
(def profile-ref
|
||||
(-> (l/key :profile)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def +profile-form+
|
||||
{:fullname [forms/required forms/string]
|
||||
:email [forms/required forms/email]
|
||||
:username [forms/required forms/string]})
|
||||
|
||||
;; --- Profile Form
|
||||
|
||||
(mx/defc profile-form
|
||||
{:mixins [mx/static mx/reactive]
|
||||
:will-unmount (forms/cleaner-fn :profile)}
|
||||
[]
|
||||
;; TODO: properly persist theme
|
||||
(let [data (merge {:theme "light"}
|
||||
(mx/react profile-ref)
|
||||
(mx/react form-data))
|
||||
errors (mx/react form-errors)
|
||||
valid? (forms/valid? data +profile-form+)
|
||||
theme (:theme data)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(on-error [{:keys [code] :as payload}]
|
||||
(case code
|
||||
:uxbox.services.users/email-already-exists
|
||||
(set-error! :email "Email already exists")
|
||||
:uxbox.services.users/username-already-exists
|
||||
(set-error! :username "Username already exists")))
|
||||
(on-success []
|
||||
(forms/clear! :profile))
|
||||
(on-submit [event]
|
||||
(rs/emit! (udu/update-profile data on-success on-error)))]
|
||||
[:form.profile-form
|
||||
[:span.user-settings-label "Name, username and email"]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:on-change (partial on-change :fullname)
|
||||
:value (:fullname data "")
|
||||
:placeholder "Your name"}]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:on-change (partial on-change :username)
|
||||
:value (:username data "")
|
||||
:placeholder "Your username"}]
|
||||
(forms/input-error errors :username)
|
||||
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:on-change (partial on-change :email)
|
||||
:value (:email data "")
|
||||
:placeholder "Your email"}]
|
||||
(forms/input-error errors :email)
|
||||
|
||||
[:span.user-settings-label "Choose a color theme"]
|
||||
[:div.input-radio.radio-primary
|
||||
[:input {:type "radio"
|
||||
:checked (when (= theme "light") "checked")
|
||||
:on-change (partial on-change :theme)
|
||||
:id "light-theme"
|
||||
:name "theme"
|
||||
:value "light"}]
|
||||
[:label {:for "light-theme"} "Light theme"]
|
||||
|
||||
[:input {:type "radio"
|
||||
:checked (when (= theme "dark") "checked")
|
||||
:on-change (partial on-change :theme)
|
||||
:id "dark-theme"
|
||||
:name "theme"
|
||||
:value "dark"}]
|
||||
[:label {:for "dark-theme"} "Dark theme"]
|
||||
|
||||
[:input {:type "radio"
|
||||
:checked (when (= theme "high-contrast") "checked")
|
||||
:on-change (partial on-change :theme)
|
||||
:id "high-contrast-theme"
|
||||
:name "theme"
|
||||
:value "high-contrast"}]
|
||||
[:label {:for "high-contrast-theme"} "High-contrast theme"]]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "button"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:on-click on-submit
|
||||
:value "Update settings"}]])))
|
||||
|
||||
;; --- Profile Photo Form
|
||||
|
||||
(mx/defc profile-photo-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(letfn [(on-change [event]
|
||||
(let [target (dom/get-target event)
|
||||
file (-> (dom/get-files target)
|
||||
(iterable->seq)
|
||||
(first))]
|
||||
(rs/emit! (udu/update-photo file))
|
||||
(dom/clean-value! target)))]
|
||||
(let [{:keys [photo]} (mx/react profile-ref)
|
||||
photo (if (or (str/empty? photo) (nil? photo))
|
||||
"images/avatar.jpg"
|
||||
photo)]
|
||||
[:form.avatar-form
|
||||
[:img {:src photo}]
|
||||
[:input {:type "file"
|
||||
:value ""
|
||||
:on-change on-change}]])))
|
||||
|
||||
;; --- Profile Page
|
||||
|
||||
(mx/defc profile-page
|
||||
{:mixins [mx/static]}
|
||||
[]
|
||||
[:main.dashboard-main
|
||||
(header)
|
||||
(uum/messages)
|
||||
[:section.dashboard-content.user-settings
|
||||
[:section.user-settings-content
|
||||
[:span.user-settings-label "Your avatar"]
|
||||
(profile-photo-form)
|
||||
(profile-form)]]])
|
11
frontend/src/uxbox/main/ui/shapes.cljs
Normal file
11
frontend/src/uxbox/main/ui/shapes.cljs
Normal file
|
@ -0,0 +1,11 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes
|
||||
(:require [uxbox.main.ui.shapes.group :as group]))
|
||||
|
||||
(def render-component group/render-component)
|
||||
(def shape group/component-container)
|
48
frontend/src/uxbox/main/ui/shapes/attrs.cljs
Normal file
48
frontend/src/uxbox/main/ui/shapes/attrs.cljs
Normal file
|
@ -0,0 +1,48 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.attrs)
|
||||
|
||||
(def ^:private +style-attrs+
|
||||
#{:fill :fill-opacity :opacity
|
||||
:stroke :stroke-opacity :stroke-width
|
||||
:stroke-type :rx :ry})
|
||||
|
||||
(defn- transform-stroke-type
|
||||
[attrs]
|
||||
(if-let [type (:stroke-type attrs)]
|
||||
(let [value (case type
|
||||
:mixed "5,5,1,5"
|
||||
:dotted "5,5"
|
||||
:dashed "10,10"
|
||||
nil)]
|
||||
(if value
|
||||
(-> attrs
|
||||
(assoc! :stroke-dasharray value)
|
||||
(dissoc! :stroke-type))
|
||||
(dissoc! attrs :stroke-type)))
|
||||
attrs))
|
||||
|
||||
(defn- transform-stroke-attrs
|
||||
[attrs]
|
||||
(if (= (:stroke-type attrs :none) :none)
|
||||
(dissoc! attrs :stroke-type :stroke-width :stroke-opacity :stroke)
|
||||
(transform-stroke-type attrs)))
|
||||
|
||||
(defn- extract-style-attrs
|
||||
"Extract predefinet attrs from shapes."
|
||||
[shape]
|
||||
(let [attrs (select-keys shape +style-attrs+)]
|
||||
(-> (transient attrs)
|
||||
(transform-stroke-attrs)
|
||||
(persistent!))))
|
||||
|
||||
(defn- make-debug-attrs
|
||||
[shape]
|
||||
(let [attrs (select-keys shape [:rotation :width :height :x :y])
|
||||
xf (map (fn [[x v]]
|
||||
[(keyword (str "data-" (name x))) v]))]
|
||||
(into {} xf attrs)))
|
54
frontend/src/uxbox/main/ui/shapes/circle.cljs
Normal file
54
frontend/src/uxbox/main/ui/shapes/circle.cljs
Normal file
|
@ -0,0 +1,54 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.circle
|
||||
(:require [sablono.core :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.geom :as geom]))
|
||||
|
||||
;; --- Circle Component
|
||||
|
||||
(declare circle-shape)
|
||||
|
||||
(defn- circle-component-render
|
||||
[own shape]
|
||||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(html
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
(circle-shape shape identity)])))
|
||||
|
||||
(def circle-component
|
||||
(mx/component
|
||||
{:render circle-component-render
|
||||
:name "circle-component"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
|
||||
;; --- Circle Shape
|
||||
|
||||
(defn- circle-shape-render
|
||||
[own {:keys [id] :as shape}]
|
||||
(let [key (str "shape-" id)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
props (select-keys shape [:cx :cy :rx :ry])
|
||||
attrs (-> (attrs/extract-style-attrs shape)
|
||||
(merge {:id key :key key :transform (str rfm)})
|
||||
(merge props))]
|
||||
(html
|
||||
[:ellipse attrs])))
|
||||
|
||||
(def circle-shape
|
||||
(mx/component
|
||||
{:render circle-shape-render
|
||||
:name "circle-shape"
|
||||
:mixins [mx/static]}))
|
84
frontend/src/uxbox/main/ui/shapes/common.cljs
Normal file
84
frontend/src/uxbox/main/ui/shapes/common.cljs
Normal file
|
@ -0,0 +1,84 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.common
|
||||
(:require [sablono.core :refer-macros [html]]
|
||||
[lentes.core :as l]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
;; (defonce edition-ref (atom nil))
|
||||
(def edition-ref
|
||||
(-> (l/in [:workspace :edition])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def drawing-state-ref
|
||||
(-> (l/in [:workspace :drawing])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def selected-ref
|
||||
(-> (l/in [:workspace :selected])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Movement
|
||||
|
||||
(defn start-move
|
||||
[]
|
||||
(letfn [(on-start [shape]
|
||||
(let [stoper (->> (rx/map first wb/events-s)
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (rx/take-until stoper wb/mouse-delta-s)
|
||||
on-move #(rs/emit! (uds/move-shape shape %))
|
||||
on-stop #(rlocks/release! :shape/move)]
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-align-shape shape)))
|
||||
(rx/subscribe stream on-move nil on-stop)))]
|
||||
|
||||
(rlocks/acquire! :shape/move)
|
||||
(run! on-start @selected-ref)))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(defn on-mouse-down
|
||||
[event {:keys [id group] :as shape} selected]
|
||||
(let [selected? (contains? selected id)
|
||||
drawing? @drawing-state-ref]
|
||||
(when-not (:blocked shape)
|
||||
(cond
|
||||
(or drawing?
|
||||
(and group (:locked (geom/resolve-parent shape))))
|
||||
nil
|
||||
|
||||
(and (not selected?) (empty? selected))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(rs/emit! (uds/select-shape id))
|
||||
(start-move))
|
||||
|
||||
(and (not selected?) (not (empty? selected)))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(if (kbd/shift? event)
|
||||
(rs/emit! (uds/select-shape id))
|
||||
(do
|
||||
(rs/emit! (uds/deselect-all)
|
||||
(uds/select-shape id))
|
||||
(start-move))))
|
||||
|
||||
:else
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(start-move))))))
|
78
frontend/src/uxbox/main/ui/shapes/group.cljs
Normal file
78
frontend/src/uxbox/main/ui/shapes/group.cljs
Normal file
|
@ -0,0 +1,78 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.group
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[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.main.geom :as geom]))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(declare group-component)
|
||||
|
||||
(defn- focus-shape
|
||||
[id]
|
||||
(-> (l/in [:shapes id])
|
||||
(l/derive st/state)))
|
||||
|
||||
(defn render-component
|
||||
[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)))
|
||||
|
||||
(mx/defc component-container
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[id]
|
||||
(let [shape (mx/react (focus-shape id))]
|
||||
(when-not (:hidden shape)
|
||||
(render-component shape))))
|
||||
|
||||
;; --- Group Component
|
||||
|
||||
(declare group-shape)
|
||||
|
||||
(mx/defc group-component
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id x y width height group] :as shape}]
|
||||
(let [selected (mx/react common/selected-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
[:g.shape.group-shape
|
||||
{:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
(group-shape shape component-container)]))
|
||||
|
||||
;; --- Group Shape
|
||||
|
||||
(mx/defc group-shape
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [items id dx dy rotation] :as shape} factory]
|
||||
(let [key (str "shape-" id)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
attrs (merge {:id key :key key :transform (str rfm)}
|
||||
(attrs/extract-style-attrs shape)
|
||||
(attrs/make-debug-attrs shape))]
|
||||
[:g attrs
|
||||
(for [item (reverse items)
|
||||
:let [key (str item)]]
|
||||
(-> (factory item)
|
||||
(mx/with-key key)))]))
|
||||
|
54
frontend/src/uxbox/main/ui/shapes/icon.cljs
Normal file
54
frontend/src/uxbox/main/ui/shapes/icon.cljs
Normal file
|
@ -0,0 +1,54 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.icon
|
||||
(:require [uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.geom :as geom]))
|
||||
|
||||
;; --- Icon Component
|
||||
|
||||
(declare icon-shape)
|
||||
|
||||
(mx/defc icon-component
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id] :as shape}]
|
||||
(let [selected (mx/react common/selected-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
(icon-shape shape identity)]))
|
||||
|
||||
;; --- Icon Shape
|
||||
|
||||
(mx/defc icon-shape
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [x1 y1 content id metadata] :as shape} factory]
|
||||
(let [key (str "shape-" id)
|
||||
;; rfm (geom/transformation-matrix shape)
|
||||
view-box (apply str (interpose " " (:view-box metadata)))
|
||||
size (geom/size shape)
|
||||
attrs (merge {:id key :key key ;; :transform (str rfm)
|
||||
:x x1 :y y1 :view-box view-box
|
||||
:preserve-aspect-ratio "none"
|
||||
:dangerouslySetInnerHTML {:__html content}}
|
||||
size
|
||||
(attrs/extract-style-attrs shape)
|
||||
(attrs/make-debug-attrs shape))]
|
||||
[:svg attrs]))
|
||||
|
||||
;; --- Icon SVG
|
||||
|
||||
(mx/defc icon-svg
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [content id metadata] :as shape}]
|
||||
(let [view-box (apply str (interpose " " (:view-box metadata)))
|
||||
id (str "icon-svg-" id)
|
||||
props {:view-box view-box :id id
|
||||
:dangerouslySetInnerHTML {:__html content}}]
|
||||
[:svg props]))
|
64
frontend/src/uxbox/main/ui/shapes/image.cljs
Normal file
64
frontend/src/uxbox/main/ui/shapes/image.cljs
Normal file
|
@ -0,0 +1,64 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.image
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.http :as http]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.data.images :as udi]
|
||||
[uxbox.main.geom :as geom]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(defn image-ref
|
||||
[id]
|
||||
(-> (l/in [:images id])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Image Component
|
||||
|
||||
(declare image-shape)
|
||||
|
||||
(defn- will-mount
|
||||
[own]
|
||||
(let [{:keys [image]} (first (:rum/args own))]
|
||||
(println (:rum/args own))
|
||||
(rs/emit! (udi/fetch-image image))
|
||||
own))
|
||||
|
||||
(mx/defcs image-component
|
||||
{:mixins [mx/static mx/reactive]
|
||||
:will-mount will-mount}
|
||||
[own {:keys [id image] :as shape}]
|
||||
(let [selected (mx/react common/selected-ref)
|
||||
image (mx/react (image-ref image))
|
||||
selected? (contains? selected id)
|
||||
local (:rum/local own)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
(when image
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
(image-shape (assoc shape :image image))])))
|
||||
|
||||
;; --- Image Shape
|
||||
|
||||
(mx/defc image-shape
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id x1 y1 image] :as shape}]
|
||||
(let [key (str "shape-" id)
|
||||
;; rfm (geom/transformation-matrix shape)
|
||||
size (geom/size shape)
|
||||
props {:x x1 :y y1 :id key :key key
|
||||
:preserve-aspect-ratio "none"
|
||||
:xlink-href (:url image)}
|
||||
attrs (-> (attrs/extract-style-attrs shape)
|
||||
(merge props size))]
|
||||
[:image attrs]))
|
54
frontend/src/uxbox/main/ui/shapes/path.cljs
Normal file
54
frontend/src/uxbox/main/ui/shapes/path.cljs
Normal file
|
@ -0,0 +1,54 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.path
|
||||
(:require [uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.geom :as geom]))
|
||||
|
||||
;; --- Path Component
|
||||
|
||||
(declare path-shape)
|
||||
|
||||
(mx/defc path-component
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [id] :as shape}]
|
||||
(let [selected (mx/react common/selected-ref)
|
||||
selected? (contains? selected id)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(common/on-mouse-down event shape selected))
|
||||
(on-double-click [event]
|
||||
(when selected?
|
||||
(rs/emit! (uds/start-edition-mode id))))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
(path-shape shape identity)])))
|
||||
|
||||
;; --- Path Shape
|
||||
|
||||
(defn- render-path
|
||||
[{:keys [points close?] :as shape}]
|
||||
{:pre [(pos? (count points))]}
|
||||
(let [start (first points)
|
||||
init (str "M " (:x start) " " (:y start))
|
||||
path (reduce #(str %1 " L" (:x %2) " " (:y %2)) init points)]
|
||||
(cond-> path
|
||||
close? (str " Z"))))
|
||||
|
||||
(mx/defc path-shape
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id drawing?] :as shape}]
|
||||
(let [key (str "shape-" id)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
attrs (-> (attrs/extract-style-attrs shape)
|
||||
(merge {:id key :key key :d (render-path shape)})
|
||||
(merge (when-not drawing?
|
||||
#_{:transform (str rfm)})))]
|
||||
[:path attrs]))
|
40
frontend/src/uxbox/main/ui/shapes/rect.cljs
Normal file
40
frontend/src/uxbox/main/ui/shapes/rect.cljs
Normal file
|
@ -0,0 +1,40 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.rect
|
||||
(:require [uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Rect Component
|
||||
|
||||
(declare rect-shape)
|
||||
|
||||
(mx/defc rect-component
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[shape]
|
||||
(let [{:keys [id x y width height group]} shape
|
||||
selected (mx/react common/selected-ref)
|
||||
selected? (contains? selected id)
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
(rect-shape shape identity)]))
|
||||
|
||||
;; --- Rect Shape
|
||||
|
||||
(mx/defc rect-shape
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id x1 y1] :as shape}]
|
||||
(let [key (str "shape-" id)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
size (geom/size shape)
|
||||
props {:x x1 :y y1 :id key :key key :transform (str rfm)}
|
||||
attrs (-> (attrs/extract-style-attrs shape)
|
||||
(merge props size))]
|
||||
[:rect attrs]))
|
205
frontend/src/uxbox/main/ui/shapes/selection.cljs
Normal file
205
frontend/src/uxbox/main/ui/shapes/selection.cljs
Normal file
|
@ -0,0 +1,205 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 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]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.rlocks :as rlocks]
|
||||
[uxbox.main.ui.shapes.common :as scommon]
|
||||
[uxbox.main.geom :as geom]
|
||||
[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
|
||||
(-> (l/lens focus-selected-shapes)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def edition-ref scommon/edition-ref)
|
||||
|
||||
;; --- Resize Implementation
|
||||
|
||||
(defn- start-resize
|
||||
[vid sid]
|
||||
(letfn [(on-resize [[delta ctrl?]]
|
||||
(let [params {:vid vid :delta (assoc delta :lock ctrl?)}]
|
||||
(rs/emit! (uds/update-vertex-position sid params))))
|
||||
(on-end []
|
||||
(rlocks/release! :shape/resize))]
|
||||
(let [stoper (->> wb/events-s
|
||||
(rx/map first)
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (->> wb/mouse-delta-s
|
||||
(rx/take-until stoper)
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))]
|
||||
(rlocks/acquire! :shape/resize)
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-vertext-align sid vid)))
|
||||
(rx/subscribe stream on-resize nil on-end))))
|
||||
|
||||
;; --- Selection Handlers (Component)
|
||||
|
||||
(mx/defc multiple-selection-handlers
|
||||
[shapes]
|
||||
(let [{:keys [width height x y]} (geom/outer-rect-coll shapes)]
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:stroke-dasharray "5,5"
|
||||
:style {:stroke "#333" :fill "transparent"
|
||||
:stroke-opacity "1"}}]]))
|
||||
|
||||
(mx/defc single-not-editable-selection-handlers
|
||||
[{:keys [id] :as shape}]
|
||||
(let [{:keys [width height x y]} (geom/outer-rect shape)]
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:stroke-dasharray "5,5"
|
||||
:style {:stroke "#333"
|
||||
:fill "transparent"
|
||||
:stroke-opacity "1"}}]]))
|
||||
|
||||
(mx/defc single-selection-handlers
|
||||
[{:keys [id] :as shape}]
|
||||
(letfn [(on-mouse-down [vid event]
|
||||
(dom/stop-propagation event)
|
||||
(start-resize vid id))]
|
||||
(let [{:keys [x y width height]} (geom/outer-rect shape)]
|
||||
[:g.controls
|
||||
[:rect.main {:x x :y y
|
||||
:width width
|
||||
:height height
|
||||
:stroke-dasharray "5,5"
|
||||
:style {:stroke "#333"
|
||||
:fill "transparent"
|
||||
:stroke-opacity "1"}}]
|
||||
[:circle.top
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :top %)
|
||||
:cx (+ x (/ width 2))
|
||||
:cy (- y 2)})]
|
||||
[:circle.right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :right %)
|
||||
:cy (+ y (/ height 2))
|
||||
:cx (+ x width 1)})]
|
||||
[:circle.bottom
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :bottom %)
|
||||
:cx (+ x (/ width 2))
|
||||
:cy (+ y height 2)})]
|
||||
[:circle.left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :left %)
|
||||
:cy (+ y (/ height 2))
|
||||
:cx (- x 3)})]
|
||||
[:circle.top-left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :top-left %)
|
||||
:cx x
|
||||
:cy y})]
|
||||
[:circle.top-right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :top-right %)
|
||||
:cx (+ x width)
|
||||
:cy y})]
|
||||
[:circle.bottom-left
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :bottom-left %)
|
||||
:cx x
|
||||
:cy (+ y height)})]
|
||||
[:circle.bottom-right
|
||||
(merge +circle-props+
|
||||
{:on-mouse-down #(on-mouse-down :bottom-right %)
|
||||
:cx (+ x width)
|
||||
:cy (+ y height)})]])))
|
||||
|
||||
(defn start-path-edition
|
||||
[shape-id index]
|
||||
(letfn [(on-move [delta]
|
||||
(rs/emit! (uds/update-path shape-id index delta)))
|
||||
(on-end []
|
||||
(rlocks/release! :shape/resize))]
|
||||
(let [stoper (->> wb/events-s
|
||||
(rx/map first)
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (rx/take-until stoper wb/mouse-delta-s)]
|
||||
(rlocks/acquire! :shape/resize)
|
||||
(when @wb/alignment-ref
|
||||
(rs/emit! (uds/initial-path-point-align shape-id index)))
|
||||
(rx/subscribe stream on-move nil on-end))))
|
||||
|
||||
(mx/defc path-edition-selection-handlers
|
||||
[{:keys [id points] :as shape}]
|
||||
(letfn [(on-mouse-down [index event]
|
||||
(dom/stop-propagation event)
|
||||
(rlocks/acquire! :shape/resize)
|
||||
(start-path-edition id index))]
|
||||
[:g.controls
|
||||
(for [[index {:keys [x y]}] (map-indexed vector points)]
|
||||
[:circle {:cx x :cy y :r 6
|
||||
:on-mouse-down (partial on-mouse-down index)
|
||||
:fill "#31e6e0"
|
||||
:stroke "#28c4d4"
|
||||
:style {:cursor "pointer"}}])]))
|
||||
|
||||
(mx/defc selection-handlers
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[]
|
||||
(let [shapes (mx/react selected-shapes-ref)
|
||||
edition (mx/react scommon/edition-ref)
|
||||
shapes-num (count shapes)
|
||||
shape (first shapes)]
|
||||
(cond
|
||||
(zero? shapes-num)
|
||||
nil
|
||||
|
||||
(> shapes-num 1)
|
||||
(multiple-selection-handlers shapes)
|
||||
|
||||
:else
|
||||
(cond
|
||||
(= :path (:type shape))
|
||||
(if (= @edition-ref (:id shape))
|
||||
(path-edition-selection-handlers shape)
|
||||
(single-not-editable-selection-handlers shape))
|
||||
|
||||
(= :text (:type shape))
|
||||
(if (= @edition-ref (:id shape))
|
||||
(single-not-editable-selection-handlers shape)
|
||||
(single-selection-handlers (first shapes)))
|
||||
|
||||
(= :group (:type shape))
|
||||
(single-not-editable-selection-handlers shape)
|
||||
|
||||
:else
|
||||
(single-selection-handlers (first shapes))))))
|
139
frontend/src/uxbox/main/ui/shapes/text.cljs
Normal file
139
frontend/src/uxbox/main/ui/shapes/text.cljs
Normal file
|
@ -0,0 +1,139 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.text
|
||||
(:require [cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[goog.events :as events]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.color :as color]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.util.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(defn handle-mouse-down
|
||||
[event {:keys [id group] :as shape} selected]
|
||||
(if (and (not (:blocked shape))
|
||||
(or @common/drawing-state-ref
|
||||
@common/edition-ref
|
||||
(and group (:locked (geom/resolve-parent shape)))))
|
||||
(dom/stop-propagation event)
|
||||
(common/on-mouse-down event shape selected)))
|
||||
|
||||
;; --- Text Component
|
||||
|
||||
(declare text-shape)
|
||||
(declare text-shape-edit)
|
||||
|
||||
(mx/defcs text-component
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own {:keys [id x1 y1 content group] :as shape}]
|
||||
(let [selected (mx/react common/selected-ref)
|
||||
edition? (= (mx/react common/edition-ref) id)
|
||||
selected? (and (contains? selected id)
|
||||
(= (count selected) 1))]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(handle-mouse-down event shape selected))
|
||||
(on-double-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(rs/emit! (uds/start-edition-mode id)))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:ref "main"
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
(if edition?
|
||||
(text-shape-edit shape)
|
||||
(text-shape shape))])))
|
||||
|
||||
;; --- Text Styles Helpers
|
||||
|
||||
(def +style-attrs+ [:font-size])
|
||||
(def +select-rect-attrs+
|
||||
{:stroke-dasharray "5,5"
|
||||
:style {:stroke "#333" :fill "transparent"
|
||||
:stroke-opacity "0.4"}})
|
||||
|
||||
(defn- make-style
|
||||
[{:keys [font fill opacity]
|
||||
:or {fill "#000000" opacity 1}}]
|
||||
(let [{:keys [family weight style size align
|
||||
line-height letter-spacing]
|
||||
:or {family "sourcesanspro"
|
||||
weight "normal"
|
||||
style "normal"
|
||||
line-height 1.4
|
||||
letter-spacing 1
|
||||
align "left"
|
||||
size 16}} font
|
||||
color (-> fill
|
||||
(color/hex->rgba opacity)
|
||||
(color/rgb->str))]
|
||||
(merge
|
||||
{:fontSize (str size "px")
|
||||
:color color
|
||||
:whiteSpace "pre"
|
||||
:textAlign align
|
||||
:fontFamily family
|
||||
:fontWeight weight
|
||||
:fontStyle style}
|
||||
(when line-height {:lineHeight line-height})
|
||||
(when letter-spacing {:letterSpacing letter-spacing}))))
|
||||
|
||||
;; --- Text Shape Edit
|
||||
|
||||
(defn- text-shape-edit-did-mount
|
||||
[own]
|
||||
(let [[shape] (:rum/args own)
|
||||
dom (mx/ref-node own "container")]
|
||||
(set! (.-textContent dom) (:content shape ""))
|
||||
(.focus dom)
|
||||
own))
|
||||
|
||||
(mx/defc text-shape-edit
|
||||
{:did-mount text-shape-edit-did-mount
|
||||
:mixins [mx/static]}
|
||||
[{:keys [id x1 y1 content] :as shape}]
|
||||
(let [size (geom/size shape)
|
||||
style (make-style shape)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
props {:x x1 :y y1 :transform (str rfm)}
|
||||
props (merge props size)]
|
||||
(letfn [#_(on-blur [ev]
|
||||
(rlocks/release! :ui/text-edit)
|
||||
(on-done))
|
||||
(on-input [ev]
|
||||
(let [content (dom/event->inner-text ev)]
|
||||
(rs/emit! (uds/update-text id {:content content}))))]
|
||||
[:g
|
||||
[:rect (merge props +select-rect-attrs+)]
|
||||
[:foreignObject props
|
||||
[:p {:ref "container"
|
||||
;; :on-blur on-blur
|
||||
:on-input on-input
|
||||
:contentEditable true
|
||||
:style style}]]])))
|
||||
|
||||
;; --- Text Shape
|
||||
|
||||
(mx/defc text-shape
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [id x1 y1 content] :as shape}]
|
||||
(let [key (str "shape-" id)
|
||||
rfm (geom/transformation-matrix shape)
|
||||
size (geom/size shape)
|
||||
props {:x x1 :y y1
|
||||
:transform (str rfm)}
|
||||
attrs (merge props size)
|
||||
style (make-style shape)]
|
||||
[:foreignObject attrs
|
||||
[:p {:style style} content]]))
|
75
frontend/src/uxbox/main/ui/users.cljs
Normal file
75
frontend/src/uxbox/main/ui/users.cljs
Normal file
|
@ -0,0 +1,75 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.users
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rum.core :as rum]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as s]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.mixins :as mx :include-macros true]))
|
||||
|
||||
;; --- User Menu
|
||||
|
||||
(defn menu-render
|
||||
[own open?]
|
||||
(let [open-settings-dialog #(udl/open! :settings)]
|
||||
(html
|
||||
[:ul.dropdown {:class (when-not open?
|
||||
"hide")}
|
||||
[:li
|
||||
i/page
|
||||
[:span "Page settings"]]
|
||||
[:li {:on-click open-settings-dialog}
|
||||
i/grid
|
||||
[:span "Grid settings"]]
|
||||
[:li
|
||||
i/eye
|
||||
[:span "Preview"]]
|
||||
[:li {:on-click #(r/go :settings/profile)}
|
||||
i/user
|
||||
[:span "Your account"]]
|
||||
[:li {:on-click #(rs/emit! (da/logout))}
|
||||
i/exit
|
||||
[:span "Exit"]]])))
|
||||
|
||||
(def user-menu
|
||||
(mx/component
|
||||
{:render menu-render
|
||||
:name "user-menu"
|
||||
:mixins []}))
|
||||
|
||||
;; --- User Widget
|
||||
|
||||
(def profile-ref
|
||||
(as-> (l/key :profile) $
|
||||
(l/derive $ s/state)))
|
||||
|
||||
(defn user-render
|
||||
[own]
|
||||
(let [profile (mx/react profile-ref)
|
||||
local (:rum/local own)
|
||||
photo (if (str/empty? (:photo profile ""))
|
||||
"/images/avatar.jpg"
|
||||
(:photo profile))]
|
||||
(html
|
||||
[:div.user-zone {:on-mouse-enter #(swap! local assoc :open true)
|
||||
:on-mouse-leave #(swap! local assoc :open false)}
|
||||
[:span (:fullname profile)]
|
||||
[:img {:src photo}]
|
||||
(user-menu (:open @local))])))
|
||||
|
||||
(def user
|
||||
(mx/component
|
||||
{:render user-render
|
||||
:name "user"
|
||||
:mixins [mx/reactive (rum/local {:open false})]}))
|
167
frontend/src/uxbox/main/ui/workspace.cljs
Normal file
167
frontend/src/uxbox/main/ui/workspace.cljs
Normal file
|
@ -0,0 +1,167 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.data.undo :as udu]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.data :refer (classnames)]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.messages :as uum]
|
||||
[uxbox.main.ui.confirm]
|
||||
[uxbox.main.ui.workspace.images]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.workspace.scroll :as scroll]
|
||||
[uxbox.main.ui.workspace.download]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.shortcuts :refer (shortcuts-mixin)]
|
||||
[uxbox.main.ui.workspace.header :refer (header)]
|
||||
[uxbox.main.ui.workspace.rules :refer (horizontal-rule vertical-rule)]
|
||||
[uxbox.main.ui.workspace.sidebar.history :refer (history-dialog)]
|
||||
[uxbox.main.ui.workspace.sidebar :refer (left-sidebar right-sidebar)]
|
||||
[uxbox.main.ui.workspace.colorpalette :refer (colorpalette)]
|
||||
[uxbox.main.ui.workspace.canvas :refer (viewport)]))
|
||||
|
||||
;; --- Workspace
|
||||
|
||||
(defn- workspace-will-mount
|
||||
[own]
|
||||
(let [[projectid pageid] (:rum/args own)]
|
||||
(rs/emit! (dw/initialize projectid pageid))
|
||||
own))
|
||||
|
||||
(defn- workspace-did-mount
|
||||
[own]
|
||||
(let [[projectid pageid] (:rum/args own)
|
||||
sub1 (scroll/watch-scroll-interactions own)
|
||||
sub2 (udp/watch-page-changes pageid)
|
||||
sub3 (udu/watch-page-changes pageid)
|
||||
sub4 (udh/watch-page-changes)
|
||||
dom (mx/ref-node own "workspace-canvas")]
|
||||
|
||||
;; Set initial scroll position
|
||||
(set! (.-scrollLeft dom) (* c/canvas-start-scroll-x @wb/zoom-ref))
|
||||
(set! (.-scrollTop dom) (* c/canvas-start-scroll-y @wb/zoom-ref))
|
||||
|
||||
(assoc own
|
||||
::sub1 sub1
|
||||
::sub2 sub2
|
||||
::sub3 sub3
|
||||
::sub4 sub4)))
|
||||
|
||||
(defn- workspace-will-unmount
|
||||
[own]
|
||||
;; Close subscriptions
|
||||
(.close (::sub1 own))
|
||||
(.close (::sub2 own))
|
||||
(.close (::sub3 own))
|
||||
(.close (::sub4 own))
|
||||
(dissoc own ::sub1 ::sub2 ::sub3 ::sub4))
|
||||
|
||||
(defn- workspace-did-remount
|
||||
[old-state state]
|
||||
(let [[projectid pageid] (:rum/args state)
|
||||
[oldprojectid oldpageid] (:rum/args old-state)]
|
||||
(if (not= pageid oldpageid)
|
||||
(do
|
||||
(rs/emit! (dw/initialize projectid pageid))
|
||||
(.close (::sub2 old-state))
|
||||
(.close (::sub3 old-state))
|
||||
(assoc state
|
||||
::sub1 (::sub1 old-state)
|
||||
::sub2 (udp/watch-page-changes pageid)
|
||||
::sub3 (udu/watch-page-changes pageid)
|
||||
::sub4 (::sub4 old-state)))
|
||||
(assoc state
|
||||
::sub1 (::sub1 old-state)
|
||||
::sub2 (::sub2 old-state)
|
||||
::sub3 (::sub3 old-state)
|
||||
::sub4 (::sub4 old-state)))))
|
||||
|
||||
(defn- on-scroll
|
||||
[event]
|
||||
(let [target (.-target event)
|
||||
top (.-scrollTop target)
|
||||
left (.-scrollLeft target)]
|
||||
(rx/push! wb/scroll-b (gpt/point left top))))
|
||||
|
||||
(defn- on-wheel
|
||||
[own event]
|
||||
(when (kbd/ctrl? event)
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(if (pos? (.-deltaY event))
|
||||
(rs/emit! (dw/increase-zoom))
|
||||
(rs/emit! (dw/decrease-zoom)))
|
||||
|
||||
(let [dom (mx/ref-node own "workspace-canvas")]
|
||||
(set! (.-scrollLeft dom) (* c/canvas-start-scroll-x @wb/zoom-ref))
|
||||
(set! (.-scrollTop dom) (* c/canvas-start-scroll-y @wb/zoom-ref)))))
|
||||
|
||||
(defn- workspace-render
|
||||
[own]
|
||||
(let [{:keys [flags zoom page] :as workspace} (mx/react wb/workspace-ref)
|
||||
left-sidebar? (not (empty? (keep flags [:layers :sitemap
|
||||
:document-history])))
|
||||
right-sidebar? (not (empty? (keep flags [:icons :drawtools
|
||||
:element-options])))
|
||||
local (:rum/local own)
|
||||
classes (classnames
|
||||
:no-tool-bar-right (not right-sidebar?)
|
||||
:no-tool-bar-left (not left-sidebar?)
|
||||
:scrolling (:scrolling @local false))]
|
||||
(html
|
||||
[:div
|
||||
(header)
|
||||
(colorpalette)
|
||||
(uum/messages)
|
||||
|
||||
[:main.main-content
|
||||
|
||||
[:section.workspace-content
|
||||
{:class classes
|
||||
:on-scroll on-scroll
|
||||
:on-wheel (partial on-wheel own)}
|
||||
|
||||
|
||||
(history-dialog page)
|
||||
|
||||
;; Rules
|
||||
(horizontal-rule zoom)
|
||||
(vertical-rule zoom)
|
||||
|
||||
;; Canvas
|
||||
[:section.workspace-canvas
|
||||
{:ref "workspace-canvas"}
|
||||
(viewport)]]
|
||||
|
||||
;; Aside
|
||||
(when left-sidebar?
|
||||
(left-sidebar))
|
||||
(when right-sidebar?
|
||||
(right-sidebar))]])))
|
||||
|
||||
(def workspace
|
||||
(mx/component
|
||||
{:render workspace-render
|
||||
:did-remount workspace-did-remount
|
||||
:will-mount workspace-will-mount
|
||||
:will-unmount workspace-will-unmount
|
||||
:did-mount workspace-did-mount
|
||||
:name "workspace"
|
||||
:mixins [mx/static
|
||||
mx/reactive
|
||||
shortcuts-mixin
|
||||
(mx/local)]}))
|
133
frontend/src/uxbox/main/ui/workspace/base.cljs
Normal file
133
frontend/src/uxbox/main/ui/workspace/base.cljs
Normal file
|
@ -0,0 +1,133 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.base
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[goog.events :as events])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; FIXME: split this namespace in two:
|
||||
;; uxbox.main.ui.streams and uxbox.main.ui.workspace.refs
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def workspace-ref
|
||||
(-> (l/in [:workspace])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def project-ref
|
||||
(letfn [(getter [state]
|
||||
(let [project (get-in state [:workspace :project])]
|
||||
(get-in state [:projects project])))]
|
||||
(-> (l/lens getter)
|
||||
(l/derive st/state))))
|
||||
|
||||
(def page-ref
|
||||
(letfn [(getter [state]
|
||||
(let [page (get-in state [:workspace :page])]
|
||||
(get-in state [:pages page])))]
|
||||
(-> (l/lens getter)
|
||||
(l/derive st/state))))
|
||||
|
||||
(def selected-shapes-ref
|
||||
(as-> (l/in [:selected]) $
|
||||
(l/derive $ workspace-ref)))
|
||||
|
||||
(def toolboxes-ref
|
||||
(as-> (l/in [:toolboxes]) $
|
||||
(l/derive $ workspace-ref)))
|
||||
|
||||
(def flags-ref
|
||||
(as-> (l/in [:flags]) $
|
||||
(l/derive $ workspace-ref)))
|
||||
|
||||
(def shapes-by-id-ref
|
||||
(as-> (l/key :shapes) $
|
||||
(l/derive $ st/state)))
|
||||
|
||||
(def zoom-ref
|
||||
(-> (l/in [:workspace :zoom])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def alignment-ref
|
||||
(letfn [(getter [flags]
|
||||
(and (contains? flags :grid-indexed)
|
||||
(contains? flags :grid-alignment)
|
||||
(contains? flags :grid)))]
|
||||
(-> (l/lens getter)
|
||||
(l/derive flags-ref))))
|
||||
|
||||
;; --- Scroll Stream
|
||||
|
||||
(defonce scroll-b (rx/bus))
|
||||
|
||||
(defonce scroll-s
|
||||
(as-> scroll-b $
|
||||
(rx/sample 10 $)
|
||||
(rx/merge $ (rx/of (gpt/point)))
|
||||
(rx/dedupe $)))
|
||||
|
||||
(defonce scroll-a
|
||||
(rx/to-atom scroll-s))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(defonce events-b (rx/bus))
|
||||
(defonce events-s (rx/dedupe events-b))
|
||||
|
||||
;; --- Mouse Position Stream
|
||||
|
||||
(defonce mouse-b (rx/bus))
|
||||
(defonce mouse-s (rx/dedupe mouse-b))
|
||||
|
||||
(defonce mouse-canvas-s
|
||||
(->> mouse-s
|
||||
(rx/map :canvas-coords)
|
||||
(rx/share)))
|
||||
|
||||
(defonce mouse-canvas-a
|
||||
(rx/to-atom mouse-canvas-s))
|
||||
|
||||
(defonce mouse-viewport-s
|
||||
(->> mouse-s
|
||||
(rx/map :viewport-coords)
|
||||
(rx/share)))
|
||||
|
||||
(defonce mouse-viewport-a
|
||||
(rx/to-atom mouse-viewport-s))
|
||||
|
||||
(defonce mouse-absolute-s
|
||||
(->> mouse-s
|
||||
(rx/map :window-coords)
|
||||
(rx/share)))
|
||||
|
||||
(defonce mouse-ctrl-s
|
||||
(->> mouse-s
|
||||
(rx/map :ctrl)
|
||||
(rx/share)))
|
||||
|
||||
(defn- coords-delta
|
||||
[[old new]]
|
||||
(gpt/subtract new old))
|
||||
|
||||
(defonce mouse-delta-s
|
||||
(->> mouse-viewport-s
|
||||
(rx/sample 10)
|
||||
(rx/map #(gpt/divide % @zoom-ref))
|
||||
(rx/mapcat (fn [point]
|
||||
(if @alignment-ref
|
||||
(uds/align-point point)
|
||||
(rx/of point))))
|
||||
(rx/buffer 2 1)
|
||||
(rx/map coords-delta)
|
||||
(rx/share)))
|
190
frontend/src/uxbox/main/ui/workspace/canvas.cljs
Normal file
190
frontend/src/uxbox/main/ui/workspace/canvas.cljs
Normal file
|
@ -0,0 +1,190 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.canvas
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[goog.events :as events]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int)]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rlocks :as rlocks]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.shapes :as uus]
|
||||
[uxbox.main.ui.shapes.selection :refer (selection-handlers)]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.drawarea :refer (draw-area)]
|
||||
[uxbox.main.ui.workspace.ruler :refer (ruler)]
|
||||
[uxbox.main.ui.workspace.selrect :refer (selrect)]
|
||||
[uxbox.main.ui.workspace.grid :refer (grid)])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Background
|
||||
|
||||
(mx/defc background
|
||||
[]
|
||||
[:rect
|
||||
{:x 0 :y 0
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:fill "white"}])
|
||||
|
||||
;; --- Canvas
|
||||
|
||||
(mx/defc canvas
|
||||
{:mixins [mx/reactive]}
|
||||
[{:keys [metadata id] :as page}]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
flags (:flags workspace)
|
||||
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)
|
||||
[:svg.page-layout
|
||||
[:g.main {}
|
||||
(for [item (reverse (:shapes page))]
|
||||
(-> (uus/shape item)
|
||||
(mx/with-key (str item))))
|
||||
(selection-handlers)
|
||||
(draw-area)]]]))
|
||||
|
||||
;; --- Viewport
|
||||
|
||||
(defn- viewport-did-mount
|
||||
[own]
|
||||
(letfn [(translate-point-to-viewport [pt]
|
||||
(let [viewport (mx/ref-node own "viewport")
|
||||
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 (mx/ref-node own "viewport")]
|
||||
(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 [opts {:key (.-keyCode event)
|
||||
:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:key/down opts])
|
||||
(when (kbd/space? event)
|
||||
(rlocks/acquire! :workspace/scroll))))
|
||||
|
||||
(on-key-up [event]
|
||||
(let [opts {:key (.-keyCode event)
|
||||
:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:key/up opts])))
|
||||
|
||||
(on-mousemove [event]
|
||||
(let [wpt (gpt/point (.-clientX event)
|
||||
(.-clientY event))
|
||||
vppt (translate-point-to-viewport wpt)
|
||||
cvpt (translate-point-to-canvas wpt)
|
||||
event {:ctrl (kbd/ctrl? event)
|
||||
:shift (kbd/shift? event)
|
||||
:window-coords wpt
|
||||
:viewport-coords vppt
|
||||
:canvas-coords cvpt}]
|
||||
;; FIXME: refactor streams in order to use the wb/events-b
|
||||
;; for all keyboard and mouse events and then derive
|
||||
;; all the other from it.
|
||||
(rx/push! wb/mouse-b event)))]
|
||||
|
||||
(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))))
|
||||
|
||||
(defn- viewport-will-unmount
|
||||
[own]
|
||||
(events/unlistenByKey (::key1 own))
|
||||
(events/unlistenByKey (::key2 own))
|
||||
(events/unlistenByKey (::key3 own))
|
||||
(dissoc own ::key1 ::key2 ::key3))
|
||||
|
||||
(mx/defc viewport
|
||||
{:did-mount viewport-did-mount
|
||||
:will-unmount viewport-will-unmount
|
||||
:mixins [mx/reactive]}
|
||||
[]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
page (mx/react wb/page-ref)
|
||||
flags (:flags workspace)
|
||||
drawing? (:drawing workspace)
|
||||
zoom (or (:zoom workspace) 1)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [opts {:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:mouse/down opts]))
|
||||
(if (:drawing workspace)
|
||||
(rlocks/acquire! :ui/draw)
|
||||
(do
|
||||
(when (seq (:selected workspace))
|
||||
(rlocks/release! :shape/edition))
|
||||
(rlocks/acquire! :ui/selrect))))
|
||||
(on-context-menu [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [opts {:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:mouse/right-click opts])))
|
||||
(on-mouse-up [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [opts {:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:mouse/up])))
|
||||
(on-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [opts {:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:mouse/click opts])))
|
||||
(on-double-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [opts {:shift? (kbd/shift? event)
|
||||
:ctrl? (kbd/ctrl? event)}]
|
||||
(rx/push! wb/events-b [:mouse/double-click opts])))]
|
||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||
:height (* c/viewport-height zoom)
|
||||
:ref "viewport"
|
||||
: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 ")")}
|
||||
(if page
|
||||
(canvas page))
|
||||
(if (contains? flags :grid)
|
||||
(grid))]
|
||||
(ruler)
|
||||
(selrect)])))
|
||||
|
||||
|
45
frontend/src/uxbox/main/ui/workspace/clipboard.cljs
Normal file
45
frontend/src/uxbox/main/ui/workspace/clipboard.cljs
Normal file
|
@ -0,0 +1,45 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.clipboard
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.datetime :as dt]))
|
||||
|
||||
(def ^:private clipboard-ref
|
||||
(-> (l/key :clipboard)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mx/defc clipboard-dialog
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(letfn [(on-paste [item]
|
||||
(rs/emit! (udw/paste-from-clipboard (:id item)))
|
||||
(udl/close!))
|
||||
(on-close [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!))]
|
||||
[:div.lightbox-body.clipboard
|
||||
[:div.clipboard-list
|
||||
(for [item (mx/react clipboard-ref)]
|
||||
[:div.clipboard-item
|
||||
{:key (str (:id item))
|
||||
:on-click (partial on-paste item)}
|
||||
[:span.clipboard-icon i/box]
|
||||
[:span (str "Copied (" (dt/timeago (:created-at item)) ")")]])]
|
||||
[:a.close {:href "#" :on-click on-close} i/close]]))
|
||||
|
||||
(defmethod lbx/render-lightbox :clipboard
|
||||
[_]
|
||||
(clipboard-dialog))
|
90
frontend/src/uxbox/main/ui/workspace/colorpalette.cljs
Normal file
90
frontend/src/uxbox/main/ui/workspace/colorpalette.cljs
Normal file
|
@ -0,0 +1,90 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.colorpalette
|
||||
(:require [beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[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.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.lens :as ul]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.color :refer (hex->rgb)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.mixins :as mx :include-macros true]))
|
||||
|
||||
(defn- get-selected-collection
|
||||
[local collections]
|
||||
(if-let [selected (:selected @local)]
|
||||
(first (filter #(= selected (:id %)) collections))
|
||||
(first collections)))
|
||||
|
||||
(mx/defc palette-items
|
||||
{:mixins [mx/static]}
|
||||
[colors]
|
||||
(letfn [(select-color [event color]
|
||||
(dom/prevent-default event)
|
||||
(if (kbd/shift? event)
|
||||
(rs/emit! (uds/update-selected-shapes-stroke {:color color}))
|
||||
(rs/emit! (uds/update-selected-shapes-fill {:color color}))))]
|
||||
[:div.color-palette-content
|
||||
(for [hex-color colors
|
||||
:let [rgb-vec (hex->rgb hex-color)
|
||||
rgb-color (apply str "" (interpose ", " rgb-vec))]]
|
||||
[:div.color-cell {:key (str hex-color)
|
||||
:on-click #(select-color % hex-color)}
|
||||
[:span.color {:style {:background hex-color}}]
|
||||
[:span.color-text hex-color]
|
||||
[:span.color-text rgb-color]])]))
|
||||
|
||||
(mx/defcs palette
|
||||
{:mixins [mx/static mx/reactive (mx/local)]}
|
||||
[own]
|
||||
(let [local (:rum/local own)
|
||||
collections (->> (mx/react collections-ref)
|
||||
(vals)
|
||||
(sort-by :name))
|
||||
collection (get-selected-collection local collections)]
|
||||
(letfn [(select-collection [event]
|
||||
(let [value (read-string (dom/event->value event))]
|
||||
(swap! local assoc :selected value)))
|
||||
(close [event]
|
||||
(rs/emit! (dw/toggle-flag :colorpalette)))]
|
||||
[:div.color-palette
|
||||
[:div.color-palette-actions
|
||||
[:select.input-select {:on-change select-collection}
|
||||
(for [collection collections]
|
||||
[:option {:key (str (:id collection))
|
||||
:value (pr-str (:id collection))}
|
||||
(:name collection "Storage")])]
|
||||
#_[:div.color-palette-buttons
|
||||
[:div.btn-palette.edit.current i/pencil]
|
||||
[:div.btn-palette.create i/close]]]
|
||||
[:span.left-arrow i/arrow-slide]
|
||||
(palette-items (:colors collection))
|
||||
[:span.right-arrow i/arrow-slide]
|
||||
[:span.close-palette {:on-click close}
|
||||
i/close]])))
|
||||
|
||||
(defn- colorpalette-will-mount
|
||||
[own]
|
||||
(rs/emit! (dc/fetch-collections))
|
||||
own)
|
||||
|
||||
(mx/defc colorpalette
|
||||
{:mixins [mx/static mx/reactive]
|
||||
:will-mount colorpalette-will-mount}
|
||||
[]
|
||||
(let [flags (mx/react wb/flags-ref)]
|
||||
(when (contains? flags :colorpalette)
|
||||
(palette))))
|
68
frontend/src/uxbox/main/ui/workspace/colorpicker.cljs
Normal file
68
frontend/src/uxbox/main/ui/workspace/colorpicker.cljs
Normal file
|
@ -0,0 +1,68 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.colorpicker
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.colorpicker :as cp]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(defn- focus-shape
|
||||
[id]
|
||||
(-> (l/in [:shapes id])
|
||||
(l/derive st/state)))
|
||||
|
||||
(defn- colorpicker-render
|
||||
[own {:keys [x y shape attr] :as opts}]
|
||||
(let [shape (mx/react (focus-shape shape))
|
||||
left (- x 260)
|
||||
top (- y 50)]
|
||||
(letfn [(change-color [color]
|
||||
(let [attrs {:color color}]
|
||||
(rs/emit!
|
||||
(case attr
|
||||
:stroke (uds/update-stroke-attrs (:id shape) attrs)
|
||||
:fill (uds/update-fill-attrs (:id shape) attrs)))))
|
||||
(on-change-color [event]
|
||||
(let [color (dom/event->value event)]
|
||||
(change-color color)))]
|
||||
(html
|
||||
[:div.colorpicker-tooltip
|
||||
{:style {:left (str left "px")
|
||||
:top (str top "px")}}
|
||||
|
||||
(cp/colorpicker
|
||||
:theme :small
|
||||
:value (get shape attr "#000000")
|
||||
:on-change change-color)
|
||||
|
||||
(recent-colors shape change-color)]))))
|
||||
|
||||
(def colorpicker
|
||||
(mx/component
|
||||
{:render colorpicker-render
|
||||
:name "colorpicker"
|
||||
:mixins [mx/reactive mx/static]}))
|
||||
|
||||
(defmethod lbx/render-lightbox :workspace/colorpicker
|
||||
[params]
|
||||
(colorpicker params))
|
142
frontend/src/uxbox/main/ui/workspace/download.cljs
Normal file
142
frontend/src/uxbox/main/ui/workspace/download.cljs
Normal file
|
@ -0,0 +1,142 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.download
|
||||
(:require [cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.exports :as exports]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.datetime :as dt]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.zip :as zip]
|
||||
[lentes.core :as l]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(defn- resolve-pages
|
||||
[state]
|
||||
(let [project (get-in state [:workspace :project])]
|
||||
(->> (vals (:pages state))
|
||||
(filter #(= project (:project %)))
|
||||
(sort-by :created-at))))
|
||||
|
||||
(def pages-ref
|
||||
(-> (l/lens resolve-pages)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def current-page-ref
|
||||
(-> (l/in [:workspace :page])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Download Lightbox (Component)
|
||||
|
||||
(defn- download-page-svg
|
||||
[{:keys [name id] :as page}]
|
||||
(let [content (exports/render-page id)
|
||||
blob (blob/create content "image/svg+xml")
|
||||
uri (blob/create-uri blob)
|
||||
link (.createElement js/document "a")
|
||||
event (js/MouseEvent. "click")
|
||||
now (dt/now)]
|
||||
|
||||
(.setAttribute link "href" uri)
|
||||
(.setAttribute link "download" (str (str/uslug name) "_"
|
||||
(dt/format now :unix)
|
||||
".svg"))
|
||||
|
||||
(.appendChild (.-body js/document) link)
|
||||
(.dispatchEvent link event)
|
||||
(blob/revoke-uri uri)
|
||||
(.removeChild (.-body js/document) link)))
|
||||
|
||||
(defn- generate-files
|
||||
[pages]
|
||||
(reduce (fn [acc {:keys [id name]}]
|
||||
(let [content (exports/render-page id)]
|
||||
(conj acc [(str (str/uslug name) ".svg")
|
||||
(blob/create content "image/svg+xml")])))
|
||||
[]
|
||||
pages))
|
||||
|
||||
(defn- download-project-zip
|
||||
[{:keys [name] :as project} pages]
|
||||
(let [event (js/MouseEvent. "click")
|
||||
link (.createElement js/document "a")
|
||||
now (dt/now)
|
||||
stream (->> (rx/from-coll (generate-files pages))
|
||||
(rx/reduce conj [])
|
||||
(rx/mapcat zip/build)
|
||||
(rx/map blob/create-uri)
|
||||
(rx/take 1))
|
||||
download (str (str/uslug name) "_" (dt/format now :unix) ".zip")]
|
||||
(rx/subscribe stream (fn [uri]
|
||||
(.setAttribute link "download" download)
|
||||
(.setAttribute link "href" uri)
|
||||
(.appendChild (.-body js/document) link)
|
||||
(.dispatchEvent link event)
|
||||
(blob/revoke-uri uri)
|
||||
(.removeChild (.-body js/document) link)))))
|
||||
|
||||
(mx/defcs download-dialog
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own]
|
||||
(let [project (mx/react wb/project-ref)
|
||||
pages (mx/react pages-ref)
|
||||
current (mx/react current-page-ref)]
|
||||
(letfn [(on-close [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!))
|
||||
(download-page [event]
|
||||
(dom/prevent-default event)
|
||||
(let [id (-> (mx/ref-node own "page")
|
||||
(dom/get-value)
|
||||
(read-string))
|
||||
page (->> pages
|
||||
(filter #(= id (:id %)))
|
||||
(first))]
|
||||
(download-page-svg page)
|
||||
(udl/close!)))
|
||||
(download-zip [event]
|
||||
(dom/prevent-default event)
|
||||
(download-project-zip project pages)
|
||||
(udl/close!))
|
||||
(download-html [event]
|
||||
(dom/prevent-default event))]
|
||||
[:div.lightbox-body.export-dialog
|
||||
[:h3 "Export options"]
|
||||
[:div.row-flex
|
||||
[:div.content-col
|
||||
[:span.icon i/file-svg]
|
||||
[:span.title "Export page"]
|
||||
[:p.info "Download a single page of your project in SVG."]
|
||||
[:select.input-select {:ref "page" :default-value (pr-str current)}
|
||||
(for [{:keys [id name]} pages]
|
||||
[:option {:value (pr-str id)} name])]
|
||||
[:a.btn-primary {:href "#" :on-click download-page} "Export page"]]
|
||||
[:div.content-col
|
||||
[:span.icon i/folder-zip]
|
||||
[:span.title "Export project"]
|
||||
[:p.info "Download the whole project as a ZIP file."]
|
||||
[:a.btn-primary {:href "#" :on-click download-zip} "Expor project"]]
|
||||
[:div.content-col
|
||||
[:span.icon i/file-html]
|
||||
[:span.title "Export as HTML"]
|
||||
[:p.info "Download your project as HTML files."]
|
||||
[:a.btn-primary {:href "#" :on-click download-html} "Export HTML"]]]
|
||||
[:a.close {:href "#" :on-click on-close} i/close]])))
|
||||
|
||||
(defmethod lbx/render-lightbox :download
|
||||
[_]
|
||||
(download-dialog))
|
311
frontend/src/uxbox/main/ui/workspace/drawarea.cljs
Normal file
311
frontend/src/uxbox/main/ui/workspace/drawarea.cljs
Normal file
|
@ -0,0 +1,311 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.drawarea
|
||||
"Draw interaction and component."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.shapes :as shapes]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.rlocks :as rlocks]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.path :as path]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- State
|
||||
|
||||
(defonce drawing-stoper (rx/bus))
|
||||
(defonce drawing-shape (atom nil))
|
||||
(defonce drawing-position (atom nil))
|
||||
|
||||
(def ^:private canvas-coords
|
||||
(gpt/point c/canvas-start-x
|
||||
c/canvas-start-y))
|
||||
|
||||
;; --- Draw Area (Component)
|
||||
|
||||
(declare watch-draw-actions)
|
||||
(declare on-init-draw)
|
||||
|
||||
(defn- watch-draw-actions
|
||||
[]
|
||||
(let [stream (->> (rx/map first rlocks/stream)
|
||||
(rx/filter #(= % :ui/draw)))]
|
||||
(rx/subscribe stream on-init-draw)))
|
||||
|
||||
(defn- draw-area-will-mount
|
||||
[own]
|
||||
(assoc own ::sub (watch-draw-actions)))
|
||||
|
||||
(defn- draw-area-will-unmount
|
||||
[own]
|
||||
(.close (::sub own))
|
||||
(dissoc own ::sub))
|
||||
|
||||
(declare generic-shape-draw-area)
|
||||
(declare path-shape-draw-area)
|
||||
|
||||
(mx/defc draw-area
|
||||
{:will-mount draw-area-will-mount
|
||||
:will-unmount draw-area-will-unmount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [shape (mx/react drawing-shape)
|
||||
position (mx/react drawing-position)]
|
||||
(when shape
|
||||
(if (= (:type shape) :path)
|
||||
(path-shape-draw-area shape)
|
||||
(generic-shape-draw-area shape position)))))
|
||||
|
||||
(mx/defc generic-shape-draw-area
|
||||
[shape position]
|
||||
(if position
|
||||
(-> (assoc shape :drawing? true)
|
||||
(geom/resize position)
|
||||
(shapes/render-component))
|
||||
(-> (assoc shape :drawing? true)
|
||||
(shapes/render-component))))
|
||||
|
||||
(mx/defc path-shape-draw-area
|
||||
[{:keys [points] :as shape}]
|
||||
(letfn [(on-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! drawing-shape drop-last-point)
|
||||
(rx/push! drawing-stoper true))
|
||||
(drop-last-point [shape]
|
||||
(let [points (:points shape)
|
||||
points (vec (butlast points))]
|
||||
(assoc shape :points points :close? true)))]
|
||||
(let [{:keys [x y]} (first points)]
|
||||
[:g
|
||||
(-> (assoc shape :drawing? true)
|
||||
(shapes/render-component))
|
||||
(when-not (:free shape)
|
||||
[:circle {:cx x
|
||||
:cy y
|
||||
:r 5
|
||||
:fill "red"
|
||||
:on-click on-click}])])))
|
||||
|
||||
;; --- Drawing Initialization
|
||||
|
||||
(declare on-init-draw-icon)
|
||||
(declare on-init-draw-path)
|
||||
(declare on-init-draw-free-path)
|
||||
(declare on-init-draw-generic)
|
||||
|
||||
(defn- on-init-draw
|
||||
"Function execution when draw shape operation is requested.
|
||||
This is a entry point for the draw interaction."
|
||||
[]
|
||||
(when-let [shape (:drawing @wb/workspace-ref)]
|
||||
(case (:type shape)
|
||||
:icon (on-init-draw-icon shape)
|
||||
:image (on-init-draw-icon shape)
|
||||
:path (if (:free shape)
|
||||
(on-init-draw-free-path shape)
|
||||
(on-init-draw-path shape))
|
||||
(on-init-draw-generic shape))))
|
||||
|
||||
;; --- Icon Drawing
|
||||
|
||||
(defn- on-init-draw-icon
|
||||
[{:keys [metadata] :as shape}]
|
||||
(let [{:keys [x y]} (gpt/divide @wb/mouse-canvas-a @wb/zoom-ref)
|
||||
{:keys [width height]} metadata
|
||||
proportion (/ width height)
|
||||
props {:x1 x
|
||||
:y1 y
|
||||
:x2 (+ x 200)
|
||||
:y2 (+ y (/ 200 proportion))}
|
||||
shape (geom/setup shape props)]
|
||||
(rs/emit! (uds/add-shape shape)
|
||||
(udw/select-for-drawing nil)
|
||||
(uds/select-first-shape))
|
||||
(rlocks/release! :ui/draw)))
|
||||
|
||||
;; --- Path Drawing
|
||||
|
||||
(def ^:private immanted-zones
|
||||
(let [transform #(vector (- % 7) (+ % 7) %)]
|
||||
(concat
|
||||
(mapv transform (range 0 181 15))
|
||||
(mapv (comp transform -) (range 0 181 15)))))
|
||||
|
||||
(defn- align-position
|
||||
[angle pos]
|
||||
(reduce (fn [pos [a1 a2 v]]
|
||||
(if (< a1 angle a2)
|
||||
(reduced (gpt/update-angle pos v))
|
||||
pos))
|
||||
pos
|
||||
immanted-zones))
|
||||
|
||||
(defn- translate-to-canvas
|
||||
[point]
|
||||
(let [zoom @wb/zoom-ref
|
||||
ccords (gpt/multiply canvas-coords zoom)]
|
||||
(-> point
|
||||
(gpt/subtract ccords)
|
||||
(gpt/divide zoom))))
|
||||
|
||||
(defn- conditional-align
|
||||
[point]
|
||||
(if @wb/alignment-ref
|
||||
(uds/align-point point)
|
||||
(rx/of point)))
|
||||
|
||||
(defn- on-init-draw-path
|
||||
[shape]
|
||||
(letfn [(stoper-event? [[type opts]]
|
||||
(or (and (= type :key/down)
|
||||
(= (:key opts) 13))
|
||||
(and (= type :mouse/double-click)
|
||||
(true? (:shift? opts)))
|
||||
(= type :mouse/right-click)))
|
||||
|
||||
(new-point-event? [[type opts]]
|
||||
(and (= type :mouse/click)
|
||||
(false? (:shift? opts))))]
|
||||
|
||||
(let [mouse (->> (rx/sample 10 wb/mouse-viewport-s)
|
||||
(rx/mapcat conditional-align)
|
||||
(rx/map translate-to-canvas))
|
||||
stoper (->> (rx/merge
|
||||
(rx/take 1 drawing-stoper)
|
||||
(rx/filter stoper-event? wb/events-s))
|
||||
(rx/take 1))
|
||||
firstpos (rx/take 1 mouse)
|
||||
stream (->> (rx/take-until stoper mouse)
|
||||
(rx/skip-while #(nil? @drawing-shape))
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))
|
||||
ptstream (->> (rx/take-until stoper wb/events-s)
|
||||
(rx/filter new-point-event?)
|
||||
(rx/with-latest-from vector mouse)
|
||||
(rx/map second))
|
||||
counter (atom 0)]
|
||||
(letfn [(append-point [{:keys [type] :as shape} point]
|
||||
(let [point (gpt/point point)]
|
||||
(update shape :points conj point)))
|
||||
|
||||
(update-point [{:keys [type points] :as shape} point index]
|
||||
(let [point (gpt/point point)
|
||||
points (:points shape)]
|
||||
(assoc-in shape [:points index] point)))
|
||||
|
||||
(on-first-point [point]
|
||||
(let [shape (append-point shape point)]
|
||||
(swap! counter inc)
|
||||
(reset! drawing-shape shape)))
|
||||
|
||||
(on-click [point]
|
||||
(let [shape (append-point @drawing-shape point)]
|
||||
(swap! counter inc)
|
||||
(reset! drawing-shape shape)))
|
||||
|
||||
(on-assisted-draw [point]
|
||||
(let [center (get-in @drawing-shape [:points (dec @counter)])
|
||||
point (as-> point $
|
||||
(gpt/subtract $ center)
|
||||
(align-position (gpt/angle $) $)
|
||||
(gpt/add $ center))]
|
||||
(->> (update-point @drawing-shape point @counter)
|
||||
(reset! drawing-shape))))
|
||||
|
||||
(on-free-draw [point]
|
||||
(->> (update-point @drawing-shape point @counter)
|
||||
(reset! drawing-shape)))
|
||||
|
||||
(on-draw [[point ctrl?]]
|
||||
(if ctrl?
|
||||
(on-assisted-draw point)
|
||||
(on-free-draw point)))
|
||||
|
||||
(on-end []
|
||||
(let [shape @drawing-shape]
|
||||
(rs/emit! (uds/add-shape shape)
|
||||
(udw/select-for-drawing nil)
|
||||
(uds/select-first-shape))
|
||||
(reset! drawing-shape nil)
|
||||
(reset! drawing-position nil)
|
||||
(rlocks/release! :ui/draw)))]
|
||||
|
||||
(rx/subscribe firstpos on-first-point)
|
||||
(rx/subscribe ptstream on-click)
|
||||
(rx/subscribe stream on-draw nil on-end)))))
|
||||
|
||||
(defn- on-init-draw-free-path
|
||||
[shape]
|
||||
(let [mouse (->> (rx/sample 10 wb/mouse-viewport-s)
|
||||
(rx/mapcat conditional-align)
|
||||
(rx/map translate-to-canvas))
|
||||
stoper (->> wb/events-s
|
||||
(rx/map first)
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (rx/take-until stoper mouse)]
|
||||
(letfn [(simplify-shape [{:keys [points] :as shape}]
|
||||
(let [prevnum (count points)
|
||||
points (path/simplify points 0.2)]
|
||||
(println "path simplification: previous=" prevnum
|
||||
" current=" (count points))
|
||||
(assoc shape :points points)))
|
||||
|
||||
(on-draw [point]
|
||||
(let [point (gpt/point point)
|
||||
shape (-> (or @drawing-shape shape)
|
||||
(update :points conj point))]
|
||||
(reset! drawing-shape shape)))
|
||||
|
||||
(on-end []
|
||||
(let [shape (simplify-shape @drawing-shape)]
|
||||
(rs/emit! (uds/add-shape shape)
|
||||
(udw/select-for-drawing nil)
|
||||
(uds/select-first-shape))
|
||||
(reset! drawing-shape nil)
|
||||
(reset! drawing-position nil)
|
||||
(rlocks/release! :ui/draw)))]
|
||||
|
||||
(rx/subscribe stream on-draw nil on-end))))
|
||||
|
||||
(defn- on-init-draw-generic
|
||||
[shape]
|
||||
(let [mouse (->> wb/mouse-viewport-s
|
||||
(rx/mapcat conditional-align)
|
||||
(rx/map translate-to-canvas))
|
||||
stoper (->> wb/events-s
|
||||
(rx/map first)
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
firstpos (rx/take 1 mouse)
|
||||
stream (->> (rx/take-until stoper mouse)
|
||||
(rx/skip-while #(nil? @drawing-shape))
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))]
|
||||
|
||||
(letfn [(on-start [{:keys [x y] :as pt}]
|
||||
(let [shape (geom/setup shape {:x1 x :y1 y :x2 x :y2 y})]
|
||||
(reset! drawing-shape shape)))
|
||||
|
||||
(on-draw [[pt ctrl?]]
|
||||
(reset! drawing-position (assoc pt :lock ctrl?)))
|
||||
(on-end []
|
||||
(let [shape @drawing-shape
|
||||
shpos @drawing-position
|
||||
shape (geom/resize shape shpos)]
|
||||
(rs/emit! (uds/add-shape shape)
|
||||
(udw/select-for-drawing nil)
|
||||
(uds/select-first-shape))
|
||||
(reset! drawing-position nil)
|
||||
(reset! drawing-shape nil)
|
||||
(rlocks/release! :ui/draw)))]
|
||||
(rx/subscribe firstpos on-start)
|
||||
(rx/subscribe stream on-draw nil on-end))))
|
58
frontend/src/uxbox/main/ui/workspace/grid.cljs
Normal file
58
frontend/src/uxbox/main/ui/workspace/grid.cljs
Normal file
|
@ -0,0 +1,58 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.grid
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.base :as wb]))
|
||||
|
||||
;; --- Grid (Component)
|
||||
|
||||
(declare vertical-line)
|
||||
(declare horizontal-line)
|
||||
|
||||
(defn- grid-render
|
||||
[own]
|
||||
(let [options (:options (mx/react wb/page-ref))
|
||||
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))]
|
||||
(html
|
||||
[:g.grid {:style {:pointer-events "none"}}
|
||||
[:path {:d (str/join " " path) :stroke color :opacity "0.3"}]])))
|
||||
|
||||
(def grid
|
||||
(mx/component
|
||||
{:render grid-render
|
||||
:name "grid"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn- horizontal-line
|
||||
[width acc value]
|
||||
(let [pos (+ value c/canvas-start-y)]
|
||||
(conj acc (str/format "M %s %s L %s %s" 0 pos width pos))))
|
||||
|
||||
(defn- vertical-line
|
||||
[height acc value]
|
||||
(let [pos (+ value c/canvas-start-y)]
|
||||
(conj acc (str/format "M %s %s L %s %s" pos 0 pos height))))
|
160
frontend/src/uxbox/main/ui/workspace/header.cljs
Normal file
160
frontend/src/uxbox/main/ui/workspace/header.cljs
Normal file
|
@ -0,0 +1,160 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.header
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.workspace.clipboard]
|
||||
[uxbox.main.ui.workspace.settings]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.users :as ui.u]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.math :as mth]))
|
||||
|
||||
;; --- Coordinates Widget
|
||||
|
||||
(defn- coordenates-render
|
||||
[own]
|
||||
(let [zoom (mx/react wb/zoom-ref)
|
||||
coords (some-> (mx/react wb/mouse-canvas-a)
|
||||
(gpt/divide zoom)
|
||||
(gpt/round 1))
|
||||
increase #(rs/emit! (dw/increase-zoom))
|
||||
decrease #(rs/emit! (dw/decrease-zoom))]
|
||||
(html
|
||||
[:ul.options-view
|
||||
[:li.coordinates {:alt "x"}
|
||||
(str "X: " (:x coords "-"))]
|
||||
[:li.coordinates {:alt "y"}
|
||||
(str "Y: " (:y coords "-"))]
|
||||
[:li.zoom-input
|
||||
[:span.add-zoom {:on-click increase} "+"]
|
||||
[:span (str (mth/round (* 100 zoom)) "%")]
|
||||
[:span.remove-zoom {:on-click decrease} "-"]]])))
|
||||
|
||||
(def coordinates
|
||||
(mx/component
|
||||
{:render coordenates-render
|
||||
:name "coordenates"
|
||||
:mixins [mx/reactive]}))
|
||||
|
||||
;; --- Header Component
|
||||
|
||||
(defn on-view-clicked
|
||||
[event project page]
|
||||
(let [token (:share-token project)
|
||||
index (:index page)
|
||||
url (str cfg/viewurl "#/" token "/" index)]
|
||||
(js/open url "new tab" "")))
|
||||
|
||||
(defn header-render
|
||||
[own]
|
||||
(let [project (mx/react wb/project-ref)
|
||||
page (mx/react wb/page-ref)
|
||||
flags (mx/react wb/flags-ref)
|
||||
toggle #(rs/emit! (dw/toggle-flag %))
|
||||
on-undo #(rs/emit! (udh/backwards-to-previous-version))
|
||||
on-redo #(rs/emit! (udh/forward-to-next-version))
|
||||
on-image #(udl/open! :import-image)
|
||||
on-download #(udl/open! :download)]
|
||||
(html
|
||||
[:header#workspace-bar.workspace-bar
|
||||
[:div.main-icon
|
||||
(nav/link (r/route-for :dashboard/projects) i/logo-icon)]
|
||||
[:div.project-tree-btn
|
||||
{:alt "Sitemap (Ctrl + Shift + M)"
|
||||
:class (when (contains? flags :sitemap) "selected")
|
||||
:on-click (partial toggle :sitemap)}
|
||||
i/project-tree
|
||||
[:span (:name page)]]
|
||||
[:div.workspace-options
|
||||
[:ul.options-btn
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Shapes (Ctrl + Shift + S)"
|
||||
:class (when (contains? flags :drawtools) "selected")
|
||||
:on-click (partial toggle :drawtools)}
|
||||
i/shapes]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Color Palette (---)"
|
||||
:class (when (contains? flags :colorpalette) "selected")
|
||||
:on-click (partial toggle :colorpalette)}
|
||||
i/palette]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Icons (Ctrl + Shift + I)"
|
||||
:class (when (contains? flags :icons) "selected")
|
||||
:on-click (partial toggle :icons)}
|
||||
i/icon-set]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Layers (Ctrl + Shift + L)"
|
||||
:class (when (contains? flags :layers) "selected")
|
||||
:on-click (partial toggle :layers)}
|
||||
i/layers]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Element options (Ctrl + Shift + O)"
|
||||
:class (when (contains? flags :element-options) "selected")
|
||||
:on-click (partial toggle :element-options)}
|
||||
i/options]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "History (Ctrl + Shift + H)"
|
||||
:class (when (contains? flags :document-history) "selected")
|
||||
:on-click (partial toggle :document-history)}
|
||||
i/undo-history]]
|
||||
[:ul.options-btn
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Undo (Ctrl + Z)"
|
||||
:on-click on-undo}
|
||||
i/undo]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Redo (Ctrl + Shift + Z)"
|
||||
:on-click on-redo}
|
||||
i/redo]]
|
||||
[:ul.options-btn
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Export (Ctrl + E)"
|
||||
:on-click on-download}
|
||||
i/export]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Image (Ctrl + I)"
|
||||
:on-click on-image}
|
||||
i/image]]
|
||||
[:ul.options-btn
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Ruler (Ctrl + R)"
|
||||
:class (when (contains? flags :ruler) "selected")
|
||||
:on-click (partial toggle :ruler)}
|
||||
i/ruler]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Grid (Ctrl + G)"
|
||||
:class (when (contains? flags :grid) "selected")
|
||||
:on-click (partial toggle :grid)}
|
||||
i/grid]
|
||||
[:li.tooltip.tooltip-bottom
|
||||
{:alt "Align (Ctrl + A)"}
|
||||
i/alignment]]
|
||||
[:ul.options-btn
|
||||
[:li.tooltip.tooltip-bottom.view-mode
|
||||
{:alt "View mode (Ctrl + P)"
|
||||
:on-click #(on-view-clicked % project page)}
|
||||
i/play]]
|
||||
(coordinates)]
|
||||
(ui.u/user)])))
|
||||
|
||||
(def header
|
||||
(mx/component
|
||||
{:render header-render
|
||||
:name "workspace-header"
|
||||
:mixins [mx/static mx/reactive]}))
|
187
frontend/src/uxbox/main/ui/workspace/images.cljs
Normal file
187
frontend/src/uxbox/main/ui/workspace/images.cljs
Normal file
|
@ -0,0 +1,187 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.images
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.data :as data :refer (read-string)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.images :as udi]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.lightbox :as lbx]))
|
||||
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private dashboard-ref
|
||||
(-> (l/in [:dashboard :images])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private collections-ref
|
||||
(-> (l/key :images-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private images-ref
|
||||
(-> (l/key :images)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private uploading?-ref
|
||||
(-> (l/key :uploading)
|
||||
(l/derive dashboard-ref)))
|
||||
|
||||
;; --- Components
|
||||
|
||||
(mx/defcs import-image-lightbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own]
|
||||
(letfn [(on-upload-click [event]
|
||||
(let [input (mx/ref-node own "input")]
|
||||
(dom/click input)))
|
||||
(on-uploaded [[image]]
|
||||
(let [{:keys [id name width height]} image
|
||||
shape {:type :image
|
||||
:name name
|
||||
:metadata {:width width
|
||||
:height height}
|
||||
:image id}]
|
||||
(rs/emit! (udw/select-for-drawing shape))
|
||||
(udl/close!)))
|
||||
(on-files-selected [event]
|
||||
(let [files (dom/get-event-files event)]
|
||||
(rs/emit! (udi/create-images nil files on-uploaded))))
|
||||
(on-select-from-library [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/open! :import-image-from-collections))
|
||||
(on-close [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!))]
|
||||
(let [uploading? (mx/react uploading?-ref)]
|
||||
[:div.lightbox-body
|
||||
[:h3 "New image"]
|
||||
[:div.row-flex
|
||||
[:div.lightbox-big-btn
|
||||
{:on-click on-select-from-library}
|
||||
[:span.big-svg i/image]
|
||||
[:span.text "Select from library"]]
|
||||
[:div.lightbox-big-btn
|
||||
{:on-click on-upload-click}
|
||||
(if uploading?
|
||||
[:span.big-svg.upload i/loader-pencil]
|
||||
[:span.big-svg.upload i/exit])
|
||||
[:span.text "Upload file"]
|
||||
[:input.upload-image-input
|
||||
{:style {:display "none"}
|
||||
:accept "image/jpeg,image/png"
|
||||
:type "file"
|
||||
:ref "input"
|
||||
:on-change on-files-selected}]]]
|
||||
[:a.close {:on-click on-close} i/close]])))
|
||||
|
||||
(mx/defc image-item
|
||||
{:mixins [mx/static]}
|
||||
[{:keys [thumbnail name id width height] :as image}]
|
||||
(letfn [(on-double-click [event]
|
||||
(let [shape {:type :image
|
||||
:name name
|
||||
:metadata {:width width
|
||||
:height height}
|
||||
:image id}]
|
||||
(rs/emit! (udw/select-for-drawing shape))
|
||||
(udl/close!)))]
|
||||
[:div.library-item {:key (str id)
|
||||
:on-double-click on-double-click}
|
||||
[:div.library-item-th
|
||||
{:style {:background-image (str "url('" thumbnail "')")}}]
|
||||
[:span name]]))
|
||||
|
||||
(mx/defc image-collection
|
||||
{:mixins [mx/static]}
|
||||
[images]
|
||||
[:div.library-content
|
||||
(for [image images]
|
||||
(-> (image-item image)
|
||||
(mx/with-key (str (:id image)))))])
|
||||
|
||||
(defn will-mount
|
||||
[own]
|
||||
(let [local (:rum/local own)]
|
||||
(rs/emit! (udi/fetch-collections))
|
||||
(rs/emit! (udi/fetch-images nil))
|
||||
(add-watch local ::key (fn [_ _ _ v]
|
||||
(rs/emit! (udi/fetch-images (:id v)))))
|
||||
own))
|
||||
|
||||
(defn will-unmount
|
||||
[own]
|
||||
(let [local (:rum/local own)]
|
||||
(remove-watch local ::key)
|
||||
own))
|
||||
|
||||
(mx/defcs image-collections-lightbox
|
||||
{:mixins [mx/reactive (mx/local)]
|
||||
:will-mount will-mount
|
||||
:will-unmount will-unmount}
|
||||
[own]
|
||||
(let [local (:rum/local own)
|
||||
id (:id @local)
|
||||
type (:type @local :own)
|
||||
own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
colls (mx/react collections-ref)
|
||||
colls (->> (vals colls)
|
||||
(filter #(= type (:type %)))
|
||||
(sort-by :name))
|
||||
id (if (and (nil? id) builtin?)
|
||||
(:id (first colls) ::no-value)
|
||||
id)
|
||||
images (mx/react images-ref)
|
||||
images (->> (vals images)
|
||||
(filter #(= id (:collection %))))]
|
||||
(letfn [(on-close [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!))
|
||||
(select-type [event type]
|
||||
(swap! local assoc :type type))
|
||||
(on-coll-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (read-string value)]
|
||||
(swap! local assoc :id value)))]
|
||||
[:div.lightbox-body.big-lightbox
|
||||
[:h3 "Import image from library"]
|
||||
[:div.import-img-library
|
||||
[:div.library-actions
|
||||
[:ul.toggle-library
|
||||
[:li.your-images {:class (when own? "current")
|
||||
:on-click #(select-type % :own)}
|
||||
"YOUR IMAGES"]
|
||||
[:li.standard {:class (when builtin? "current")
|
||||
:on-click #(select-type % :builtin)}
|
||||
"IMAGES STORE"]]
|
||||
[:select.input-select {:on-change on-coll-change}
|
||||
(when own?
|
||||
[:option {:value (pr-str nil)} "Storage"])
|
||||
(for [coll colls
|
||||
:let [id (:id coll)
|
||||
name (:name coll)]]
|
||||
[:option {:key (str id) :value (pr-str id)} name])]]
|
||||
(image-collection images)]
|
||||
[:a.close {:href "#" :on-click on-close} i/close]])))
|
||||
|
||||
(defmethod lbx/render-lightbox :import-image
|
||||
[_]
|
||||
(import-image-lightbox))
|
||||
|
||||
|
||||
(defmethod lbx/render-lightbox :import-image-from-collections
|
||||
[_]
|
||||
(image-collections-lightbox))
|
64
frontend/src/uxbox/main/ui/workspace/recent_colors.cljs
Normal file
64
frontend/src/uxbox/main/ui/workspace/recent_colors.cljs
Normal file
|
@ -0,0 +1,64 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.recent-colors
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.workspace.base :as wb]))
|
||||
|
||||
;; --- 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) $ shapes)
|
||||
(reduce #(count-color %1 %2 :stroke) $ shapes)
|
||||
(remove nil? $)
|
||||
(sort-by second (into [] $))
|
||||
(take 5 (map first $))))
|
||||
|
||||
;; --- Component
|
||||
|
||||
(defn- recent-colors-render
|
||||
[own {:keys [page id] :as shape} callback]
|
||||
(let [shapes-by-id (mx/react wb/shapes-by-id-ref)
|
||||
shapes (->> (vals shapes-by-id)
|
||||
(filter #(= (:page %) page)))
|
||||
colors (calculate-colors shapes)]
|
||||
(html
|
||||
[: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]]])))
|
||||
|
||||
(def recent-colors
|
||||
(mx/component
|
||||
{:render recent-colors-render
|
||||
:name "recent-colors"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
|
150
frontend/src/uxbox/main/ui/workspace/ruler.cljs
Normal file
150
frontend/src/uxbox/main/ui/workspace/ruler.cljs
Normal file
|
@ -0,0 +1,150 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.ruler
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(def ^:private immanted-zones
|
||||
(let [transform #(vector (- % 7) (+ % 7) %)]
|
||||
(concat
|
||||
(mapv transform (range 0 181 15))
|
||||
(mapv (comp transform -) (range 0 181 15)))))
|
||||
|
||||
(defn- resolve-position
|
||||
[own pt]
|
||||
(let [overlay (mx/ref-node own "overlay")
|
||||
brect (.getBoundingClientRect overlay)
|
||||
bpt (gpt/point (.-left brect) (.-top brect))]
|
||||
(gpt/subtract pt bpt)))
|
||||
|
||||
(defn- get-position
|
||||
[own event]
|
||||
(->> (gpt/point (.-clientX event)
|
||||
(.-clientY event))
|
||||
(resolve-position own)))
|
||||
|
||||
(defn- on-mouse-down
|
||||
[own local event]
|
||||
(dom/stop-propagation event)
|
||||
(let [pos (get-position own event)]
|
||||
(reset! local {:active true :pos1 pos :pos2 pos})))
|
||||
|
||||
(defn- on-mouse-up
|
||||
[own local event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local assoc :active false))
|
||||
|
||||
(defn- align-position
|
||||
[angle pos]
|
||||
(reduce (fn [pos [a1 a2 v]]
|
||||
(if (< a1 angle a2)
|
||||
(reduced (gpt/update-angle pos v))
|
||||
pos))
|
||||
pos
|
||||
immanted-zones))
|
||||
|
||||
(defn- overlay-will-mount
|
||||
[own local]
|
||||
(letfn [(on-value-aligned [pos2]
|
||||
(let [center (:pos1 @local)]
|
||||
(as-> pos2 $
|
||||
(gpt/subtract $ center)
|
||||
(align-position (gpt/angle $) $)
|
||||
(gpt/add $ center)
|
||||
(swap! local assoc :pos2 $))))
|
||||
|
||||
(on-value-simple [pos2]
|
||||
(swap! local assoc :pos2 pos2))
|
||||
|
||||
(on-value [[pos ctrl?]]
|
||||
(if ctrl?
|
||||
(on-value-aligned pos)
|
||||
(on-value-simple pos)))]
|
||||
|
||||
(let [stream (->> wb/mouse-absolute-s
|
||||
(rx/filter #(:active @local))
|
||||
(rx/map #(resolve-position own %))
|
||||
(rx/with-latest-from vector wb/mouse-ctrl-s))
|
||||
sub (rx/on-value stream on-value)]
|
||||
(assoc own ::sub sub))))
|
||||
|
||||
(defn- overlay-will-unmount
|
||||
[own]
|
||||
(let [subscription (::sub own)]
|
||||
(subscription)
|
||||
(dissoc own ::sub)))
|
||||
|
||||
(declare overlay-line-render)
|
||||
|
||||
(defn- overlay-render
|
||||
[own local]
|
||||
(let [p1 (:pos1 @local)
|
||||
p2 (:pos2 @local)]
|
||||
(html
|
||||
[:svg {:on-mouse-down #(on-mouse-down own local %)
|
||||
:on-mouse-up #(on-mouse-up own local %)
|
||||
:ref "overlay"}
|
||||
[:rect {:style {:fill "transparent"
|
||||
:stroke "transparent"
|
||||
:cursor "cell"}
|
||||
:width c/viewport-width
|
||||
:height c/viewport-height}]
|
||||
(if (and p1 p2)
|
||||
(overlay-line-render own p1 p2))])))
|
||||
|
||||
(def overlay
|
||||
(mx/component
|
||||
{:render #(overlay-render % (:rum/local %))
|
||||
:will-mount #(overlay-will-mount % (:rum/local %))
|
||||
:will-unmount overlay-will-unmount
|
||||
:name "overlay"
|
||||
:mixins [mx/static (mx/local) mx/reactive]}))
|
||||
|
||||
(defn- overlay-line-render
|
||||
[own center pt]
|
||||
(let [distance (-> (gpt/distance
|
||||
(gpt/divide pt @wb/zoom-ref)
|
||||
(gpt/divide center @wb/zoom-ref))
|
||||
(mth/precision 4))
|
||||
angle (-> (gpt/angle pt center)
|
||||
(mth/precision 4))
|
||||
{x1 :x y1 :y} center
|
||||
{x2 :x y2 :y} pt]
|
||||
(html
|
||||
[:g
|
||||
[:line {:x1 x1 :y1 y1
|
||||
:x2 x2 :y2 y2
|
||||
:style {:cursor "cell"}
|
||||
:stroke-width "1"
|
||||
:stroke "red"}]
|
||||
[:text
|
||||
{:transform (str "translate(" (+ x2 15) "," (- y2 10) ")")}
|
||||
[:tspan {:x "0" :dy="1.2em"}
|
||||
(str distance " px")]
|
||||
[:tspan {:x "0" :y "20" :dy="1.2em"}
|
||||
(str angle "°")]]])))
|
||||
|
||||
(defn- ruler-render
|
||||
[own]
|
||||
(let [flags (mx/react wb/flags-ref)]
|
||||
(when (contains? flags :ruler)
|
||||
(overlay))))
|
||||
|
||||
(def ruler
|
||||
(mx/component
|
||||
{:render ruler-render
|
||||
:name "ruler"
|
||||
:mixins [mx/static mx/reactive (mx/local)]}))
|
187
frontend/src/uxbox/main/ui/workspace/rules.cljs
Normal file
187
frontend/src/uxbox/main/ui/workspace/rules.cljs
Normal file
|
@ -0,0 +1,187 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.rules
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.state :as s]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.mixins :as mx :include-macros true]))
|
||||
|
||||
;; --- Constants & Helpers
|
||||
|
||||
(def step-padding 20)
|
||||
(def step-size 10)
|
||||
|
||||
(defn big-ticks-mod [zoom] (/ 100 zoom))
|
||||
(defn mid-ticks-mod [zoom] (/ 50 zoom))
|
||||
|
||||
(def +ticks+
|
||||
(concat (range (- (/ c/viewport-width 1)) 0 step-size)
|
||||
(range 0 (/ c/viewport-width 1) step-size)))
|
||||
|
||||
(def rule-padding 20)
|
||||
|
||||
(defn- make-vertical-tick
|
||||
[zoom acc value]
|
||||
(let [big-ticks-mod (big-ticks-mod zoom)
|
||||
mid-ticks-mod (mid-ticks-mod zoom)
|
||||
pos (+ (* value zoom)
|
||||
rule-padding
|
||||
(* c/canvas-start-x zoom)
|
||||
c/canvas-scroll-padding)]
|
||||
(cond
|
||||
(< (mod value big-ticks-mod) step-size)
|
||||
(conj acc (str/format "M %s %s L %s %s" pos 5 pos step-padding))
|
||||
|
||||
(< (mod value mid-ticks-mod) step-size)
|
||||
(conj acc (str/format "M %s %s L %s %s" pos 10 pos step-padding))
|
||||
|
||||
:else
|
||||
(conj acc (str/format "M %s %s L %s %s" pos 15 pos step-padding)))))
|
||||
|
||||
(defn- make-horizontal-tick
|
||||
[zoom acc value]
|
||||
(let [big-ticks-mod (big-ticks-mod zoom)
|
||||
mid-ticks-mod (mid-ticks-mod zoom)
|
||||
pos (+ (* value zoom)
|
||||
(* c/canvas-start-x zoom)
|
||||
c/canvas-scroll-padding)]
|
||||
(cond
|
||||
(< (mod value big-ticks-mod) step-size)
|
||||
(conj acc (str/format "M %s %s L %s %s" 5 pos step-padding pos))
|
||||
|
||||
(< (mod value mid-ticks-mod) step-size)
|
||||
(conj acc (str/format "M %s %s L %s %s" 10 pos step-padding pos))
|
||||
|
||||
:else
|
||||
(conj acc (str/format "M %s %s L %s %s" 15 pos step-padding pos)))))
|
||||
|
||||
;; --- Horizontal Text Label
|
||||
|
||||
(defn- horizontal-text-label
|
||||
[zoom value]
|
||||
(let [big-ticks-mod (big-ticks-mod zoom)
|
||||
pos (+ (* value zoom)
|
||||
rule-padding
|
||||
(* c/canvas-start-x zoom)
|
||||
c/canvas-scroll-padding)]
|
||||
(when (< (mod value big-ticks-mod) step-size)
|
||||
(html
|
||||
[:text {:x (+ pos 2)
|
||||
:y 13
|
||||
:key (str pos)
|
||||
:fill "#9da2a6"
|
||||
:style {:font-size "12px"}}
|
||||
value]))))
|
||||
|
||||
;; --- Horizontal Text Label
|
||||
|
||||
(defn- vertical-text-label
|
||||
[zoom value]
|
||||
(let [big-ticks-mod (big-ticks-mod zoom)
|
||||
pos (+ (* value zoom)
|
||||
(* c/canvas-start-x zoom)
|
||||
;; c/canvas-start-x
|
||||
c/canvas-scroll-padding)]
|
||||
(when (< (mod value big-ticks-mod) step-size)
|
||||
(html
|
||||
[:text {:y (- pos 3)
|
||||
:x 5
|
||||
:key (str pos)
|
||||
:fill "#9da2a6"
|
||||
:transform (str/format "rotate(90 0 %s)" pos)
|
||||
:style {:font-size "12px"}}
|
||||
value]))))
|
||||
|
||||
;; --- Horizontal Rule Ticks (Component)
|
||||
|
||||
(defn- horizontal-rule-ticks-render
|
||||
[own zoom]
|
||||
(let [zoom (or zoom 1)
|
||||
path (reduce (partial make-vertical-tick zoom) [] +ticks+)
|
||||
labels (->> (map (partial horizontal-text-label zoom) +ticks+)
|
||||
(filterv identity))]
|
||||
(html
|
||||
[:g
|
||||
[:path {:d (str/join " " path) :stroke "#9da2a6"}]
|
||||
labels])))
|
||||
|
||||
(def ^:private horizontal-rule-ticks
|
||||
(mx/component
|
||||
{:render horizontal-rule-ticks-render
|
||||
:name "horizontal-rule-ticks"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- Vertical Rule Ticks (Component)
|
||||
|
||||
(defn- vertical-rule-ticks-render
|
||||
[own zoom]
|
||||
(let [zoom (or zoom 1)
|
||||
path (reduce (partial make-horizontal-tick zoom) [] +ticks+)
|
||||
labels (->> (map (partial vertical-text-label zoom) +ticks+)
|
||||
(filterv identity))]
|
||||
(html
|
||||
[:g
|
||||
[:path {:d (str/join " " path) :stroke "#9da2a6"}]
|
||||
labels])))
|
||||
|
||||
(def ^:private vertical-rule-ticks
|
||||
(mx/component
|
||||
{:render vertical-rule-ticks-render
|
||||
:name "vertical-rule-ticks"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- Horizontal Rule (Component)
|
||||
|
||||
(defn horizontal-rule-render
|
||||
[own zoom]
|
||||
(let [scroll (mx/react wb/scroll-a)
|
||||
scroll-x (:x scroll)
|
||||
translate-x (- (- c/canvas-scroll-padding) (:x scroll))]
|
||||
(html
|
||||
[:svg.horizontal-rule
|
||||
{:width c/viewport-width
|
||||
:height 20}
|
||||
[:g {:transform (str "translate(" translate-x ", 0)")}
|
||||
(horizontal-rule-ticks zoom)]])))
|
||||
|
||||
(def horizontal-rule
|
||||
(mx/component
|
||||
{:render horizontal-rule-render
|
||||
:name "horizontal-rule"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
|
||||
;; --- Vertical Rule (Component)
|
||||
|
||||
(defn vertical-rule-render
|
||||
[own zoom]
|
||||
(let [scroll (mx/react wb/scroll-a)
|
||||
scroll-y (:y scroll)
|
||||
translate-y (- (- c/canvas-scroll-padding) (:y scroll))]
|
||||
(html
|
||||
[: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
|
||||
:fill "rgb(233, 234, 235)"}]])))
|
||||
|
||||
(def vertical-rule
|
||||
(mx/component
|
||||
{:render vertical-rule-render
|
||||
:name "vertical-rule"
|
||||
:mixins [mx/static mx/reactive]}))
|
46
frontend/src/uxbox/main/ui/workspace/scroll.cljs
Normal file
46
frontend/src/uxbox/main/ui/workspace/scroll.cljs
Normal file
|
@ -0,0 +1,46 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.scroll
|
||||
"Workspace scroll events handling."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.rlocks :as rlocks]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
(defn watch-scroll-interactions
|
||||
[own]
|
||||
(letfn [(is-space-up? [[type {:keys [key]}]]
|
||||
(and (= 32 key) (= :key/up type)))
|
||||
|
||||
(on-start []
|
||||
(let [stoper (->> wb/events-s
|
||||
(rx/filter is-space-up?)
|
||||
(rx/take 1))
|
||||
local (:rum/local own)
|
||||
initial @wb/mouse-viewport-a
|
||||
stream (rx/take-until stoper wb/mouse-viewport-s)]
|
||||
(swap! local assoc :scrolling true)
|
||||
(rx/subscribe stream #(on-scroll % initial) nil on-scroll-end)))
|
||||
|
||||
(on-scroll-end []
|
||||
(rlocks/release! :workspace/scroll)
|
||||
(let [local (:rum/local own)]
|
||||
(swap! local assoc :scrolling false)))
|
||||
|
||||
(on-scroll [pt initial]
|
||||
(let [{:keys [x y]} (gpt/subtract pt initial)
|
||||
el (mx/ref-node own "workspace-canvas")
|
||||
cx (.-scrollLeft el)
|
||||
cy (.-scrollTop el)]
|
||||
(set! (.-scrollLeft el) (- cx x))
|
||||
(set! (.-scrollTop el) (- cy y))))]
|
||||
|
||||
(let [stream (->> (rx/map first rlocks/stream)
|
||||
(rx/filter #(= % :workspace/scroll)))]
|
||||
(rx/subscribe stream on-start))))
|
112
frontend/src/uxbox/main/ui/workspace/selrect.cljs
Normal file
112
frontend/src/uxbox/main/ui/workspace/selrect.cljs
Normal file
|
@ -0,0 +1,112 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.selrect
|
||||
"Mouse selection interaction and component."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.rlocks :as rlocks]))
|
||||
|
||||
(defonce position (atom nil))
|
||||
|
||||
;; --- Selrect (Component)
|
||||
|
||||
(declare selrect->rect)
|
||||
(declare watch-selrect-actions)
|
||||
|
||||
(defn- will-mount
|
||||
[own]
|
||||
(assoc own ::sub (watch-selrect-actions)))
|
||||
|
||||
(defn- will-unmount
|
||||
[own]
|
||||
(.close (::sub own))
|
||||
(dissoc own ::sub))
|
||||
|
||||
(mx/defc selrect
|
||||
{:will-mount will-mount
|
||||
:will-unmount will-unmount
|
||||
:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(when-let [data (mx/react position)]
|
||||
(let [{:keys [x y width height]} (selrect->rect data)]
|
||||
[:rect.selection-rect
|
||||
{:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height}])))
|
||||
|
||||
;; --- Interaction
|
||||
|
||||
(defn- selrect->rect
|
||||
[data]
|
||||
(let [start (:start data)
|
||||
current (:current data)
|
||||
start-x (min (:x start) (:x current))
|
||||
start-y (min (:y start) (:y current))
|
||||
current-x (max (:x start) (:x current))
|
||||
current-y (max (:y start) (:y current))
|
||||
width (- current-x start-x)
|
||||
height (- current-y start-y)]
|
||||
{:x start-x
|
||||
:y start-y
|
||||
:width (- current-x start-x)
|
||||
:height (- current-y start-y)}))
|
||||
|
||||
(defn- translate-to-canvas
|
||||
"Translate the given rect to the canvas coordinates system."
|
||||
[rect]
|
||||
(let [zoom @wb/zoom-ref
|
||||
startx (* c/canvas-start-x zoom)
|
||||
starty (* c/canvas-start-y zoom)]
|
||||
(assoc rect
|
||||
:x (/ (- (:x rect) startx) zoom)
|
||||
:y (/ (- (:y rect) starty) zoom)
|
||||
:width (/ (:width rect) zoom)
|
||||
:height (/ (:height rect) zoom))))
|
||||
|
||||
(declare on-start)
|
||||
|
||||
(defn- watch-selrect-actions
|
||||
[]
|
||||
(let [stream (->> (rx/map first rlocks/stream)
|
||||
(rx/filter #(= % :ui/selrect)))]
|
||||
(rx/subscribe stream on-start)))
|
||||
|
||||
(defn- on-move
|
||||
"Function executed on each mouse movement while selrect
|
||||
interaction is active."
|
||||
[pos]
|
||||
(swap! position assoc :current pos))
|
||||
|
||||
(defn- on-complete
|
||||
"Function executed when the selection rect
|
||||
interaction is terminated."
|
||||
[]
|
||||
(let [rect (-> (selrect->rect @position)
|
||||
(translate-to-canvas))]
|
||||
(rs/emit! (uds/deselect-all)
|
||||
(uds/select-shapes rect))
|
||||
(rlocks/release! :ui/selrect)
|
||||
(reset! position nil)))
|
||||
|
||||
(defn- on-start
|
||||
"Function execution when selrect action is started."
|
||||
[]
|
||||
(let [stoper (->> wb/events-s
|
||||
(rx/map first)
|
||||
(rx/filter #(= % :mouse/up))
|
||||
(rx/take 1))
|
||||
stream (rx/take-until stoper wb/mouse-viewport-s)
|
||||
pos @wb/mouse-viewport-a]
|
||||
(reset! position {:start pos :current pos})
|
||||
(rx/subscribe stream on-move nil on-complete)))
|
120
frontend/src/uxbox/main/ui/workspace/settings.cljs
Normal file
120
frontend/src/uxbox/main/ui/workspace/settings.cljs
Normal file
|
@ -0,0 +1,120 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.settings
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.colorpicker :as uucp]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int)]))
|
||||
|
||||
(def form-data (forms/focus-data :workspace-settings st/state))
|
||||
(def form-errors (forms/focus-errors :workspace-settings st/state))
|
||||
(def set-value! (partial forms/set-value! :workspace-settings))
|
||||
(def set-errors! (partial forms/set-errors! :workspace-settings))
|
||||
(def page-ref wb/page-ref)
|
||||
|
||||
;; --- Form Component
|
||||
|
||||
(def +settings-defaults+
|
||||
{:grid-x-axis c/grid-x-axis
|
||||
:grid-y-axis c/grid-y-axis
|
||||
:grid-color "#b5bdb9"
|
||||
:grid-alignment false})
|
||||
|
||||
(def +settings-form+
|
||||
{:grid-y-axis [forms/required forms/integer [forms/in-range 2 100]]
|
||||
:grid-x-axis [forms/required forms/integer [forms/in-range 2 100]]
|
||||
:grid-alignment [forms/boolean]
|
||||
:grid-color [forms/required forms/color]})
|
||||
|
||||
(mx/defc settings-form
|
||||
{:mixins [mx/reactive]}
|
||||
[]
|
||||
(let [{:keys [id] :as page} (mx/react page-ref)
|
||||
errors (mx/react form-errors)
|
||||
data (merge +settings-defaults+
|
||||
(:metadata page)
|
||||
(mx/react form-data))]
|
||||
(letfn [(on-field-change [field event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value "")]
|
||||
(set-value! field value)))
|
||||
(on-color-change [color]
|
||||
(set-value! :grid-color color))
|
||||
(on-align-change [event]
|
||||
(let [checked? (-> (dom/get-target event)
|
||||
(dom/checked?))]
|
||||
(set-value! :grid-alignment checked?)))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(let [[errors data] (forms/validate data +settings-form+)]
|
||||
(if errors
|
||||
(set-errors! errors)
|
||||
(rs/emit! (udw/update-metadata id data)
|
||||
(forms/clear :workspace-settings)
|
||||
(udl/hide-lightbox)))))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:span.lightbox-label "Grid size"]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:input#grid-x.input-text
|
||||
{:placeholder "X"
|
||||
:type "number"
|
||||
:class (forms/error-class errors :grid-x-axis)
|
||||
:value (:grid-x-axis data "")
|
||||
:on-change (partial on-field-change :grid-x-axis)
|
||||
:min 2
|
||||
:max 100}]]
|
||||
[:div.input-element.pixels
|
||||
[:input#grid-y.input-text
|
||||
{:placeholder "Y"
|
||||
:type "number"
|
||||
:class (forms/error-class errors :grid-y-axis)
|
||||
:value (:grid-y-axis data "")
|
||||
:on-change (partial on-field-change :grid-y-axis)
|
||||
:min 2
|
||||
:max 100}]]]
|
||||
[:span.lightbox-label "Grid color"]
|
||||
(uucp/colorpicker
|
||||
:value (:grid-color data)
|
||||
:on-change on-color-change)
|
||||
[:span.lightbox-label "Grid magnet option"]
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input
|
||||
{:type "checkbox"
|
||||
:on-change on-align-change
|
||||
:checked (:grid-alignment data)
|
||||
:id "magnet"
|
||||
:value "Yes"}]
|
||||
[:label {:for "magnet"} "Activate magnet"]]
|
||||
[:input.btn-primary
|
||||
{:type "submit"
|
||||
:value "Save"}]])))
|
||||
|
||||
(mx/defc settings-dialog
|
||||
[own]
|
||||
[:div.lightbox-body.settings
|
||||
[:h3 "Grid settings"]
|
||||
(settings-form)
|
||||
[:a.close {:href "#"
|
||||
:on-click #(do (dom/prevent-default %)
|
||||
(udl/close!))} i/close]])
|
||||
|
||||
(defmethod lbx/render-lightbox :settings
|
||||
[_]
|
||||
(settings-dialog))
|
109
frontend/src/uxbox/main/ui/workspace/shortcuts.cljs
Normal file
109
frontend/src/uxbox/main/ui/workspace/shortcuts.cljs
Normal file
|
@ -0,0 +1,109 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.shortcuts
|
||||
(:require [goog.events :as events]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.undo :as udu]
|
||||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.ui.workspace.sidebar.drawtools :as wsd])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes
|
||||
goog.ui.KeyboardShortcutHandler
|
||||
goog.ui.KeyboardShortcutHandler))
|
||||
|
||||
(declare move-selected)
|
||||
|
||||
;; --- Shortcuts
|
||||
|
||||
(defonce +shortcuts+
|
||||
{:shift+g #(rs/emit! (dw/toggle-flag :grid))
|
||||
:ctrl+g #(rs/emit! (uds/group-selected))
|
||||
:ctrl+shift+g #(rs/emit! (uds/degroup-selected))
|
||||
:ctrl+shift+m #(rs/emit! (dw/toggle-flag :sitemap))
|
||||
:ctrl+shift+f #(rs/emit! (dw/toggle-flag :drawtools))
|
||||
:ctrl+shift+i #(rs/emit! (dw/toggle-flag :icons))
|
||||
:ctrl+shift+l #(rs/emit! (dw/toggle-flag :layers))
|
||||
:ctrl+0 #(rs/emit! (dw/reset-zoom))
|
||||
:ctrl+r #(rs/emit! (dw/toggle-flag :ruler))
|
||||
:ctrl+d #(rs/emit! (uds/duplicate-selected))
|
||||
:ctrl+c #(rs/emit! (dw/copy-to-clipboard))
|
||||
:ctrl+v #(rs/emit! (dw/paste-from-clipboard))
|
||||
:ctrl+shift+v #(udl/open! :clipboard)
|
||||
:ctrl+z #(rs/emit! (udu/undo))
|
||||
:ctrl+shift+z #(rs/emit! (udu/redo))
|
||||
:ctrl+b #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-rect+))
|
||||
:ctrl+e #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-circle+))
|
||||
:ctrl+t #(rs/emit! (dw/select-for-drawing wsd/+draw-tool-text+))
|
||||
:esc #(rs/emit! (uds/deselect-all))
|
||||
:delete #(rs/emit! (uds/delete-selected))
|
||||
:ctrl+up #(rs/emit! (uds/move-selected-layer :up))
|
||||
:ctrl+down #(rs/emit! (uds/move-selected-layer :down))
|
||||
:ctrl+shift+up #(rs/emit! (uds/move-selected-layer :top))
|
||||
:ctrl+shift+down #(rs/emit! (uds/move-selected-layer :bottom))
|
||||
:shift+up #(move-selected :up :fast)
|
||||
:shift+down #(move-selected :down :fast)
|
||||
:shift+right #(move-selected :right :fast)
|
||||
:shift+left #(move-selected :left :fast)
|
||||
:up #(move-selected :up :std)
|
||||
:down #(move-selected :down :std)
|
||||
:right #(move-selected :right :std)
|
||||
:left #(move-selected :left :std)})
|
||||
|
||||
;; --- Shortcuts Setup Functions
|
||||
|
||||
(defn- watch-shortcuts
|
||||
[sink]
|
||||
(let [handler (KeyboardShortcutHandler. js/document)]
|
||||
|
||||
;; Register shortcuts.
|
||||
(doseq [item (keys +shortcuts+)]
|
||||
(let [identifier (name item)]
|
||||
(.registerShortcut handler identifier identifier)))
|
||||
|
||||
;; Initialize shortcut listener.
|
||||
(let [event KeyboardShortcutHandler.EventType.SHORTCUT_TRIGGERED
|
||||
callback #(sink (keyword (.-identifier %)))
|
||||
key (events/listen handler event callback)]
|
||||
(fn []
|
||||
(events/unlistenByKey key)
|
||||
(.clearKeyListener handler)))))
|
||||
|
||||
(defn- initialize
|
||||
[]
|
||||
(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 (rs/emit! (uds/move-selected dir 1))
|
||||
:fast (rs/emit! (uds/move-selected dir 20))))
|
||||
|
||||
;; --- Mixin
|
||||
|
||||
(defn- will-mount
|
||||
[own]
|
||||
(assoc own ::sub (initialize)))
|
||||
|
||||
(defn- will-unmount
|
||||
[own]
|
||||
(.close (::sub own))
|
||||
(dissoc own ::sub))
|
||||
|
||||
(def shortcuts-mixin
|
||||
{:will-mount will-mount
|
||||
:will-unmount will-unmount})
|
51
frontend/src/uxbox/main/ui/workspace/sidebar.cljs
Normal file
51
frontend/src/uxbox/main/ui/workspace/sidebar.cljs
Normal file
|
@ -0,0 +1,51 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.sidebar.options :refer (options-toolbox)]
|
||||
[uxbox.main.ui.workspace.sidebar.layers :refer (layers-toolbox)]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap :refer (sitemap-toolbox)]
|
||||
[uxbox.main.ui.workspace.sidebar.history :refer (history-toolbox)]
|
||||
[uxbox.main.ui.workspace.sidebar.icons :refer (icons-toolbox)]
|
||||
[uxbox.main.ui.workspace.sidebar.drawtools :refer (draw-toolbox)]))
|
||||
|
||||
;; --- Left Sidebar (Component)
|
||||
|
||||
(mx/defc left-sidebar
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[]
|
||||
(let [flags (mx/react wb/flags-ref)]
|
||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
(sitemap-toolbox))
|
||||
(when (contains? flags :document-history)
|
||||
(history-toolbox))
|
||||
(when (contains? flags :layers)
|
||||
(layers-toolbox))]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
(mx/defc right-sidebar
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[]
|
||||
(let [flags (mx/react wb/flags-ref)]
|
||||
[:aside#settings-bar.settings-bar
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :drawtools)
|
||||
(draw-toolbox))
|
||||
(when (contains? flags :element-options)
|
||||
(options-toolbox))
|
||||
(when (contains? flags :icons)
|
||||
(icons-toolbox))]]))
|
||||
|
108
frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs
Normal file
108
frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs
Normal file
|
@ -0,0 +1,108 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.drawtools
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private drawing-shape
|
||||
"A focused vision of the drawing property
|
||||
of the workspace status. This avoids
|
||||
rerender the whole toolbox on each workspace
|
||||
change."
|
||||
(-> (l/in [:workspace :drawing])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Constants
|
||||
|
||||
(def +draw-tool-rect+
|
||||
{:type :rect
|
||||
:name "Rect"
|
||||
:stroke "#000000"})
|
||||
|
||||
(def +draw-tool-circle+
|
||||
{:type :circle
|
||||
:name "Circle"})
|
||||
|
||||
(def +draw-tool-path+
|
||||
{:type :path
|
||||
:name "Path"
|
||||
:stroke-type :solid
|
||||
:stroke "#000000"
|
||||
:stroke-width 2
|
||||
:fill "#000000"
|
||||
:fill-opacity 0
|
||||
;; :close? true
|
||||
:points []})
|
||||
|
||||
(def +draw-tool-text+
|
||||
{:type :text
|
||||
:name "Text"
|
||||
:content "Hello world"})
|
||||
|
||||
(def +draw-tools+
|
||||
[{:icon i/box
|
||||
:help (tr "ds.help.rect")
|
||||
:shape +draw-tool-rect+
|
||||
:priority 1}
|
||||
{:icon i/circle
|
||||
:help (tr "ds.help.circle")
|
||||
:shape +draw-tool-circle+
|
||||
:priority 2}
|
||||
{:icon i/text
|
||||
:help (tr "ds.help.text")
|
||||
:shape +draw-tool-text+
|
||||
:priority 4}
|
||||
{:icon i/curve
|
||||
:help (tr "ds.help.path")
|
||||
:shape +draw-tool-path+
|
||||
:priority 5}
|
||||
{:icon i/pencil
|
||||
:help (tr "ds.help.path")
|
||||
:shape (assoc +draw-tool-path+ :free true)
|
||||
:priority 6}])
|
||||
|
||||
;; --- Draw Toolbox (Component)
|
||||
|
||||
(defn- select-for-draw
|
||||
[shape]
|
||||
(rs/emit! (dw/select-for-drawing shape)))
|
||||
|
||||
(mx/defc draw-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[own]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
drawing (mx/react drawing-shape)
|
||||
close #(rs/emit! (dw/toggle-flag :drawtools))
|
||||
tools (->> (into [] +draw-tools+)
|
||||
(sort-by (comp :priority second)))]
|
||||
[: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
|
||||
(for [[i props] (map-indexed vector tools)
|
||||
:let [selected? (= drawing (:shape props))]]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (:help props)
|
||||
:class (when selected? "selected")
|
||||
:key (str i)
|
||||
:on-click (partial select-for-draw (:shape props))}
|
||||
(:icon props)])]]))
|
175
frontend/src/uxbox/main/ui/workspace/sidebar/history.cljs
Normal file
175
frontend/src/uxbox/main/ui/workspace/sidebar/history.cljs
Normal file
|
@ -0,0 +1,175 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.history
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.data.messages :as udm]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.datetime :as dt]
|
||||
[uxbox.util.data :refer (read-string)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Lenses
|
||||
|
||||
(def history-ref
|
||||
(as-> (l/in [:workspace :history]) $
|
||||
(l/derive $ st/state)))
|
||||
|
||||
;; --- History Item (Component)
|
||||
|
||||
(defn history-item-render
|
||||
[own item selected]
|
||||
(letfn [(on-select [event]
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (udh/select-page-history (:version item))))
|
||||
(on-pinned [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [item (assoc item
|
||||
:label "no label"
|
||||
:pinned (not (:pinned item)))]
|
||||
(rs/emit! (udh/update-history-item item))))]
|
||||
(let [selected? (= (:version item) selected)]
|
||||
(html
|
||||
[:li {:class (when selected? "current") :on-click on-select}
|
||||
[:div.pin-icon {:on-click on-pinned
|
||||
:class (when (:pinned item) "selected")}
|
||||
i/pin]
|
||||
[:span (str "Version " (:version item)
|
||||
" (" (dt/timeago (:created-at item)) ")")]]))))
|
||||
|
||||
(def history-item
|
||||
(mx/component
|
||||
{:render history-item-render
|
||||
:name "history-item"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- History List (Component)
|
||||
|
||||
(defn history-list-render
|
||||
[own page history]
|
||||
(letfn [(on-select [event]
|
||||
(dom/prevent-default event)
|
||||
(rs/emit! (udh/deselect-page-history (:id page))))
|
||||
|
||||
(on-load-more [event]
|
||||
(dom/prevent-default event)
|
||||
(let [since (:min-version history)
|
||||
params {:since since}]
|
||||
(rs/emit! (udh/fetch-page-history (:id page) params))))]
|
||||
|
||||
(let [selected (:selected history)
|
||||
show-more? (pos? (:min-version history))]
|
||||
(html
|
||||
[:ul.history-content
|
||||
[:li {:class (when-not selected "current")
|
||||
:on-click on-select}
|
||||
[:div.pin-icon i/pin]
|
||||
[:span (str "Version " (:version page) " (current)")]]
|
||||
(for [version (:items history)
|
||||
:let [item (get-in history [:by-version version])]]
|
||||
(-> (history-item item selected)
|
||||
(rum/with-key (str (:id item)))))
|
||||
(if show-more?
|
||||
[:li {:on-click on-load-more}
|
||||
[:a.btn-primary.btn-small
|
||||
"view more"]])]))))
|
||||
|
||||
(def history-list
|
||||
(mx/component
|
||||
{:render history-list-render
|
||||
:name "history-list"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- History Pinned List (Component)
|
||||
|
||||
(defn history-pinned-list-render
|
||||
[own history]
|
||||
(html
|
||||
[:ul.history-content
|
||||
(for [version (:pinned-items history)
|
||||
:let [item (get-in history [:by-version version])]]
|
||||
(-> (history-item item (:selected history))
|
||||
(rum/with-key (str (:id item)))))]))
|
||||
|
||||
(def history-pinned-list
|
||||
(mx/component
|
||||
{:render history-pinned-list-render
|
||||
:name "history-pinned-list"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- History Toolbox (Component)
|
||||
|
||||
(defn history-toolbox-render
|
||||
[own]
|
||||
(let [local (:rum/local own)
|
||||
page (mx/react wb/page-ref)
|
||||
history (mx/react history-ref)
|
||||
section (:section @local :main)
|
||||
close #(rs/emit! (dw/toggle-flag :document-history))
|
||||
main? (= section :main)
|
||||
pinned? (= section :pinned)
|
||||
show-main #(swap! local assoc :section :main)
|
||||
show-pinned #(swap! local assoc :section :pinned)]
|
||||
(html
|
||||
[:div.document-history.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/undo-history]
|
||||
[:span (tr "ds.document-history")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:ul.history-tabs
|
||||
[:li {:on-click show-main
|
||||
:class (when main? "selected")}
|
||||
"History"]
|
||||
[:li {:on-click show-pinned
|
||||
:class (when pinned? "selected")}
|
||||
"Pinned"]]
|
||||
(if (= section :pinned)
|
||||
(history-pinned-list history)
|
||||
(history-list page history))]])))
|
||||
|
||||
(def history-toolbox
|
||||
(mx/component
|
||||
{:render history-toolbox-render
|
||||
:name "document-history-toolbox"
|
||||
:mixins [mx/static mx/reactive (mx/local)]}))
|
||||
|
||||
;; --- History Dialog
|
||||
|
||||
(defn history-dialog-render
|
||||
[own page]
|
||||
(let [history (mx/react history-ref)
|
||||
version (:selected history)
|
||||
on-accept #(rs/emit! (udh/apply-selected-history page))
|
||||
on-cancel #(rs/emit! (udh/deselect-page-history page))]
|
||||
(when (or version (:deselecting history))
|
||||
(html
|
||||
[:div.message-version
|
||||
{:class (when (:deselecting history) "hide-message")}
|
||||
[:span (tr "history.alert-message" (or version "00"))
|
||||
[:div.message-action
|
||||
[:a.btn-transparent {:on-click on-accept} "Accept"]
|
||||
[:a.btn-transparent {:on-click on-cancel} "Cancel"]]]]))))
|
||||
|
||||
(def history-dialog
|
||||
(mx/component
|
||||
{:render history-dialog-render
|
||||
:name "history-dialog"
|
||||
:mixins [mx/static mx/reactive]}))
|
||||
|
100
frontend/src/uxbox/main/ui/workspace/sidebar/icons.cljs
Normal file
100
frontend/src/uxbox/main/ui/workspace/sidebar/icons.cljs
Normal file
|
@ -0,0 +1,100 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.icons
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.dashboard.icons :as icons]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (read-string)]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private drawing-shape
|
||||
"A focused vision of the drawing property
|
||||
of the workspace status. This avoids
|
||||
rerender the whole toolbox on each workspace
|
||||
change."
|
||||
(-> (l/in [:workspace :drawing])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Icons (Component)
|
||||
|
||||
(mx/defc icon-wrapper
|
||||
{:mixins [mx/static]}
|
||||
[icon]
|
||||
(icon/icon-svg icon))
|
||||
|
||||
(defn- icons-toolbox-will-mount
|
||||
[own]
|
||||
(let [local (:rum/local own)]
|
||||
(rs/emit! (udi/fetch-collections))
|
||||
(rs/emit! (udi/fetch-icons nil))
|
||||
(add-watch local ::key (fn [_ _ _ {:keys [id]}]
|
||||
(rs/emit! (udi/fetch-icons id))))
|
||||
own))
|
||||
|
||||
(defn- icons-toolbox-will-unmount
|
||||
[own]
|
||||
(let [local (:rum/local own)]
|
||||
(remove-watch local ::key)
|
||||
own))
|
||||
|
||||
(mx/defcs icons-toolbox
|
||||
{:mixins [(mx/local) mx/reactive]
|
||||
:will-mount icons-toolbox-will-mount
|
||||
:will-unmount icons-toolbox-will-unmount}
|
||||
[{:keys [rum/local] :as own}]
|
||||
(let [drawing (mx/react drawing-shape)
|
||||
|
||||
colls-map (mx/react icons/collections-ref)
|
||||
colls (->> (vals colls-map)
|
||||
(sort-by :name))
|
||||
coll (get colls-map (:id @local))
|
||||
|
||||
icons (mx/react icons/icons-ref)
|
||||
icons (->> (vals icons)
|
||||
(filter #(= (:id coll) (:collection %))))]
|
||||
|
||||
(letfn [(on-close [event]
|
||||
(rs/emit! (dw/toggle-flag :icons)))
|
||||
(on-select [icon event]
|
||||
(rs/emit! (dw/select-for-drawing icon)))
|
||||
(on-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(read-string))]
|
||||
(swap! local assoc :id value)
|
||||
(rs/emit! (dw/select-for-drawing nil))))]
|
||||
[:div#form-figures.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/icon-set]
|
||||
[:span "Icons"]
|
||||
[:div.tool-window-close {:on-click on-close} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:div.figures-catalog
|
||||
;; extract component: set selector
|
||||
[:select.input-select.small {:on-change on-change
|
||||
:value (pr-str (:id coll))}
|
||||
[:option {:value (pr-str nil)} "Storage"]
|
||||
(for [coll colls]
|
||||
[:option {:key (str "icon-coll" (:id coll))
|
||||
:value (pr-str (:id coll))}
|
||||
(:name coll)])]]
|
||||
(for [icon icons
|
||||
:let [selected? (= drawing icon)]]
|
||||
[:div.figure-btn {:key (str (:id icon))
|
||||
:class (when selected? "selected")
|
||||
:on-click (partial on-select icon)}
|
||||
(icon-wrapper icon)])]])))
|
337
frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs
Normal file
337
frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs
Normal file
|
@ -0,0 +1,337 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.util.data :refer (read-string classnames)]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom.dnd :as dnd]
|
||||
[uxbox.util.dom :as dom])
|
||||
(: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)
|
||||
(let [id (:id item)]
|
||||
(cond
|
||||
(or (:blocked item)
|
||||
(:hidden item))
|
||||
nil
|
||||
|
||||
(.-ctrlKey event)
|
||||
(rs/emit! (uds/select-shape id))
|
||||
|
||||
(> (count selected) 1)
|
||||
(rs/emit! (uds/deselect-all)
|
||||
(uds/select-shape id))
|
||||
|
||||
(contains? selected id)
|
||||
(rs/emit! (uds/select-shape id))
|
||||
|
||||
:else
|
||||
(rs/emit! (uds/deselect-all)
|
||||
(uds/select-shape id)))))
|
||||
|
||||
(defn- toggle-visibility
|
||||
[selected item event]
|
||||
(dom/stop-propagation event)
|
||||
(let [id (:id item)
|
||||
hidden? (:hidden item)]
|
||||
(if hidden?
|
||||
(rs/emit! (uds/show-shape id))
|
||||
(rs/emit! (uds/hide-shape id)))
|
||||
(when (contains? selected id)
|
||||
(rs/emit! (uds/select-shape id)))))
|
||||
|
||||
(defn- toggle-blocking
|
||||
[item event]
|
||||
(dom/stop-propagation event)
|
||||
(let [id (:id item)
|
||||
blocked? (:blocked item)]
|
||||
(if blocked?
|
||||
(rs/emit! (uds/unblock-shape id))
|
||||
(rs/emit! (uds/block-shape id)))))
|
||||
|
||||
(defn- element-icon
|
||||
[item]
|
||||
(case (:type item)
|
||||
:icon (icon/icon-svg item)
|
||||
:image i/image
|
||||
:line i/line
|
||||
:circle i/circle
|
||||
:path i/curve
|
||||
:rect i/box
|
||||
:text i/text
|
||||
:group i/folder))
|
||||
|
||||
(defn- get-hover-position
|
||||
[event group?]
|
||||
(let [target (.-currentTarget event)
|
||||
brect (.getBoundingClientRect target)
|
||||
width (.-offsetHeight target)
|
||||
y (- (.-clientY event) (.-top brect))
|
||||
part (/ (* 30 width) 100)]
|
||||
(if group?
|
||||
(cond
|
||||
(> part y) :top
|
||||
(< (- width part) y) :bottom
|
||||
:else :middle)
|
||||
(if (>= y (/ width 2))
|
||||
:bottom
|
||||
:top))))
|
||||
|
||||
;; --- Shape Name (Component)
|
||||
|
||||
(mx/defcs shape-name
|
||||
"A generic component that displays the shape name
|
||||
if it is available and allows inline edition of it."
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[own shape]
|
||||
(let [local (:rum/local own)]
|
||||
(letfn [(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
parent (.-parentNode target)
|
||||
data {:id (:id shape)
|
||||
:name (dom/get-value target)}]
|
||||
(set! (.-draggable parent) true)
|
||||
(rs/emit! (uds/update-shape data))
|
||||
(swap! local assoc :edition false)))
|
||||
(on-key-down [event]
|
||||
(js/console.log event)
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-click [event]
|
||||
(dom/stop-propagation 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-click on-click}
|
||||
(:name shape "")]))))
|
||||
|
||||
;; --- Layer Simple (Component)
|
||||
|
||||
(mx/defcs layer-simple
|
||||
{:mixins [mx/static (mx/local)]}
|
||||
[own item selected]
|
||||
(let [selected? (contains? selected (:id item))
|
||||
select #(select-shape selected item %)
|
||||
toggle-visibility #(toggle-visibility selected item %)
|
||||
toggle-blocking #(toggle-blocking item %)
|
||||
local (:rum/local own)
|
||||
classes (classnames
|
||||
:selected selected?
|
||||
:drag-active (:dragging @local)
|
||||
:drag-top (= :top (:over @local))
|
||||
:drag-bottom (= :bottom (:over @local))
|
||||
:drag-inside (= :middle (:over @local)))]
|
||||
(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-image! event target 50 10)
|
||||
(swap! local assoc :dragging true)))
|
||||
(on-drag-end [event]
|
||||
(swap! local assoc :dragging false :over nil))
|
||||
(on-drop [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [id (dnd/get-data event)
|
||||
over (:over @local)]
|
||||
(case (:over @local)
|
||||
:top (rs/emit! (uds/drop-shape id (:id item) :before))
|
||||
:bottom (rs/emit! (uds/drop-shape id (:id item) :after)))
|
||||
(swap! local assoc :dragging false :over nil)))
|
||||
(on-drag-over [event]
|
||||
(dom/prevent-default event)
|
||||
(dnd/set-drop-effect! event "move")
|
||||
(let [over (get-hover-position event false)]
|
||||
(swap! local assoc :over over)))
|
||||
(on-drag-enter [event]
|
||||
(swap! local assoc :over true))
|
||||
(on-drag-leave [event]
|
||||
(swap! local assoc :over false))]
|
||||
[:li {:key (str (:id item))
|
||||
:class (when selected? "selected")}
|
||||
[:div.element-list-body
|
||||
{:class classes
|
||||
:style {:opacity (if (:dragging @local)
|
||||
"0.5"
|
||||
"1")}
|
||||
:on-click select
|
||||
:on-drag-start on-drag-start
|
||||
:on-drag-enter on-drag-enter
|
||||
:on-drag-leave on-drag-leave
|
||||
:on-drag-over on-drag-over
|
||||
:on-drag-end on-drag-end
|
||||
:on-drop on-drop
|
||||
:draggable true}
|
||||
|
||||
[:div.element-actions
|
||||
[:div.toggle-element
|
||||
{:class (when-not (:hidden item) "selected")
|
||||
:on-click toggle-visibility}
|
||||
i/eye]
|
||||
[:div.block-element
|
||||
{:class (when (:blocked item) "selected")
|
||||
:on-click toggle-blocking}
|
||||
i/lock]]
|
||||
[:div.element-icon (element-icon item)]
|
||||
(shape-name item)]])))
|
||||
|
||||
;; --- Layer Group (Component)
|
||||
|
||||
(mx/defcs layer-group
|
||||
{:mixins [mx/static mx/reactive (mx/local)]}
|
||||
[own {:keys [id] :as item} selected]
|
||||
(let [local (:rum/local own)
|
||||
selected? (contains? selected (:id item))
|
||||
collapsed? (:collapsed item true)
|
||||
shapes-map (mx/react wb/shapes-by-id-ref)
|
||||
classes (classnames
|
||||
:selected selected?
|
||||
:drag-top (= :top (:over @local))
|
||||
:drag-bottom (= :bottom (:over @local))
|
||||
:drag-inside (= :middle (:over @local)))
|
||||
select #(select-shape selected item %)
|
||||
toggle-visibility #(toggle-visibility selected item %)
|
||||
toggle-blocking #(toggle-blocking item %)]
|
||||
(letfn [(toggle-collapse [event]
|
||||
(dom/stop-propagation event)
|
||||
(if (:collapsed item)
|
||||
(rs/emit! (uds/uncollapse-shape id))
|
||||
(rs/emit! (uds/collapse-shape id))))
|
||||
(toggle-locking [event]
|
||||
(dom/stop-propagation event)
|
||||
(if (:locked item)
|
||||
(rs/emit! (uds/unlock-shape id))
|
||||
(rs/emit! (uds/lock-shape id))))
|
||||
(on-drag-start [event]
|
||||
(let [target (dom/event->target event)]
|
||||
(dnd/set-allowed-effect! event "move")
|
||||
(dnd/set-data! event (:id item))
|
||||
(swap! local assoc :dragging true)))
|
||||
(on-drag-end [event]
|
||||
(swap! local assoc :dragging false :over nil))
|
||||
(on-drop [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [coming-id (dnd/get-data event)
|
||||
over (:over @local)]
|
||||
(case (:over @local)
|
||||
:top (rs/emit! (uds/drop-shape coming-id id :before))
|
||||
:bottom (rs/emit! (uds/drop-shape coming-id id :after))
|
||||
:middle (rs/emit! (uds/drop-shape coming-id id :inside)))
|
||||
(swap! local assoc :dragging false :over nil)))
|
||||
(on-drag-over [event]
|
||||
(dom/prevent-default event)
|
||||
(dnd/set-drop-effect! event "move")
|
||||
(let [over (get-hover-position event true)]
|
||||
(swap! local assoc :over over)))
|
||||
(on-drag-enter [event]
|
||||
(swap! local assoc :over true))
|
||||
(on-drag-leave [event]
|
||||
(swap! local assoc :over false))]
|
||||
[:li.group {:class (when-not collapsed? "open")}
|
||||
[:div.element-list-body
|
||||
{:class classes
|
||||
:draggable true
|
||||
:on-drag-start on-drag-start
|
||||
:on-drag-enter on-drag-enter
|
||||
:on-drag-leave on-drag-leave
|
||||
:on-drag-over on-drag-over
|
||||
:on-drag-end on-drag-end
|
||||
:on-drop on-drop
|
||||
:on-click select}
|
||||
[:div.element-actions
|
||||
[:div.toggle-element
|
||||
{:class (when-not (:hidden item) "selected")
|
||||
:on-click toggle-visibility}
|
||||
i/eye]
|
||||
[:div.block-element
|
||||
{:class (when (:blocked item) "selected")
|
||||
:on-click toggle-blocking}
|
||||
i/lock]
|
||||
[:div.chain-element
|
||||
{:class (when (:locked item) "selected")
|
||||
:on-click toggle-locking}
|
||||
i/chain]]
|
||||
[:div.element-icon i/folder]
|
||||
(shape-name item)
|
||||
[:span.toggle-content
|
||||
{:on-click toggle-collapse
|
||||
:class (when-not collapsed? "inverse")}
|
||||
i/arrow-slide]]
|
||||
(if-not collapsed?
|
||||
[:ul
|
||||
(for [shape (map #(get shapes-map %) (:items item))
|
||||
:let [key (str (:id shape))]]
|
||||
(if (= (:type shape) :group)
|
||||
(-> (layer-group shape selected)
|
||||
(mx/with-key key))
|
||||
(-> (layer-simple shape selected)
|
||||
(mx/with-key key))))])])))
|
||||
|
||||
;; --- Layers Toolbox (Component)
|
||||
|
||||
(mx/defc layers-toolbox
|
||||
{:mixins [mx/reactive]}
|
||||
[]
|
||||
(let [workspace (mx/react wb/workspace-ref)
|
||||
selected (:selected workspace)
|
||||
shapes-map (mx/react wb/shapes-by-id-ref)
|
||||
page (mx/react (focus-page (:page workspace)))
|
||||
close #(rs/emit! (udw/toggle-flag :layers))
|
||||
duplicate #(rs/emit! (uds/duplicate-selected))
|
||||
group #(rs/emit! (uds/group-selected))
|
||||
degroup #(rs/emit! (uds/degroup-selected))
|
||||
delete #(rs/emit! (uds/delete-selected))
|
||||
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 [shape (map #(get shapes-map %) (:shapes page))
|
||||
:let [key (str (:id shape))]]
|
||||
(if (= (:type shape) :group)
|
||||
(-> (layer-group shape selected)
|
||||
(mx/with-key key))
|
||||
(-> (layer-simple shape selected)
|
||||
(mx/with-key key))))]]
|
||||
[:div.layers-tools
|
||||
[:ul.layers-tools-content
|
||||
[:li.clone-layer {:on-click duplicate} i/copy]
|
||||
[:li.group-layer {:on-click group} i/folder]
|
||||
[:li.degroup-layer {:on-click degroup} i/ungroup]
|
||||
[:li.delete-layer {:on-click delete} i/trash]]]]))
|
144
frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs
Normal file
144
frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs
Normal file
|
@ -0,0 +1,144 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.ui.workspace.sidebar.options.icon-measures :as options-iconm]
|
||||
[uxbox.main.ui.workspace.sidebar.options.circle-measures :as options-circlem]
|
||||
[uxbox.main.ui.workspace.sidebar.options.rect-measures :as options-rectm]
|
||||
[uxbox.main.ui.workspace.sidebar.options.line-measures :as options-linem]
|
||||
[uxbox.main.ui.workspace.sidebar.options.fill :as options-fill]
|
||||
[uxbox.main.ui.workspace.sidebar.options.text :as options-text]
|
||||
[uxbox.main.ui.workspace.sidebar.options.stroke :as options-stroke]
|
||||
[uxbox.main.ui.workspace.sidebar.options.page :as options-page]
|
||||
[uxbox.main.ui.workspace.sidebar.options.interactions :as options-interactions]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :as data]))
|
||||
|
||||
;; --- Constants
|
||||
|
||||
(def ^:private +menus-map+
|
||||
{:icon [::icon-measures ::fill ::stroke ::interactions]
|
||||
:rect [::rect-measures ::fill ::stroke ::interactions]
|
||||
:line [::line-measures ::stroke ::interactions]
|
||||
:path [::fill ::stroke ::interactions]
|
||||
:circle [::circle-measures ::fill ::stroke ::interactions]
|
||||
:text [::fill ::text ::interactions]
|
||||
:image [::interactions]
|
||||
:group [::interactions]
|
||||
::page [::page-measures ::page-grid-options]})
|
||||
|
||||
(def ^:private +menus+
|
||||
[{:name "Size, position & rotation"
|
||||
:id ::icon-measures
|
||||
:icon i/infocard
|
||||
:comp options-iconm/icon-measures-menu}
|
||||
{:name "Size, position & rotation"
|
||||
:id ::rect-measures
|
||||
:icon i/infocard
|
||||
:comp options-rectm/rect-measures-menu}
|
||||
{:name "Size, position & rotation"
|
||||
:id ::line-measures
|
||||
:icon i/infocard
|
||||
:comp options-linem/line-measures-menu}
|
||||
{:name "Size, position & rotation"
|
||||
:id ::circle-measures
|
||||
:icon i/infocard
|
||||
:comp options-circlem/circle-measures-menu}
|
||||
{:name "Fill"
|
||||
:id ::fill
|
||||
:icon i/fill
|
||||
:comp options-fill/fill-menu}
|
||||
{:name "Stroke"
|
||||
:id ::stroke
|
||||
:icon i/stroke
|
||||
:comp options-stroke/stroke-menu}
|
||||
{:name "Text"
|
||||
:id ::text
|
||||
:icon i/text
|
||||
:comp options-text/text-menu}
|
||||
{:name "Interactions"
|
||||
:id ::interactions
|
||||
:icon i/action
|
||||
:comp options-interactions/interactions-menu}
|
||||
{:name "Page Measures (TODO)"
|
||||
:id ::page-measures
|
||||
:icon i/action
|
||||
:comp options-page/measures-menu}
|
||||
{:name "Grid Options (TODO)"
|
||||
:id ::page-grid-options
|
||||
:icon i/action
|
||||
:comp options-page/grid-options-menu}])
|
||||
|
||||
(def ^:private +menus-by-id+
|
||||
(data/index-by-id +menus+))
|
||||
|
||||
;; --- Options
|
||||
|
||||
(defn- options-did-remount
|
||||
[old-own own]
|
||||
(let [[prev-shape] (:rum/args old-own)
|
||||
[curr-shape] (:rum/args own)]
|
||||
(when (not (identical? prev-shape curr-shape))
|
||||
(reset! (:rum/local own) {}))
|
||||
own))
|
||||
|
||||
(mx/defcs options
|
||||
{:mixins [mx/static (mx/local)]
|
||||
:did-remount options-did-remount}
|
||||
[{:keys [rum/local] :as own} shape]
|
||||
(let [menus (get +menus-map+ (:type shape ::page))
|
||||
contained-in? (into #{} menus)
|
||||
active (:menu @local (first menus))]
|
||||
(println "options" active)
|
||||
[:div
|
||||
[: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))]))
|
||||
|
||||
(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))))
|
||||
|
||||
(mx/defc options-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [shape (mx/react selected-shape-ref)
|
||||
close #(rs/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)]]]))
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.circle-measures
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(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}]
|
||||
(rs/emit! (uds/update-size sid props))))
|
||||
(on-rotation-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(rs/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)
|
||||
props {attr value}]
|
||||
(rs/emit! (uds/update-position sid props))))
|
||||
(on-proportion-lock-change [event]
|
||||
(if (:proportion-lock shape)
|
||||
(rs/emit! (uds/unlock-proportions id))
|
||||
(rs/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 (:rx shape)
|
||||
:on-change (partial on-size-change :rx)}]]
|
||||
[:div.lock-size
|
||||
{:class (when (:proportion-lock shape) "selected")
|
||||
:on-click on-proportion-lock-change}
|
||||
i/lock]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min "0"
|
||||
:value (:ry shape)
|
||||
:on-change (partial on-size-change :ry)}]]]
|
||||
|
||||
[:span "Position"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "cx"
|
||||
:type "number"
|
||||
:value (:cx shape "")
|
||||
:on-change (partial on-pos-change :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "cy"
|
||||
:type "number"
|
||||
:value (:cy shape "")
|
||||
:on-change (partial on-pos-change :y)}]]]
|
||||
|
||||
[: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 (:rotation shape "0")
|
||||
:on-change on-rotation-change
|
||||
}]]
|
||||
[:input.input-text
|
||||
{:style {:visibility "hidden"}}]]]]))
|
|
@ -0,0 +1,75 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.fill
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(defn fill-menu-render
|
||||
[own menu shape]
|
||||
(letfn [(change-fill [value]
|
||||
(let [sid (:id shape)]
|
||||
(rs/emit! (uds/update-fill-attrs sid value))))
|
||||
(on-color-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(change-fill {:color value})))
|
||||
(on-opacity-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-float value 1)
|
||||
value (/ value 10000)]
|
||||
(change-fill {:opacity value})))
|
||||
(on-color-picker-event [color]
|
||||
(change-fill {:color color}))
|
||||
(show-color-picker [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:shape (:id shape)
|
||||
:attr :fill
|
||||
:transparent? true}]
|
||||
(udl/open! :workspace/colorpicker opts)))]
|
||||
|
||||
(html
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
|
||||
[:span "Color"]
|
||||
[:div.row-flex.color-data
|
||||
[:span.color-th
|
||||
{:style {:background-color (:fill shape "#000000")}
|
||||
:on-click show-color-picker}]
|
||||
[:div.color-info
|
||||
[:span (:fill shape "#000000")]]]
|
||||
|
||||
;; SLIDEBAR FOR ROTATION AND OPACITY
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min "0"
|
||||
:max "10000"
|
||||
:value (* 10000 (:fill-opacity shape 1))
|
||||
:step "1"
|
||||
:on-change on-opacity-change}]]]])))
|
||||
|
||||
(def fill-menu
|
||||
(mx/component
|
||||
{:render fill-menu-render
|
||||
:name "fill-menu"
|
||||
:mixins [mx/static]}))
|
|
@ -0,0 +1,111 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.icon-measures
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(defn- icon-measures-menu-render
|
||||
[own 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}]
|
||||
(rs/emit! (uds/update-size sid props))))
|
||||
(on-rotation-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(rs/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)
|
||||
props {attr value}]
|
||||
(rs/emit! (uds/update-position sid props))))]
|
||||
(let [size (geom/size shape)]
|
||||
(html
|
||||
[: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 (:width size)
|
||||
:on-change (partial on-size-change :width)}]]
|
||||
[:div.lock-size i/lock]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min "0"
|
||||
:value (:height size)
|
||||
:on-change (partial on-size-change :height)}]]]
|
||||
|
||||
[:span "Position"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "X"
|
||||
:type "number"
|
||||
:value (:x1 shape "")
|
||||
:on-change (partial on-pos-change :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:type "number"
|
||||
:value (:y1 shape "")
|
||||
:on-change (partial on-pos-change :y)}]]]
|
||||
|
||||
[: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 (:rotation shape "0")
|
||||
:on-change on-rotation-change
|
||||
}]]
|
||||
[:input.input-text
|
||||
{:style {:visibility "hidden"}}]
|
||||
]]]))))
|
||||
|
||||
(def icon-measures-menu
|
||||
(mx/component
|
||||
{:render icon-measures-menu-render
|
||||
:name "icon-measures-menu"
|
||||
:mixins [mx/static]}))
|
|
@ -0,0 +1,610 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.interactions
|
||||
(:require [sablono.core :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap :refer (pages-ref)]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.colorpicker :as cp]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn- on-change
|
||||
([form attr event]
|
||||
(dom/prevent-default event)
|
||||
(let [value (dom/event->value event)
|
||||
value (read-string value)]
|
||||
(swap! form assoc attr value)))
|
||||
([form attr keep event]
|
||||
(let [data (select-keys @form keep)]
|
||||
(reset! form data)
|
||||
(on-change form attr event))))
|
||||
|
||||
;; --- Interactions List
|
||||
|
||||
(defn- translate-trigger-name
|
||||
[trigger]
|
||||
(case trigger
|
||||
:click "Click"
|
||||
:doubleclick "Double Click"
|
||||
:rightclick "Right Click"
|
||||
:hover "Hover"
|
||||
:mousein "Mouse In"
|
||||
:mouseout "Mouse Out"
|
||||
;; :swiperight "Swipe Right"
|
||||
;; :swipeleft "Swipe Left"
|
||||
;; :swipedown "Swipe Down"
|
||||
;; :touchandhold "Touch and Hold"
|
||||
;; :holdrelease "Hold release"
|
||||
(pr-str trigger)))
|
||||
|
||||
(defn- interactions-list-render
|
||||
[own shape form-ref]
|
||||
(letfn [(on-edit [item event]
|
||||
(dom/prevent-default event)
|
||||
(reset! form-ref item))
|
||||
(delete [item]
|
||||
(let [sid (:id shape)
|
||||
id (:id item)]
|
||||
(rs/emit! (uds/delete-interaction sid id))))
|
||||
(on-delete [item event]
|
||||
(dom/prevent-default event)
|
||||
(let [delete (partial delete item)]
|
||||
(udl/open! :confirm {:on-accept delete})))]
|
||||
(html
|
||||
[:ul.element-list
|
||||
(for [item (vals (:interactions shape))
|
||||
:let [key (pr-str (:id item))]]
|
||||
[:li {:key key}
|
||||
[:div.list-icon i/action]
|
||||
[:span (translate-trigger-name (:trigger item))]
|
||||
[:div.list-actions
|
||||
[:a {:on-click (partial on-edit item)} i/pencil]
|
||||
[:a {:on-click (partial on-delete item)} i/trash]]])])))
|
||||
|
||||
(def interactions-list
|
||||
(mx/component
|
||||
{:render interactions-list-render
|
||||
:name "interactions-list"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- Trigger Input
|
||||
|
||||
(defn- trigger-input-render
|
||||
[own form-ref]
|
||||
(when-not (:trigger @form-ref)
|
||||
(swap! form-ref assoc :trigger :click))
|
||||
(html
|
||||
[: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))}
|
||||
[:option {:value ":click"} "Click"]
|
||||
[:option {:value ":doubleclick"} "Double-click"]
|
||||
[:option {:value ":rightclick"} "Right-click"]
|
||||
[:option {:value ":hover"} "Hover"]
|
||||
[:option {:value ":mousein"} "Mouse in"]
|
||||
[:option {:value ":mouseout"} "Mouse out"]
|
||||
#_[:option {:value ":swiperight"} "Swipe right"]
|
||||
#_[:option {:value ":swipeleft"} "Swipe left"]
|
||||
#_[:option {:value ":swipedown"} "Swipe dpwn"]
|
||||
#_[:option {:value ":touchandhold"} "Touch and hold"]
|
||||
#_[:option {:value ":holdrelease"} "Hold release"]
|
||||
#_[:option {:value ":keypress"} "Key press"]
|
||||
#_[:option {:value ":pageisloaded"} "Page is loaded"]
|
||||
#_[:option {:value ":windowscroll"} "Window is scrolled to"]]]]))
|
||||
|
||||
(def trigger-input
|
||||
(mx/component
|
||||
{:render trigger-input-render
|
||||
:name "trigger-input"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
;; --- URL Input
|
||||
|
||||
(defn- url-input-render
|
||||
[own form-ref]
|
||||
(html
|
||||
[:div
|
||||
[:span "Url"]
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "http://"
|
||||
:on-change (partial on-change form-ref :url)
|
||||
:value (:url @form-ref "")
|
||||
:type "url"}]]]))
|
||||
|
||||
(def url-input
|
||||
(mx/component
|
||||
{:render url-input-render
|
||||
:name "url-input"}))
|
||||
|
||||
;; --- Elements Input
|
||||
|
||||
(defn- collect-shapes
|
||||
[state page]
|
||||
(let [shapes-by-id (:shapes state)
|
||||
shapes (get-in state [:pages page :shapes])]
|
||||
(letfn [(resolve-shape [acc id]
|
||||
(let [shape (get shapes-by-id id)]
|
||||
(if (= (:type shape) :group)
|
||||
(reduce resolve-shape (conj acc shape) (:items shape))
|
||||
(conj acc shape))))]
|
||||
(reduce resolve-shape [] shapes))))
|
||||
|
||||
(defn- elements-input-render
|
||||
[own page form-ref]
|
||||
(let [shapes (collect-shapes @st/state page)]
|
||||
(html
|
||||
[: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))}
|
||||
[:option {:value "nil"} "---"]
|
||||
(for [shape shapes
|
||||
:let [key (pr-str (:id shape))]]
|
||||
[:option {:key key :value key} (:name shape)])]]])))
|
||||
|
||||
(def elements-input
|
||||
(mx/component
|
||||
{:render elements-input-render
|
||||
:name "elements-input"}))
|
||||
|
||||
;; --- Page Input
|
||||
|
||||
(defn- pages-input-render
|
||||
[own form-ref path]
|
||||
(let [pages @pages-ref]
|
||||
(when (and (not (:page @form-ref))
|
||||
(pos? (count pages)))
|
||||
(swap! form-ref assoc :page (:id (first pages))))
|
||||
(html
|
||||
[:div
|
||||
[:span "Page"]
|
||||
[:div.row-flex
|
||||
[:select.input-select {:placeholder "Choose a page"
|
||||
:on-change (partial on-change form-ref :page)
|
||||
:value (pr-str (:page @form-ref))}
|
||||
(for [page pages
|
||||
:let [key (:index page)]]
|
||||
[:option {:key key :value key} (:name page)])]]])))
|
||||
|
||||
(def pages-input
|
||||
(mx/component
|
||||
{:render pages-input-render
|
||||
:name "pages-input"}))
|
||||
|
||||
;; --- Animation
|
||||
|
||||
(defn- animation-input-render
|
||||
[own form-ref]
|
||||
(when-not (:action @form-ref)
|
||||
(swap! form-ref assoc :animation :none))
|
||||
(html
|
||||
[: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))}
|
||||
[:option {:value ":none"} "None"]
|
||||
[:option {:value ":fade"} "Fade"]
|
||||
[:option {:value ":slide"} "Slide"]]]]))
|
||||
|
||||
(def animation-input
|
||||
(mx/component
|
||||
{:render animation-input-render
|
||||
:name "animation-input"}))
|
||||
|
||||
;; --- MoveTo Input
|
||||
|
||||
(defn- moveto-input-render
|
||||
[own 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))
|
||||
(html
|
||||
[: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)
|
||||
:type "number"
|
||||
:value (:moveto-x @form-ref "")}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:on-change (partial on-change form-ref :moveto-y)
|
||||
:type "number"
|
||||
:value (:moveto-y @form-ref "")}]]]]))
|
||||
|
||||
(def moveto-input
|
||||
(mx/component
|
||||
{:render moveto-input-render
|
||||
:name "moveto-input"}))
|
||||
|
||||
;; --- MoveBy Input
|
||||
|
||||
(defn- moveby-input-render
|
||||
[own 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))
|
||||
(html
|
||||
[: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)
|
||||
:type "number"
|
||||
:value (:moveby-x @form-ref "")}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Y"
|
||||
:on-change (partial on-change form-ref :moveby-y)
|
||||
:type "number"
|
||||
:value (:moveby-y @form-ref "")}]]]]))
|
||||
|
||||
(def moveby-input
|
||||
(mx/component
|
||||
{:render moveby-input-render
|
||||
:name "moveby-input"}))
|
||||
|
||||
;; --- Opacity Input
|
||||
|
||||
(defn- opacity-input-render
|
||||
[own form-ref]
|
||||
(when-not (:opacity @form-ref)
|
||||
(swap! form-ref assoc :opacity 100))
|
||||
(html
|
||||
[:div
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.percentail
|
||||
[:input.input-text
|
||||
{:placeholder "%"
|
||||
:on-change (partial on-change form-ref :opacity)
|
||||
:min "0"
|
||||
:max "100"
|
||||
:type "number"
|
||||
:value (:opacity @form-ref "")}]]]]))
|
||||
|
||||
(def opacity-input
|
||||
(mx/component
|
||||
{:render opacity-input-render
|
||||
:name "opacity-input"}))
|
||||
|
||||
;; --- Rotate Input
|
||||
|
||||
(defn rotate-input-render
|
||||
[own form-ref]
|
||||
(html
|
||||
[: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 "")}]]]]))
|
||||
|
||||
(def rotate-input
|
||||
(mx/component
|
||||
{:render rotate-input-render
|
||||
:name "rotate-input"}))
|
||||
|
||||
;; --- Resize Input
|
||||
|
||||
(defn- resize-input-render
|
||||
[own form-ref]
|
||||
(html
|
||||
[:div
|
||||
[:span "Resize"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Width"
|
||||
:on-change (partial on-change form-ref :resize-width)
|
||||
:type "number"
|
||||
:value (:resize-width @form-ref "")}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Height"
|
||||
:on-change (partial on-change form-ref :resize-height)
|
||||
:type "number"
|
||||
:value (:resize-height @form-ref "")}]]]]))
|
||||
|
||||
(def resize-input
|
||||
(mx/component
|
||||
{:render resize-input-render
|
||||
:name "resize-input"}))
|
||||
|
||||
;; --- Color Input
|
||||
|
||||
(defn- colorpicker-render
|
||||
[own {:keys [x y on-change value]}]
|
||||
(let [left (- x 260)
|
||||
top (- y 50)]
|
||||
(html
|
||||
[:div.colorpicker-tooltip
|
||||
{:style {:left (str left "px")
|
||||
:top (str top "px")}}
|
||||
|
||||
(cp/colorpicker
|
||||
:theme :small
|
||||
:value value
|
||||
:on-change on-change)])))
|
||||
|
||||
(def ^:private colorpicker
|
||||
(mx/component
|
||||
{:render colorpicker-render
|
||||
:name "colorpicker"
|
||||
:mixins [mx/reactive mx/static]}))
|
||||
|
||||
(defmethod lbx/render-lightbox :interactions/colorpicker
|
||||
[params]
|
||||
(colorpicker params))
|
||||
|
||||
(defn- color-input-render
|
||||
[own 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"))
|
||||
(letfn [(on-change [attr color]
|
||||
(swap! form-ref assoc attr color))
|
||||
(show-picker [attr event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:on-change (partial on-change attr)
|
||||
:value (get @form-ref attr)
|
||||
:transparent? true}]
|
||||
(udl/open! :interactions/colorpicker opts)))]
|
||||
(let [stroke-color (:stroke-color @form-ref)
|
||||
fill-color (:fill-color @form-ref)]
|
||||
(html
|
||||
[:div
|
||||
[:div.row-flex
|
||||
[:div.column-half
|
||||
[:span "Fill"]
|
||||
[:div.color-data
|
||||
[:span.color-th
|
||||
{:style {:background-color fill-color}
|
||||
:on-click (partial show-picker :fill-color)}]
|
||||
[:div.color-info
|
||||
[:span fill-color]]]]
|
||||
[:div.column-half
|
||||
[:span "Stroke"]
|
||||
[:div.color-data
|
||||
[:span.color-th
|
||||
{:style {:background-color stroke-color}
|
||||
:on-click (partial show-picker :stroke-color)}]
|
||||
[:div.color-info
|
||||
[:span stroke-color]]]]]]))))
|
||||
|
||||
(def color-input
|
||||
(mx/component
|
||||
{:render color-input-render
|
||||
:name "color-input"}))
|
||||
|
||||
;; --- Easing Input
|
||||
|
||||
(defn- easing-input-render
|
||||
[own form-ref]
|
||||
(when-not (:easing @form-ref)
|
||||
(swap! form-ref assoc :easing :linear))
|
||||
(html
|
||||
[: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))}
|
||||
[:option {:value ":linear"} "Linear"]
|
||||
[:option {:value ":easein"} "Ease in"]
|
||||
[:option {:value ":easeout"} "Ease out"]
|
||||
[:option {:value ":easeinout"} "Ease in out"]]]]))
|
||||
|
||||
(def easing-input
|
||||
(mx/component
|
||||
{:render easing-input-render
|
||||
:name "easing-input"}))
|
||||
|
||||
;; --- Duration Input
|
||||
|
||||
(defn- duration-input-render
|
||||
[own form-ref]
|
||||
(when-not (:duration @form-ref)
|
||||
(swap! form-ref assoc :duration 300))
|
||||
(when-not (:delay @form-ref)
|
||||
(swap! form-ref assoc :delay 0))
|
||||
(html
|
||||
[:div
|
||||
[:span "Duration | Delay"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.miliseconds
|
||||
[:input.input-text
|
||||
{:placeholder "Duration"
|
||||
:type "number"
|
||||
:on-change (partial on-change form-ref :duration)
|
||||
:value (pr-str (:duration @form-ref))}]]
|
||||
[: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))}]]]]))
|
||||
|
||||
(def duration-input
|
||||
(mx/component
|
||||
{:render duration-input-render
|
||||
:name "duration-input"}))
|
||||
|
||||
;; --- Action Input
|
||||
|
||||
(defn- action-input-render
|
||||
[own page form-ref]
|
||||
(when-not (:action @form-ref)
|
||||
(swap! form-ref assoc :action :show))
|
||||
|
||||
(let [form @form-ref
|
||||
simple? #{:gotourl :gotopage}
|
||||
elements? (complement simple?)
|
||||
animation? #{:show :hide :toggle}
|
||||
only-easing? (complement animation?)]
|
||||
(html
|
||||
[:div
|
||||
[:span "Action"]
|
||||
[:div.row-flex
|
||||
[:select.input-select
|
||||
{:placeholder "Choose an action"
|
||||
:on-change (partial on-change form-ref :action [:trigger])
|
||||
:value (pr-str (:action form))}
|
||||
[:option {:value ":show"} "Show"]
|
||||
[:option {:value ":hide"} "Hide"]
|
||||
[:option {:value ":toggle"} "Toggle"]
|
||||
;; [:option {:value ":moveto"} "Move to"]
|
||||
[:option {:value ":moveby"} "Move by"]
|
||||
[:option {:value ":opacity"} "Opacity"]
|
||||
[:option {:value ":size"} "Size"]
|
||||
[:option {:value ":color"} "Color"]
|
||||
;; [:option {:value ":rotate"} "Rotate"]
|
||||
[:option {:value ":gotopage"} "Go to page"]
|
||||
[:option {:value ":gotourl"} "Go to URL"]
|
||||
#_[:option {:value ":goback"} "Go back"]
|
||||
[:option {:value ":scrolltoelement"} "Scroll to element"]]]
|
||||
|
||||
(case (:action form)
|
||||
: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)
|
||||
nil)
|
||||
|
||||
(when (elements? (:action form))
|
||||
(elements-input page form-ref))
|
||||
|
||||
(when (and (animation? (:action form))
|
||||
(:element @form-ref))
|
||||
(animation-input form-ref))
|
||||
|
||||
(when (or (not= (:animation form :none) :none)
|
||||
(and (only-easing? (:action form))
|
||||
(:element form)))
|
||||
(mx/concat
|
||||
(easing-input form-ref)
|
||||
(duration-input form-ref)))
|
||||
])))
|
||||
|
||||
(def action-input
|
||||
(mx/component
|
||||
{:render action-input-render
|
||||
:name "action-input"}))
|
||||
|
||||
;; --- Form
|
||||
|
||||
(defn- interactions-form-render
|
||||
[own shape form-ref]
|
||||
(letfn [(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(let [shape-id (:id shape)
|
||||
data (deref form-ref)]
|
||||
(rs/emit! (uds/update-interaction shape-id data))
|
||||
(reset! form-ref nil)))
|
||||
(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(reset! form-ref nil))]
|
||||
(html
|
||||
[:form {:on-submit on-submit}
|
||||
(trigger-input form-ref)
|
||||
(action-input (:page shape) form-ref)
|
||||
[:div.row-flex
|
||||
[:input.btn-primary.btn-small.save-btn
|
||||
{:value "Save" :type "submit"}]
|
||||
[:a.cancel-btn {:on-click on-cancel}
|
||||
"Cancel"]]])))
|
||||
|
||||
(def interactions-form
|
||||
(mx/component
|
||||
{:render interactions-form-render
|
||||
:name "interactions-form"}))
|
||||
|
||||
;; --- Interactions Menu
|
||||
|
||||
(defn- interactions-menu-render
|
||||
[own menu shape]
|
||||
(let [local (:rum/local own)
|
||||
form-ref (l/derive (l/key :form) local)
|
||||
interactions (:interactions shape)
|
||||
create-interaction #(reset! form-ref {})]
|
||||
(html
|
||||
[: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)
|
||||
[:div
|
||||
(interactions-list shape form-ref)
|
||||
[:input.btn-primary.btn-small
|
||||
{:value "New interaction"
|
||||
:on-click create-interaction
|
||||
:type "button"}]])]])))
|
||||
|
||||
(def interactions-menu
|
||||
(mx/component
|
||||
{:render interactions-menu-render
|
||||
:name "interactions-menu"
|
||||
:mixins [mx/static (mx/local)]}))
|
||||
|
||||
|
||||
;; --- Not implemented stuff
|
||||
|
||||
;; [:span "Key"]
|
||||
;; [:div.row-flex
|
||||
;; [:select.input-select {:placeholder "Choose a key"
|
||||
;; :value ""}
|
||||
;; [:option {:value ":1"} "key 1"]
|
||||
;; [:option {:value ":2"} "key 2"]
|
||||
;; [:option {:value ":3"} "key 3"]
|
||||
;; [:option {:value ":4"} "key 4"]
|
||||
;; [:option {:value ":5"} "key 5"]]]
|
||||
|
||||
;; [:span "Scrolled to (px)"]
|
||||
;; [:div.row-flex
|
||||
;; [:input.input-text {:placeholder "px"
|
||||
;; :type "number"
|
||||
;; :value ""}]]
|
|
@ -0,0 +1,95 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.line-measures
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(defn- line-measures-menu-render
|
||||
[own menu shape]
|
||||
(letfn [(on-rotation-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(rs/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)
|
||||
props {attr value}]
|
||||
(rs/emit! (uds/update-line-attrs sid props))))]
|
||||
(html
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
[:span "Position"]
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "x1"
|
||||
:type "number"
|
||||
:value (:x1 shape "")
|
||||
:on-change (partial on-pos-change :x1)}]
|
||||
[:input.input-text
|
||||
{:placeholder "y1"
|
||||
:type "number"
|
||||
:value (:y1 shape "")
|
||||
:on-change (partial on-pos-change :y1)}]]
|
||||
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "x2"
|
||||
:type "number"
|
||||
:value (:x2 shape "")
|
||||
:on-change (partial on-pos-change :x2)}]
|
||||
[:input.input-text
|
||||
{:placeholder "y2"
|
||||
:type "number"
|
||||
:value (:y2 shape "")
|
||||
:on-change (partial on-pos-change :y2)}]]
|
||||
|
||||
[: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
|
||||
[:input.input-text
|
||||
{:placeholder ""
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 360
|
||||
:value (:rotation shape "0")
|
||||
:on-change on-rotation-change
|
||||
}]
|
||||
[:input.input-text
|
||||
{:style {:visibility "hidden"}}]
|
||||
]]]
|
||||
)))
|
||||
|
||||
(def line-measures-menu
|
||||
(mx/component
|
||||
{:render line-measures-menu-render
|
||||
:name "line-measures-menu"
|
||||
:mixins [mx/static]}))
|
|
@ -0,0 +1,41 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.page
|
||||
"Page options menu entries."
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
|
||||
(mx/defc measures-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
[:strong "Content here"]]])
|
||||
|
||||
(mx/defc grid-options-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
[:strong "Content here"]]])
|
||||
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 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]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(mx/defc rect-measures-menu
|
||||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(letfn [(on-size-change [attr event]
|
||||
(let [value (-> (dom/event->value event) (parse-int 0))
|
||||
sid (:id shape)
|
||||
props {attr value}]
|
||||
(rs/emit! (uds/update-size sid props))))
|
||||
(on-rotation-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(rs/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)
|
||||
props {attr value}]
|
||||
(rs/emit! (uds/update-position sid props))))
|
||||
(on-border-change [attr event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value nil)
|
||||
sid (:id shape)
|
||||
props {attr value}]
|
||||
(rs/emit! (uds/update-radius-attrs sid props))))
|
||||
(on-proportion-lock-change [event]
|
||||
(if (:proportion-lock shape)
|
||||
(rs/emit! (uds/unlock-proportions id))
|
||||
(rs/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 (:width size)
|
||||
:on-change (partial on-size-change :width)}]]
|
||||
[:div.lock-size
|
||||
{:class (when (:proportion-lock shape) "selected")
|
||||
:on-click on-proportion-lock-change}
|
||||
i/lock]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min "0"
|
||||
:value (:height size)
|
||||
:on-change (partial on-size-change :height)}]]]
|
||||
|
||||
[:span "Position"]
|
||||
[:div.row-flex
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "x"
|
||||
:type "number"
|
||||
:value (:x1 shape "")
|
||||
:on-change (partial on-pos-change :x)}]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "y"
|
||||
:type "number"
|
||||
:value (:y1 shape "")
|
||||
:on-change (partial on-pos-change :y)}]]]
|
||||
|
||||
[:span "Border radius"]
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "rx"
|
||||
:type "number"
|
||||
:value (:rx shape "")
|
||||
:on-change (partial on-border-change :rx)}]
|
||||
[:div.lock-size i/lock]
|
||||
[:input.input-text
|
||||
{:placeholder "ry"
|
||||
:type "number"
|
||||
:value (:ry shape "")
|
||||
:on-change (partial on-border-change :ry)}]]
|
||||
|
||||
[: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 (:rotation shape "0")
|
||||
:on-change on-rotation-change
|
||||
}]]
|
||||
[:input.input-text
|
||||
{:style {:visibility "hidden"}}]
|
||||
]]])))
|
|
@ -0,0 +1,97 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.stroke
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]))
|
||||
|
||||
(defn- stroke-menu-render
|
||||
[own menu shape]
|
||||
(letfn [(change-stroke [value]
|
||||
(let [sid (:id shape)]
|
||||
(rs/emit! (uds/update-stroke-attrs sid value))))
|
||||
(on-width-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-float value 1)]
|
||||
(change-stroke {:width value})))
|
||||
(on-opacity-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-float value 1)
|
||||
value (/ value 10000)]
|
||||
(change-stroke {:opacity value})))
|
||||
(on-color-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(change-stroke {:color value})))
|
||||
(on-stroke-style-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
value (read-string value)]
|
||||
(change-stroke {:type value})))
|
||||
(show-color-picker [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
opts {:x x :y y
|
||||
:shape (:id shape)
|
||||
:attr :stroke
|
||||
:transparent? true}]
|
||||
(udl/open! :workspace/colorpicker opts)))]
|
||||
(let [local (:rum/local own)]
|
||||
(html
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
[:span "Style"]
|
||||
[:div.row-flex
|
||||
[:select#style.input-select {:placeholder "Style"
|
||||
:value (:stroke-type shape)
|
||||
:on-change on-stroke-style-change}
|
||||
[:option {:value ":none"} "None"]
|
||||
[:option {:value ":solid"} "Solid"]
|
||||
[:option {:value ":dotted"} "Dotted"]
|
||||
[:option {:value ":dashed"} "Dashed"]
|
||||
[:option {:value ":mixed"} "Mixed"]]
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text
|
||||
{:placeholder "Width"
|
||||
:type "number"
|
||||
:min "0"
|
||||
:value (:stroke-width shape "1")
|
||||
:on-change on-width-change}]]]
|
||||
|
||||
[:span "Color"]
|
||||
[:div.row-flex.color-data
|
||||
[:span.color-th
|
||||
{:style {:background-color (:stroke shape "#000000")}
|
||||
:on-click show-color-picker}]
|
||||
[:div.color-info
|
||||
[:span (:stroke shape "#000000")]]]
|
||||
|
||||
[:span "Opacity"]
|
||||
[:div.row-flex
|
||||
[:input.slidebar
|
||||
{:type "range"
|
||||
:min "0"
|
||||
:max "10000"
|
||||
:value (* 10000 (:stroke-opacity shape 1))
|
||||
:step "1"
|
||||
:on-change on-opacity-change}]]]]))))
|
||||
|
||||
(def stroke-menu
|
||||
(mx/component
|
||||
{:render stroke-menu-render
|
||||
:name "stroke-menu"
|
||||
:mixed [mx/static]}))
|
344
frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs
Normal file
344
frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs
Normal file
|
@ -0,0 +1,344 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.options.text
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer (colorpicker)]
|
||||
[uxbox.main.ui.workspace.recent-colors :refer (recent-colors)]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer (parse-int
|
||||
parse-float
|
||||
read-string
|
||||
index-by-id)]))
|
||||
|
||||
(declare +fonts+)
|
||||
(declare +fonts-by-id+)
|
||||
|
||||
(defn- text-menu-render
|
||||
[own menu {:keys [font] :as shape}]
|
||||
(letfn [(on-font-family-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
sid (:id shape)
|
||||
params {:family (read-string value)
|
||||
:weight "normal"
|
||||
:style "normal"}]
|
||||
(rs/emit! (uds/update-font-attrs sid params))))
|
||||
(on-font-size-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
params {:size (parse-int value)}
|
||||
sid (:id shape)]
|
||||
(rs/emit! (uds/update-font-attrs sid params))))
|
||||
(on-font-letter-spacing-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
params {:letter-spacing (parse-float value)}
|
||||
sid (:id shape)]
|
||||
(rs/emit! (uds/update-font-attrs sid params))))
|
||||
(on-font-line-height-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
params {:line-height (parse-float value)}
|
||||
sid (:id shape)]
|
||||
(rs/emit! (uds/update-font-attrs sid params))))
|
||||
(on-font-align-change [event value]
|
||||
(let [params {:align value}
|
||||
sid (:id shape)]
|
||||
(rs/emit! (uds/update-font-attrs sid params))))
|
||||
|
||||
(on-font-style-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
[weight style] (read-string value)
|
||||
sid (:id shape)
|
||||
params {:style style
|
||||
:weight weight}]
|
||||
(rs/emit! (uds/update-font-attrs sid params))))]
|
||||
(let [{:keys [family style weight size align line-height letter-spacing]
|
||||
:or {family "sourcesanspro"
|
||||
align "left"
|
||||
style "normal"
|
||||
weight "normal"
|
||||
letter-spacing 1
|
||||
line-height 1.4
|
||||
size 16}} font
|
||||
styles (:styles (first (filter #(= (:id %) family) +fonts+)))]
|
||||
(html
|
||||
[:div.element-set {:key (str (:id menu))}
|
||||
[:div.element-set-title (:name menu)]
|
||||
[:div.element-set-content
|
||||
|
||||
[:span "Font family"]
|
||||
[:div.row-flex
|
||||
[:select.input-select {:value (pr-str family)
|
||||
:on-change on-font-family-change}
|
||||
(for [font +fonts+]
|
||||
[:option {:value (pr-str (:id font))
|
||||
:key (:id font)} (:name font)])]]
|
||||
|
||||
[:span "Size and Weight"]
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "Font Size"
|
||||
:type "number"
|
||||
:min "0"
|
||||
:max "200"
|
||||
:value size
|
||||
:on-change on-font-size-change}]
|
||||
[:select.input-select {:value (pr-str [weight style])
|
||||
:on-change on-font-style-change}
|
||||
(for [style styles
|
||||
:let [data (mapv #(get style %) [:weight :style])]]
|
||||
[:option {:value (pr-str data)
|
||||
:key (:name style)} (:name style)])]]
|
||||
|
||||
[:span "Line height and Letter spacing"]
|
||||
[:div.row-flex
|
||||
[:input.input-text
|
||||
{:placeholder "Line height"
|
||||
:type "number"
|
||||
:step "0.1"
|
||||
:min "0"
|
||||
:max "200"
|
||||
:value line-height
|
||||
:on-change on-font-line-height-change}]
|
||||
[:input.input-text
|
||||
{:placeholder "Letter spacing"
|
||||
:type "number"
|
||||
:step "0.1"
|
||||
:min "0"
|
||||
:max "200"
|
||||
:value letter-spacing
|
||||
:on-change on-font-letter-spacing-change}]]
|
||||
|
||||
|
||||
[:span "Text align"]
|
||||
[:div.row-flex.align-icons
|
||||
[:span {:class (when (= align "left") "current")
|
||||
:on-click #(on-font-align-change % "left")}
|
||||
i/align-left]
|
||||
[:span {:class (when (= align "right") "current")
|
||||
:on-click #(on-font-align-change % "right")}
|
||||
i/align-right]
|
||||
[:span {:class (when (= align "center") "current")
|
||||
:on-click #(on-font-align-change % "center")}
|
||||
i/align-center]
|
||||
[:span {:class (when (= align "justify") "current")
|
||||
:on-click #(on-font-align-change % "justify")}
|
||||
i/align-justify]]]]))))
|
||||
|
||||
(def text-menu
|
||||
(mx/component
|
||||
{:render text-menu-render
|
||||
:name "text-menu"
|
||||
:mixins [mx/static]}))
|
||||
|
||||
|
||||
(def +fonts+
|
||||
[{:id "sourcesanspro"
|
||||
:name "Source Sans Pro"
|
||||
:styles [{:name "Extra-Light"
|
||||
:weight "100"
|
||||
:style "normal"}
|
||||
{:name "Extra-Light Italic"
|
||||
:weight "100"
|
||||
:style "italic"}
|
||||
{:name "Light"
|
||||
:weight "200"
|
||||
:style "normal"}
|
||||
{:name "Light Italic"
|
||||
:weight "200"
|
||||
:style "italic"}
|
||||
{:name "Regular"
|
||||
:weight "normal"
|
||||
:style "normal"}
|
||||
{:name "Italic"
|
||||
:weight "normal"
|
||||
:style "italic"}
|
||||
{:name "Semi-Bold"
|
||||
:weight "500"
|
||||
:style "normal"}
|
||||
{:name "Semi-Bold Italic"
|
||||
:weight "500"
|
||||
:style "italic"}
|
||||
{:name "Bold"
|
||||
:weight "bold"
|
||||
:style "normal"}
|
||||
{:name "Bold Italic"
|
||||
:weight "bold"
|
||||
:style "italic"}
|
||||
{:name "Black"
|
||||
:weight "900"
|
||||
:style "normal"}
|
||||
{:name "Black Italic"
|
||||
:weight "900"
|
||||
:style "italic"}]}
|
||||
{:id "opensans"
|
||||
:name "Open Sans"
|
||||
:styles [{:name "Extra-Light"
|
||||
:weight "100"
|
||||
:style "normal"}
|
||||
{:name "Extra-Light Italic"
|
||||
:weight "100"
|
||||
:style "italic"}
|
||||
{:name "Light"
|
||||
:weight "200"
|
||||
:style "normal"}
|
||||
{:name "Light Italic"
|
||||
:weight "200"
|
||||
:style "italic"}
|
||||
{:name "Regular"
|
||||
:weight "normal"
|
||||
:style "normal"}
|
||||
{:name "Italic"
|
||||
:weight "normal"
|
||||
:style "italic"}
|
||||
{:name "Semi-Bold"
|
||||
:weight "500"
|
||||
:style "normal"}
|
||||
{:name "Semi-Bold Italic"
|
||||
:weight "500"
|
||||
:style "italic"}
|
||||
{:name "Bold"
|
||||
:weight "bold"
|
||||
:style "normal"}
|
||||
{:name "Bold Italic"
|
||||
:weight "bold"
|
||||
:style "italic"}
|
||||
{:name "Black"
|
||||
:weight "900"
|
||||
:style "normal"}
|
||||
{:name "Black Italic"
|
||||
:weight "900"
|
||||
:style "italic"}]}
|
||||
{:id "bebas"
|
||||
:name "Bebas"
|
||||
:styles [{:name "Normal"
|
||||
:weight "normal"
|
||||
:style "normal"}]}
|
||||
{:id "gooddog"
|
||||
:name "Good Dog"
|
||||
:styles [{:name "Normal"
|
||||
:weight "normal"
|
||||
:style "normal"}]}
|
||||
{:id "caviardreams"
|
||||
:name "Caviar Dreams"
|
||||
:styles [{:name "Normal"
|
||||
:weight "normal"
|
||||
:style "normal"}
|
||||
{:name "Normal Italic"
|
||||
:weight "normal"
|
||||
:style "italic"}
|
||||
{:name "Bold"
|
||||
:weight "bold"
|
||||
:style "normal"}
|
||||
{:name "Bold Italic"
|
||||
:weight "bold"
|
||||
:style "italic"}]}
|
||||
{:id "ptsans"
|
||||
:name "PT Sans"
|
||||
:styles [{:name "Normal"
|
||||
:weight "normal"
|
||||
:style "normal"}
|
||||
{:name "Normal Italic"
|
||||
:weight "normal"
|
||||
:style "italic"}
|
||||
{:name "Bold"
|
||||
:weight "bold"
|
||||
:style "normal"}
|
||||
{:name "Bold Italic"
|
||||
:weight "bold"
|
||||
:style "italic"}]}
|
||||
{:id "roboto"
|
||||
:name "Roboto"
|
||||
:styles [{:name "Extra-Light"
|
||||
:weight "100"
|
||||
:style "normal"}
|
||||
{:name "Extra-Light Italic"
|
||||
:weight "100"
|
||||
:style "italic"}
|
||||
{:name "Light"
|
||||
:weight "200"
|
||||
:style "normal"}
|
||||
{:name "Light Italic"
|
||||
:weight "200"
|
||||
:style "italic"}
|
||||
{:name "Regular"
|
||||
:weight "normal"
|
||||
:style "normal"}
|
||||
{:name "Italic"
|
||||
:weight "normal"
|
||||
:style "italic"}
|
||||
{:name "Semi-Bold"
|
||||
:weight "500"
|
||||
:style "normal"}
|
||||
{:name "Semi-Bold Italic"
|
||||
:weight "500"
|
||||
:style "italic"}
|
||||
{:name "Bold"
|
||||
:weight "bold"
|
||||
:style "normal"}
|
||||
{:name "Bold Italic"
|
||||
:weight "bold"
|
||||
:style "italic"}
|
||||
{:name "Black"
|
||||
:weight "900"
|
||||
:style "normal"}
|
||||
{:name "Black Italic"
|
||||
:weight "900"
|
||||
:style "italic"}]}
|
||||
{:id "robotocondensed"
|
||||
:name "Roboto Condensed"
|
||||
:styles [{:name "Extra-Light"
|
||||
:weight "100"
|
||||
:style "normal"}
|
||||
{:name "Extra-Light Italic"
|
||||
:weight "100"
|
||||
:style "italic"}
|
||||
{:name "Light"
|
||||
:weight "200"
|
||||
:style "normal"}
|
||||
{:name "Light Italic"
|
||||
:weight "200"
|
||||
:style "italic"}
|
||||
{:name "Regular"
|
||||
:weight "normal"
|
||||
:style "normal"}
|
||||
{:name "Italic"
|
||||
:weight "normal"
|
||||
:style "italic"}
|
||||
{:name "Semi-Bold"
|
||||
:weight "500"
|
||||
:style "normal"}
|
||||
{:name "Semi-Bold Italic"
|
||||
:weight "500"
|
||||
:style "italic"}
|
||||
{:name "Bold"
|
||||
:weight "bold"
|
||||
:style "normal"}
|
||||
{:name "Bold Italic"
|
||||
:weight "bold"
|
||||
:style "italic"}
|
||||
{:name "Black"
|
||||
:weight "900"
|
||||
:style "normal"}
|
||||
{:name "Black Italic"
|
||||
:weight "900"
|
||||
:style "italic"}]}
|
||||
])
|
||||
|
||||
(def +fonts-by-id+
|
||||
(index-by-id +fonts+))
|
88
frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs
Normal file
88
frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs
Normal file
|
@ -0,0 +1,88 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.sitemap
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.workspace.base :as wb]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(defn- resolve-pages
|
||||
[state]
|
||||
(let [project (get-in state [:workspace :project])]
|
||||
(->> (vals (:pages state))
|
||||
(filter #(= project (:project %)))
|
||||
(sort-by :created-at))))
|
||||
|
||||
(def pages-ref
|
||||
(-> (l/lens resolve-pages)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Component
|
||||
|
||||
(mx/defc page-item
|
||||
{:mixins [(mx/local) mx/static mx/reactive]}
|
||||
[page total active?]
|
||||
(letfn [(on-edit [event]
|
||||
(udl/open! :page-form {:page page}))
|
||||
|
||||
(on-navigate [event]
|
||||
(rs/emit! (dp/go-to (:project page) (:id page))))
|
||||
|
||||
(delete []
|
||||
(let [next #(rs/emit! (dp/go-to (:project page)))]
|
||||
(rs/emit! (udp/delete-page (:id page) next))))
|
||||
|
||||
(on-delete [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(udl/open! :confirm {:on-accept delete}))]
|
||||
[:li {:class (when active? "selected")
|
||||
:on-click on-navigate}
|
||||
[:div.page-icon i/page]
|
||||
[:span (:name page)]
|
||||
[:div.page-actions
|
||||
[:a {:on-click on-edit} i/pencil]
|
||||
(if (> total 1)
|
||||
[:a {:on-click on-delete} i/trash])]]))
|
||||
|
||||
(mx/defc sitemap-toolbox
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [project (mx/react wb/project-ref)
|
||||
pages (mx/react pages-ref)
|
||||
current (mx/react wb/page-ref)
|
||||
create #(udl/open! :page-form {:page {:project (:id project)}})
|
||||
close #(rs/emit! (dw/toggle-flag :sitemap))]
|
||||
[:div.sitemap.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/project-tree]
|
||||
[:span (tr "ds.sitemap")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:div.project-title
|
||||
[:span (:name project)]
|
||||
[:div.add-page {:on-click create} i/close]]
|
||||
[:ul.element-list
|
||||
(for [page pages
|
||||
:let [active? (= (:id page) (:id current))]]
|
||||
(-> (page-item page (count pages) active?)
|
||||
(mx/with-key (:id page))))]]]))
|
|
@ -0,0 +1,145 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.sitemap-pageform
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.state :as st]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.dashboard.projects :refer (+layouts+)]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.rstore :as rs]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.data :refer (deep-merge parse-int)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(def form-data (forms/focus-data :workspace-page-form st/state))
|
||||
(def set-value! (partial forms/set-value! :workspace-page-form))
|
||||
|
||||
;; --- Lightbox
|
||||
|
||||
(def +page-defaults+
|
||||
{:width 1920
|
||||
:height 1080
|
||||
:layout :desktop})
|
||||
|
||||
(def +page-form+
|
||||
{:name [forms/required forms/string]
|
||||
:width [forms/required forms/number]
|
||||
:height [forms/required forms/number]
|
||||
:layout [forms/required forms/string]})
|
||||
|
||||
(mx/defc layout-input
|
||||
[data id]
|
||||
(let [{:keys [id name width height]} (get +layouts+ id)]
|
||||
(letfn [(on-change [event]
|
||||
(set-value! :layout id)
|
||||
(set-value! :width width)
|
||||
(set-value! :height height))]
|
||||
[:div
|
||||
[:input {:type "radio"
|
||||
:id id
|
||||
:name "project-layout"
|
||||
:value id
|
||||
:checked (when (= id (:layout data)) "checked")
|
||||
:on-change on-change}]
|
||||
[:label {:value id :for id} name]])))
|
||||
|
||||
(mx/defc page-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[{:keys [metadata id] :as page}]
|
||||
(let [data (merge +page-defaults+
|
||||
(select-keys page [:name :id])
|
||||
(select-keys metadata [:width :height :layout])
|
||||
(mx/react form-data))
|
||||
valid? (forms/valid? data +page-form+)]
|
||||
(letfn [(update-size [field e]
|
||||
(let [value (dom/event->value e)
|
||||
value (parse-int value)]
|
||||
(set-value! field value)))
|
||||
(update-name [e]
|
||||
(let [value (dom/event->value e)]
|
||||
(set-value! :name value)))
|
||||
(toggle-sizes []
|
||||
(let [{:keys [width height]} data]
|
||||
(set-value! :width height)
|
||||
(set-value! :height width)))
|
||||
(on-cancel [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/close!))
|
||||
(on-save [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/close!)
|
||||
(if (nil? id)
|
||||
(rs/emit! (udp/create-page data))
|
||||
(rs/emit! (udp/update-page id data))))]
|
||||
[:form
|
||||
[:input#project-name.input-text
|
||||
{:placeholder "Page name"
|
||||
:type "text"
|
||||
:value (:name data "")
|
||||
:auto-focus true
|
||||
:on-change update-name}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder "Width"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 4000
|
||||
:value (:width data)
|
||||
:on-change #(update-size :width %)}]]
|
||||
[:a.toggle-layout {:on-click toggle-sizes} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:input#project-height.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 4000
|
||||
:value (:height data)
|
||||
:on-change #(update-size :height %)}]]]
|
||||
|
||||
[:div.input-radio.radio-primary
|
||||
(layout-input data "mobile")
|
||||
(layout-input data "tablet")
|
||||
(layout-input data "notebook")
|
||||
(layout-input data "desktop")]
|
||||
|
||||
(when valid?
|
||||
[:input#project-btn.btn-primary
|
||||
{:value "Go go go!"
|
||||
:on-click on-save
|
||||
:type "button"}])])))
|
||||
|
||||
(mx/defc page-form-lightbox
|
||||
{:mixins [mx/static]
|
||||
:will-unmount (fn [own]
|
||||
(forms/clear! :workspace-page-form)
|
||||
own)}
|
||||
[{:keys [id] :as page}]
|
||||
(letfn [(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!))]
|
||||
(let [creation? (nil? id)]
|
||||
[:div.lightbox-body
|
||||
(if creation?
|
||||
[:h3 "New page"]
|
||||
[:h3 "Edit page"])
|
||||
(page-form page)
|
||||
[:a.close {:on-click on-cancel} i/close]])))
|
||||
|
||||
(defmethod lbx/render-lightbox :page-form
|
||||
[{:keys [page]}]
|
||||
(page-form-lightbox page))
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue