mirror of
https://github.com/penpot/penpot.git
synced 2025-06-01 22:21:50 +02:00
Merge branch 'wip/multicanvas' of github.com:uxbox/uxbox into i18n/multicanvas
This commit is contained in:
commit
d8afb97c7a
84 changed files with 3327 additions and 3774 deletions
|
@ -7,10 +7,10 @@
|
|||
(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]))
|
||||
#_[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)
|
||||
;; (def recovery-page recovery/recovery-page)
|
||||
;; (def recovery-request-page recovery-request/recovery-request-page)
|
||||
|
|
|
@ -8,44 +8,30 @@
|
|||
(ns uxbox.main.ui.auth.login
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as rt]))
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.spec :as us]))
|
||||
|
||||
(def form-data (fm/focus-data :login st/state))
|
||||
(def form-errors (fm/focus-errors :login st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :login))
|
||||
(def assoc-errors (partial fm/assoc-errors :login))
|
||||
(def clear-form (partial fm/clear-form :login))
|
||||
|
||||
(s/def ::username ::fm/non-empty-string)
|
||||
(s/def ::password ::fm/non-empty-string)
|
||||
(s/def ::username ::us/not-empty-string)
|
||||
(s/def ::password ::us/not-empty-string)
|
||||
|
||||
(s/def ::login-form
|
||||
(s/keys :req-un [::username ::password]))
|
||||
|
||||
(defn- on-change
|
||||
[event field]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
|
||||
(defn- on-submit
|
||||
[event data]
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (da/login {:username (:username data)
|
||||
:password (:password data)})))
|
||||
(let [{:keys [username password]} (:clean-data form)]
|
||||
(st/emit! (da/login {:username username
|
||||
:password password}))))
|
||||
|
||||
(mf/defc demo-warning
|
||||
[_]
|
||||
|
@ -56,36 +42,41 @@
|
|||
[:strong "DO NOT USE"] " for real work, " [:br]
|
||||
" the projects will be periodicaly wiped."]])
|
||||
|
||||
|
||||
(mf/defc login-form
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[]
|
||||
(let [data (mf/react form-data)
|
||||
valid? (fm/valid? ::login-form data)]
|
||||
[:form {:on-submit #(on-submit % data)}
|
||||
(let [{:keys [data] :as form} (fm/use-form ::login-form {})]
|
||||
[:form {:on-submit #(on-submit % form)}
|
||||
[:div.login-content
|
||||
(when cfg/isdemo
|
||||
[:& demo-warning])
|
||||
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
{:name "username"
|
||||
:tab-index "2"
|
||||
:value (:username data "")
|
||||
:on-change #(on-change % :username)
|
||||
:class (fm/error-class form :username)
|
||||
:on-blur (fm/on-input-blur form :username)
|
||||
:on-change (fm/on-input-change form :username)
|
||||
:placeholder (tr "auth.email-or-username")
|
||||
:type "text"}]
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:tab-index "3"
|
||||
:value (:password data "")
|
||||
:on-change #(on-change % :password)
|
||||
:class (fm/error-class form :password)
|
||||
:on-blur (fm/on-input-blur form :password)
|
||||
:on-change (fm/on-input-change form :password)
|
||||
:placeholder (tr "auth.password")
|
||||
:type "password"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:tab-index "4"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "auth.signin")
|
||||
:type "submit"}]
|
||||
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth/recovery-request))
|
||||
:tab-index "5"}
|
||||
|
@ -94,14 +85,10 @@
|
|||
:tab-index "6"}
|
||||
(tr "auth.no-account")]]]]))
|
||||
|
||||
|
||||
;; {:mixins [mx/static (fm/clear-mixin st/store :login)]}
|
||||
|
||||
(mf/defc login-page
|
||||
[]
|
||||
(mf/use-effect (constantly #(st/emit! (fm/clear-form :login))))
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(messages-widget)
|
||||
[:& messages-widget]
|
||||
[:a i/logo]
|
||||
[:& login-form]]])
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
(ns uxbox.main.ui.auth.recovery
|
||||
(:require
|
||||
[cljs.spec.alpha :as s :include-macros true]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
|
@ -21,62 +21,62 @@
|
|||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(def form-data (fm/focus-data :recovery st/state))
|
||||
(def form-errors (fm/focus-errors :recovery st/state))
|
||||
;; (def form-data (fm/focus-data :recovery st/state))
|
||||
;; (def form-errors (fm/focus-errors :recovery st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :recovery))
|
||||
(def assoc-errors (partial fm/assoc-errors :recovery))
|
||||
(def clear-form (partial fm/clear-form :recovery))
|
||||
;; (def assoc-value (partial fm/assoc-value :recovery))
|
||||
;; (def assoc-errors (partial fm/assoc-errors :recovery))
|
||||
;; (def clear-form (partial fm/clear-form :recovery))
|
||||
|
||||
;; --- Recovery Form
|
||||
;; ;; --- Recovery Form
|
||||
|
||||
(s/def ::password ::fm/non-empty-string)
|
||||
(s/def ::recovery-form
|
||||
(s/keys :req-un [::password]))
|
||||
;; (s/def ::password ::fm/non-empty-string)
|
||||
;; (s/def ::recovery-form
|
||||
;; (s/keys :req-un [::password]))
|
||||
|
||||
(mx/defc recovery-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[token]
|
||||
(let [data (merge (mx/react form-data) {:token token})
|
||||
valid? (fm/valid? ::recovery-form data)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/recovery data)
|
||||
(clear-form)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:value (:password data "")
|
||||
:on-change (partial on-change :password)
|
||||
:placeholder (tr "recover.password.placeholder")
|
||||
:type "password"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value (tr "recover.recover-password")
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]])))
|
||||
;; (mx/defc recovery-form
|
||||
;; {:mixins [mx/static mx/reactive]}
|
||||
;; [token]
|
||||
;; (let [data (merge (mx/react form-data) {:token token})
|
||||
;; valid? (fm/valid? ::recovery-form data)]
|
||||
;; (letfn [(on-change [field event]
|
||||
;; (let [value (dom/event->value event)]
|
||||
;; (st/emit! (assoc-value field value))))
|
||||
;; (on-submit [event]
|
||||
;; (dom/prevent-default event)
|
||||
;; (st/emit! (uda/recovery data)
|
||||
;; (clear-form)))]
|
||||
;; [:form {:on-submit on-submit}
|
||||
;; [:div.login-content
|
||||
;; [:input.input-text
|
||||
;; {:name "password"
|
||||
;; :value (:password data "")
|
||||
;; :on-change (partial on-change :password)
|
||||
;; :placeholder (tr "recover.password.placeholder")
|
||||
;; :type "password"}]
|
||||
;; [:input.btn-primary
|
||||
;; {:name "login"
|
||||
;; :class (when-not valid? "btn-disabled")
|
||||
;; :disabled (not valid?)
|
||||
;; :value (tr "recover.recover-password")
|
||||
;; :type "submit"}]
|
||||
;; [:div.login-links
|
||||
;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]])))
|
||||
|
||||
;; --- Recovery Page
|
||||
;; ;; --- Recovery Page
|
||||
|
||||
(defn- recovery-page-init
|
||||
[own]
|
||||
(let [[token] (::mx/args own)]
|
||||
(st/emit! (uda/validate-recovery-token token))
|
||||
own))
|
||||
;; (defn- recovery-page-init
|
||||
;; [own]
|
||||
;; (let [[token] (::mx/args own)]
|
||||
;; (st/emit! (uda/validate-recovery-token token))
|
||||
;; own))
|
||||
|
||||
(mx/defc recovery-page
|
||||
{:mixins [mx/static (fm/clear-mixin st/store :recovery)]
|
||||
:init recovery-page-init}
|
||||
[token]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(messages-widget)
|
||||
[:a i/logo]
|
||||
(recovery-form token)]])
|
||||
;; (mx/defc recovery-page
|
||||
;; {:mixins [mx/static (fm/clear-mixin st/store :recovery)]
|
||||
;; :init recovery-page-init}
|
||||
;; [token]
|
||||
;; [:div.login
|
||||
;; [:div.login-body
|
||||
;; (messages-widget)
|
||||
;; [:a i/logo]
|
||||
;; (recovery-form token)]])
|
||||
|
|
|
@ -20,52 +20,52 @@
|
|||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(def form-data (fm/focus-data :recovery-request st/state))
|
||||
(def form-errors (fm/focus-errors :recovery-request st/state))
|
||||
;; (def form-data (fm/focus-data :recovery-request st/state))
|
||||
;; (def form-errors (fm/focus-errors :recovery-request st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :profile-password))
|
||||
(def assoc-errors (partial fm/assoc-errors :profile-password))
|
||||
(def clear-form (partial fm/clear-form :profile-password))
|
||||
;; (def assoc-value (partial fm/assoc-value :profile-password))
|
||||
;; (def assoc-errors (partial fm/assoc-errors :profile-password))
|
||||
;; (def clear-form (partial fm/clear-form :profile-password))
|
||||
|
||||
(s/def ::username ::fm/non-empty-string)
|
||||
(s/def ::recovery-request-form (s/keys :req-un [::username]))
|
||||
;; (s/def ::username ::fm/non-empty-string)
|
||||
;; (s/def ::recovery-request-form (s/keys :req-un [::username]))
|
||||
|
||||
(mx/defc recovery-request-form
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
valid? (fm/valid? ::recovery-request-form data)]
|
||||
(letfn [(on-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value :username value))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/recovery-request data)
|
||||
(clear-form)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "username"
|
||||
:value (:username data "")
|
||||
:on-change on-change
|
||||
:placeholder (tr "recovery-request.username-or-email.placeholder")
|
||||
:type "text"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value (tr "recovery-request.recover-password")
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]])))
|
||||
;; (mx/defc recovery-request-form
|
||||
;; {:mixins [mx/static mx/reactive]}
|
||||
;; []
|
||||
;; (let [data (mx/react form-data)
|
||||
;; valid? (fm/valid? ::recovery-request-form data)]
|
||||
;; (letfn [(on-change [event]
|
||||
;; (let [value (dom/event->value event)]
|
||||
;; (st/emit! (assoc-value :username value))))
|
||||
;; (on-submit [event]
|
||||
;; (dom/prevent-default event)
|
||||
;; (st/emit! (uda/recovery-request data)
|
||||
;; (clear-form)))]
|
||||
;; [:form {:on-submit on-submit}
|
||||
;; [:div.login-content
|
||||
;; [:input.input-text
|
||||
;; {:name "username"
|
||||
;; :value (:username data "")
|
||||
;; :on-change on-change
|
||||
;; :placeholder (tr "recovery-request.username-or-email.placeholder")
|
||||
;; :type "text"}]
|
||||
;; [:input.btn-primary
|
||||
;; {:name "login"
|
||||
;; :class (when-not valid? "btn-disabled")
|
||||
;; :disabled (not valid?)
|
||||
;; :value (tr "recovery-request.recover-password")
|
||||
;; :type "submit"}]
|
||||
;; [:div.login-links
|
||||
;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]])))
|
||||
|
||||
;; --- Recovery Request Page
|
||||
;; ;; --- Recovery Request Page
|
||||
|
||||
(mx/defc recovery-request-page
|
||||
{:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]}
|
||||
[]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(messages-widget)
|
||||
[:a i/logo]
|
||||
(recovery-request-form)]])
|
||||
;; (mx/defc recovery-request-page
|
||||
;; {:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]}
|
||||
;; []
|
||||
;; [:div.login
|
||||
;; [:div.login-body
|
||||
;; (messages-widget)
|
||||
;; [:a i/logo]
|
||||
;; (recovery-request-form)]])
|
||||
|
|
|
@ -6,118 +6,134 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.auth.register
|
||||
(:require [cljs.spec.alpha :as s :include-macros true]
|
||||
[lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[rumext.core :as mx :include-macros true]
|
||||
[uxbox.util.router :as rt]))
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(def form-data (fm/focus-data :register st/state))
|
||||
(def form-errors (fm/focus-errors :register st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :register))
|
||||
(def assoc-error (partial fm/assoc-error :register))
|
||||
(def clear-form (partial fm/clear-form :register))
|
||||
|
||||
;; TODO: add better password validation
|
||||
|
||||
(s/def ::username ::fm/non-empty-string)
|
||||
(s/def ::fullname ::fm/non-empty-string)
|
||||
(s/def ::password ::fm/non-empty-string)
|
||||
(s/def ::username ::fm/not-empty-string)
|
||||
(s/def ::fullname ::fm/not-empty-string)
|
||||
(s/def ::password ::fm/not-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
|
||||
(s/def ::register-form
|
||||
(s/keys :req-un [::username
|
||||
::password
|
||||
::fullname
|
||||
::email
|
||||
::password]))
|
||||
::email]))
|
||||
|
||||
(mx/defc register-form
|
||||
{:mixins [mx/static mx/reactive
|
||||
(fm/clear-mixin st/store :register)]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
errors (mx/react form-errors)
|
||||
valid? (fm/valid? ::register-form data)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-error [{:keys [type code] :as payload}]
|
||||
(case code
|
||||
:uxbox.services.users/registration-disabled
|
||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
||||
:uxbox.services.users/email-already-exists
|
||||
(st/emit! (assoc-error :email (tr "errors.api.form.email-already-exists")))
|
||||
:uxbox.services.users/username-already-exists
|
||||
(st/emit! (assoc-error :username (tr "errors.api.form.username-already-exists")))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/register data on-error)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "fullname"
|
||||
:tab-index "2"
|
||||
:value (:fullname data "")
|
||||
:on-change (partial on-change :fullname)
|
||||
:placeholder (tr "register.fullname.placeholder")
|
||||
:type "text"}]
|
||||
(fm/input-error errors :fullname)
|
||||
(defn- on-error
|
||||
[error form]
|
||||
(case (:code error)
|
||||
:uxbox.services.users/registration-disabled
|
||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
||||
|
||||
[:input.input-text
|
||||
{:name "username"
|
||||
:tab-index "3"
|
||||
:value (:username data "")
|
||||
:on-change (partial on-change :username)
|
||||
:placeholder (tr "register.username.placeholder")
|
||||
:type "text"}]
|
||||
(fm/input-error errors :username)
|
||||
:uxbox.services.users/email-already-exists
|
||||
(swap! form assoc-in [:errors :email]
|
||||
{:type ::api
|
||||
:message "errors.api.form.email-already-exists"})
|
||||
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
:tab-index "4"
|
||||
:ref "email"
|
||||
:value (:email data "")
|
||||
:on-change (partial on-change :email)
|
||||
:placeholder (tr "register.email.placeholder")
|
||||
:type "text"}]
|
||||
(fm/input-error errors :email)
|
||||
:uxbox.services.users/username-already-exists
|
||||
(swap! form assoc-in [:errors :username]
|
||||
{:type ::api
|
||||
:message "errors.api.form.username-already-exists"})))
|
||||
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:tab-index "5"
|
||||
:ref "password"
|
||||
:value (:password data "")
|
||||
:on-change (partial on-change :password)
|
||||
:placeholder (tr "register.password.placeholder")
|
||||
:type "password"}]
|
||||
(fm/input-error errors :password)
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)
|
||||
on-error #(on-error % form)]
|
||||
(st/emit! (uda/register data on-error))))
|
||||
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:tab-index "6"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:value (tr "register.get-started")
|
||||
:type "submit"}]
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "register.already-have-account")]]]])))
|
||||
(mf/defc register-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::register-form {})]
|
||||
[:form {:on-submit #(on-submit % form)}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "fullname"
|
||||
:tab-index "1"
|
||||
:value (:fullname data "")
|
||||
:class (fm/error-class form :fullname)
|
||||
:on-blur (fm/on-input-blur form :fullname)
|
||||
:on-change (fm/on-input-change form :fullname)
|
||||
:placeholder (tr "register.fullname.placeholder")
|
||||
:type "text"}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :fullname}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:name "username"
|
||||
:tab-index "2"
|
||||
:class (fm/error-class form :username)
|
||||
:on-blur (fm/on-input-blur form :username)
|
||||
:on-change (fm/on-input-change form :username)
|
||||
:value (:username data "")
|
||||
:placeholder (tr "settings.profile.your-username")}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :username}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:name "email"
|
||||
:tab-index "3"
|
||||
:class (fm/error-class form :email)
|
||||
:on-blur (fm/on-input-blur form :email)
|
||||
:on-change (fm/on-input-change form :email)
|
||||
:value (:email data "")
|
||||
:placeholder (tr "settings.profile.your-email")}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :email}]
|
||||
|
||||
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:tab-index "4"
|
||||
:value (:password data "")
|
||||
:class (fm/error-class form :password)
|
||||
:on-blur (fm/on-input-blur form :password)
|
||||
:on-change (fm/on-input-change form :password)
|
||||
:placeholder (tr "register.password.placeholder")
|
||||
:type "password"}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :email}]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "submit"
|
||||
:tab-index "5"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "register.get-started")}]
|
||||
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth/login))}
|
||||
(tr "register.already-have-account")]]]]))
|
||||
|
||||
;; --- Register Page
|
||||
|
||||
(mx/defc register-page
|
||||
{:mixins [mx/static]}
|
||||
[own]
|
||||
(mf/defc register-page
|
||||
[props]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
(messages-widget)
|
||||
[:a i/logo]
|
||||
(register-form)]])
|
||||
[:& register-form]]])
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
[{:keys [route] :as props}]
|
||||
(let [[section type id] (parse-route route)]
|
||||
[:main.dashboard-main
|
||||
(messages-widget)
|
||||
[:& messages-widget]
|
||||
[:& header {:section section}]
|
||||
(case section
|
||||
:dashboard/icons
|
||||
|
|
|
@ -9,20 +9,18 @@
|
|||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.projects :as udp]
|
||||
[uxbox.main.exports :as exports]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.dashboard.projects-createform]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.dashboard.projects-forms :refer [create-project-dialog]]
|
||||
[uxbox.util.data :refer [read-string]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as t :refer (tr)]
|
||||
[uxbox.util.i18n :as t :refer [tr]]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.time :as dt]))
|
||||
|
||||
|
@ -34,15 +32,14 @@
|
|||
|
||||
;; --- Refs
|
||||
|
||||
(def opts-ref
|
||||
(def opts-iref
|
||||
(-> (l/in [:dashboard :projects])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def projects-ref
|
||||
(def projects-iref
|
||||
(-> (l/key :projects)
|
||||
(l/derive st/state)))
|
||||
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn sort-projects-by
|
||||
|
@ -117,26 +114,9 @@
|
|||
|
||||
(mf/defc grid-item-thumbnail
|
||||
[{:keys [project] :as props}]
|
||||
(let [url (mf/use-state nil)]
|
||||
#_(mf/use-effect
|
||||
{:deps #js [(:page-id project)]
|
||||
:init (fn []
|
||||
(when-let [page-id (:page-id project)]
|
||||
(let [svg (exports/render-page page-id)
|
||||
uri (some-> svg
|
||||
(blob/create "image/svg+xml")
|
||||
(blob/create-uri))]
|
||||
(reset! url uri)
|
||||
uri)))
|
||||
:end #(when % (blob/revoke-uri %))})
|
||||
(if @url
|
||||
[:div.grid-item-th
|
||||
{:style {:background-image (str "url('" @url "')")}}]
|
||||
[:div.grid-item-th
|
||||
[:img.img-th {:src "/images/project-placeholder.svg"
|
||||
:alt (tr "ds.project-thumbnail.alt")}]])))
|
||||
|
||||
|
||||
[:div.grid-item-th
|
||||
[:img.img-th {:src "/images/project-placeholder.svg"
|
||||
:alt (tr "ds.project-thumbnail.alt")}]])
|
||||
|
||||
;; --- Grid Item
|
||||
|
||||
|
@ -148,7 +128,7 @@
|
|||
delete #(st/emit! (udp/delete-project project))
|
||||
on-delete #(do
|
||||
(dom/stop-propagation %)
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(modal/show! confirm-dialog {:on-accept delete}))
|
||||
on-blur #(let [target (dom/event->target %)
|
||||
name (dom/get-value target)
|
||||
id (:id project)]
|
||||
|
@ -193,13 +173,14 @@
|
|||
[{:keys [opts] :as props}]
|
||||
(let [order (:order opts :created)
|
||||
filter (:filter opts "")
|
||||
projects (mf/deref projects-ref)
|
||||
projects (mf/deref projects-iref)
|
||||
projects (->> (vals projects)
|
||||
(filter-projects-by filter)
|
||||
(sort-projects-by order))
|
||||
on-click #(do
|
||||
(dom/prevent-default %)
|
||||
(udl/open! :create-project))]
|
||||
(modal/show! create-project-dialog {})
|
||||
#_(udl/open! :create-project))]
|
||||
[:section.dashboard-grid
|
||||
[:h2 (tr "ds.project-title")]
|
||||
[:div.dashboard-grid-content
|
||||
|
@ -214,7 +195,7 @@
|
|||
(mf/defc projects-page
|
||||
[_]
|
||||
(mf/use-effect #(st/emit! (udp/initialize)))
|
||||
(let [opts (mf/deref opts-ref)]
|
||||
(let [opts (mf/deref opts-iref)]
|
||||
[:section.dashboard-content
|
||||
[:& menu {:opts opts}]
|
||||
[:& grid {:opts opts}]]))
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.projects-createform
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.projects :as udp]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.data :refer [read-string parse-int]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as t :refer [tr]]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.time :as dt]))
|
||||
|
||||
(def form-data (fm/focus-data :create-project st/state))
|
||||
(def form-errors (fm/focus-errors :create-project st/state))
|
||||
(def assoc-value (partial fm/assoc-value :create-project))
|
||||
(def clear-form (partial fm/clear-form :create-project))
|
||||
|
||||
(s/def ::name ::fm/non-empty-string)
|
||||
(s/def ::layout ::fm/non-empty-string)
|
||||
(s/def ::width number?)
|
||||
(s/def ::height number?)
|
||||
|
||||
(s/def ::project-form
|
||||
(s/keys :req-un [::name
|
||||
::width
|
||||
::height
|
||||
::layout]))
|
||||
|
||||
;; --- Create Project Form
|
||||
|
||||
(mx/defc layout-input
|
||||
[{:keys [::layout-id] :as data}]
|
||||
(let [layout (get c/page-layouts layout-id)]
|
||||
[:div
|
||||
[:input {:type "radio"
|
||||
:key layout-id
|
||||
:id layout-id
|
||||
:name "project-layout"
|
||||
:value (:name layout)
|
||||
:checked (= layout-id (:layout data))
|
||||
:on-change #(st/emit! (assoc-value :layout layout-id)
|
||||
(assoc-value :width (:width layout))
|
||||
(assoc-value :height (:height layout)))}]
|
||||
[:label {:value (:name layout)
|
||||
:for layout-id}
|
||||
(:name layout)]]))
|
||||
|
||||
(mx/defc layout-selector
|
||||
[props]
|
||||
[:div.input-radio.radio-primary
|
||||
(layout-input (assoc props ::layout-id "mobile"))
|
||||
(layout-input (assoc props ::layout-id "tablet"))
|
||||
(layout-input (assoc props ::layout-id "notebook"))
|
||||
(layout-input (assoc props ::layout-id "desktop"))])
|
||||
|
||||
(mf/def create-project-form
|
||||
:mixins #{mf/reactive}
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [data (merge c/project-defaults (mf/react form-data))
|
||||
valid? (fm/valid? ::project-form data)]
|
||||
(letfn [(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(when valid?
|
||||
(st/emit! (udp/create-project data)
|
||||
(udl/close-lightbox))))
|
||||
|
||||
(update-size [field e]
|
||||
(let [value (dom/event->value e)
|
||||
value (parse-int value)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
|
||||
(update-name [e]
|
||||
(let [value (dom/event->value e)]
|
||||
(st/emit! (assoc-value :name value))))
|
||||
(swap-size []
|
||||
(st/emit! (assoc-value :width (:height data))
|
||||
(assoc-value :height (:width data))))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:input#project-name.input-text
|
||||
{:placeholder (tr "ds.project.placeholder")
|
||||
:type "text"
|
||||
:value (:name data)
|
||||
:auto-focus true
|
||||
:on-change update-name}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span (tr "ds.width")]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder (tr "ds.width")
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value (str (:width data))
|
||||
:on-change (partial update-size :width)}]]
|
||||
[:a.toggle-layout {:on-click swap-size} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:span (tr "ds.height")]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder (tr "ds.height")
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value (str (:height data))
|
||||
:on-change (partial update-size :height)}]]]
|
||||
|
||||
;; Layout selector
|
||||
(layout-selector data)
|
||||
|
||||
;; Submit
|
||||
[:input#project-btn.btn-primary
|
||||
{:value (tr "ds.go")
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:type "submit"}]]))))
|
||||
|
||||
;; --- Create Project Lightbox
|
||||
|
||||
(mf/def create-project-lightbox
|
||||
:will-unmount
|
||||
(fn [own]
|
||||
(st/emit! (fm/clear-form :create-project))
|
||||
own)
|
||||
|
||||
:render
|
||||
(fn [own]
|
||||
[:div.lightbox-body
|
||||
[:h3 (tr "ds.project.new")]
|
||||
(create-project-form)
|
||||
[:a.close {:on-click #(st/emit! (udl/close-lightbox))}
|
||||
i/close]]))
|
||||
|
||||
(defmethod lbx/render-lightbox :create-project
|
||||
[_]
|
||||
(create-project-lightbox))
|
||||
|
102
frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs
Normal file
102
frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs
Normal file
|
@ -0,0 +1,102 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.projects-forms
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.projects :as udp]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as t :refer [tr]]))
|
||||
|
||||
(s/def ::name ::fm/not-empty-string)
|
||||
(s/def ::width ::fm/number-str)
|
||||
(s/def ::height ::fm/number-str)
|
||||
|
||||
(s/def ::project-form
|
||||
(s/keys :req-un [::name ::width ::height]))
|
||||
|
||||
(def defaults
|
||||
{:name ""
|
||||
:width "1366"
|
||||
:height "768"})
|
||||
|
||||
;; --- Create Project Form
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)]
|
||||
(st/emit! (udp/create-project data))
|
||||
(modal/hide!)))
|
||||
|
||||
(defn- swap-size
|
||||
[event {:keys [data] :as form}]
|
||||
(swap! data assoc
|
||||
:width (:height data)
|
||||
:height (:width data)))
|
||||
|
||||
(mf/defc create-project-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::project-form defaults)]
|
||||
[:form {:on-submit #(on-submit % form)}
|
||||
[:input.input-text
|
||||
{:placeholder "New project name"
|
||||
:type "text"
|
||||
:name "name"
|
||||
:value (:name data)
|
||||
:class (fm/error-class form :name)
|
||||
:on-blur (fm/on-input-blur form :name)
|
||||
:on-change (fm/on-input-change form :name)
|
||||
:auto-focus true}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span "Width"]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder "Width"
|
||||
:name "width"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 5000
|
||||
:class (fm/error-class form :width)
|
||||
:on-blur (fm/on-input-blur form :width)
|
||||
:on-change (fm/on-input-change form :width)
|
||||
:value (:width data)}]]
|
||||
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:span "Height"]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:name "height"
|
||||
:min 0
|
||||
:max 5000
|
||||
:class (fm/error-class form :height)
|
||||
:on-blur (fm/on-input-blur form :height)
|
||||
:on-change (fm/on-input-change form :height)
|
||||
:value (:height data)}]]]
|
||||
|
||||
;; Submit
|
||||
[:input#project-btn.btn-primary
|
||||
{:value "Go go go!"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:type "submit"}]]))
|
||||
|
||||
;; --- Create Project Lightbox
|
||||
|
||||
(mf/defc create-project-dialog
|
||||
[props]
|
||||
[:div.lightbox-body
|
||||
[:h3 "New project"]
|
||||
[:& create-project-form]
|
||||
[:a.close {:on-click modal/hide!} i/close]])
|
||||
|
|
@ -2,17 +2,17 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.loader
|
||||
(:require [uxbox.main.store :as st]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]))
|
||||
|
||||
;; --- Component
|
||||
|
||||
(mx/defc loader
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
(mf/defc loader
|
||||
[]
|
||||
(when (mx/react st/loader)
|
||||
(when (mf/deref st/loader)
|
||||
[:div.loader-content i/loader]))
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
(ns uxbox.main.ui.messages
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.messages :as uum]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.messages :as um]))
|
||||
|
||||
(def ^:private message-ref
|
||||
(def ^:private message-iref
|
||||
(-> (l/key :message)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mx/defc messages-widget
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
(mf/defc messages-widget
|
||||
[]
|
||||
(let [message (mx/react message-ref)
|
||||
on-close #(st/emit! (uum/hide))]
|
||||
(uum/messages-widget (assoc message :on-close on-close))))
|
||||
(let [message (mf/deref message-iref)
|
||||
on-close #(st/emit! (um/hide))]
|
||||
[:& um/messages-widget {:message message
|
||||
:on-close on-close}]))
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
[{:keys [route] :as props}]
|
||||
(let [section (get-in route [:data :name])]
|
||||
[:main.dashboard-main
|
||||
(messages-widget)
|
||||
[:& messages-widget]
|
||||
[:& header {:section section}]
|
||||
(case section
|
||||
:settings/profile (mf/element profile/profile-page)
|
||||
|
|
|
@ -2,99 +2,93 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings.password
|
||||
(:require [cljs.spec.alpha :as s :include-macros true]
|
||||
[lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.settings.header :refer [header]]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.messages :as um]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cljs.spec.alpha :as s]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.messages :as um]))
|
||||
|
||||
(def form-data (fm/focus-data :profile-password st/state))
|
||||
(def form-errors (fm/focus-errors :profile-password st/state))
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(case (:code error)
|
||||
:uxbox.services.users/old-password-not-match
|
||||
(swap! form assoc-in [:errors :password-old]
|
||||
{:type ::api :message "settings.password.wrong-old-password"})
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :profile-password))
|
||||
(def assoc-error (partial fm/assoc-error :profile-password))
|
||||
(def clear-form (partial fm/clear-form :profile-password))
|
||||
:else (throw (ex-info "unexpected" {:error error}))))
|
||||
|
||||
;; TODO: add better password validation
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)
|
||||
opts {:on-success #(st/emit! (um/info (tr "settings.password.password-saved")))
|
||||
:on-error #(on-error form %)}]
|
||||
(st/emit! (udu/update-password data opts))))
|
||||
|
||||
(s/def ::password-1 ::fm/non-empty-string)
|
||||
(s/def ::password-2 ::fm/non-empty-string)
|
||||
(s/def ::password-old ::fm/non-empty-string)
|
||||
(s/def ::password-1 ::fm/not-empty-string)
|
||||
(s/def ::password-2 ::fm/not-empty-string)
|
||||
(s/def ::password-old ::fm/not-empty-string)
|
||||
|
||||
(s/def ::password-form
|
||||
(s/keys :req-un [::password-1
|
||||
::password-2
|
||||
::password-old]))
|
||||
|
||||
(mx/defc password-form
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
errors (mx/react form-errors)
|
||||
valid? (fm/valid? ::password-form data)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-success []
|
||||
(st/emit! (um/info (tr "settings.password.password-saved"))))
|
||||
(on-error [{:keys [code] :as payload}]
|
||||
(case code
|
||||
:uxbox.services.users/old-password-not-match
|
||||
(st/emit! (assoc-error :password-old (tr "settings.password.wrong-old-password")))
|
||||
(mf/defc password-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::password-form {})]
|
||||
[:form.password-form {:on-submit #(on-submit % form)}
|
||||
[:span.user-settings-label (tr "settings.password.change-password")]
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:name "password-old"
|
||||
:value (:password-old data "")
|
||||
:class (fm/error-class form :password-old)
|
||||
:on-blur (fm/on-input-blur form :password-old)
|
||||
:on-change (fm/on-input-change form :password-old)
|
||||
:placeholder (tr "settings.password.old-password")}]
|
||||
|
||||
:else
|
||||
(throw (ex-info "unexpected" {:error payload}))))
|
||||
(on-submit [event]
|
||||
(st/emit! (udu/update-password data
|
||||
:on-success on-success
|
||||
:on-error on-error)))]
|
||||
[:form.password-form
|
||||
[:span.user-settings-label (tr "settings.password.change-password")]
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (fm/error-class errors :password-old)
|
||||
:value (:password-old data "")
|
||||
:on-change (partial on-change :password-old)
|
||||
:placeholder (tr "settings.password.old-password")}]
|
||||
(fm/input-error errors :password-old)
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (fm/error-class errors :password-1)
|
||||
:value (:password-1 data "")
|
||||
:on-change (partial on-change :password-1)
|
||||
:placeholder (tr "settings.password.new-password")}]
|
||||
(fm/input-error errors :password-1)
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (fm/error-class errors :password-2)
|
||||
:value (:password-2 data "")
|
||||
:on-change (partial on-change :password-2)
|
||||
:placeholder (tr "settings.password.confirm-password")}]
|
||||
(fm/input-error errors :password-2)
|
||||
[:input.btn-primary
|
||||
{:type "button"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:on-click on-submit
|
||||
:value (tr "settings.update-settings")}]])))
|
||||
[:& fm/field-error {:form form :field :password-old :type ::api}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:name "password-1"
|
||||
:value (:password-1 data "")
|
||||
:class (fm/error-class form :password-1)
|
||||
:on-blur (fm/on-input-blur form :password-1)
|
||||
:on-change (fm/on-input-change form :password-1)
|
||||
:placeholder (tr "settings.password.new-password")}]
|
||||
;; [:& fm/field-error {:form form :field :password-1}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:name "password-2"
|
||||
:value (:password-2 data "")
|
||||
:class (fm/error-class form :password-2)
|
||||
:on-blur (fm/on-input-blur form :password-2)
|
||||
:on-change (fm/on-input-change form :password-2)
|
||||
:placeholder (tr "settings.password.confirm-password")}]
|
||||
;; [:& fm/field-error {:form form :field :password-2}]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "settings.update-settings")}]]))
|
||||
|
||||
;; --- Password Page
|
||||
|
||||
(mx/defc password-page
|
||||
[]
|
||||
(mf/defc password-page
|
||||
[props]
|
||||
[:section.dashboard-content.user-settings
|
||||
[:section.user-settings-content
|
||||
(password-form)]])
|
||||
[:& password-form]]])
|
||||
|
|
|
@ -14,35 +14,28 @@
|
|||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer [read-string]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||
[uxbox.util.interop :refer [iterable->seq]]))
|
||||
[uxbox.util.interop :refer [iterable->seq]]
|
||||
[uxbox.util.messages :as um]))
|
||||
|
||||
|
||||
(def form-data (fm/focus-data :profile st/state))
|
||||
(def form-errors (fm/focus-errors :profile st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :profile))
|
||||
(def assoc-error (partial fm/assoc-error :profile))
|
||||
(def clear-form (partial fm/clear-form :profile))
|
||||
|
||||
(defn profile->form
|
||||
(defn- profile->form
|
||||
[profile]
|
||||
(let [language (get-in profile [:metadata :language])]
|
||||
(-> (select-keys profile [:fullname :username :email])
|
||||
(cond-> language (assoc :language language)))))
|
||||
|
||||
(def profile-ref
|
||||
(-> (comp (l/key :profile)
|
||||
(l/lens profile->form))
|
||||
(def ^:private profile-ref
|
||||
(-> (l/key :profile)
|
||||
(l/derive st/state)))
|
||||
|
||||
(s/def ::fullname ::fm/non-empty-string)
|
||||
(s/def ::username ::fm/non-empty-string)
|
||||
(s/def ::fullname ::fm/not-empty-string)
|
||||
(s/def ::username ::fm/not-empty-string)
|
||||
(s/def ::language ::fm/not-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
(s/def ::language #{"en" "fr"})
|
||||
|
||||
(s/def ::profile-form
|
||||
(s/keys :req-un [::fullname
|
||||
|
@ -51,69 +44,94 @@
|
|||
::email]))
|
||||
|
||||
(defn- on-error
|
||||
[{:keys [code] :as payload}]
|
||||
(case code
|
||||
:uxbox.services.users/registration-disabled
|
||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
||||
[error form]
|
||||
(case (:code error)
|
||||
:uxbox.services.users/email-already-exists
|
||||
(st/emit! (assoc-error :email (tr "errors.api.form.email-already-exists")))
|
||||
:uxbox.services.users/username-already-exists
|
||||
(st/emit! (assoc-error :username (tr "errors.api.form.username-already-exists")))))
|
||||
(swap! form assoc-in [:errors :email]
|
||||
{:type ::api
|
||||
:message "errors.api.form.email-already-exists"})
|
||||
|
||||
(defn- on-field-change
|
||||
[event field]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
:uxbox.services.users/username-already-exists
|
||||
(swap! form assoc-in [:errors :username]
|
||||
{:type ::api
|
||||
:message "errors.api.form.username-already-exists"})))
|
||||
|
||||
(defn- initial-data
|
||||
[]
|
||||
(merge {:language @i18n/locale}
|
||||
(profile->form (deref profile-ref))))
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)
|
||||
on-success #(st/emit! (um/info (tr "settings.profile.profile-saved")))
|
||||
on-error #(on-error % form)]
|
||||
(st/emit! (udu/form->update-profile data on-success on-error))))
|
||||
|
||||
;; --- Profile Form
|
||||
(mf/def profile-form
|
||||
:mixins [mf/memo mf/reactive mf/sync-render (fm/clear-mixin st/store :profile)]
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [data (merge {:language @i18n/locale}
|
||||
(mf/react profile-ref)
|
||||
(mf/react form-data))
|
||||
errors (mf/react form-errors)
|
||||
valid? (fm/valid? ::profile-form data)
|
||||
on-success #(st/emit! (clear-form))
|
||||
on-submit #(st/emit! (udu/update-profile data on-success on-error))]
|
||||
[:form.profile-form
|
||||
[:span.user-settings-label (tr "settings.profile.section-basic-data")]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:on-change #(on-field-change % :fullname)
|
||||
:value (:fullname data "")
|
||||
:placeholder (tr "settings.profile.your-name")}]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:on-change #(on-field-change % :username)
|
||||
:value (:username data "")
|
||||
:placeholder (tr "settings.profile.your-username")}]
|
||||
(fm/input-error errors :username)
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:on-change #(on-field-change % :email)
|
||||
:value (:email data "")
|
||||
:placeholder (tr "settings.profile.your-email")}]
|
||||
(fm/input-error errors :email)
|
||||
|
||||
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
|
||||
[:select.input-select {:value (:language data)
|
||||
:on-change #(on-field-change % :language)}
|
||||
[:option {:value "en"} "English"]
|
||||
[:option {:value "fr"} "Français"]]
|
||||
(mf/defc profile-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::profile-form initial-data)]
|
||||
[:form.profile-form {:on-submit #(on-submit % form)}
|
||||
[:span.user-settings-label (tr "settings.profile.section-basic-data")]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:name "fullname"
|
||||
:class (fm/error-class form :fullname)
|
||||
:on-blur (fm/on-input-blur form :fullname)
|
||||
:on-change (fm/on-input-change form :fullname)
|
||||
:value (:fullname data "")
|
||||
:placeholder (tr "settings.profile.your-name")}]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "button"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:on-click on-submit
|
||||
:value (tr "settings.update-settings")}]])))
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :fullname}]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:name "username"
|
||||
:class (fm/error-class form :username)
|
||||
:on-blur (fm/on-input-blur form :username)
|
||||
:on-change (fm/on-input-change form :username)
|
||||
:value (:username data "")
|
||||
:placeholder (tr "settings.profile.your-username")}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :username}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:name "email"
|
||||
:class (fm/error-class form :email)
|
||||
:on-blur (fm/on-input-blur form :email)
|
||||
:on-change (fm/on-input-change form :email)
|
||||
:value (:email data "")
|
||||
:placeholder (tr "settings.profile.your-email")}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :email}]
|
||||
|
||||
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
|
||||
[:select.input-select {:value (:language data)
|
||||
:name "language"
|
||||
:class (fm/error-class form :language)
|
||||
:on-blur (fm/on-input-blur form :language)
|
||||
:on-change (fm/on-input-change form :language)}
|
||||
[:option {:value "en"} "English"]
|
||||
[:option {:value "fr"} "Français"]]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "settings.update-settings")}]]))
|
||||
|
||||
;; --- Profile Photo Form
|
||||
|
||||
(mf/defc profile-photo-form
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[]
|
||||
(letfn [(on-change [event]
|
||||
(let [target (dom/get-target event)
|
||||
|
@ -122,7 +140,7 @@
|
|||
(first))]
|
||||
(st/emit! (udu/update-photo file))
|
||||
(dom/clean-value! target)))]
|
||||
(let [{:keys [photo]} (mf/react profile-ref)
|
||||
(let [{:keys [photo] :as profile} (mf/deref profile-ref)
|
||||
photo (if (or (str/empty? photo) (nil? photo))
|
||||
"images/avatar.jpg"
|
||||
photo)]
|
||||
|
@ -140,4 +158,4 @@
|
|||
[:section.user-settings-content
|
||||
[:span.user-settings-label (tr "settings.profile.your-avatar")]
|
||||
[:& profile-photo-form]
|
||||
(profile-form)]])
|
||||
[:& profile-form]]])
|
||||
|
|
|
@ -14,12 +14,15 @@
|
|||
[uxbox.main.ui.shapes.image :as image]
|
||||
[uxbox.main.ui.shapes.path :as path]
|
||||
[uxbox.main.ui.shapes.rect :as rect]
|
||||
[uxbox.main.ui.shapes.canvas :as canvas]
|
||||
[uxbox.main.ui.shapes.text :as text]))
|
||||
|
||||
(defn render-shape
|
||||
[shape]
|
||||
(mf/html
|
||||
(case (:type shape)
|
||||
:canvas [:& canvas/canvas-component {:shape shape}]
|
||||
:curve [:& path/path-component {:shape shape}]
|
||||
:text [:& text/text-component {:shape shape}]
|
||||
:icon [:& icon/icon-component {:shape shape}]
|
||||
:rect [:& rect/rect-component {:shape shape}]
|
||||
|
|
42
frontend/src/uxbox/main/ui/shapes/canvas.cljs
Normal file
42
frontend/src/uxbox/main/ui/shapes/canvas.cljs
Normal file
|
@ -0,0 +1,42 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.shapes.canvas
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.rect :refer [rect-shape]]
|
||||
;; [uxbox.main.ui.workspace.streams :as uws]
|
||||
[uxbox.util.data :refer [parse-int]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
(def canvas-default-props
|
||||
{:fill-color "#ffffff"})
|
||||
|
||||
(mf/defc canvas-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
shape (merge canvas-default-props shape)]
|
||||
(letfn [(on-double-click [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape (:id shape))))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& rect-shape {:shape shape}]])))
|
||||
|
||||
|
|
@ -21,37 +21,37 @@
|
|||
|
||||
(mf/defc circle-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [modifiers (mf/deref (refs/selected-modifiers (:id shape)))
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& circle-shape {:shape shape :modifiers modifiers}]]))
|
||||
[:& circle-shape {:shape shape}]]))
|
||||
|
||||
;; --- Circle Shape
|
||||
|
||||
(mf/defc circle-shape
|
||||
[{:keys [shape modifiers] :as props}]
|
||||
(let [{:keys [id rotation cx cy]} shape
|
||||
{:keys [resize displacement]} modifiers
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id rotation cx cy modifier-mtx]} shape
|
||||
|
||||
shape (cond-> shape
|
||||
displacement (geom/transform displacement)
|
||||
resize (geom/transform resize))
|
||||
shape (cond
|
||||
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||
:else shape)
|
||||
|
||||
center (gpt/point (:cx shape)
|
||||
(:cy shape))
|
||||
|
||||
rotation (or rotation 0)
|
||||
|
||||
moving? (boolean displacement)
|
||||
moving? (boolean modifier-mtx)
|
||||
|
||||
xfmt (-> (gmt/matrix)
|
||||
(gmt/rotate* rotation center))
|
||||
transform (when (pos? rotation)
|
||||
(str (-> (gmt/matrix)
|
||||
(gmt/rotate* rotation center))))
|
||||
|
||||
props {:id (str "shape-" id)
|
||||
:class (classnames :move-cursor moving?)
|
||||
:transform (str xfmt)}
|
||||
:transform transform}
|
||||
|
||||
attrs (merge props
|
||||
(attrs/extract-style-attrs shape)
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.workspace.streams :as ws]
|
||||
[uxbox.main.ui.workspace.streams :as uws]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Shape Movement (by mouse)
|
||||
|
@ -24,20 +25,17 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
wst (get-in state [:workspace pid])
|
||||
stoper (->> ws/interaction-events
|
||||
(rx/filter ws/mouse-up?)
|
||||
(rx/take 1))
|
||||
stream (->> ws/mouse-position-deltas
|
||||
(rx/take-until stoper))]
|
||||
(rx/concat
|
||||
(when (refs/alignment-activated? (:flags wst))
|
||||
(rx/of (dw/initial-shape-align id)))
|
||||
(rx/map #(dw/apply-temporal-displacement id %) stream)
|
||||
(rx/of (dw/apply-displacement id)))))))
|
||||
flags (get-in state [:workspace pid :flags])
|
||||
stoper (rx/filter uws/mouse-up? stream)]
|
||||
(rx/concat
|
||||
(when (refs/alignment-activated? flags)
|
||||
(rx/of (dw/initial-shape-align id)))
|
||||
(->> uws/mouse-position-deltas
|
||||
(rx/map #(dw/apply-temporal-displacement id %))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-current-modifier id)))))))
|
||||
|
||||
(defn start-move-selected
|
||||
[]
|
||||
(def start-move-selected
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -45,9 +43,8 @@
|
|||
selected (get-in state [:workspace pid :selected])]
|
||||
(rx/from-coll (map start-move selected))))))
|
||||
|
||||
|
||||
(defn on-mouse-down
|
||||
[event {:keys [id] :as shape} selected]
|
||||
[event {:keys [id type] :as shape} selected]
|
||||
(let [selected? (contains? selected id)
|
||||
drawing? @refs/selected-drawing-tool]
|
||||
(when-not (:blocked shape)
|
||||
|
@ -55,11 +52,17 @@
|
|||
drawing?
|
||||
nil
|
||||
|
||||
(= type :canvas)
|
||||
(when selected?
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! start-move-selected))
|
||||
|
||||
(and (not selected?) (empty? selected))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/select-shape id)
|
||||
(start-move-selected)))
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id)
|
||||
start-move-selected))
|
||||
|
||||
(and (not selected?) (not (empty? selected)))
|
||||
(do
|
||||
|
@ -68,8 +71,8 @@
|
|||
(st/emit! (dw/select-shape id))
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id)
|
||||
(start-move-selected))))
|
||||
start-move-selected)))
|
||||
:else
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (start-move-selected)))))))
|
||||
(st/emit! start-move-selected))))))
|
||||
|
|
|
@ -23,14 +23,12 @@
|
|||
|
||||
(mf/defc icon-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [id (:id shape)
|
||||
modifiers (mf/deref (refs/selected-modifiers id))
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected id)
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& icon-shape {:shape shape :modifiers modifiers}]]))
|
||||
[:& icon-shape {:shape shape}]]))
|
||||
|
||||
;; --- Icon Shape
|
||||
|
||||
|
@ -43,22 +41,20 @@
|
|||
(gmt/rotate* mt rotation center)))
|
||||
|
||||
(mf/defc icon-shape
|
||||
[{:keys [shape modifiers] :as props}]
|
||||
(let [{:keys [id content metadata rotation x1 y1]} shape
|
||||
{:keys [resize displacement]} modifiers
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id content metadata rotation modifier-mtx]} shape
|
||||
|
||||
xfmt (cond-> (gmt/matrix)
|
||||
displacement (gmt/multiply displacement)
|
||||
resize (gmt/multiply resize))
|
||||
shape (cond
|
||||
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||
:else shape)
|
||||
|
||||
{:keys [x1 y1 width height] :as shape} (-> (geom/transform shape xfmt)
|
||||
(geom/size))
|
||||
{:keys [x1 y1 width height] :as shape} (geom/size shape)
|
||||
|
||||
transform (when (pos? rotation)
|
||||
(str (rotate (gmt/matrix) shape)))
|
||||
|
||||
view-box (apply str (interpose " " (:view-box metadata)))
|
||||
xfmt (cond-> (gmt/matrix)
|
||||
(pos? rotation) (rotate shape))
|
||||
|
||||
moving? (boolean displacement)
|
||||
moving? (boolean modifier-mtx)
|
||||
props {:id (str id)
|
||||
:x x1
|
||||
:y y1
|
||||
|
@ -70,7 +66,7 @@
|
|||
:dangerouslySetInnerHTML #js {:__html content}}
|
||||
|
||||
attrs (merge props (attrs/extract-style-attrs shape))]
|
||||
[:g {:transform (str xfmt)}
|
||||
[:g {:transform transform}
|
||||
[:> :svg (normalize-props attrs) ]]))
|
||||
|
||||
;; --- Icon SVG
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
|
||||
(mf/defc image-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [modifiers (mf/deref (refs/selected-modifiers (:id shape)))
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
image (mf/deref (image-ref (:image shape)))
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
|
@ -42,27 +41,23 @@
|
|||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& image-shape {:shape shape
|
||||
:image image
|
||||
:modifiers modifiers}]])))
|
||||
:image image}]])))
|
||||
|
||||
;; --- Image Shape
|
||||
|
||||
(mf/defc image-shape
|
||||
[{:keys [shape image modifiers] :as props}]
|
||||
(let [{:keys [id x1 y1 width height]} (geom/size shape)
|
||||
{:keys [resize displacement]} modifiers
|
||||
[{:keys [shape image] :as props}]
|
||||
(let [{:keys [id x1 y1 width height modifier-mtx]} (geom/size shape)
|
||||
moving? (boolean modifier-mtx)
|
||||
transform (when (gmt/matrix? modifier-mtx)
|
||||
(str modifier-mtx))
|
||||
|
||||
xfmt (cond-> (gmt/matrix)
|
||||
resize (gmt/multiply resize)
|
||||
displacement (gmt/multiply displacement))
|
||||
|
||||
moving? (boolean displacement)
|
||||
props {:x x1 :y y1
|
||||
:id (str "shape-" id)
|
||||
:preserve-aspect-ratio "none"
|
||||
:class (classnames :move-cursor moving?)
|
||||
:xlink-href (:url image)
|
||||
:transform (str xfmt)
|
||||
:transform transform
|
||||
:width width
|
||||
:height height}
|
||||
attrs (merge props (attrs/extract-style-attrs shape))]
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
(:require
|
||||
[cuerdas.core :as str :include-macros true]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.data :refer [classnames normalize-props]]))
|
||||
[uxbox.util.data :refer [classnames normalize-props]]
|
||||
[uxbox.util.geom.matrix :as gmt]))
|
||||
|
||||
;; --- Path Component
|
||||
|
||||
|
@ -22,20 +23,17 @@
|
|||
|
||||
(mf/defc path-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [modifiers (mf/deref (refs/selected-modifiers (:id shape)))
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected (:id shape))]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(common/on-mouse-down event shape selected))
|
||||
(on-double-click [event]
|
||||
(when selected?
|
||||
(prn "on-double-click")
|
||||
(st/emit! (udw/start-edition-mode (:id shape)))))]
|
||||
(st/emit! (dw/start-edition-mode (:id shape)))))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& path-shape {:shape shape
|
||||
:modifiers modifiers
|
||||
:background? true}]])))
|
||||
|
||||
;; --- Path Shape
|
||||
|
@ -62,12 +60,13 @@
|
|||
(recur buffer (inc index)))))))
|
||||
|
||||
(mf/defc path-shape
|
||||
[{:keys [shape modifiers background?] :as props}]
|
||||
(let [{:keys [resize displacement]} modifiers
|
||||
shape (cond-> shape
|
||||
displacement (geom/transform displacement)
|
||||
resize (geom/transform resize))
|
||||
moving? (boolean displacement)
|
||||
[{:keys [shape background?] :as props}]
|
||||
(let [modifier-mtx (:modifier-mtx shape)
|
||||
shape (cond
|
||||
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||
:else shape)
|
||||
|
||||
moving? (boolean modifier-mtx)
|
||||
|
||||
pdata (render-path shape)
|
||||
props {:id (str (:id shape))
|
||||
|
|
|
@ -22,16 +22,12 @@
|
|||
|
||||
(mf/defc rect-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [id (:id shape)
|
||||
modifiers (mf/deref (refs/selected-modifiers id))
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected id)
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
;; shape (assoc shape :modifiers modifiers)]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& rect-shape {:shape shape
|
||||
:modifiers modifiers}]]))
|
||||
[:& rect-shape {:shape shape}]]))
|
||||
|
||||
;; --- Rect Shape
|
||||
|
||||
|
@ -43,27 +39,25 @@
|
|||
(gmt/rotate* mt rotation center)))
|
||||
|
||||
(mf/defc rect-shape
|
||||
[{:keys [shape modifiers] :as props}]
|
||||
(let [{:keys [id rotation]} shape
|
||||
{:keys [displacement resize]} modifiers
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id rotation modifier-mtx]} shape
|
||||
|
||||
xfmt (cond-> (gmt/matrix)
|
||||
displacement (gmt/multiply displacement)
|
||||
resize (gmt/multiply resize))
|
||||
shape (cond
|
||||
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||
:else shape)
|
||||
|
||||
{:keys [x1 y1 width height] :as shape} (-> (geom/transform shape xfmt)
|
||||
(geom/size))
|
||||
{:keys [x1 y1 width height] :as shape} (geom/size shape)
|
||||
|
||||
xfmt (cond-> (gmt/matrix)
|
||||
(pos? rotation) (rotate shape))
|
||||
transform (when (pos? rotation)
|
||||
(str (rotate (gmt/matrix) shape)))
|
||||
|
||||
moving? (boolean displacement)
|
||||
moving? (boolean modifier-mtx)
|
||||
|
||||
props {:x x1 :y y1
|
||||
:id (str "shape-" id)
|
||||
:class-name (classnames :move-cursor moving?)
|
||||
:width width
|
||||
:height height
|
||||
:transform (str xfmt)}
|
||||
:transform transform}
|
||||
attrs (merge (attrs/extract-style-attrs shape) props)]
|
||||
[:> :rect (normalize-props attrs)]))
|
||||
|
|
|
@ -39,30 +39,25 @@
|
|||
(declare text-shape-wrapper)
|
||||
(declare text-shape-edit)
|
||||
|
||||
(mf/def text-component
|
||||
:mixins [mf/memo mf/reactive]
|
||||
:render
|
||||
(fn [own {:keys [shape] :as props}]
|
||||
(let [{:keys [id x1 y1 content group]} shape
|
||||
modifiers (mf/react (refs/selected-modifiers id))
|
||||
selected (mf/react refs/selected-shapes)
|
||||
edition? (= (mf/react refs/selected-edition) id)
|
||||
selected? (and (contains? selected id)
|
||||
(= (count selected) 1))
|
||||
shape (assoc shape :modifiers modifiers)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(handle-mouse-down event shape selected))
|
||||
(on-double-click [event]
|
||||
;; TODO: handle grouping event propagation
|
||||
;; TODO: handle actions locking properly
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (udw/start-edition-mode id)))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
(if edition?
|
||||
[:& text-shape-edit {:shape shape}]
|
||||
[:& text-shape-wrapper {:shape shape}])]))))
|
||||
(mf/defc text-component
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id x1 y1 content group]} shape
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
edition (mf/deref refs/selected-edition)
|
||||
edition? (= edition 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)
|
||||
(st/emit! (udw/start-edition-mode id)))]
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down}
|
||||
(if edition?
|
||||
[:& text-shape-edit {:shape shape}]
|
||||
[:& text-shape-wrapper {:shape shape}])])))
|
||||
|
||||
;; --- Text Styles Helpers
|
||||
|
||||
|
@ -126,7 +121,7 @@
|
|||
style (make-style shape)
|
||||
on-input (fn [ev]
|
||||
(let [content (dom/event->inner-text ev)]
|
||||
(st/emit! (uds/update-text id content))))]
|
||||
(st/emit! (udw/update-shape-attrs id {:content content}))))]
|
||||
[:foreignObject {:x x1 :y y1 :width width :height height}
|
||||
[:div {:style (normalize-props style)
|
||||
:ref (::container own)
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
[:li {:on-click #(on-click % :settings/notifications)}
|
||||
i/mail
|
||||
[:span (tr "ds.user.notifications")]]
|
||||
[:li {:on-click #(on-click % (da/logout))}
|
||||
[:li {:on-click #(on-click % da/logout)}
|
||||
i/exit
|
||||
[:span (tr "ds.user.exit")]]]))
|
||||
|
||||
|
@ -49,10 +49,9 @@
|
|||
(l/derive st/state)))
|
||||
|
||||
(mf/defc user
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[_]
|
||||
[props]
|
||||
(let [open (mf/use-state false)
|
||||
profile (mf/react profile-ref)
|
||||
profile (mf/deref profile-ref)
|
||||
photo (if (str/empty? (:photo profile ""))
|
||||
"/images/avatar.jpg"
|
||||
(:photo profile))]
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
(:require
|
||||
[beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.history :as udh]
|
||||
|
@ -52,7 +51,7 @@
|
|||
(let [prev-zoom @refs/selected-zoom
|
||||
dom (mf/ref-node canvas)
|
||||
scroll-position (scroll/get-current-position-absolute dom)
|
||||
mouse-point @uws/viewport-mouse-position]
|
||||
mouse-point @uws/mouse-position]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(if (pos? (.-deltaY event))
|
||||
|
@ -62,7 +61,7 @@
|
|||
|
||||
(defn- subscribe
|
||||
[canvas page]
|
||||
(scroll/scroll-to-page-center (mf/ref-node canvas) page)
|
||||
;; (scroll/scroll-to-page-center (mf/ref-node canvas) page)
|
||||
(st/emit! (udp/watch-page-changes (:id page))
|
||||
(udu/watch-page-changes (:id page)))
|
||||
(let [sub (shortcuts/init)]
|
||||
|
@ -83,10 +82,10 @@
|
|||
:no-tool-bar-left (not left-sidebar?)
|
||||
:scrolling (:viewport-positionig workspace))]
|
||||
|
||||
(mf/use-effect {:deps #js [canvas page]
|
||||
:fn #(subscribe canvas page)})
|
||||
(mf/use-effect #(subscribe canvas page)
|
||||
#js [(:id page)])
|
||||
[:*
|
||||
(messages-widget)
|
||||
[:& messages-widget]
|
||||
[:& header {:page page
|
||||
:flags flags
|
||||
:key (:id page)}]
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.canvas
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes :as uus]
|
||||
[uxbox.main.ui.workspace.drawarea :refer [draw-area]]
|
||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
;; --- Background
|
||||
|
||||
(mf/def background
|
||||
:mixins [mf/memo]
|
||||
:render
|
||||
(fn [own {:keys [background] :as metadata}]
|
||||
[:rect
|
||||
{:x 0 :y 0
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:fill (or background "#ffffff")}]))
|
||||
|
||||
;; --- Canvas
|
||||
|
||||
(mf/defc canvas
|
||||
[{:keys [page wst] :as props}]
|
||||
(let [{:keys [metadata id]} page
|
||||
zoom (:zoom wst 1) ;; NOTE: maybe forward wst to draw-area
|
||||
width (:width metadata)
|
||||
height (:height metadata)]
|
||||
[:svg.page-canvas {:x c/canvas-start-x
|
||||
:y c/canvas-start-y
|
||||
:width width
|
||||
:height height}
|
||||
[:& background metadata]
|
||||
#_[:svg.page-layout
|
||||
[:g.main
|
||||
(for [id (reverse (:shapes page))]
|
||||
[:& uus/shape-component {:id id :key id}])
|
||||
(when (seq (:selected wst))
|
||||
[:& selection-handlers {:wst wst}])
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])]]]))
|
|
@ -21,19 +21,24 @@
|
|||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer [seek]]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.path :as path]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(declare handle-drawing)
|
||||
(declare handle-drawing-generic)
|
||||
(declare handle-drawing-path)
|
||||
(declare handle-drawing-free-path)
|
||||
(declare handle-drawing-curve)
|
||||
(declare handle-finish-drawing)
|
||||
(declare conditional-align)
|
||||
|
||||
(defn start-drawing
|
||||
[object]
|
||||
[type]
|
||||
{:pre [(keyword? type)]}
|
||||
(let [id (gensym "drawing")]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
|
@ -42,35 +47,69 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [lock (get-in state [:workspace :drawing-lock])]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
lock (get-in state [:workspace :drawing-lock])]
|
||||
(if (= lock id)
|
||||
(rx/merge (->> stream
|
||||
(rx/filter #(= % handle-finish-drawing))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
||||
(rx/of (handle-drawing object)))
|
||||
(rx/merge
|
||||
(->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
||||
(rx/of (handle-drawing type)))
|
||||
(rx/empty)))))))
|
||||
|
||||
(defn- conditional-align [point align?]
|
||||
(if align?
|
||||
(uwrk/align-point point)
|
||||
(rx/of point)))
|
||||
(def ^:private minimal-shapes
|
||||
[{:type :rect
|
||||
:name "Rect"
|
||||
:stroke-color "#000000"}
|
||||
{:type :image}
|
||||
{:type :circle
|
||||
:name "Circle"}
|
||||
{:type :path
|
||||
:name "Path"
|
||||
:stroke-style :solid
|
||||
:stroke-color "#000000"
|
||||
:stroke-width 2
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
:segments []}
|
||||
{:type :canvas
|
||||
:name "Canvas"
|
||||
:stroke-color "#000000"}
|
||||
{:type :curve
|
||||
:name "Path"
|
||||
:stroke-style :solid
|
||||
:stroke-color "#000000"
|
||||
:stroke-width 2
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
:segments []}
|
||||
{:type :text
|
||||
:name "Text"
|
||||
:content "Type your text here"}])
|
||||
|
||||
(defn- make-minimal-shape
|
||||
[type]
|
||||
(let [tool (seek #(= type (:type %)) minimal-shapes)]
|
||||
(assert tool "unexpected drawing tool")
|
||||
(assoc tool :id (uuid/random))))
|
||||
|
||||
;; TODO: maybe this should be a simple function
|
||||
(defn handle-drawing
|
||||
[shape]
|
||||
[type]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
data (make-minimal-shape type)]
|
||||
(update-in state [:workspace pid :drawing] merge data)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of
|
||||
(if (= :path (:type shape))
|
||||
(if (:free shape)
|
||||
(handle-drawing-free-path shape)
|
||||
(handle-drawing-path shape))
|
||||
(handle-drawing-generic shape))))))
|
||||
(case type
|
||||
:path (rx/of handle-drawing-path)
|
||||
:curve (rx/of handle-drawing-curve)
|
||||
(rx/of handle-drawing-generic)))))
|
||||
|
||||
(defn- handle-drawing-generic
|
||||
[shape]
|
||||
(def handle-drawing-generic
|
||||
(letfn [(initialize-drawing [state point]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
shape (get-in state [:workspace pid :drawing])
|
||||
|
@ -80,24 +119,13 @@
|
|||
:y2 (+ (:y point) 2)})]
|
||||
(assoc-in state [:workspace pid :drawing] (assoc shape ::initialized? true))))
|
||||
|
||||
;; TODO: this is a new approach for resizing, when all the
|
||||
;; subsystem are migrated to the new resize approach, this
|
||||
;; function should be moved into uxbox.main.geom ns.
|
||||
(resize-shape [shape point lock?]
|
||||
(if (= (:type shape) :circle)
|
||||
(let [rx (mth/abs (- (:x point) (:cx shape)))
|
||||
ry (mth/abs (- (:y point) (:cy shape)))]
|
||||
(if lock?
|
||||
(assoc shape :rx rx :ry ry)
|
||||
(assoc shape :rx rx :ry rx)))
|
||||
(let [width (- (:x point) (:x1 shape))
|
||||
height (- (:y point) (:y1 shape))
|
||||
proportion (:proportion shape 1)]
|
||||
(assoc shape
|
||||
:x2 (+ (:x1 shape) width)
|
||||
:y2 (if lock?
|
||||
(+ (:y1 shape) (/ width proportion))
|
||||
(+ (:y1 shape) height))))))
|
||||
(let [shape (-> (geom/shape->rect-shape shape)
|
||||
(geom/size))
|
||||
result (geom/resize-shape :bottom-right shape point lock?)
|
||||
scale (geom/calculate-scale-ratio shape result)
|
||||
mtx (geom/generate-resize-matrix :bottom-right shape scale)]
|
||||
(assoc shape :modifier-mtx mtx)))
|
||||
|
||||
(update-drawing [state point lock?]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
|
@ -107,28 +135,26 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
zoom (get-in state [:workspace pid :zoom])
|
||||
flags (get-in state [:workspace pid :flags])
|
||||
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||
align? (refs/alignment-activated? flags)
|
||||
|
||||
stoper (->> (rx/filter #(or (uws/mouse-up? %) (= % :interrupt)) stream)
|
||||
(rx/take 1))
|
||||
stoper? #(or (uws/mouse-up? %) (= % :interrupt))
|
||||
stoper (rx/filter stoper? stream)
|
||||
|
||||
mouse (->> uws/viewport-mouse-position
|
||||
mouse (->> uws/mouse-position
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/with-latest vector uws/mouse-position-ctrl))]
|
||||
(rx/map #(gpt/divide % zoom)))]
|
||||
(rx/concat
|
||||
(->> uws/viewport-mouse-position
|
||||
(->> mouse
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/map (fn [pt] #(initialize-drawing % pt))))
|
||||
(->> mouse
|
||||
(rx/with-latest vector uws/mouse-position-ctrl)
|
||||
(rx/map (fn [[pt ctrl?]] #(update-drawing % pt ctrl?)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of handle-finish-drawing)))))))
|
||||
|
||||
(defn handle-drawing-path
|
||||
[shape]
|
||||
(def handle-drawing-path
|
||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||
(or (= event :interrupt)
|
||||
(and (uws/mouse-event? event)
|
||||
|
@ -162,17 +188,17 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
zoom (get-in state [:workspace pid :zoom])
|
||||
flags (get-in state [:workspace pid :flags])
|
||||
align? (refs/alignment-activated? flags)
|
||||
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||
|
||||
last-point (volatile! @uws/viewport-mouse-position)
|
||||
align? (refs/alignment-activated? flags)
|
||||
last-point (volatile! (gpt/divide @uws/mouse-position zoom))
|
||||
|
||||
stoper (->> (rx/filter stoper-event? stream)
|
||||
(rx/take 1))
|
||||
(rx/share))
|
||||
|
||||
mouse (->> (rx/sample 10 uws/viewport-mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?)))
|
||||
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/map #(gpt/divide % zoom)))
|
||||
|
||||
points (->> stream
|
||||
(rx/filter uws/mouse-click?)
|
||||
|
@ -186,7 +212,6 @@
|
|||
(rx/with-latest vector counter)
|
||||
(rx/map flatten))
|
||||
|
||||
|
||||
imm-transform #(vector (- % 7) (+ % 7) %)
|
||||
immanted-zones (vec (concat
|
||||
(map imm-transform (range 0 181 15))
|
||||
|
@ -205,8 +230,8 @@
|
|||
|
||||
(->> points
|
||||
(rx/take-until stoper)
|
||||
(rx/map (fn [pt]
|
||||
#(insert-point-segment % pt))))
|
||||
(rx/map (fn [pt]#(insert-point-segment % pt))))
|
||||
|
||||
(rx/concat
|
||||
(->> stream'
|
||||
(rx/map (fn [[point ctrl? index :as xxx]]
|
||||
|
@ -221,8 +246,7 @@
|
|||
(rx/of remove-dangling-segmnet
|
||||
handle-finish-drawing))))))))
|
||||
|
||||
(defn- handle-drawing-free-path
|
||||
[shape]
|
||||
(def handle-drawing-curve
|
||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||
(or (= event :interrupt)
|
||||
(and (uws/mouse-event? event) (= type :up))))
|
||||
|
@ -242,15 +266,14 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
zoom (get-in state [:workspace pid :zoom])
|
||||
flags (get-in state [:workspace pid :flags])
|
||||
{:keys [zoom flags]} (get-in state [:workspace pid])
|
||||
|
||||
align? (refs/alignment-activated? flags)
|
||||
stoper (rx/filter stoper-event? stream)
|
||||
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/map #(gpt/divide % zoom)))]
|
||||
|
||||
stoper (->> (rx/filter stoper-event? stream)
|
||||
(rx/take 1))
|
||||
|
||||
mouse (->> (rx/sample 10 uws/viewport-mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?)))]
|
||||
(rx/concat
|
||||
(rx/of initialize-drawing)
|
||||
(->> mouse
|
||||
|
@ -265,22 +288,17 @@
|
|||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
shape (get-in state [:workspace pid :drawing])]
|
||||
(if (::initialized? shape)
|
||||
(let [resize-mtx (get-in state [:workspace pid :modifiers (:id shape) :resize])
|
||||
shape (cond-> shape
|
||||
resize-mtx (geom/transform resize-mtx))]
|
||||
(rx/of
|
||||
;; Remove the stalled modifiers
|
||||
;; TODO: maybe a specific event for "clear modifiers"
|
||||
#(update-in % [:workspace pid :modifiers] dissoc (:id shape))
|
||||
|
||||
;; Unselect the drawing tool
|
||||
#(update-in % [:workspace pid] dissoc :drawing :drawing-tool)
|
||||
|
||||
(rx/concat
|
||||
(rx/of dw/clear-drawing)
|
||||
(when (::initialized? shape)
|
||||
(let [modifier-mtx (:modifier-mtx shape)
|
||||
shape (if (gmt/matrix? modifier-mtx)
|
||||
(geom/transform shape modifier-mtx)
|
||||
shape)
|
||||
shape (dissoc shape ::initialized? :modifier-mtx)]
|
||||
;; Add & select the cred shape to the workspace
|
||||
(ds/add-shape shape)
|
||||
(dw/select-first-shape)))
|
||||
(rx/of #(update-in % [:workspace pid] dissoc :drawing :drawing-tool)))))))
|
||||
(rx/of (dw/add-shape shape)
|
||||
(dw/select-first-shape)))))))))
|
||||
|
||||
(def close-drawing-path
|
||||
(reify
|
||||
|
@ -295,11 +313,12 @@
|
|||
(declare path-draw-area)
|
||||
|
||||
(mf/defc draw-area
|
||||
[{:keys [zoom shape modifiers] :as props}]
|
||||
(if (= (:type shape) :path)
|
||||
[:& path-draw-area {:shape shape}]
|
||||
[:& generic-draw-area {:shape (assoc shape :modifiers modifiers)
|
||||
:zoom zoom}]))
|
||||
[{:keys [zoom shape] :as props}]
|
||||
(when (:id shape)
|
||||
(case (:type shape)
|
||||
(:path :curve) [:& path-draw-area {:shape shape}]
|
||||
[:& generic-draw-area {:shape shape
|
||||
:zoom zoom}])))
|
||||
|
||||
(mf/defc generic-draw-area
|
||||
[{:keys [shape zoom]}]
|
||||
|
@ -328,7 +347,7 @@
|
|||
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
||||
[:g
|
||||
(shapes/render-shape shape)
|
||||
(when-not (:free shape)
|
||||
(when (not= :curve (:type shape))
|
||||
[:circle.close-bezier
|
||||
{:cx x
|
||||
:cy y
|
||||
|
@ -336,3 +355,8 @@
|
|||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}])])))
|
||||
|
||||
(defn- conditional-align [point align?]
|
||||
(if align?
|
||||
(uwrk/align-point point)
|
||||
(rx/of point)))
|
||||
|
|
|
@ -30,9 +30,8 @@
|
|||
;; --- Zoom Widget
|
||||
|
||||
(mf/defc zoom-widget
|
||||
{:wrap [mf/wrap-reactive]}
|
||||
[props]
|
||||
(let [zoom (mf/react refs/selected-zoom)
|
||||
(let [zoom (mf/deref refs/selected-zoom)
|
||||
increase #(st/emit! (dw/increase-zoom))
|
||||
decrease #(st/emit! (dw/decrease-zoom))]
|
||||
[:ul.options-view
|
||||
|
|
|
@ -48,13 +48,11 @@
|
|||
|
||||
(on-uploaded [[image]]
|
||||
(let [{:keys [id name width height]} image
|
||||
shape {:type :image
|
||||
:name name
|
||||
:id (uuid/random)
|
||||
shape {:name name
|
||||
:metadata {:width width
|
||||
:height height}
|
||||
:image id}]
|
||||
(st/emit! (dw/select-for-drawing shape))
|
||||
(st/emit! (dw/select-for-drawing :image shape))
|
||||
(modal/hide!)))
|
||||
|
||||
(on-files-selected [event]
|
||||
|
@ -93,13 +91,11 @@
|
|||
(mf/defc image-item
|
||||
[{:keys [image] :as props}]
|
||||
(letfn [(on-click [event]
|
||||
(let [shape {:type :image
|
||||
:name (:name image)
|
||||
:id (uuid/random)
|
||||
(let [shape {:name (:name image)
|
||||
:metadata {:width (:width image)
|
||||
:height (:height image)}
|
||||
:image (:id image)}]
|
||||
(st/emit! (dw/select-for-drawing shape))
|
||||
(st/emit! (dw/select-for-drawing :image shape))
|
||||
(modal/hide!)))]
|
||||
[:div.library-item {:on-click on-click}
|
||||
[:div.library-item-th
|
||||
|
|
|
@ -10,10 +10,9 @@
|
|||
(:require
|
||||
[beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -34,19 +33,13 @@
|
|||
|
||||
;; --- Resize Implementation
|
||||
|
||||
;; TODO: this function need to be refactored
|
||||
|
||||
(defn- start-resize
|
||||
[vid ids shape]
|
||||
(letfn [(on-resize [shape [point lock?]]
|
||||
(letfn [(resize [shape [point lock?]]
|
||||
(let [result (geom/resize-shape vid shape point lock?)
|
||||
scale (geom/calculate-scale-ratio shape result)
|
||||
mtx (geom/generate-resize-matrix vid shape scale)
|
||||
xfm (map #(udw/apply-temporal-resize % mtx))]
|
||||
(apply st/emit! (sequence xfm ids))))
|
||||
|
||||
(on-end []
|
||||
(apply st/emit! (map udw/apply-resize ids)))
|
||||
mtx (geom/generate-resize-matrix vid shape scale)]
|
||||
(apply rx/of (map #(dw/assoc-temporal-modifier % mtx) ids))))
|
||||
|
||||
;; Unifies the instantaneous proportion lock modifier
|
||||
;; activated by Ctrl key and the shapes own proportion
|
||||
|
@ -65,19 +58,23 @@
|
|||
;; Apply the current zoom factor to the point.
|
||||
(apply-zoom [point]
|
||||
(gpt/divide point @refs/selected-zoom))]
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape (->> (geom/shape->rect-shape shape)
|
||||
(geom/size))
|
||||
stoper (rx/filter ws/mouse-up? stream)]
|
||||
(rx/concat
|
||||
(->> ws/mouse-position
|
||||
(rx/map apply-zoom)
|
||||
(rx/mapcat apply-grid-alignment)
|
||||
(rx/with-latest vector ws/mouse-position-ctrl)
|
||||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape))
|
||||
(rx/take-until stoper))
|
||||
(rx/from-coll (map dw/materialize-current-modifier ids))))))))
|
||||
|
||||
(let [shape (->> (geom/shape->rect-shape shape)
|
||||
(geom/size))
|
||||
stoper (->> ws/interaction-events
|
||||
(rx/filter ws/mouse-up?)
|
||||
(rx/take 1))
|
||||
stream (->> ws/viewport-mouse-position
|
||||
(rx/take-until stoper)
|
||||
(rx/map apply-zoom)
|
||||
(rx/mapcat apply-grid-alignment)
|
||||
(rx/with-latest vector ws/mouse-position-ctrl)
|
||||
(rx/map normalize-proportion-lock))]
|
||||
(rx/subscribe stream (partial on-resize shape) nil on-end))))
|
||||
;; (rx/subscribe stream (partial on-resize shape) nil on-end))))
|
||||
|
||||
;; --- Controls (Component)
|
||||
|
||||
|
@ -160,22 +157,23 @@
|
|||
(letfn [(on-mouse-down [event index]
|
||||
(dom/stop-propagation event)
|
||||
|
||||
(let [stoper (get-edition-stream-stoper ws/interaction-events)
|
||||
;; TODO: this need code ux refactor
|
||||
(let [stoper (get-edition-stream-stoper)
|
||||
stream (rx/take-until stoper ws/mouse-position-deltas)]
|
||||
(when @refs/selected-alignment
|
||||
(st/emit! (uds/initial-path-point-align (:id shape) index)))
|
||||
(st/emit! (dw/initial-path-point-align (:id shape) index)))
|
||||
(rx/subscribe stream #(on-handler-move % index))))
|
||||
|
||||
(get-edition-stream-stoper [stream]
|
||||
(get-edition-stream-stoper []
|
||||
(let [stoper? #(and (ws/mouse-event? %) (= (:type %) :up))]
|
||||
(rx/merge
|
||||
(rx/filter stoper? stream)
|
||||
(->> stream
|
||||
(rx/filter stoper? st/stream)
|
||||
(->> st/stream
|
||||
(rx/filter #(= % :interrupt))
|
||||
(rx/take 1)))))
|
||||
|
||||
(on-handler-move [delta index]
|
||||
(st/emit! (uds/update-path (:id shape) index delta)))]
|
||||
(st/emit! (dw/update-path (:id shape) index delta)))]
|
||||
|
||||
(let [displacement (:displacement modifiers)
|
||||
segments (cond->> (:segments shape)
|
||||
|
@ -191,30 +189,20 @@
|
|||
:style {:cursor "pointer"}}])])))
|
||||
|
||||
(mf/defc multiple-selection-handlers
|
||||
[{:keys [shapes modifiers zoom] :as props}]
|
||||
[{:keys [shapes zoom] :as props}]
|
||||
(let [shape (->> shapes
|
||||
(map #(assoc % :modifiers (get modifiers (:id %))))
|
||||
(map #(geom/selection-rect %))
|
||||
(geom/shapes->rect-shape)
|
||||
(geom/selection-rect))
|
||||
on-click #(do (dom/stop-propagation %2)
|
||||
(start-resize %1 (map :id shapes) shape))]
|
||||
(st/emit! (start-resize %1 (mapv :id shapes) shape)))]
|
||||
[:& controls {:shape shape
|
||||
:zoom zoom
|
||||
:on-click on-click}]))
|
||||
|
||||
(mf/defc single-selection-handlers
|
||||
[{:keys [shape zoom modifiers] :as props}]
|
||||
(let [on-click #(do (dom/stop-propagation %2)
|
||||
(start-resize %1 #{(:id shape)} shape))
|
||||
shape (-> (assoc shape :modifiers modifiers)
|
||||
(geom/selection-rect))]
|
||||
[:& controls {:shape shape :zoom zoom :on-click on-click}]))
|
||||
|
||||
(mf/defc text-edition-selection-handlers
|
||||
[{:keys [shape modifiers zoom] :as props}]
|
||||
(let [{:keys [x1 y1 width height] :as shape} (-> (assoc shape :modifiers modifiers)
|
||||
(geom/selection-rect))]
|
||||
[{:keys [shape zoom] :as props}]
|
||||
(let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)]
|
||||
[:g.controls
|
||||
[:rect.main {:x x1 :y y1
|
||||
:width width
|
||||
|
@ -225,6 +213,13 @@
|
|||
:stroke-opacity "0.5"
|
||||
:fill "transparent"}}]]))
|
||||
|
||||
(mf/defc single-selection-handlers
|
||||
[{:keys [shape zoom] :as props}]
|
||||
(let [on-click #(do (dom/stop-propagation %2)
|
||||
(st/emit! (start-resize %1 #{(:id shape)} shape)))
|
||||
shape (geom/selection-rect shape)]
|
||||
[:& controls {:shape shape :zoom zoom :on-click on-click}]))
|
||||
|
||||
(def ^:private shapes-map-iref
|
||||
(-> (l/key :shapes)
|
||||
(l/derive st/state)))
|
||||
|
@ -233,35 +228,29 @@
|
|||
[{:keys [wst] :as props}]
|
||||
(let [shapes-map (mf/deref shapes-map-iref)
|
||||
shapes (map #(get shapes-map %) (:selected wst))
|
||||
edition? (:edition wst)
|
||||
modifiers (:modifiers wst)
|
||||
edition (:edition wst)
|
||||
zoom (:zoom wst 1)
|
||||
num (count shapes)
|
||||
{:keys [id type] :as shape} (first shapes)]
|
||||
|
||||
|
||||
(cond
|
||||
(zero? num)
|
||||
nil
|
||||
|
||||
(> num 1)
|
||||
[:& multiple-selection-handlers {:shapes shapes
|
||||
:modifiers modifiers
|
||||
:zoom zoom}]
|
||||
|
||||
(and (= type :text)
|
||||
(= edition? (:id shape)))
|
||||
(= edition (:id shape)))
|
||||
[:& text-edition-selection-handlers {:shape shape
|
||||
:modifiers (get modifiers id)
|
||||
:zoom zoom}]
|
||||
(and (= type :path)
|
||||
(= edition? (:id shape)))
|
||||
(and (or (= type :path)
|
||||
(= type :curve))
|
||||
(= edition (:id shape)))
|
||||
[:& path-edition-selection-handlers {:shape shape
|
||||
:zoom zoom
|
||||
:modifiers (get modifiers id)}]
|
||||
|
||||
:zoom zoom}]
|
||||
|
||||
:else
|
||||
[:& single-selection-handlers {:shape shape
|
||||
:modifiers (get modifiers id)
|
||||
:zoom zoom}])))
|
||||
|
|
|
@ -12,10 +12,7 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.lightbox :as dl]
|
||||
[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])
|
||||
[uxbox.main.data.undo :as du])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes
|
||||
goog.ui.KeyboardShortcutHandler
|
||||
|
@ -27,26 +24,24 @@
|
|||
|
||||
(defonce +shortcuts+
|
||||
{:shift+g #(st/emit! (dw/toggle-flag :grid))
|
||||
:ctrl+g #(st/emit! (uds/group-selected))
|
||||
:ctrl+shift+g #(st/emit! (uds/ungroup-selected))
|
||||
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap))
|
||||
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools))
|
||||
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons))
|
||||
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers))
|
||||
:ctrl+0 #(st/emit! (dw/reset-zoom))
|
||||
:ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
||||
:ctrl+d #(st/emit! (uds/duplicate-selected))
|
||||
:ctrl+d #(st/emit! dw/duplicate-selected)
|
||||
:ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
||||
:ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
||||
:ctrl+shift+v #(dl/open! :clipboard)
|
||||
:ctrl+z #(st/emit! (udu/undo))
|
||||
:ctrl+shift+z #(st/emit! (udu/redo))
|
||||
:ctrl+y #(st/emit! (udu/redo))
|
||||
:ctrl+b #(st/emit! (dw/select-for-drawing wsd/+draw-tool-rect+))
|
||||
:ctrl+e #(st/emit! (dw/select-for-drawing wsd/+draw-tool-circle+))
|
||||
:ctrl+t #(st/emit! (dw/select-for-drawing wsd/+draw-tool-text+))
|
||||
:ctrl+z #(st/emit! (du/undo))
|
||||
:ctrl+shift+z #(st/emit! (du/redo))
|
||||
:ctrl+y #(st/emit! (du/redo))
|
||||
:ctrl+b #(st/emit! (dw/select-for-drawing :rect))
|
||||
:ctrl+e #(st/emit! (dw/select-for-drawing :circle))
|
||||
:ctrl+t #(st/emit! (dw/select-for-drawing :text))
|
||||
:esc #(st/emit! (dw/deselect-all))
|
||||
:delete #(st/emit! (dw/delete-selected))
|
||||
:delete #(st/emit! dw/delete-selected)
|
||||
:ctrl+up #(st/emit! (dw/move-selected-layer :up))
|
||||
:ctrl+down #(st/emit! (dw/move-selected-layer :down))
|
||||
:ctrl+shift+up #(st/emit! (dw/move-selected-layer :top))
|
||||
|
|
|
@ -21,17 +21,16 @@
|
|||
(mf/defc left-sidebar
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [flags page] :as props}]
|
||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||
[:> rdnd/provider {:backend rdnd/html5}
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:project-id (:project page)
|
||||
:current-page-id (:id page)
|
||||
:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page}])]]])
|
||||
[:aside.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:project-id (:project page)
|
||||
:current-page-id (:id page)
|
||||
:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page}])]])
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
|
@ -43,6 +42,5 @@
|
|||
[:& draw-toolbox {:flags flags}])
|
||||
(when (contains? flags :element-options)
|
||||
[:& options-toolbox {:page page}])
|
||||
(when (contains? flags :icons)
|
||||
#_(icons-toolbox))]])
|
||||
|
||||
#_(when (contains? flags :icons)
|
||||
(icons-toolbox))]])
|
||||
|
|
|
@ -17,96 +17,69 @@
|
|||
|
||||
;; --- Constants
|
||||
|
||||
(def +draw-tool-rect+
|
||||
{:type :rect
|
||||
:id (uuid/random)
|
||||
:name "Rect"
|
||||
:stroke-color "#000000"})
|
||||
|
||||
(def +draw-tool-circle+
|
||||
{:type :circle
|
||||
:id (uuid/random)
|
||||
:name "Circle"})
|
||||
|
||||
(def +draw-tool-path+
|
||||
{:type :path
|
||||
:id (uuid/random)
|
||||
:name "Path"
|
||||
:stroke-style :solid
|
||||
:stroke-color "#000000"
|
||||
:stroke-width 2
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
;; :close? true
|
||||
:points []})
|
||||
|
||||
(def +draw-tool-curve+
|
||||
(assoc +draw-tool-path+
|
||||
:id (uuid/random)
|
||||
:free true))
|
||||
|
||||
(def +draw-tool-text+
|
||||
{:type :text
|
||||
:id (uuid/random)
|
||||
:name "Text"
|
||||
:content "Hello world"})
|
||||
|
||||
(def +draw-tools+
|
||||
[{:icon i/box
|
||||
:help "ds.help.rect"
|
||||
:shape +draw-tool-rect+
|
||||
:type :rect
|
||||
:priority 1}
|
||||
{:icon i/circle
|
||||
:help "ds.help.circle"
|
||||
:shape +draw-tool-circle+
|
||||
:type :circle
|
||||
:priority 2}
|
||||
{:icon i/text
|
||||
:help "ds.help.text"
|
||||
:shape +draw-tool-text+
|
||||
:type :text
|
||||
:priority 4}
|
||||
{:icon i/curve
|
||||
:help "ds.help.path"
|
||||
:shape +draw-tool-path+
|
||||
:type :path
|
||||
:priority 5}
|
||||
{:icon i/pencil
|
||||
:help "ds.help.curve"
|
||||
:shape +draw-tool-curve+
|
||||
:priority 6}])
|
||||
:type :curve
|
||||
:priority 6}
|
||||
;; TODO: we need an icon for canvas creation
|
||||
{:icon i/box
|
||||
:help "ds.help.canvas"
|
||||
:type :canvas
|
||||
:priority 7}])
|
||||
|
||||
;; --- Draw Toolbox (Component)
|
||||
|
||||
(mf/defc draw-toolbox
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [flags] :as props}]
|
||||
(let [close #(st/emit! (dw/toggle-flag :drawtools))
|
||||
dtool (mf/deref refs/selected-drawing-tool)
|
||||
tools (->> (into [] +draw-tools+)
|
||||
(sort-by (comp :priority second)))
|
||||
(letfn [(close [event]
|
||||
(st/emit! (dw/deactivate-flag :drawtools)))
|
||||
(select [event tool]
|
||||
(st/emit! :interrupt
|
||||
(dw/deactivate-ruler)
|
||||
(dw/select-for-drawing tool)))
|
||||
(toggle-ruler [event]
|
||||
(st/emit! (dw/select-for-drawing nil)
|
||||
(dw/deselect-all)
|
||||
(dw/toggle-ruler)))]
|
||||
|
||||
select-drawtool #(st/emit! :interrupt
|
||||
(dw/deactivate-ruler)
|
||||
(dw/select-for-drawing %))
|
||||
toggle-ruler #(st/emit! (dw/select-for-drawing nil)
|
||||
(dw/deselect-all)
|
||||
(dw/toggle-ruler))]
|
||||
(let [selected (mf/deref refs/selected-drawing-tool)
|
||||
tools (sort-by (comp :priority second) +draw-tools+)]
|
||||
[:div.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.settings.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
(for [item tools]
|
||||
(let [selected? (= (:type item) selected)]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr (:help item))
|
||||
:class (when selected? "selected")
|
||||
:key (:type item)
|
||||
:on-click #(select % (:type item))}
|
||||
(:icon item)]))
|
||||
|
||||
[:div#form-tools.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.settings.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
(for [[i props] (map-indexed vector tools)]
|
||||
(let [selected? (= dtool (:shape props))]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr (:help props))
|
||||
:class (when selected? "selected")
|
||||
:key i
|
||||
:on-click (partial select-drawtool (:shape props))}
|
||||
(:icon props)]))
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr "ds.help.ruler")
|
||||
:on-click toggle-ruler
|
||||
:class (when (contains? flags :ruler) "selected")}
|
||||
i/ruler-tool]]]))
|
||||
#_[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr "ds.help.ruler")
|
||||
:on-click toggle-ruler
|
||||
:class (when (contains? flags :ruler) "selected")}
|
||||
i/ruler-tool]]])))
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.dashboard.icons :as icons]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
|
@ -23,12 +22,12 @@
|
|||
|
||||
;; --- Refs
|
||||
|
||||
(def ^:private drawing-shape-ref
|
||||
"A focused vision of the drawing property
|
||||
of the workspace status. This avoids
|
||||
rerender the whole toolbox on each workspace
|
||||
change."
|
||||
(l/derive ul/selected-drawing st/state))
|
||||
;; (def ^:private drawing-shape-ref
|
||||
;; "A focused vision of the drawing property
|
||||
;; of the workspace status. This avoids
|
||||
;; rerender the whole toolbox on each workspace
|
||||
;; change."
|
||||
;; (l/derive ul/selected-drawing st/state))
|
||||
|
||||
(def ^:private icons-toolbox-ref
|
||||
(-> (l/in [:workspace :icons-toolbox])
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
|
@ -45,9 +44,10 @@
|
|||
on-blur (fn [event]
|
||||
(let [target (dom/event->target event)
|
||||
parent (.-parentNode target)
|
||||
parent (.-parentNode parent)
|
||||
name (dom/get-value target)]
|
||||
(set! (.-draggable parent) true)
|
||||
(st/emit! (uds/rename-shape (:id shape) name))
|
||||
(st/emit! (dw/rename-shape (:id shape) name))
|
||||
(swap! local assoc :edition false)))
|
||||
on-key-down (fn [event]
|
||||
(js/console.log event)
|
||||
|
@ -55,7 +55,8 @@
|
|||
(on-blur event)))
|
||||
on-click (fn [event]
|
||||
(dom/prevent-default event)
|
||||
(let [parent (.-parentNode (.-target event))]
|
||||
(let [parent (.-parentNode (.-target event))
|
||||
parent (.-parentNode parent)]
|
||||
(set! (.-draggable parent) false))
|
||||
(swap! local assoc :edition true))]
|
||||
(if (:edition @local)
|
||||
|
@ -78,18 +79,18 @@
|
|||
(let [id (:id shape)
|
||||
blocked? (:blocked shape)]
|
||||
(if blocked?
|
||||
(st/emit! (uds/unblock-shape id))
|
||||
(st/emit! (uds/block-shape id)))))
|
||||
(st/emit! (dw/unblock-shape id))
|
||||
(st/emit! (dw/block-shape id)))))
|
||||
|
||||
(toggle-visibility [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [id (:id shape)
|
||||
hidden? (:hidden shape)]
|
||||
(if hidden?
|
||||
(st/emit! (uds/show-shape id))
|
||||
(st/emit! (uds/hide-shape id)))
|
||||
(st/emit! (dw/show-shape id))
|
||||
(st/emit! (dw/hide-shape id)))
|
||||
(when (contains? selected id)
|
||||
(st/emit! (udw/select-shape id)))))
|
||||
(st/emit! (dw/select-shape id)))))
|
||||
|
||||
(select-shape [event]
|
||||
(dom/prevent-default event)
|
||||
|
@ -100,24 +101,20 @@
|
|||
nil
|
||||
|
||||
(.-ctrlKey event)
|
||||
(st/emit! (udw/select-shape id))
|
||||
(st/emit! (dw/select-shape id))
|
||||
|
||||
(> (count selected) 1)
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id))
|
||||
|
||||
(contains? selected id)
|
||||
(st/emit! (udw/select-shape id))
|
||||
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id))
|
||||
:else
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id)))))
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id)))))
|
||||
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/persist-page (:page shape))))
|
||||
|
||||
(on-hover [item monitor]
|
||||
(st/emit! (udw/change-shape-order {:id (:shape-id item)
|
||||
(st/emit! (dw/change-shape-order {:id (:shape-id item)
|
||||
:index index})))]
|
||||
(let [selected? (contains? selected (:id shape))
|
||||
[dprops dnd-ref] (use-sortable
|
||||
|
@ -133,8 +130,7 @@
|
|||
:dragging-TODO (:dragging? dprops))}
|
||||
[:div.element-list-body {:class (classnames :selected selected?)
|
||||
:on-click select-shape
|
||||
:on-double-click #(dom/stop-propagation %)
|
||||
:draggable true}
|
||||
:on-double-click #(dom/stop-propagation %)}
|
||||
[:div.element-actions
|
||||
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||
:on-click toggle-visibility}
|
||||
|
@ -145,20 +141,52 @@
|
|||
[:div.element-icon (element-icon shape)]
|
||||
[:& layer-name {:shape shape}]]])))
|
||||
|
||||
|
||||
;; --- Layer Canvas
|
||||
|
||||
;; (mf/defc layer-canvas
|
||||
;; [{:keys [canvas selected index] :as props}]
|
||||
;; (letfn [(select-shape [event]
|
||||
;; (dom/prevent-default event)
|
||||
;; (st/emit! (dw/select-canvas (:id canvas))))
|
||||
;; (let [selected? (contains? selected (:id shape))]
|
||||
;; [:li {:class (classnames
|
||||
;; :selected selected?)}
|
||||
;; [:div.element-list-body {:class (classnames :selected selected?)
|
||||
;; :on-click select-shape
|
||||
;; :on-double-click #(dom/stop-propagation %)
|
||||
;; :draggable true}
|
||||
;; [:div.element-actions
|
||||
;; [:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||
;; :on-click toggle-visibility}
|
||||
;; i/eye]
|
||||
;; [:div.block-element {:class (when (:blocked shape) "selected")
|
||||
;; :on-click toggle-blocking}
|
||||
;; i/lock]]
|
||||
;; [:div.element-icon (element-icon shape)]
|
||||
;; [:& layer-name {:shape shape}]]])))
|
||||
|
||||
;; --- Layers List
|
||||
|
||||
(def ^:private shapes-iref
|
||||
(-> (l/key :shapes)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private canvas-iref
|
||||
(-> (l/key :canvas)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc layers-list
|
||||
[{:keys [shapes selected] :as props}]
|
||||
(let [shapes-map (mf/deref shapes-iref)]
|
||||
(let [shapes-map (mf/deref shapes-iref)
|
||||
canvas-map (mf/deref canvas-iref)
|
||||
selected-shapes (mf/deref refs/selected-shapes)
|
||||
selected-canvas (mf/deref refs/selected-canvas)]
|
||||
[:div.tool-window-content
|
||||
[:ul.element-list
|
||||
(for [[index id] (map-indexed vector shapes)]
|
||||
[:& layer-item {:shape (get shapes-map id)
|
||||
:selected selected
|
||||
:selected selected-shapes
|
||||
:index index
|
||||
:key id}])]]))
|
||||
|
||||
|
@ -166,7 +194,7 @@
|
|||
|
||||
(mf/defc layers-toolbox
|
||||
[{:keys [page selected] :as props}]
|
||||
(let [on-click #(st/emit! (udw/toggle-flag :layers))
|
||||
(let [on-click #(st/emit! (dw/toggle-flag :layers))
|
||||
selected (mf/deref refs/selected-shapes)]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
|
|
@ -93,14 +93,14 @@
|
|||
value (parse-int value 0)
|
||||
sid (:id shape)
|
||||
props {attr value}]
|
||||
(st/emit! (uds/update-dimensions sid props))))
|
||||
(st/emit! (udw/update-dimensions sid props))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(st/emit! (uds/update-rotation sid value))))
|
||||
(st/emit! (udw/update-shape-attrs sid {:rotation value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
|
@ -108,11 +108,11 @@
|
|||
value (parse-int value nil)
|
||||
sid (:id shape)
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position sid point))))
|
||||
(st/emit! (udw/update-position sid point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||
|
||||
|
|
|
@ -90,14 +90,13 @@
|
|||
value (parse-int value 0)
|
||||
sid (:id shape)
|
||||
props {attr value}]
|
||||
(st/emit! (uds/update-dimensions sid props))))
|
||||
(st/emit! (udw/update-dimensions sid props))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(st/emit! (uds/update-rotation sid value))))
|
||||
value (parse-int value 0)]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
|
@ -105,11 +104,11 @@
|
|||
value (parse-int value nil)
|
||||
sid (:id shape)
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position sid point))))
|
||||
(st/emit! (udw/update-position sid point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
|
@ -106,30 +105,30 @@
|
|||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
props {attr value}]
|
||||
(st/emit! (uds/update-dimensions (:id shape) props))))
|
||||
(st/emit! (dw/update-dimensions (:id shape) props))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)]
|
||||
(st/emit! (uds/update-rotation (:id shape) value))))
|
||||
(st/emit! (dw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||
|
||||
(defn- on-opacity-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-float value 1)
|
||||
value (/ value 10000)]
|
||||
(st/emit! (uds/update-attrs (:id shape) {:opacity value}))))
|
||||
(st/emit! (dw/update-shape-attrs (:id shape) {:opacity value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value nil)
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position (:id shape) point))))
|
||||
(st/emit! (dw/update-position (:id shape) point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (dw/unlock-proportions (:id shape)))
|
||||
(st/emit! (dw/lock-proportions (:id shape)))))
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.colorpicker :as cp]
|
||||
|
@ -59,7 +59,7 @@
|
|||
(delete [item]
|
||||
(let [sid (:id shape)
|
||||
id (:id item)]
|
||||
(st/emit! (uds/delete-interaction sid id))))
|
||||
(st/emit! (dw/delete-interaction sid id))))
|
||||
(on-delete [item event]
|
||||
(dom/prevent-default event)
|
||||
(let [delete (partial delete item)]
|
||||
|
@ -455,7 +455,7 @@
|
|||
(dom/prevent-default event)
|
||||
(let [sid (:id shape)
|
||||
data (deref form)]
|
||||
(st/emit! (uds/update-interaction sid data))
|
||||
(st/emit! (dw/update-interaction sid data))
|
||||
(reset! form nil)))
|
||||
(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
(on-name-change [event]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(str/trim))]
|
||||
(st/emit! (->> (assoc page :name value)
|
||||
(udp/update-page (:id page))))))
|
||||
(st/emit! (-> (assoc page :name value)
|
||||
(udp/update-page-attrs)))))
|
||||
|
||||
(show-color-picker [event]
|
||||
(let [x (.-clientX event)
|
||||
|
|
|
@ -87,23 +87,23 @@
|
|||
[event shape attr]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int 0))]
|
||||
(st/emit! (uds/update-dimensions (:id shape) {attr value}))))
|
||||
(st/emit! (udw/update-dimensions (:id shape) {attr value}))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int 0))]
|
||||
(st/emit! (uds/update-rotation (:id shape) value))))
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int nil))
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position (:id shape) point))))
|
||||
(st/emit! (udw/update-position (:id shape) point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(letfn [(update-attrs [attrs]
|
||||
(st/emit! (uds/update-attrs id attrs)))
|
||||
(st/emit! (udw/update-shape-attrs id attrs)))
|
||||
(on-font-family-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
attrs {:font-family (read-string value)
|
||||
|
|
|
@ -10,21 +10,18 @@
|
|||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[rumext.util :as mfu]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap-forms :refer [page-form-dialog]]
|
||||
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.dom.dnd :as dnd]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
;; --- Page Item
|
||||
|
@ -32,17 +29,15 @@
|
|||
(mf/defc page-item
|
||||
[{:keys [page index deletable? selected?] :as props}]
|
||||
(letfn [(on-edit [event]
|
||||
(udl/open! :page-form {:page page}))
|
||||
(modal/show! page-form-dialog {:page page}))
|
||||
(delete []
|
||||
(let [next #(st/emit! (dp/go-to (:project page)))]
|
||||
(st/emit! (udp/delete-page (:id page) next))))
|
||||
|
||||
(st/emit! (dw/delete-page (:id page))))
|
||||
(on-delete [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(modal/show! confirm-dialog {:on-accept delete}))
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/reorder-pages (:project page))))
|
||||
(st/emit! (udp/rehash-pages (:project page))))
|
||||
(on-hover [item monitor]
|
||||
(st/emit! (udp/move-page {:project-id (:project-id item)
|
||||
:page-id (:page-id item)
|
||||
|
@ -101,7 +96,7 @@
|
|||
:fn #(-> (l/in [:projects project-id])
|
||||
(l/derive st/state))})
|
||||
project (mf/deref project-iref)
|
||||
create #(udl/open! :page-form {:page {:project project-id}})
|
||||
create #(modal/show! page-form-dialog {:page {:project project-id}})
|
||||
close #(st/emit! (dw/toggle-flag :sitemap))]
|
||||
[:div.sitemap.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
|
114
frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs
Normal file
114
frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs
Normal file
|
@ -0,0 +1,114 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.sitemap-forms
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cljs.spec.alpha :as s]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr]]))
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::project ::us/uuid)
|
||||
(s/def ::name ::us/not-empty-string)
|
||||
(s/def ::width ::us/number-str)
|
||||
(s/def ::height ::us/number-str)
|
||||
|
||||
(s/def ::page-form
|
||||
(s/keys :req-un [::id
|
||||
::project
|
||||
::name
|
||||
::width
|
||||
::height]))
|
||||
|
||||
(def defaults
|
||||
{:name ""
|
||||
:width "1366"
|
||||
:height "768"})
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(modal/hide!)
|
||||
(let [data (:clean-data form)]
|
||||
(if (nil? (:id data))
|
||||
(st/emit! (udp/form->create-page data))
|
||||
(st/emit! (udp/form->update-page data)))))
|
||||
|
||||
(defn- swap-size
|
||||
[event {:keys [data] :as form}]
|
||||
(swap! data assoc
|
||||
:width (:height data)
|
||||
:height (:width data)))
|
||||
|
||||
(defn- initial-data
|
||||
[page]
|
||||
(merge {:name "" :width "1366" :height "768"}
|
||||
(select-keys page [:name :id :project])
|
||||
(select-keys (:metadata page) [:width :height])))
|
||||
|
||||
(mf/defc page-form
|
||||
[{:keys [page] :as props}]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::page-form #(initial-data page))]
|
||||
[:form {:on-submit #(on-submit % form)}
|
||||
[:input.input-text
|
||||
{:placeholder "Page name"
|
||||
:type "text"
|
||||
:name "name"
|
||||
:class (fm/error-class form :name)
|
||||
:on-blur (fm/on-input-blur form :name)
|
||||
:on-change (fm/on-input-change form :name)
|
||||
:value (:name data)
|
||||
:auto-focus true}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span "Width"]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder "Width"
|
||||
:name "width"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 5000
|
||||
:class (fm/error-class form :width)
|
||||
:on-blur (fm/on-input-blur form :width)
|
||||
:on-change (fm/on-input-change form :width)
|
||||
:value (:width data)}]]
|
||||
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:span "Height"]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder "Height"
|
||||
:name "height"
|
||||
:type "number"
|
||||
:min 0
|
||||
:max 5000
|
||||
:class (fm/error-class form :height)
|
||||
:on-blur (fm/on-input-blur form :height)
|
||||
:on-change (fm/on-input-change form :height)
|
||||
:value (:height data)}]]]
|
||||
[:input.btn-primary
|
||||
{:value "Go go go!"
|
||||
:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))}]]))
|
||||
|
||||
(mf/defc page-form-dialog
|
||||
[{:keys [page] :as props}]
|
||||
[:div.lightbox-body
|
||||
(if (nil? (:id page))
|
||||
[:h3 "New page"]
|
||||
[:h3 "Edit page"])
|
||||
[:& page-form {:page page}]
|
||||
[:a.close {:on-click modal/hide!} i/close]])
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-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 [cljs.spec.alpha :as s :include-macros true]
|
||||
[lentes.core :as l]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.util.data :refer [parse-int]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]
|
||||
[rumext.core :as mx :include-macros true]))
|
||||
|
||||
|
||||
(def form-data (fm/focus-data :workspace-page-form st/state))
|
||||
(def form-errors (fm/focus-errors :workspace-page-form st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :workspace-page-form))
|
||||
(def assoc-error (partial fm/assoc-error :workspace-page-form))
|
||||
(def clear-form (partial fm/clear-form :workspace-page-form))
|
||||
|
||||
;; --- Lightbox
|
||||
|
||||
(s/def ::name ::fm/non-empty-string)
|
||||
(s/def ::layout ::fm/non-empty-string)
|
||||
(s/def ::width number?)
|
||||
(s/def ::height number?)
|
||||
|
||||
(s/def ::page-form
|
||||
(s/keys :req-un [::name
|
||||
::width
|
||||
::height
|
||||
::layout]))
|
||||
|
||||
(mx/defc layout-input
|
||||
[data id]
|
||||
(let [{:keys [id name width height]} (get c/page-layouts id)]
|
||||
(letfn [(on-change [event]
|
||||
(st/emit! (assoc-value :layout id)
|
||||
(assoc-value :width width)
|
||||
(assoc-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 c/page-defaults
|
||||
(select-keys page [:name :id :project])
|
||||
(select-keys metadata [:width :height :layout])
|
||||
(mx/react form-data))
|
||||
valid? (fm/valid? ::page-form data)]
|
||||
(letfn [(update-size [field e]
|
||||
(let [value (dom/event->value e)
|
||||
value (parse-int value)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
(update-name [e]
|
||||
(let [value (dom/event->value e)]
|
||||
(st/emit! (assoc-value :name value))))
|
||||
(toggle-sizes []
|
||||
(let [{:keys [width height]} data]
|
||||
(st/emit! (assoc-value :width width)
|
||||
(assoc-value :height height))))
|
||||
(on-cancel [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/close!))
|
||||
(on-save [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/close!)
|
||||
(if (nil? id)
|
||||
(st/emit! (udp/create-page data))
|
||||
(st/emit! (udp/persist-page-update-form id data))))]
|
||||
[:form
|
||||
[:input#project-name.input-text
|
||||
{:placeholder (tr "ds.page.placeholder")
|
||||
:type "text"
|
||||
:value (:name data "")
|
||||
:auto-focus true
|
||||
:on-change update-name}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span (tr "ds.width")]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder (tr "ds.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
|
||||
[:span (tr "ds.height")]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder (tr "ds.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")]
|
||||
|
||||
[:input#project-btn.btn-primary
|
||||
{:value (tr "ds.go")
|
||||
:disabled (not valid?)
|
||||
:on-click on-save
|
||||
:type "button"}]])))
|
||||
|
||||
(mx/defc page-form-lightbox
|
||||
{:mixins [mx/static (fm/clear-mixin st/store :workspace-page-form)]}
|
||||
[{:keys [id] :as page}]
|
||||
(letfn [(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
(udl/close!))]
|
||||
(let [creation? (nil? id)]
|
||||
[:div.lightbox-body
|
||||
(if creation?
|
||||
[:h3 (tr "ds.page.new")]
|
||||
[:h3 (tr "ds.page.edit")])
|
||||
(page-form page)
|
||||
[:a.close {:on-click on-cancel} i/close]])))
|
||||
|
||||
(defmethod lbx/render-lightbox :page-form
|
||||
[{:keys [page]}]
|
||||
(page-form-lightbox page))
|
|
@ -52,21 +52,7 @@
|
|||
(and (mouse-event? v)
|
||||
(= :click (:type v))))
|
||||
|
||||
(defrecord PointerEvent [window
|
||||
viewport
|
||||
ctrl
|
||||
shift])
|
||||
|
||||
(defn pointer-event
|
||||
[window viewport ctrl shift]
|
||||
{:pre [(gpt/point? window)
|
||||
(gpt/point? viewport)
|
||||
(boolean? ctrl)
|
||||
(boolean? shift)]}
|
||||
(PointerEvent. window
|
||||
viewport
|
||||
ctrl
|
||||
shift))
|
||||
(defrecord PointerEvent [source pt ctrl shift])
|
||||
|
||||
(defn pointer-event?
|
||||
[v]
|
||||
|
@ -90,33 +76,26 @@
|
|||
|
||||
;; --- Derived streams
|
||||
|
||||
;; TODO: this shoul be DEPRECATED
|
||||
(defonce interaction-events
|
||||
(rx/filter interaction-event? st/stream))
|
||||
|
||||
(defonce mouse-position
|
||||
(rx/filter pointer-event? st/stream))
|
||||
|
||||
(defonce viewport-mouse-position
|
||||
(let [sub (rx/behavior-subject nil)]
|
||||
(-> (rx/map :viewport mouse-position)
|
||||
(rx/subscribe-with sub))
|
||||
sub))
|
||||
|
||||
(defonce window-mouse-position
|
||||
(let [sub (rx/behavior-subject nil)]
|
||||
(-> (rx/map :window mouse-position)
|
||||
(rx/subscribe-with sub))
|
||||
(let [sub (rx/behavior-subject nil)
|
||||
ob (->> st/stream
|
||||
(rx/filter pointer-event?)
|
||||
(rx/filter #(= :viewport (:source %)))
|
||||
(rx/map :pt))]
|
||||
(rx/subscribe-with ob sub)
|
||||
sub))
|
||||
|
||||
(defonce mouse-position-ctrl
|
||||
(let [sub (rx/behavior-subject nil)]
|
||||
(-> (rx/map :ctrl mouse-position)
|
||||
(rx/subscribe-with sub))
|
||||
(let [sub (rx/behavior-subject nil)
|
||||
ob (->> st/stream
|
||||
(rx/filter pointer-event?)
|
||||
(rx/map :ctrl)
|
||||
(rx/dedupe))]
|
||||
(rx/subscribe-with ob sub)
|
||||
sub))
|
||||
|
||||
(defonce mouse-position-deltas
|
||||
(->> viewport-mouse-position
|
||||
(->> mouse-position
|
||||
(rx/sample 10)
|
||||
(rx/map #(gpt/divide % @refs/selected-zoom))
|
||||
(rx/mapcat (fn [point]
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.workspace.canvas :refer [canvas]]
|
||||
[uxbox.main.ui.workspace.grid :refer [grid]]
|
||||
[uxbox.main.ui.workspace.ruler :refer [ruler]]
|
||||
[uxbox.main.ui.workspace.streams :as uws]
|
||||
|
@ -37,7 +36,7 @@
|
|||
|
||||
(mf/defc coordinates
|
||||
[{:keys [zoom] :as props}]
|
||||
(let [coords (some-> (use-rxsub uws/viewport-mouse-position)
|
||||
(let [coords (some-> (use-rxsub uws/mouse-position)
|
||||
(gpt/divide zoom)
|
||||
(gpt/round 0))]
|
||||
[:ul.coordinates
|
||||
|
@ -60,16 +59,16 @@
|
|||
:circle "Drag to draw a Circle"
|
||||
nil))
|
||||
|
||||
(mf/defc cursor-tooltip
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [tooltip]}]
|
||||
(let [coords (mf/deref refs/window-mouse-position)]
|
||||
[:span.cursor-tooltip
|
||||
{:style
|
||||
{:position "fixed"
|
||||
:left (str (+ (:x coords) 5) "px")
|
||||
:top (str (- (:y coords) 25) "px")}}
|
||||
tooltip]))
|
||||
;; (mf/defc cursor-tooltip
|
||||
;; {:wrap [mf/wrap-memo]}
|
||||
;; [{:keys [tooltip]}]
|
||||
;; (let [coords (mf/deref refs/window-mouse-position)]
|
||||
;; [:span.cursor-tooltip
|
||||
;; {:style
|
||||
;; {:position "fixed"
|
||||
;; :left (str (+ (:x coords) 5) "px")
|
||||
;; :top (str (- (:y coords) 25) "px")}}
|
||||
;; tooltip]))
|
||||
|
||||
;; --- Selection Rect
|
||||
|
||||
|
@ -89,15 +88,13 @@
|
|||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (->> (rx/merge (rx/filter #(= % :interrupt) stream)
|
||||
(rx/filter uws/mouse-up? stream))
|
||||
(rx/take 1))]
|
||||
(let [stoper (rx/filter #(or (dw/interrupt? %) (uws/mouse-up? %)) stream)]
|
||||
(rx/concat
|
||||
(->> uws/viewport-mouse-position
|
||||
(rx/of (dw/deselect-all))
|
||||
(->> uws/mouse-position
|
||||
(rx/map (fn [pos] #(update-state % pos)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/deselect-all)
|
||||
dw/select-shapes-by-current-selrect
|
||||
(rx/of dw/select-shapes-by-current-selrect
|
||||
clear-state)))))))
|
||||
|
||||
(mf/defc selrect
|
||||
|
@ -115,34 +112,76 @@
|
|||
;; --- Viewport Positioning
|
||||
|
||||
(def handle-viewport-positioning
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (->> (rx/filter #(= ::finish-positioning %) stream)
|
||||
(rx/take 1))
|
||||
reference @uws/viewport-mouse-position
|
||||
dom (dom/get-element "workspace-viewport")]
|
||||
(->> uws/viewport-mouse-position
|
||||
(rx/map (fn [point]
|
||||
(let [{:keys [x y]} (gpt/subtract point reference)
|
||||
cx (.-scrollLeft dom)
|
||||
cy (.-scrollTop dom)]
|
||||
(set! (.-scrollLeft dom) (- cx x))
|
||||
(set! (.-scrollTop dom) (- cy y)))))
|
||||
(rx/take-until stoper)
|
||||
(rx/ignore))))))
|
||||
(letfn [(on-point [dom reference point]
|
||||
(let [{:keys [x y]} (gpt/subtract point reference)
|
||||
cx (.-scrollLeft dom)
|
||||
cy (.-scrollTop dom)]
|
||||
(set! (.-scrollLeft dom) (- cx x))
|
||||
(set! (.-scrollTop dom) (- cy y))))]
|
||||
(reify
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [stoper (rx/filter #(= ::finish-positioning %) stream)
|
||||
reference @uws/mouse-position
|
||||
dom (dom/get-element "workspace-viewport")]
|
||||
(-> (rx/take-until stoper uws/mouse-position)
|
||||
(rx/subscribe #(on-point dom reference %))))))))
|
||||
|
||||
;; --- Viewport
|
||||
|
||||
(mf/def viewport
|
||||
:init
|
||||
(fn [own props]
|
||||
(assoc own ::viewport (mf/create-ref)))
|
||||
(mf/defc viewport
|
||||
[{:keys [page] :as props}]
|
||||
(let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/deref refs/workspace)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
tooltip (or tooltip (get-shape-tooltip drawing-tool))
|
||||
zoom (or zoom 1)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :down ctrl? shift?)))
|
||||
(when (not edition)
|
||||
(if drawing-tool
|
||||
(st/emit! (start-drawing drawing-tool))
|
||||
(st/emit! :interrupt handle-selrect))))
|
||||
|
||||
:did-mount
|
||||
(fn [own]
|
||||
(letfn [(translate-point-to-viewport [pt]
|
||||
(let [viewport (mf/ref-node (::viewport own))
|
||||
(on-context-menu [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :context-menu ctrl? shift?))))
|
||||
|
||||
(on-mouse-up [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :up ctrl? shift?))))
|
||||
|
||||
(on-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :click ctrl? shift?))))
|
||||
|
||||
(on-double-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :double-click ctrl? shift?))))
|
||||
|
||||
(translate-point-to-viewport [pt]
|
||||
(let [viewport (mf/ref-node viewport-ref)
|
||||
brect (.getBoundingClientRect viewport)
|
||||
brect (gpt/point (parse-int (.-left brect))
|
||||
(parse-int (.-top brect)))]
|
||||
|
@ -173,115 +212,56 @@
|
|||
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
|
||||
(st/emit! (uws/keyboard-event :up key ctrl? shift?))))
|
||||
|
||||
(on-mousemove [event]
|
||||
(let [wpt (gpt/point (.-clientX event)
|
||||
(.-clientY event))
|
||||
vpt (translate-point-to-viewport wpt)
|
||||
ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
event {:ctrl ctrl?
|
||||
:shift shift?
|
||||
:window-coords wpt
|
||||
:viewport-coords vpt}]
|
||||
(st/emit! (uws/pointer-event wpt vpt ctrl? shift?))))]
|
||||
(on-mouse-move [event]
|
||||
(let [pt (gpt/point (.-clientX event)
|
||||
(.-clientY event))
|
||||
pt (translate-point-to-viewport pt)]
|
||||
(st/emit! (uws/->PointerEvent :viewport pt
|
||||
(kbd/ctrl? event)
|
||||
(kbd/shift? 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))))
|
||||
(on-mount []
|
||||
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
|
||||
key2 (events/listen js/document EventType.KEYUP on-key-up)]
|
||||
(fn []
|
||||
(events/unlistenByKey key1)
|
||||
(events/unlistenByKey key2))))]
|
||||
|
||||
:will-unmount
|
||||
(fn [own]
|
||||
(events/unlistenByKey (::key1 own))
|
||||
(events/unlistenByKey (::key2 own))
|
||||
(events/unlistenByKey (::key3 own))
|
||||
(dissoc own ::key1 ::key2 ::key3))
|
||||
(mf/use-effect on-mount)
|
||||
[:*
|
||||
[:& coordinates {:zoom zoom}]
|
||||
#_[:div.tooltip-container
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:tooltip tooltip}])]
|
||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||
:height (* c/viewport-height zoom)
|
||||
:ref viewport-ref
|
||||
:class (when drawing-tool "drawing")
|
||||
:on-context-menu on-context-menu
|
||||
:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-move on-mouse-move
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||
(when page
|
||||
[:*
|
||||
(for [id (reverse (:shapes page))]
|
||||
[:& uus/shape-component {:id id :key id}])
|
||||
|
||||
:mixins [mf/reactive]
|
||||
(when (seq (:selected wst))
|
||||
[:& selection-handlers {:wst wst}])
|
||||
|
||||
:render
|
||||
(fn [own {:keys [page] :as props}]
|
||||
(let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/react refs/workspace)
|
||||
tooltip (or tooltip (get-shape-tooltip drawing-tool))
|
||||
zoom (or zoom 1)]
|
||||
(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :down ctrl? shift?)))
|
||||
(when (not edition)
|
||||
(if drawing-tool
|
||||
(st/emit! (start-drawing drawing-tool))
|
||||
(st/emit! :interrupt handle-selrect))))
|
||||
(on-context-menu [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :context-menu ctrl? shift?))))
|
||||
(on-mouse-up [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :up ctrl? shift?))))
|
||||
(on-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :click ctrl? shift?))))
|
||||
(on-double-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :double-click ctrl? shift?))))]
|
||||
[:*
|
||||
[:& coordinates {:zoom zoom}]
|
||||
[:div.tooltip-container
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:tooltip tooltip}])]
|
||||
[:svg.viewport {:width (* c/viewport-width zoom)
|
||||
:height (* c/viewport-height zoom)
|
||||
:ref (::viewport own)
|
||||
:class (when drawing-tool "drawing")
|
||||
:on-context-menu on-context-menu
|
||||
:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||
(when page
|
||||
[:& canvas {:page page :wst wst}])
|
||||
|
||||
(when page
|
||||
[:*
|
||||
(for [id (reverse (:shapes page))]
|
||||
[:& uus/shape-component {:id id :key id}])
|
||||
|
||||
(when (seq (:selected wst))
|
||||
[:& selection-handlers {:wst wst}])
|
||||
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])])
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])])
|
||||
|
||||
|
||||
|
||||
(if (contains? flags :grid)
|
||||
[:& grid {:page page}])]
|
||||
(when (contains? flags :ruler)
|
||||
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
|
||||
[:& selrect {:data (:selrect wst)}]]]))))
|
||||
(if (contains? flags :grid)
|
||||
[:& grid {:page page}])]
|
||||
(when (contains? flags :ruler)
|
||||
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
|
||||
[:& selrect {:data (:selrect wst)}]]])))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue