Move all files under frontend directory.

This commit is contained in:
Andrey Antukh 2016-11-20 20:03:17 +01:00
parent 92b45b2d05
commit e21798f1ed
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
603 changed files with 10 additions and 31 deletions

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

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

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

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

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

View file

@ -0,0 +1,224 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 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)]}))

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

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

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

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

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

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

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

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

File diff suppressed because one or more lines are too long

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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})

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

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

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

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

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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+))

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

View file

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