Merge branch 'wip/multicanvas' of github.com:uxbox/uxbox into i18n/multicanvas

This commit is contained in:
mathieu.brunot 2019-09-13 02:09:47 +02:00
commit d8afb97c7a
No known key found for this signature in database
GPG key ID: C438ED0898C9E020
84 changed files with 3327 additions and 3774 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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