Replace funcool/struct with cljs.spec.

As a result, one dependency less.
This commit is contained in:
Andrey Antukh 2017-03-08 16:58:00 +01:00
parent 6bc6ee68b6
commit 1aa236e812
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
12 changed files with 440 additions and 391 deletions

View file

@ -10,7 +10,7 @@
:profiles {:dev {:source-paths ["dev"]}} :profiles {:dev {:source-paths ["dev"]}}
:dependencies [[org.clojure/clojure "1.9.0-alpha14" :scope "provided"] :dependencies [[org.clojure/clojure "1.9.0-alpha14" :scope "provided"]
[org.clojure/clojurescript "1.9.494" :scope "provided"] [org.clojure/clojurescript "1.9.495" :scope "provided"]
;; Build ;; Build
[figwheel-sidecar "0.5.9" :scope "provided"] [figwheel-sidecar "0.5.9" :scope "provided"]
@ -24,12 +24,11 @@
[cljsjs/react-dom "15.4.2-2"] [cljsjs/react-dom "15.4.2-2"]
[cljsjs/react-dom-server "15.4.2-2"] [cljsjs/react-dom-server "15.4.2-2"]
[funcool/potok "2.0.0"]
[funcool/struct "1.0.0"]
[funcool/lentes "1.2.0"]
[funcool/beicon "3.1.1"] [funcool/beicon "3.1.1"]
[funcool/bide "1.4.0"]
[funcool/cuerdas "2.0.3"] [funcool/cuerdas "2.0.3"]
[funcool/bide "1.4.0"]] [funcool/lentes "1.2.0"]
[funcool/potok "2.0.0"]]
:plugins [[lein-ancient "0.6.10"]] :plugins [[lein-ancient "0.6.10"]]
:clean-targets ^{:protect false} ["resources/public/js" "target"] :clean-targets ^{:protect false} ["resources/public/js" "target"]
) )

View file

@ -13,11 +13,6 @@
[uxbox.util.i18n :refer (tr)] [uxbox.util.i18n :refer (tr)]
[uxbox.util.messages :as uum])) [uxbox.util.messages :as uum]))
(s/def ::fullname string?)
(s/def ::email us/email?)
(s/def ::username string?)
(s/def ::theme string?)
;; --- Profile Fetched ;; --- Profile Fetched
(deftype ProfileFetched [data] (deftype ProfileFetched [data]
@ -68,48 +63,53 @@
(rx/map profile-updated) (rx/map profile-updated)
(rx/catch rp/client-error? handle-error))))) (rx/catch rp/client-error? handle-error)))))
(s/def ::update-profile-event (s/def ::fullname string?)
(s/keys :req-un [::fullname ::email ::username ::theme])) (s/def ::email us/email?)
(s/def ::username string?)
(s/def ::theme string?)
(s/def ::update-profile
(s/keys :req-un [::fullname
::email
::username
::theme]))
(defn update-profile (defn update-profile
[data on-success on-error] [data on-success on-error]
{:pre [(us/valid? ::update-profile-event data) {:pre [(us/valid? ::update-profile data)
(fn? on-error) (fn? on-error)
(fn? on-success)]} (fn? on-success)]}
(UpdateProfile. data on-success on-error)) (UpdateProfile. data on-success on-error))
;; --- Password Updated
(deftype PasswordUpdated []
ptk/WatchEvent
(watch [_ state stream]
(rx/of (uum/info (tr "settings.password-saved")))))
(defn password-updated
[]
(PasswordUpdated.))
;; --- Update Password (Form) ;; --- Update Password (Form)
(deftype UpdatePassword [data] (deftype UpdatePassword [data on-success on-error]
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(let [params {:old-password (:old-password data) (let [params {:old-password (:password-old data)
:password (:password-1 data)}] :password (:password-1 data)}]
(->> (rp/req :update/profile-password params) (->> (rp/req :update/profile-password params)
(rx/map password-updated))))) (rx/catch rp/client-error? (fn [e]
(on-error (:payload e))
(rx/empty)))
(rx/do on-success)
(rx/ignore)))))
(s/def ::password-1 string?) (s/def ::password-1 string?)
(s/def ::password-2 string?) (s/def ::password-2 string?)
(s/def ::old-password string?) (s/def ::password-old string?)
(s/def ::update-password-event (s/def ::update-password
(s/keys :req-un [::password-1 ::password-2 ::old-password])) (s/keys :req-un [::password-1
::password-2
::password-old]))
(defn update-password (defn update-password
[data] [data & {:keys [on-success on-error]}]
{:pre [(us/valid? ::update-password-event data)]} {:pre [(us/valid? ::update-password data)
(UpdatePassword. data)) (fn? on-success)
(fn? on-error)]}
(UpdatePassword. data on-success on-error))
;; --- Update Photo ;; --- Update Photo
@ -123,5 +123,6 @@
(defn update-photo (defn update-photo
([file] (update-photo file (constantly nil))) ([file] (update-photo file (constantly nil)))
([file done] ([file done]
{:pre [(us/file? file) (fn? done)]} {:pre [(us/file? file)
(fn? done)]}
(UpdatePhoto. file done))) (UpdatePhoto. file done)))

View file

@ -6,38 +6,45 @@
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.auth.login (ns uxbox.main.ui.auth.login
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.auth :as da] [uxbox.main.data.auth :as da]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.navigation :as nav] [uxbox.main.ui.navigation :as nav]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.router :as rt]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.forms :as forms])) [uxbox.util.forms :as fm]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.router :as rt]))
(def form-data (forms/focus-data :login st/state)) (def form-data (fm/focus-data :login st/state))
(def set-value! (partial forms/set-value! st/store :login)) (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 ::login-form
(s/keys :req-un [::username ::password]))
(def +login-form+
{:email [forms/required forms/string]
:password [forms/required forms/string]})
(mx/defc login-form (mx/defc login-form
{:mixins [mx/static mx/reactive]} {:mixins [mx/static mx/reactive]}
[] []
(let [data (mx/react form-data) (let [data (mx/react form-data)
valid? (forms/valid? data +login-form+)] valid? (fm/valid? ::login-form data)]
(letfn [(on-change [event field] (letfn [(on-change [event field]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(set-value! field value))) (st/emit! (assoc-value field value))))
(on-submit [event] (on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(st/emit! (da/login {:username (:email data) (st/emit! (da/login {:username (:username data)
:password (:password data)})))] :password (:password data)})))]
[:form {:on-submit on-submit} [:form {:on-submit on-submit}
[:div.login-content [:div.login-content
@ -52,8 +59,8 @@
{:name "email" {:name "email"
:tab-index "2" :tab-index "2"
:ref "email" :ref "email"
:value (:email data "") :value (:username data "")
:on-change #(on-change % :email) :on-change #(on-change % :username)
:placeholder "Email or Username" :placeholder "Email or Username"
:type "text"}] :type "text"}]
[:input.input-text [:input.input-text
@ -80,7 +87,7 @@
"Don't have an account?"]]]]))) "Don't have an account?"]]]])))
(mx/defc login-page (mx/defc login-page
{:mixins [mx/static (forms/clear-mixin st/store :login)] {:mixins [mx/static (fm/clear-mixin st/store :login)]
:will-mount (fn [own] :will-mount (fn [own]
(when @st/auth-ref (when @st/auth-ref
(st/emit! (rt/navigate :dashboard/projects))) (st/emit! (rt/navigate :dashboard/projects)))

View file

@ -6,42 +6,44 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.auth.recovery (ns uxbox.main.ui.auth.recovery
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.auth :as uda] [uxbox.main.data.auth :as uda]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.navigation :as nav] [uxbox.main.ui.navigation :as nav]
[uxbox.util.router :as rt] [uxbox.util.dom :as dom]
[uxbox.util.forms :as forms] [uxbox.util.forms :as fm]
[uxbox.util.mixins :as mx :include-macros true] [uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom])) [uxbox.util.router :as rt]))
(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))
;; --- Recovery Form ;; --- Recovery Form
(def form-data (forms/focus-data :recovery st/state)) (s/def ::password ::fm/non-empty-string)
(def set-value! (partial forms/set-value! st/store :recovery)) (s/def ::recovery-form
(s/keys :req-un [::password]))
(def +recovery-form+
{:password [forms/required forms/string]})
(mx/defc recovery-form (mx/defc recovery-form
{:mixins [mx/static mx/reactive]} {:mixins [mx/static mx/reactive]}
[token] [token]
(let [data (merge (mx/react form-data) (let [data (merge (mx/react form-data) {:token token})
{:token token}) valid? (fm/valid? ::recovery-form data)]
valid? (forms/valid? data +recovery-form+)]
(letfn [(on-change [field event] (letfn [(on-change [field event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(set-value! field value))) (st/emit! (assoc-value field value))))
(on-submit [event] (on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(st/emit! (uda/recovery data) (st/emit! (uda/recovery data)
(forms/clear-form :recovery) (clear-form)))]
(forms/clear-errors :recovery)))]
[:form {:on-submit on-submit} [:form {:on-submit on-submit}
[:div.login-content [:div.login-content
[:input.input-text [:input.input-text
@ -68,7 +70,7 @@
own)) own))
(mx/defc recovery-page (mx/defc recovery-page
{:mixins [mx/static (forms/clear-mixin st/store :recovery)] {:mixins [mx/static (fm/clear-mixin st/store :recovery)]
:will-mount recovery-page-will-mount} :will-mount recovery-page-will-mount}
[token] [token]
[:div.login [:div.login

View file

@ -6,45 +6,47 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.auth.recovery-request (ns uxbox.main.ui.auth.recovery-request
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.auth :as uda] [uxbox.main.data.auth :as uda]
[uxbox.builtins.icons :as i]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.navigation :as nav] [uxbox.main.ui.navigation :as nav]
[uxbox.util.router :as rt] [uxbox.util.dom :as dom]
[uxbox.util.forms :as forms] [uxbox.util.forms :as fm]
[uxbox.util.mixins :as mx :include-macros true] [uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom])) [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 (forms/focus-data :recovery-request st/state)) (def assoc-value (partial fm/assoc-value :profile-password))
(def set-value! (partial forms/set-value! st/store :recovery-request)) (def assoc-errors (partial fm/assoc-errors :profile-password))
(def clear-form (partial fm/clear-form :profile-password))
(def +recovery-request-form+ (s/def ::username ::fm/non-empty-string)
{:username [forms/required forms/string]}) (s/def ::recovery-request-form (s/keys :req-un [::username]))
(mx/defc recovery-request-form (mx/defc recovery-request-form
{:mixins [mx/static mx/reactive]} {:mixins [mx/static mx/reactive]}
[] []
(let [data (mx/react form-data) (let [data (mx/react form-data)
valid? (forms/valid? data +recovery-request-form+)] valid? (fm/valid? ::recovery-request-form data)]
(letfn [(on-change [field event] (letfn [(on-change [event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(set-value! field value))) (st/emit! (assoc-value :username value))))
(on-submit [event] (on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(st/emit! (uda/recovery-request data) (st/emit! (uda/recovery-request data)
(forms/clear-form :recovery-request) (clear-form)))]
(forms/clear-errors :recovery-request)))]
[:form {:on-submit on-submit} [:form {:on-submit on-submit}
[:div.login-content [:div.login-content
[:input.input-text [:input.input-text
{:name "username" {:name "username"
:value (:username data "") :value (:username data "")
:on-change (partial on-change :username) :on-change on-change
:placeholder "username or email address" :placeholder "username or email address"
:type "text"}] :type "text"}]
[:input.btn-primary [:input.btn-primary
@ -59,7 +61,7 @@
;; --- Recovery Request Page ;; --- Recovery Request Page
(mx/defc recovery-request-page (mx/defc recovery-request-page
{:mixins [mx/static (forms/clear-mixin st/store :recovery-request)]} {:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]}
[] []
[:div.login [:div.login
[:div.login-body [:div.login-body

View file

@ -2,52 +2,59 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.auth.register (ns uxbox.main.ui.auth.register
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.auth :as uda] [uxbox.main.data.auth :as uda]
[uxbox.builtins.icons :as i]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.navigation :as nav] [uxbox.main.ui.navigation :as nav]
[uxbox.util.router :as rt] [uxbox.util.dom :as dom]
[uxbox.util.forms :as forms] [uxbox.util.forms :as fm]
[uxbox.util.mixins :as mx :include-macros true] [uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom])) [uxbox.util.router :as rt]))
;; --- Register Form (def form-data (fm/focus-data :register st/state))
(def form-errors (fm/focus-errors :register st/state))
(def form-data (forms/focus-data :register st/state)) (def assoc-value (partial fm/assoc-value :register))
(def form-errors (forms/focus-errors :register st/state)) (def assoc-error (partial fm/assoc-error :register))
(def set-value! (partial forms/set-value! st/store :register)) (def clear-form (partial fm/clear-form :register))
(def set-error! (partial forms/set-error! st/store :register))
(def +register-form+ ;; TODO: add better password validation
{:username [forms/required forms/string]
:fullname [forms/required forms/string] (s/def ::username ::fm/non-empty-string)
:email [forms/required forms/email] (s/def ::fullname ::fm/non-empty-string)
:password [forms/required forms/string]}) (s/def ::password ::fm/non-empty-string)
(s/def ::email ::fm/email)
(s/def ::register-form
(s/keys :req-un [::username
::fullname
::email
::password]))
(mx/defc register-form (mx/defc register-form
{:mixins [mx/static mx/reactive {:mixins [mx/static mx/reactive
(forms/clear-mixin st/store :register)]} (fm/clear-mixin st/store :register)]}
[] []
(let [data (mx/react form-data) (let [data (mx/react form-data)
errors (mx/react form-errors) errors (mx/react form-errors)
valid? (forms/valid? data +register-form+)] valid? (fm/valid? ::register-form data)]
(letfn [(on-change [field event] (letfn [(on-change [field event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(set-value! field value))) (st/emit! (assoc-value field value))))
(on-error [{:keys [type code] :as payload}] (on-error [{:keys [type code] :as payload}]
(case code (case code
:uxbox.services.users/email-already-exists :uxbox.services.users/email-already-exists
(set-error! :email "Email already exists") (st/emit! (assoc-error :email "Email already exists"))
:uxbox.services.users/username-already-exists :uxbox.services.users/username-already-exists
(set-error! :username "Username already exists"))) (st/emit! (assoc-error :username "Username already exists"))))
(on-submit [event] (on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(st/emit! (uda/register data on-error)))] (st/emit! (uda/register data on-error)))]
@ -60,7 +67,7 @@
:on-change (partial on-change :fullname) :on-change (partial on-change :fullname)
:placeholder "Full Name" :placeholder "Full Name"
:type "text"}] :type "text"}]
(forms/input-error errors :fullname) (fm/input-error errors :fullname)
[:input.input-text [:input.input-text
{:name "username" {:name "username"
@ -69,7 +76,7 @@
:on-change (partial on-change :username) :on-change (partial on-change :username)
:placeholder "Username" :placeholder "Username"
:type "text"}] :type "text"}]
(forms/input-error errors :username) (fm/input-error errors :username)
[:input.input-text [:input.input-text
{:name "email" {:name "email"
@ -79,7 +86,7 @@
:on-change (partial on-change :email) :on-change (partial on-change :email)
:placeholder "Email" :placeholder "Email"
:type "text"}] :type "text"}]
(forms/input-error errors :email) (fm/input-error errors :email)
[:input.input-text [:input.input-text
{:name "password" {:name "password"
@ -89,7 +96,7 @@
:on-change (partial on-change :password) :on-change (partial on-change :password)
:placeholder "Password" :placeholder "Password"
:type "password"}] :type "password"}]
(forms/input-error errors :password) (fm/input-error errors :password)
[:input.btn-primary [:input.btn-primary
{:name "login" {:name "login"

View file

@ -209,7 +209,7 @@
(sort-projects-by ordering))] (sort-projects-by ordering))]
(letfn [(on-click [e] (letfn [(on-click [e]
(dom/prevent-default e) (dom/prevent-default e)
(udl/open! :new-project))] (udl/open! :create-project))]
[:section.dashboard-grid [:section.dashboard-grid
[:h2 "Your projects"] [:h2 "Your projects"]
[:div.dashboard-grid-content [:div.dashboard-grid-content

View file

@ -6,42 +6,44 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.dashboard.projects-createform (ns uxbox.main.ui.dashboard.projects-createform
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.exports :as exports]
[uxbox.main.data.projects :as udp] [uxbox.main.data.projects :as udp]
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.builtins.icons :as i]
[uxbox.main.ui.dashboard.header :refer [header]]
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.lightbox :as lbx]
[uxbox.main.ui.keyboard :as kbd] [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.i18n :as t :refer [tr]]
[uxbox.util.router :as r] [uxbox.util.router :as r]
[uxbox.util.forms :as forms]
[uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom]
[uxbox.util.blob :as blob]
[uxbox.util.mixins :as mx :include-macros true] [uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.time :as dt])) [uxbox.util.time :as dt]))
(def form-data (forms/focus-data :create-project st/state)) (def form-data (fm/focus-data :create-project st/state))
(def form-errors (forms/focus-errors :create-project st/state)) (def form-errors (fm/focus-errors :create-project st/state))
(def set-value! (partial forms/set-value! st/store :create-project))
(def set-error! (partial forms/set-error! st/store :create-project))
(def clear! (partial forms/clear! st/store :create-project))
(def ^:private create-project-form (def assoc-value (partial fm/assoc-value :create-project))
{:name [forms/required forms/string] (def clear-form (partial fm/clear-form :create-project))
:width [forms/required forms/integer]
:height [forms/required forms/integer]
:layout [forms/required forms/string]})
;; --- Lightbox: Layout input (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 (mx/defc layout-input
{:mixins [mx/static]}
[data layout-id] [data layout-id]
(let [layout (get c/page-layouts layout-id)] (let [layout (get c/page-layouts layout-id)]
[:div [:div
@ -51,17 +53,15 @@
:name "project-layout" :name "project-layout"
:value (:name layout) :value (:name layout)
:checked (when (= layout-id (:layout data)) "checked") :checked (when (= layout-id (:layout data)) "checked")
:on-change #(do :on-change #(st/emit! (assoc-value :layout layout-id)
(set-value! :layout layout-id) (assoc-value :width (:width layout))
(set-value! :width (:width layout)) (assoc-value :height (:height layout)))}]
(set-value! :height (:height layout)))}]
[:label {:value (:name layout) [:label {:value (:name layout)
:for layout-id} :for layout-id}
(:name layout)]])) (:name layout)]]))
;; --- Lightbox: Layout selector
(mx/defc layout-selector (mx/defc layout-selector
{:mixins [mx/static]}
[data] [data]
[:div.input-radio.radio-primary [:div.input-radio.radio-primary
(layout-input data "mobile") (layout-input data "mobile")
@ -69,37 +69,38 @@
(layout-input data "notebook") (layout-input data "notebook")
(layout-input data "desktop")]) (layout-input data "desktop")])
;; -- New Project Lightbox (mx/defc create-project-form
{:mixins [mx/reactive mx/static]}
(mx/defcs new-project-lightbox []
{:mixins [mx/static mx/reactive
(forms/clear-mixin st/store :create-project)]}
[own]
(let [data (merge c/project-defaults (mx/react form-data)) (let [data (merge c/project-defaults (mx/react form-data))
errors (mx/react form-errors) errors (mx/react form-errors)
valid? (forms/valid? data create-project-form)] valid? (fm/valid? ::project-form data)]
(println data)
(println valid?)
(letfn [(on-submit [event] (letfn [(on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(when valid? (when valid?
(st/emit! (udp/create-project data)) (st/emit! (udp/create-project data))
(udl/close!))) (udl/close!)))
(set-value [event attr]
(set-value! attr (dom/event->value event))) (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 [] (swap-size []
(set-value! :width (:height data)) (st/emit! (assoc-value :width (:height data))
(set-value! :height (:width data))) (assoc-value :height (:width data))))]
(close []
(udl/close!)
(clear!))]
[:div.lightbox-body
[:h3 "New project"]
[:form {:on-submit on-submit} [:form {:on-submit on-submit}
[:input#project-name.input-text [:input#project-name.input-text
{:placeholder "New project name" {:placeholder "New project name"
:type "text" :type "text"
:value (:name data) :value (:name data)
:auto-focus true :auto-focus true
:on-change #(set-value % :name)}] :on-change update-name}]
[:div.project-size [:div.project-size
[:div.input-element.pixels [:div.input-element.pixels
[:span "Width"] [:span "Width"]
@ -109,7 +110,7 @@
:min 0 ;;TODO check this value :min 0 ;;TODO check this value
:max 666666 ;;TODO check this value :max 666666 ;;TODO check this value
:value (:width data) :value (:width data)
:on-change #(set-value % :width)}]] :on-change (partial update-size :width)}]]
[:a.toggle-layout {:on-click swap-size} i/toggle] [:a.toggle-layout {:on-click swap-size} i/toggle]
[:div.input-element.pixels [:div.input-element.pixels
[:span "Height"] [:span "Height"]
@ -119,7 +120,7 @@
:min 0 ;;TODO check this value :min 0 ;;TODO check this value
:max 666666 ;;TODO check this value :max 666666 ;;TODO check this value
:value (:height data) :value (:height data)
:on-change #(set-value % :height)}]]] :on-change (partial update-size :height)}]]]
;; Layout selector ;; Layout selector
(layout-selector data) (layout-selector data)
@ -129,10 +130,23 @@
{:value "Go go go!" {:value "Go go go!"
:class (when-not valid? "btn-disabled") :class (when-not valid? "btn-disabled")
:disabled (not valid?) :disabled (not valid?)
:type "submit"}]] :type "submit"}]])))
[:a.close {:on-click #(udl/close!)} i/close]])))
(defmethod lbx/render-lightbox :new-project ;; --- Create Project Lightbox
(mx/defcs create-project-lightbox
{:mixins [mx/static mx/reactive
(fm/clear-mixin st/store :create-project)]}
[own]
(letfn [(close []
(udl/close!)
(st/emit! (clear-form)))]
[:div.lightbox-body
[:h3 "New project"]
(create-project-form)
[:a.close {:on-click #(udl/close!)} i/close]]))
(defmethod lbx/render-lightbox :create-project
[_] [_]
(new-project-lightbox)) (create-project-lightbox))

View file

@ -2,11 +2,12 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings.password (ns uxbox.main.ui.settings.password
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.store :as st] [uxbox.main.store :as st]
@ -14,57 +15,75 @@
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.settings.header :refer [header]] [uxbox.main.ui.settings.header :refer [header]]
[uxbox.util.forms :as forms] [uxbox.util.i18n :refer [tr]]
[uxbox.util.forms :as fm]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.messages :as um]
[uxbox.util.mixins :as mx :include-macros true])) [uxbox.util.mixins :as mx :include-macros true]))
(def form-data (fm/focus-data :profile-password st/state))
(def form-errors (fm/focus-errors :profile-password st/state))
(def form-data (forms/focus-data :profile-password st/state)) (def assoc-value (partial fm/assoc-value :profile-password))
(def form-errors (forms/focus-errors :profile-password st/state)) (def assoc-error (partial fm/assoc-error :profile-password))
(def set-value! (partial forms/set-value! st/store :profile-password)) (def clear-form (partial fm/clear-form :profile-password))
(def set-errors! (partial forms/set-errors! st/store :profile-password))
(def +password-form+ ;; TODO: add better password validation
[[:password-1 forms/required forms/string [forms/min-len 6]]
[:password-2 forms/required forms/string (s/def ::password-1 ::fm/non-empty-string)
[forms/identical-to :password-1 :message "errors.form.password-not-match"]] (s/def ::password-2 ::fm/non-empty-string)
[:old-password forms/required forms/string]]) (s/def ::password-old ::fm/non-empty-string)
(s/def ::password-form
(s/keys :req-un [::password-1
::password-2
::password-old]))
(mx/defc password-form (mx/defc password-form
{:mixins [mx/reactive mx/static]} {:mixins [mx/reactive mx/static]}
[] []
(let [data (mx/react form-data) (let [data (mx/react form-data)
errors (mx/react form-errors) errors (mx/react form-errors)
valid? (forms/valid? data +password-form+)] valid? (fm/valid? ::password-form data)]
(letfn [(on-change [field event] (letfn [(on-change [field event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(set-value! field value))) (st/emit! (assoc-value field value))))
(on-success []
(st/emit! (um/info (tr "settings.password-saved"))))
(on-error [{:keys [code] :as payload}]
(case code
:uxbox.services.users/old-password-not-match
(st/emit! (assoc-error :password-old "Wrong old password"))
:else
(throw (ex-info "unexpected" {:error payload}))))
(on-submit [event] (on-submit [event]
(println "on-submit" data) (st/emit! (udu/update-password data
#_(st/emit! (udu/update-password form)))] :on-success on-success
:on-error on-error)))]
[:form.password-form [:form.password-form
[:span.user-settings-label "Change password"] [:span.user-settings-label "Change password"]
[:input.input-text [:input.input-text
{:type "password" {:type "password"
:class (forms/error-class errors :old-password) :class (fm/error-class errors :password-old)
:value (:old-password data "") :value (:password-old data "")
:on-change (partial on-change :old-password) :on-change (partial on-change :password-old)
:placeholder "Old password"}] :placeholder "Old password"}]
(forms/input-error errors :old-password) (fm/input-error errors :password-old)
[:input.input-text [:input.input-text
{:type "password" {:type "password"
:class (forms/error-class errors :password-1) :class (fm/error-class errors :password-1)
:value (:password-1 data "") :value (:password-1 data "")
:on-change (partial on-change :password-1) :on-change (partial on-change :password-1)
:placeholder "New password"}] :placeholder "New password"}]
(forms/input-error errors :password-1) (fm/input-error errors :password-1)
[:input.input-text [:input.input-text
{:type "password" {:type "password"
:class (forms/error-class errors :password-2) :class (fm/error-class errors :password-2)
:value (:password-2 data "") :value (:password-2 data "")
:on-change (partial on-change :password-2) :on-change (partial on-change :password-2)
:placeholder "Confirm password"}] :placeholder "Confirm password"}]
(forms/input-error errors :password-2) (fm/input-error errors :password-2)
[:input.btn-primary [:input.btn-primary
{:type "button" {:type "button"
:class (when-not valid? "btn-disabled") :class (when-not valid? "btn-disabled")

View file

@ -6,7 +6,8 @@
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings.profile (ns uxbox.main.ui.settings.profile
(:require [cuerdas.core :as str] (:require [cljs.spec :as s :include-macros true]
[cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.store :as st] [uxbox.main.store :as st]
@ -14,52 +15,58 @@
[uxbox.main.ui.settings.header :refer [header]] [uxbox.main.ui.settings.header :refer [header]]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.data.users :as udu] [uxbox.main.data.users :as udu]
[uxbox.util.forms :as forms] [uxbox.util.forms :as fm]
[uxbox.util.router :as r] [uxbox.util.router :as r]
[uxbox.util.mixins :as mx :include-macros true] [uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.interop :refer [iterable->seq]] [uxbox.util.interop :refer [iterable->seq]]
[uxbox.util.dom :as dom])) [uxbox.util.dom :as dom]))
(def form-data (forms/focus-data :profile st/state)) (def form-data (fm/focus-data :profile st/state))
(def form-errors (forms/focus-errors :profile st/state)) (def form-errors (fm/focus-errors :profile st/state))
(def set-value! (partial forms/set-value! st/store :profile))
(def set-error! (partial forms/set-error! st/store :profile)) (def assoc-value (partial fm/assoc-value :profile))
(def clear! (partial forms/clear! st/store :profile)) (def assoc-error (partial fm/assoc-error :profile))
(def clear-form (partial fm/clear-form :profile))
(def profile-ref (def profile-ref
(-> (l/key :profile) (-> (l/key :profile)
(l/derive st/state))) (l/derive st/state)))
(def +profile-form+ (s/def ::fullname ::fm/non-empty-string)
{:fullname [forms/required forms/string] (s/def ::username ::fm/non-empty-string)
:email [forms/required forms/email] (s/def ::email ::fm/email)
:username [forms/required forms/string]})
(s/def ::profile-form
(s/keys :req-un [::fullname
::username
::email]))
;; --- Profile Form ;; --- Profile Form
(mx/defc profile-form (mx/defc profile-form
{:mixins [mx/static mx/reactive {:mixins [mx/static mx/reactive
(forms/clear-mixin st/store :profile)]} (fm/clear-mixin st/store :profile)]}
[] []
;; TODO: properly persist theme
(let [data (merge {:theme "light"} (let [data (merge {:theme "light"}
(mx/react profile-ref) (mx/react profile-ref)
(mx/react form-data)) (mx/react form-data))
errors (mx/react form-errors) errors (mx/react form-errors)
valid? (forms/valid? data +profile-form+) valid? (fm/valid? ::profile-form data)
theme (:theme data)] theme (:theme data)]
(letfn [(on-change [field event] (letfn [(on-change [field event]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(set-value! field value))) (st/emit! (assoc-value field value))))
(on-error [{:keys [code] :as payload}] (on-error [{:keys [code] :as payload}]
(case code (case code
:uxbox.services.users/email-already-exists :uxbox.services.users/email-already-exists
(set-error! :email "Email already exists") (st/emit! (assoc-error :email "Email already exists"))
:uxbox.services.users/username-already-exists :uxbox.services.users/username-already-exists
(set-error! :username "Username already exists"))) (st/emit! (assoc-error :username "Username already exists"))))
(on-success [_]
(st/emit! (clear-form)))
(on-submit [event] (on-submit [event]
(st/emit! (udu/update-profile data clear! on-error)))] (st/emit! (udu/update-profile data on-success on-error)))]
[:form.profile-form [:form.profile-form
[:span.user-settings-label "Name, username and email"] [:span.user-settings-label "Name, username and email"]
[:input.input-text [:input.input-text
@ -72,14 +79,14 @@
:on-change (partial on-change :username) :on-change (partial on-change :username)
:value (:username data "") :value (:username data "")
:placeholder "Your username"}] :placeholder "Your username"}]
(forms/input-error errors :username) (fm/input-error errors :username)
[:input.input-text [:input.input-text
{:type "email" {:type "email"
:on-change (partial on-change :email) :on-change (partial on-change :email)
:value (:email data "") :value (:email data "")
:placeholder "Your email"}] :placeholder "Your email"}]
(forms/input-error errors :email) (fm/input-error errors :email)
#_[:span.user-settings-label "Choose a color theme"] #_[:span.user-settings-label "Choose a color theme"]
#_[:div.input-radio.radio-primary #_[:div.input-radio.radio-primary

View file

@ -6,41 +6,49 @@
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.sitemap-pageform (ns uxbox.main.ui.workspace.sidebar.sitemap-pageform
(:require [lentes.core :as l] (:require [cljs.spec :as s :include-macros true]
[cuerdas.core :as str] [lentes.core :as l]
[potok.core :as ptk] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.data.pages :as udp] [uxbox.main.data.pages :as udp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.builtins.icons :as i]
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.lightbox :as lbx]
[uxbox.util.i18n :refer (tr)] [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] [uxbox.util.router :as r]
[uxbox.util.forms :as forms] [uxbox.util.mixins :as mx :include-macros true]))
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.data :refer (deep-merge parse-int)]
[uxbox.util.dom :as dom]))
(def form-data (forms/focus-data :workspace-page-form st/state))
(def set-value! (partial forms/set-value! st/store :workspace-page-form)) (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 ;; --- Lightbox
(def +page-form+ (s/def ::name ::fm/non-empty-string)
{:name [forms/required forms/string] (s/def ::layout ::fm/non-empty-string)
:width [forms/required forms/number] (s/def ::width number?)
:height [forms/required forms/number] (s/def ::height number?)
:layout [forms/required forms/string]})
(s/def ::page-form
(s/keys :req-un [::name
::width
::height
::layout]))
(mx/defc layout-input (mx/defc layout-input
[data id] [data id]
(let [{:keys [id name width height]} (get c/page-layouts id)] (let [{:keys [id name width height]} (get c/page-layouts id)]
(letfn [(on-change [event] (letfn [(on-change [event]
(set-value! :layout id) (st/emit! (assoc-value :layout id)
(set-value! :width width) (assoc-value :width width)
(set-value! :height height))] (assoc-value :height height)))]
[:div [:div
[:input {:type "radio" [:input {:type "radio"
:id id :id id
@ -57,18 +65,18 @@
(select-keys page [:name :id :project]) (select-keys page [:name :id :project])
(select-keys metadata [:width :height :layout]) (select-keys metadata [:width :height :layout])
(mx/react form-data)) (mx/react form-data))
valid? (forms/valid? data +page-form+)] valid? (fm/valid? ::page-form data)]
(letfn [(update-size [field e] (letfn [(update-size [field e]
(let [value (dom/event->value e) (let [value (dom/event->value e)
value (parse-int value)] value (parse-int value)]
(set-value! field value))) (st/emit! (assoc-value field value))))
(update-name [e] (update-name [e]
(let [value (dom/event->value e)] (let [value (dom/event->value e)]
(set-value! :name value))) (st/emit! (assoc-value :name value))))
(toggle-sizes [] (toggle-sizes []
(let [{:keys [width height]} data] (let [{:keys [width height]} data]
(set-value! :width height) (st/emit! (assoc-value :width width)
(set-value! :height width))) (assoc-value :height height))))
(on-cancel [e] (on-cancel [e]
(dom/prevent-default e) (dom/prevent-default e)
(udl/close!)) (udl/close!))
@ -119,7 +127,7 @@
:type "button"}]]))) :type "button"}]])))
(mx/defc page-form-lightbox (mx/defc page-form-lightbox
{:mixins [mx/static (forms/clear-mixin st/store :workspace-page-form)]} {:mixins [mx/static (fm/clear-mixin st/store :workspace-page-form)]}
[{:keys [id] :as page}] [{:keys [id] :as page}]
(letfn [(on-cancel [event] (letfn [(on-cancel [event]
(dom/prevent-default event) (dom/prevent-default event)

View file

@ -2,173 +2,155 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.util.forms (ns uxbox.util.forms
(:refer-clojure :exclude [keyword uuid vector boolean map set]) (:require [cljs.spec :as s :include-macros true]
(:require [struct.core :as f] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.util.mixins :as mx :include-macros true] [uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.i18n :refer (tr)])) [uxbox.util.i18n :refer [tr]]))
;; TODO: rewrite form stuff using cljs.spec ;; --- Form Validation Api
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- interpret-problem
;; Form Validation [acc {:keys [path pred val via in] :as problem}]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (cond
(and (empty? path)
(= (first pred) 'contains?))
(let [path (conj path (last pred))]
(update-in acc path assoc :missing))
;; --- Form Validators (and (seq path)
(= 1 (count path)))
(update-in acc path assoc :invalid)
(def required :else acc))
(assoc f/required :message "errors.form.required"))
(def string
(assoc f/string :message "errors.form.string"))
(def number
(assoc f/number :message "errors.form.number"))
(def integer
(assoc f/integer :message "errors.form.integer"))
(def boolean
(assoc f/boolean :message "errors.form.bool"))
(def identical-to
(assoc f/identical-to :message "errors.form.identical-to"))
(def in-range f/in-range)
(def uuid f/uuid)
(def keyword f/keyword)
(def integer-str f/integer-str)
(def number-str f/number-str)
(def email f/email)
(def positive f/positive)
(def max-len
{:message "errors.form.max-len"
:optional true
:validate (fn [v n]
(let [len (count v)]
(>= len v)))})
(def min-len
{:message "errors.form.min-len"
:optional true
:validate (fn [v n]
(>= (count v) n))})
(def color
{:message "errors.form.color"
:optional true
:validate #(not (nil? (re-find #"^#[0-9A-Fa-f]{6}$" %)))})
;; --- Public Validation Api
(defn validate (defn validate
([data schema] [spec data]
(validate data schema nil)) (when-not (s/valid? spec data)
([data schema opts] (let [report (s/explain-data spec data)]
(f/validate data schema opts))) (reduce interpret-problem {} (::s/problems report)))))
(defn validate!
([data schema]
(validate! data schema nil))
([data schema opts]
(let [[errors data] (validate data schema opts)]
(if errors
(throw (ex-info "Invalid data" errors))
data))))
(defn valid? (defn valid?
[data schema] [spec data]
(let [[errors data] (validate data schema)] (s/valid? spec data))
(not errors)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; --- Form Specs and Conformers
;; Form Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Set Error (def ^:private email-re
#"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
(defrecord SetError [type field error] (def ^:private number-re
#"^[-+]?[0-9]*\.?[0-9]+$")
(def ^:private color-re
#"^#[0-9A-Fa-f]{6}$")
(s/def ::email
(s/and string? #(boolean (re-matches email-re %))))
(s/def ::non-empty-string
(s/and string? #(not (str/empty? %))))
(defn- parse-number
[v]
(cond
(re-matches number-re v) (js/parseFloat v)
(number? v) v
:else ::s/invalid))
(s/def ::string-number
(s/conformer parse-number str))
(s/def ::color
(s/and string? #(boolean (re-matches color-re %))))
;; --- Form State Events
;; --- Assoc Error
(defrecord AssocError [type field error]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:errors type field] error))) (assoc-in state [:errors type field] error)))
(defn set-error (defn assoc-error
([type field] ([type field]
(set-error type field nil)) (assoc-error type field nil))
([type field error] ([type field error]
{:pre [(keyword? type) {:pre [(keyword? type)
(keyword? field) (keyword? field)
(any? error)]} (any? error)]}
(SetError. type field error))) (AssocError. type field error)))
(defn set-error! ;; --- Assoc Errors
[store & args]
(ptk/emit! store (apply set-error args)))
;; --- Set Errors (defrecord AssocErrors [type errors]
(defrecord SetErrors [type errors]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:errors type] errors))) (assoc-in state [:errors type] errors)))
(defn set-errors (defn assoc-errors
([type] ([type]
(set-errors type nil)) (assoc-errors type nil))
([type errors] ([type errors]
{:pre [(keyword? type) {:pre [(keyword? type)
(or (map? errors) (or (map? errors)
(nil? errors))]} (nil? errors))]}
(SetErrors. type errors))) (AssocErrors. type errors)))
(defn set-errors! ;; --- Assoc Value
[store & args]
(ptk/emit! store (apply set-errors args)))
;; --- Set Value (declare clear-error)
(defrecord SetValue [type field value] (defrecord AssocValue [type field value]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [form-path (into [:forms type] (if (coll? field) field [field])) (let [form-path (into [:forms type] (if (coll? field) field [field]))]
errors-path (into [:errors type] (if (coll? field) field [field]))] (assoc-in state form-path value)))
(-> state
(assoc-in form-path value)
(update-in (butlast errors-path) dissoc (last errors-path))))))
(defn set-value ptk/WatchEvent
(watch [_ state stream]
(rx/of (clear-error type field))))
(defn assoc-value
[type field value] [type field value]
{:pre [(keyword? type) {:pre [(keyword? type)
(keyword? field) (keyword? field)
(any? value)]} (any? value)]}
(SetValue. type field value)) (AssocValue. type field value))
(defn set-value! ;; --- Clear Values
[store type field value]
(ptk/emit! store (set-value type field value)))
;; --- Clear Form (defrecord ClearValues [type]
(defrecord ClearForm [type]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:forms type] nil))) (assoc-in state [:forms type] nil)))
(defn clear-form (defn clear-values
[type] [type]
{:pre [(keyword? type)]} {:pre [(keyword? type)]}
(ClearForm. type)) (ClearValues. type))
(defn clear-form! ;; --- Clear Error
[store type]
(ptk/emit! store (clear-form type))) (deftype ClearError [type field]
ptk/UpdateEvent
(update [_ state]
(let [errors (get-in state [:errors type])]
(if (map? errors)
(assoc-in state [:errors type] (dissoc errors field))
(update state :errors dissoc type)))))
(defn clear-error
[type field]
{:pre [(keyword? type)
(keyword? field)]}
(ClearError. type field))
;; --- Clear Errors ;; --- Clear Errors
@ -182,17 +164,18 @@
{:pre [(keyword? type)]} {:pre [(keyword? type)]}
(ClearErrors. type)) (ClearErrors. type))
(defn clear-errors! ;; --- Clear Form
[store type]
(ptk/emit! store (clear-errors type)))
;; --- Clear (deftype ClearForm [type]
ptk/WatchEvent
(watch [_ state stream]
(rx/of (clear-values type)
(clear-errors type))))
(defn clear! (defn clear-form
[store type] [type]
(ptk/emit! store {:pre [(keyword? type)]}
(clear-form type) (ClearForm. type))
(clear-errors type)))
;; --- Helpers ;; --- Helpers
@ -224,5 +207,5 @@
(defn clear-mixin (defn clear-mixin
[store type] [store type]
{:will-unmount (fn [own] {:will-unmount (fn [own]
(clear! store type) (ptk/emit! store (clear-form type))
own)}) own)})