mirror of
https://github.com/penpot/penpot.git
synced 2025-03-13 00:01:51 -05:00
Replace funcool/struct with cljs.spec.
As a result, one dependency less.
This commit is contained in:
parent
6bc6ee68b6
commit
1aa236e812
12 changed files with 440 additions and 391 deletions
|
@ -10,7 +10,7 @@
|
|||
:profiles {:dev {:source-paths ["dev"]}}
|
||||
|
||||
: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
|
||||
[figwheel-sidecar "0.5.9" :scope "provided"]
|
||||
|
@ -24,12 +24,11 @@
|
|||
[cljsjs/react-dom "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/bide "1.4.0"]
|
||||
[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"]]
|
||||
:clean-targets ^{:protect false} ["resources/public/js" "target"]
|
||||
)
|
||||
|
|
|
@ -13,11 +13,6 @@
|
|||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.messages :as uum]))
|
||||
|
||||
(s/def ::fullname string?)
|
||||
(s/def ::email us/email?)
|
||||
(s/def ::username string?)
|
||||
(s/def ::theme string?)
|
||||
|
||||
;; --- Profile Fetched
|
||||
|
||||
(deftype ProfileFetched [data]
|
||||
|
@ -68,48 +63,53 @@
|
|||
(rx/map profile-updated)
|
||||
(rx/catch rp/client-error? handle-error)))))
|
||||
|
||||
(s/def ::update-profile-event
|
||||
(s/keys :req-un [::fullname ::email ::username ::theme]))
|
||||
(s/def ::fullname string?)
|
||||
(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
|
||||
[data on-success on-error]
|
||||
{:pre [(us/valid? ::update-profile-event data)
|
||||
{:pre [(us/valid? ::update-profile data)
|
||||
(fn? on-error)
|
||||
(fn? on-success)]}
|
||||
(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)
|
||||
|
||||
(deftype UpdatePassword [data]
|
||||
(deftype UpdatePassword [data on-success on-error]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [params {:old-password (:old-password data)
|
||||
(let [params {:old-password (:password-old data)
|
||||
:password (:password-1 data)}]
|
||||
(->> (rp/req :update/profile-password params)
|
||||
(rx/map password-updated)))))
|
||||
(->> (rp/req :update/profile-password params)
|
||||
(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-2 string?)
|
||||
(s/def ::old-password string?)
|
||||
(s/def ::password-old string?)
|
||||
|
||||
(s/def ::update-password-event
|
||||
(s/keys :req-un [::password-1 ::password-2 ::old-password]))
|
||||
(s/def ::update-password
|
||||
(s/keys :req-un [::password-1
|
||||
::password-2
|
||||
::password-old]))
|
||||
|
||||
(defn update-password
|
||||
[data]
|
||||
{:pre [(us/valid? ::update-password-event data)]}
|
||||
(UpdatePassword. data))
|
||||
[data & {:keys [on-success on-error]}]
|
||||
{:pre [(us/valid? ::update-password data)
|
||||
(fn? on-success)
|
||||
(fn? on-error)]}
|
||||
(UpdatePassword. data on-success on-error))
|
||||
|
||||
;; --- Update Photo
|
||||
|
||||
|
@ -123,5 +123,6 @@
|
|||
(defn update-photo
|
||||
([file] (update-photo file (constantly nil)))
|
||||
([file done]
|
||||
{:pre [(us/file? file) (fn? done)]}
|
||||
{:pre [(us/file? file)
|
||||
(fn? done)]}
|
||||
(UpdatePhoto. file done)))
|
||||
|
|
|
@ -6,38 +6,45 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[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.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 set-value! (partial forms/set-value! st/store :login))
|
||||
(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 ::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
|
||||
{:mixins [mx/static mx/reactive]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
valid? (forms/valid? data +login-form+)]
|
||||
valid? (fm/valid? ::login-form data)]
|
||||
(letfn [(on-change [event field]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (da/login {:username (:email data)
|
||||
(st/emit! (da/login {:username (:username data)
|
||||
:password (:password data)})))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
|
@ -52,8 +59,8 @@
|
|||
{:name "email"
|
||||
:tab-index "2"
|
||||
:ref "email"
|
||||
:value (:email data "")
|
||||
:on-change #(on-change % :email)
|
||||
:value (:username data "")
|
||||
:on-change #(on-change % :username)
|
||||
:placeholder "Email or Username"
|
||||
:type "text"}]
|
||||
[:input.input-text
|
||||
|
@ -80,7 +87,7 @@
|
|||
"Don't have an account?"]]]])))
|
||||
|
||||
(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]
|
||||
(when @st/auth-ref
|
||||
(st/emit! (rt/navigate :dashboard/projects)))
|
||||
|
|
|
@ -6,42 +6,44 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[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.router :as rt]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[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
|
||||
|
||||
(def form-data (forms/focus-data :recovery st/state))
|
||||
(def set-value! (partial forms/set-value! st/store :recovery))
|
||||
|
||||
(def +recovery-form+
|
||||
{:password [forms/required forms/string]})
|
||||
(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? (forms/valid? data +recovery-form+)]
|
||||
(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)]
|
||||
(set-value! field value)))
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/recovery data)
|
||||
(forms/clear-form :recovery)
|
||||
(forms/clear-errors :recovery)))]
|
||||
(clear-form)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
|
@ -68,7 +70,7 @@
|
|||
own))
|
||||
|
||||
(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}
|
||||
[token]
|
||||
[:div.login
|
||||
|
|
|
@ -6,45 +6,47 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[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 set-value! (partial forms/set-value! st/store :recovery-request))
|
||||
(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 +recovery-request-form+
|
||||
{:username [forms/required forms/string]})
|
||||
(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? (forms/valid? data +recovery-request-form+)]
|
||||
(letfn [(on-change [field event]
|
||||
valid? (fm/valid? ::recovery-request-form data)]
|
||||
(letfn [(on-change [event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(st/emit! (assoc-value :username value))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/recovery-request data)
|
||||
(forms/clear-form :recovery-request)
|
||||
(forms/clear-errors :recovery-request)))]
|
||||
(clear-form)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "username"
|
||||
:value (:username data "")
|
||||
:on-change (partial on-change :username)
|
||||
:on-change on-change
|
||||
:placeholder "username or email address"
|
||||
:type "text"}]
|
||||
[:input.btn-primary
|
||||
|
@ -59,7 +61,7 @@
|
|||
;; --- 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-body
|
||||
|
|
|
@ -2,52 +2,59 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[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 form-errors (forms/focus-errors :register st/state))
|
||||
(def set-value! (partial forms/set-value! st/store :register))
|
||||
(def set-error! (partial forms/set-error! st/store :register))
|
||||
(def assoc-value (partial fm/assoc-value :register))
|
||||
(def assoc-error (partial fm/assoc-error :register))
|
||||
(def clear-form (partial fm/clear-form :register))
|
||||
|
||||
(def +register-form+
|
||||
{:username [forms/required forms/string]
|
||||
:fullname [forms/required forms/string]
|
||||
:email [forms/required forms/email]
|
||||
:password [forms/required forms/string]})
|
||||
;; 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 ::email ::fm/email)
|
||||
|
||||
(s/def ::register-form
|
||||
(s/keys :req-un [::username
|
||||
::fullname
|
||||
::email
|
||||
::password]))
|
||||
|
||||
(mx/defc register-form
|
||||
{:mixins [mx/static mx/reactive
|
||||
(forms/clear-mixin st/store :register)]}
|
||||
(fm/clear-mixin st/store :register)]}
|
||||
[]
|
||||
(let [data (mx/react form-data)
|
||||
errors (mx/react form-errors)
|
||||
valid? (forms/valid? data +register-form+)]
|
||||
valid? (fm/valid? ::register-form data)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-error [{:keys [type code] :as payload}]
|
||||
(case code
|
||||
: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
|
||||
(set-error! :username "Username already exists")))
|
||||
(st/emit! (assoc-error :username "Username already exists"))))
|
||||
(on-submit [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/register data on-error)))]
|
||||
|
@ -60,7 +67,7 @@
|
|||
:on-change (partial on-change :fullname)
|
||||
:placeholder "Full Name"
|
||||
:type "text"}]
|
||||
(forms/input-error errors :fullname)
|
||||
(fm/input-error errors :fullname)
|
||||
|
||||
[:input.input-text
|
||||
{:name "username"
|
||||
|
@ -69,7 +76,7 @@
|
|||
:on-change (partial on-change :username)
|
||||
:placeholder "Username"
|
||||
:type "text"}]
|
||||
(forms/input-error errors :username)
|
||||
(fm/input-error errors :username)
|
||||
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
|
@ -79,7 +86,7 @@
|
|||
:on-change (partial on-change :email)
|
||||
:placeholder "Email"
|
||||
:type "text"}]
|
||||
(forms/input-error errors :email)
|
||||
(fm/input-error errors :email)
|
||||
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
|
@ -89,7 +96,7 @@
|
|||
:on-change (partial on-change :password)
|
||||
:placeholder "Password"
|
||||
:type "password"}]
|
||||
(forms/input-error errors :password)
|
||||
(fm/input-error errors :password)
|
||||
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
|
|
|
@ -209,7 +209,7 @@
|
|||
(sort-projects-by ordering))]
|
||||
(letfn [(on-click [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/open! :new-project))]
|
||||
(udl/open! :create-project))]
|
||||
[:section.dashboard-grid
|
||||
[:h2 "Your projects"]
|
||||
[:div.dashboard-grid-content
|
||||
|
|
|
@ -6,42 +6,44 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.exports :as exports]
|
||||
[uxbox.main.data.projects :as udp]
|
||||
[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.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.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.time :as dt]))
|
||||
|
||||
(def form-data (forms/focus-data :create-project st/state))
|
||||
(def form-errors (forms/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 form-data (fm/focus-data :create-project st/state))
|
||||
(def form-errors (fm/focus-errors :create-project st/state))
|
||||
|
||||
(def ^:private create-project-form
|
||||
{:name [forms/required forms/string]
|
||||
:width [forms/required forms/integer]
|
||||
:height [forms/required forms/integer]
|
||||
:layout [forms/required forms/string]})
|
||||
(def assoc-value (partial fm/assoc-value :create-project))
|
||||
(def clear-form (partial fm/clear-form :create-project))
|
||||
|
||||
;; --- 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
|
||||
{:mixins [mx/static]}
|
||||
[data layout-id]
|
||||
(let [layout (get c/page-layouts layout-id)]
|
||||
[:div
|
||||
|
@ -51,17 +53,15 @@
|
|||
:name "project-layout"
|
||||
:value (:name layout)
|
||||
:checked (when (= layout-id (:layout data)) "checked")
|
||||
:on-change #(do
|
||||
(set-value! :layout layout-id)
|
||||
(set-value! :width (:width layout))
|
||||
(set-value! :height (:height layout)))}]
|
||||
: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)]]))
|
||||
|
||||
;; --- Lightbox: Layout selector
|
||||
|
||||
(mx/defc layout-selector
|
||||
{:mixins [mx/static]}
|
||||
[data]
|
||||
[:div.input-radio.radio-primary
|
||||
(layout-input data "mobile")
|
||||
|
@ -69,70 +69,84 @@
|
|||
(layout-input data "notebook")
|
||||
(layout-input data "desktop")])
|
||||
|
||||
;; -- New Project Lightbox
|
||||
|
||||
(mx/defcs new-project-lightbox
|
||||
{:mixins [mx/static mx/reactive
|
||||
(forms/clear-mixin st/store :create-project)]}
|
||||
[own]
|
||||
(mx/defc create-project-form
|
||||
{:mixins [mx/reactive mx/static]}
|
||||
[]
|
||||
(let [data (merge c/project-defaults (mx/react form-data))
|
||||
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]
|
||||
(dom/prevent-default event)
|
||||
(when valid?
|
||||
(st/emit! (udp/create-project data))
|
||||
(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 []
|
||||
(set-value! :width (:height data))
|
||||
(set-value! :height (:width data)))
|
||||
(close []
|
||||
(udl/close!)
|
||||
(clear!))]
|
||||
[:div.lightbox-body
|
||||
[:h3 "New project"]
|
||||
[:form {:on-submit on-submit}
|
||||
[:input#project-name.input-text
|
||||
{:placeholder "New project name"
|
||||
:type "text"
|
||||
:value (:name data)
|
||||
:auto-focus true
|
||||
:on-change #(set-value % :name)}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span "Width"]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder "Width"
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value (:width data)
|
||||
:on-change #(set-value % :width)}]]
|
||||
[:a.toggle-layout {:on-click swap-size} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:span "Height"]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value (:height data)
|
||||
:on-change #(set-value % :height)}]]]
|
||||
(st/emit! (assoc-value :width (:height data))
|
||||
(assoc-value :height (:width data))))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:input#project-name.input-text
|
||||
{:placeholder "New project name"
|
||||
:type "text"
|
||||
:value (:name data)
|
||||
:auto-focus true
|
||||
:on-change update-name}]
|
||||
[:div.project-size
|
||||
[:div.input-element.pixels
|
||||
[:span "Width"]
|
||||
[:input#project-witdh.input-text
|
||||
{:placeholder "Width"
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value (:width data)
|
||||
:on-change (partial update-size :width)}]]
|
||||
[:a.toggle-layout {:on-click swap-size} i/toggle]
|
||||
[:div.input-element.pixels
|
||||
[:span "Height"]
|
||||
[:input#project-height.input-text
|
||||
{:placeholder "Height"
|
||||
:type "number"
|
||||
:min 0 ;;TODO check this value
|
||||
:max 666666 ;;TODO check this value
|
||||
:value (:height data)
|
||||
:on-change (partial update-size :height)}]]]
|
||||
|
||||
;; Layout selector
|
||||
(layout-selector data)
|
||||
;; Layout selector
|
||||
(layout-selector data)
|
||||
|
||||
;; Submit
|
||||
[:input#project-btn.btn-primary
|
||||
{:value "Go go go!"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:type "submit"}]]
|
||||
[:a.close {:on-click #(udl/close!)} i/close]])))
|
||||
;; Submit
|
||||
[:input#project-btn.btn-primary
|
||||
{:value "Go go go!"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:type "submit"}]])))
|
||||
|
||||
(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))
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -14,57 +15,75 @@
|
|||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[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.messages :as um]
|
||||
[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 form-errors (forms/focus-errors :profile-password st/state))
|
||||
(def set-value! (partial forms/set-value! st/store :profile-password))
|
||||
(def set-errors! (partial forms/set-errors! st/store :profile-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))
|
||||
|
||||
(def +password-form+
|
||||
[[:password-1 forms/required forms/string [forms/min-len 6]]
|
||||
[:password-2 forms/required forms/string
|
||||
[forms/identical-to :password-1 :message "errors.form.password-not-match"]]
|
||||
[:old-password forms/required forms/string]])
|
||||
;; TODO: add better password validation
|
||||
|
||||
(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-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? (forms/valid? data +password-form+)]
|
||||
valid? (fm/valid? ::password-form data)]
|
||||
(letfn [(on-change [field 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]
|
||||
(println "on-submit" data)
|
||||
#_(st/emit! (udu/update-password form)))]
|
||||
(st/emit! (udu/update-password data
|
||||
:on-success on-success
|
||||
:on-error on-error)))]
|
||||
[:form.password-form
|
||||
[:span.user-settings-label "Change password"]
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (forms/error-class errors :old-password)
|
||||
:value (:old-password data "")
|
||||
:on-change (partial on-change :old-password)
|
||||
:class (fm/error-class errors :password-old)
|
||||
:value (:password-old data "")
|
||||
:on-change (partial on-change :password-old)
|
||||
:placeholder "Old password"}]
|
||||
(forms/input-error errors :old-password)
|
||||
(fm/input-error errors :password-old)
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (forms/error-class errors :password-1)
|
||||
:class (fm/error-class errors :password-1)
|
||||
:value (:password-1 data "")
|
||||
:on-change (partial on-change :password-1)
|
||||
:placeholder "New password"}]
|
||||
(forms/input-error errors :password-1)
|
||||
(fm/input-error errors :password-1)
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:class (forms/error-class errors :password-2)
|
||||
:class (fm/error-class errors :password-2)
|
||||
:value (:password-2 data "")
|
||||
:on-change (partial on-change :password-2)
|
||||
:placeholder "Confirm password"}]
|
||||
(forms/input-error errors :password-2)
|
||||
(fm/input-error errors :password-2)
|
||||
[:input.btn-primary
|
||||
{:type "button"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -14,52 +15,58 @@
|
|||
[uxbox.main.ui.settings.header :refer [header]]
|
||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.util.forms :as forms]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.interop :refer [iterable->seq]]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
|
||||
(def form-data (forms/focus-data :profile st/state))
|
||||
(def form-errors (forms/focus-errors :profile st/state))
|
||||
(def set-value! (partial forms/set-value! st/store :profile))
|
||||
(def set-error! (partial forms/set-error! st/store :profile))
|
||||
(def clear! (partial forms/clear! st/store :profile))
|
||||
(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))
|
||||
|
||||
(def profile-ref
|
||||
(-> (l/key :profile)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def +profile-form+
|
||||
{:fullname [forms/required forms/string]
|
||||
:email [forms/required forms/email]
|
||||
:username [forms/required forms/string]})
|
||||
(s/def ::fullname ::fm/non-empty-string)
|
||||
(s/def ::username ::fm/non-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
|
||||
(s/def ::profile-form
|
||||
(s/keys :req-un [::fullname
|
||||
::username
|
||||
::email]))
|
||||
|
||||
;; --- Profile Form
|
||||
|
||||
(mx/defc profile-form
|
||||
{: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"}
|
||||
(mx/react profile-ref)
|
||||
(mx/react form-data))
|
||||
errors (mx/react form-errors)
|
||||
valid? (forms/valid? data +profile-form+)
|
||||
valid? (fm/valid? ::profile-form data)
|
||||
theme (:theme data)]
|
||||
(letfn [(on-change [field event]
|
||||
(let [value (dom/event->value event)]
|
||||
(set-value! field value)))
|
||||
(st/emit! (assoc-value field value))))
|
||||
(on-error [{:keys [code] :as payload}]
|
||||
(case code
|
||||
: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
|
||||
(set-error! :username "Username already exists")))
|
||||
(st/emit! (assoc-error :username "Username already exists"))))
|
||||
(on-success [_]
|
||||
(st/emit! (clear-form)))
|
||||
(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
|
||||
[:span.user-settings-label "Name, username and email"]
|
||||
[:input.input-text
|
||||
|
@ -72,14 +79,14 @@
|
|||
:on-change (partial on-change :username)
|
||||
:value (:username data "")
|
||||
:placeholder "Your username"}]
|
||||
(forms/input-error errors :username)
|
||||
(fm/input-error errors :username)
|
||||
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:on-change (partial on-change :email)
|
||||
:value (:email data "")
|
||||
:placeholder "Your email"}]
|
||||
(forms/input-error errors :email)
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:on-change (partial on-change :email)
|
||||
:value (:email data "")
|
||||
:placeholder "Your email"}]
|
||||
(fm/input-error errors :email)
|
||||
|
||||
#_[:span.user-settings-label "Choose a color theme"]
|
||||
#_[:div.input-radio.radio-primary
|
||||
|
|
|
@ -6,41 +6,49 @@
|
|||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.sitemap-pageform
|
||||
(:require [lentes.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
(:require [cljs.spec :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.workspace :as dw]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[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.forms :as forms]
|
||||
[uxbox.util.mixins :as mx :include-macros true]
|
||||
[uxbox.util.data :refer (deep-merge parse-int)]
|
||||
[uxbox.util.dom :as dom]))
|
||||
[uxbox.util.mixins :as mx :include-macros true]))
|
||||
|
||||
(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
|
||||
|
||||
(def +page-form+
|
||||
{:name [forms/required forms/string]
|
||||
:width [forms/required forms/number]
|
||||
:height [forms/required forms/number]
|
||||
:layout [forms/required forms/string]})
|
||||
(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]
|
||||
(set-value! :layout id)
|
||||
(set-value! :width width)
|
||||
(set-value! :height height))]
|
||||
(st/emit! (assoc-value :layout id)
|
||||
(assoc-value :width width)
|
||||
(assoc-value :height height)))]
|
||||
[:div
|
||||
[:input {:type "radio"
|
||||
:id id
|
||||
|
@ -57,18 +65,18 @@
|
|||
(select-keys page [:name :id :project])
|
||||
(select-keys metadata [:width :height :layout])
|
||||
(mx/react form-data))
|
||||
valid? (forms/valid? data +page-form+)]
|
||||
valid? (fm/valid? ::page-form data)]
|
||||
(letfn [(update-size [field e]
|
||||
(let [value (dom/event->value e)
|
||||
value (parse-int value)]
|
||||
(set-value! field value)))
|
||||
(st/emit! (assoc-value field value))))
|
||||
(update-name [e]
|
||||
(let [value (dom/event->value e)]
|
||||
(set-value! :name value)))
|
||||
(st/emit! (assoc-value :name value))))
|
||||
(toggle-sizes []
|
||||
(let [{:keys [width height]} data]
|
||||
(set-value! :width height)
|
||||
(set-value! :height width)))
|
||||
(st/emit! (assoc-value :width width)
|
||||
(assoc-value :height height))))
|
||||
(on-cancel [e]
|
||||
(dom/prevent-default e)
|
||||
(udl/close!))
|
||||
|
@ -119,7 +127,7 @@
|
|||
:type "button"}]])))
|
||||
|
||||
(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}]
|
||||
(letfn [(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
|
|
|
@ -2,173 +2,155 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.util.forms
|
||||
(:refer-clojure :exclude [keyword uuid vector boolean map set])
|
||||
(:require [struct.core :as f]
|
||||
(:require [cljs.spec :as s :include-macros true]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[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
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Form Validation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defn- interpret-problem
|
||||
[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
|
||||
(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
|
||||
:else acc))
|
||||
|
||||
(defn validate
|
||||
([data schema]
|
||||
(validate data schema nil))
|
||||
([data schema opts]
|
||||
(f/validate data schema opts)))
|
||||
|
||||
(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))))
|
||||
[spec data]
|
||||
(when-not (s/valid? spec data)
|
||||
(let [report (s/explain-data spec data)]
|
||||
(reduce interpret-problem {} (::s/problems report)))))
|
||||
|
||||
(defn valid?
|
||||
[data schema]
|
||||
(let [[errors data] (validate data schema)]
|
||||
(not errors)))
|
||||
[spec data]
|
||||
(s/valid? spec data))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Form Events
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Form Specs and Conformers
|
||||
|
||||
;; --- 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
|
||||
(update [_ state]
|
||||
(assoc-in state [:errors type field] error)))
|
||||
|
||||
(defn set-error
|
||||
(defn assoc-error
|
||||
([type field]
|
||||
(set-error type field nil))
|
||||
(assoc-error type field nil))
|
||||
([type field error]
|
||||
{:pre [(keyword? type)
|
||||
(keyword? field)
|
||||
(any? error)]}
|
||||
(SetError. type field error)))
|
||||
(AssocError. type field error)))
|
||||
|
||||
(defn set-error!
|
||||
[store & args]
|
||||
(ptk/emit! store (apply set-error args)))
|
||||
;; --- Assoc Errors
|
||||
|
||||
;; --- Set Errors
|
||||
|
||||
(defrecord SetErrors [type errors]
|
||||
(defrecord AssocErrors [type errors]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:errors type] errors)))
|
||||
|
||||
(defn set-errors
|
||||
(defn assoc-errors
|
||||
([type]
|
||||
(set-errors type nil))
|
||||
(assoc-errors type nil))
|
||||
([type errors]
|
||||
{:pre [(keyword? type)
|
||||
(or (map? errors)
|
||||
(nil? errors))]}
|
||||
(SetErrors. type errors)))
|
||||
(AssocErrors. type errors)))
|
||||
|
||||
(defn set-errors!
|
||||
[store & args]
|
||||
(ptk/emit! store (apply set-errors args)))
|
||||
;; --- Assoc Value
|
||||
|
||||
;; --- Set Value
|
||||
(declare clear-error)
|
||||
|
||||
(defrecord SetValue [type field value]
|
||||
(defrecord AssocValue [type field value]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [form-path (into [:forms type] (if (coll? field) field [field]))
|
||||
errors-path (into [:errors type] (if (coll? field) field [field]))]
|
||||
(-> state
|
||||
(assoc-in form-path value)
|
||||
(update-in (butlast errors-path) dissoc (last errors-path))))))
|
||||
(let [form-path (into [:forms type] (if (coll? field) field [field]))]
|
||||
(assoc-in state form-path value)))
|
||||
|
||||
(defn set-value
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (clear-error type field))))
|
||||
|
||||
(defn assoc-value
|
||||
[type field value]
|
||||
{:pre [(keyword? type)
|
||||
(keyword? field)
|
||||
(any? value)]}
|
||||
(SetValue. type field value))
|
||||
(AssocValue. type field value))
|
||||
|
||||
(defn set-value!
|
||||
[store type field value]
|
||||
(ptk/emit! store (set-value type field value)))
|
||||
;; --- Clear Values
|
||||
|
||||
;; --- Clear Form
|
||||
|
||||
(defrecord ClearForm [type]
|
||||
(defrecord ClearValues [type]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:forms type] nil)))
|
||||
|
||||
(defn clear-form
|
||||
(defn clear-values
|
||||
[type]
|
||||
{:pre [(keyword? type)]}
|
||||
(ClearForm. type))
|
||||
(ClearValues. type))
|
||||
|
||||
(defn clear-form!
|
||||
[store type]
|
||||
(ptk/emit! store (clear-form type)))
|
||||
;; --- Clear Error
|
||||
|
||||
(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
|
||||
|
||||
|
@ -182,17 +164,18 @@
|
|||
{:pre [(keyword? type)]}
|
||||
(ClearErrors. type))
|
||||
|
||||
(defn clear-errors!
|
||||
[store type]
|
||||
(ptk/emit! store (clear-errors type)))
|
||||
;; --- Clear Form
|
||||
|
||||
;; --- Clear
|
||||
(deftype ClearForm [type]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (clear-values type)
|
||||
(clear-errors type))))
|
||||
|
||||
(defn clear!
|
||||
[store type]
|
||||
(ptk/emit! store
|
||||
(clear-form type)
|
||||
(clear-errors type)))
|
||||
(defn clear-form
|
||||
[type]
|
||||
{:pre [(keyword? type)]}
|
||||
(ClearForm. type))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
|
@ -224,5 +207,5 @@
|
|||
(defn clear-mixin
|
||||
[store type]
|
||||
{:will-unmount (fn [own]
|
||||
(clear! store type)
|
||||
(ptk/emit! store (clear-form type))
|
||||
own)})
|
||||
|
|
Loading…
Add table
Reference in a new issue